diff --git a/.clippy.toml b/.clippy.toml index c697adfa4..d5fb315cf 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -10,10 +10,12 @@ doc-valid-idents = [ "iOS", "macOS", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "BigInt64Array", "BigUint64Array", "ConstrainDOMString", "ConstrainULong", + "DisplayMediaStreamConstraints", "DOMHighResTimeStamp", "getDisplayMedia", "getUserMedia", "gRPC", - "MediaDeviceKind", "MediaDeviceInfo", - "MediaStream", "MediaStreamConstraints", "MediaStreamTrack", + "MediaDevices", "MediaDeviceKind", "MediaDeviceInfo", + "MediaStream", "MediaStreamConstraints", + "MediaStreamTrack", "MediaStreamTrackState", "MediaTrackConstraints", "MessageEvent", "RTCAudioSenderStats", "RTCSenderAudioTrackAttachmentStats", @@ -30,5 +32,6 @@ doc-valid-idents = [ "RTCStats", "RTCStatsReport", "RTCTrackEvent", "RTCVideoSenderStats", + "VideoFacingModeEnum", "WebDriver", "WebRTC", "WebSocket", ] diff --git a/Cargo.lock b/Cargo.lock index edd29282e..44938c724 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,7 +100,7 @@ dependencies = [ "bitflags", "brotli2", "bytes 0.5.6", - "cookie 0.14.3", + "cookie 0.14.4", "copyless", "derive_more", "either", @@ -348,9 +348,9 @@ dependencies = [ [[package]] name = "adler" -version = "0.2.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" @@ -494,7 +494,7 @@ dependencies = [ "async-global-executor", "async-io", "async-lock", - "crossbeam-utils 0.8.2", + "crossbeam-utils 0.8.3", "futures-channel", "futures-core", "futures-io", @@ -505,7 +505,7 @@ dependencies = [ "memchr", "num_cpus", "once_cell", - "pin-project-lite 0.2.4", + "pin-project-lite 0.2.5", "pin-utils", "slab", "wasm-bindgen-futures", @@ -675,7 +675,7 @@ dependencies = [ "block-padding", "byte-tools", "byteorder", - "generic-array 0.12.3", + "generic-array 0.12.4", ] [[package]] @@ -840,7 +840,7 @@ dependencies = [ "bytes 1.0.1", "futures-util", "memchr", - "pin-project-lite 0.2.4", + "pin-project-lite 0.2.5", "tokio 0.2.25", ] @@ -902,9 +902,9 @@ dependencies = [ [[package]] name = "cookie" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784ad0fbab4f3e9cef09f20e0aea6000ae08d2cb98ac4c0abc53df18803d702f" +checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" dependencies = [ "percent-encoding", "time 0.2.25", @@ -965,7 +965,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.2", + "crossbeam-utils 0.8.3", ] [[package]] @@ -992,14 +992,13 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae8f328835f8f5a6ceb6a7842a7f2d0c03692adb5c889347235d59194731fe3" +checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" dependencies = [ "autocfg", "cfg-if 1.0.0", "lazy_static", - "loom", ] [[package]] @@ -1072,12 +1071,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bfca34df8d0d73d61f26ce03a8be6da606d6c419a10c099009a879c165d2a71" +checksum = "a06d4a9551359071d1890820e3571252b91229e0712e7c36b08940e603c5a8fc" dependencies = [ - "darling_core 0.12.1", - "darling_macro 0.12.1", + "darling_core 0.12.2", + "darling_macro 0.12.2", ] [[package]] @@ -1096,9 +1095,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "893119a69874649edb8ee1b912a7f367f251c5c862c45d3eed7abad2535e766b" +checksum = "b443e5fb0ddd56e0c9bfa47dc060c5306ee500cb731f2b91432dd65589a77684" dependencies = [ "fnv", "ident_case", @@ -1121,11 +1120,11 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86184131efd474ebc1264537cfc8bbe9710f73846d02cc0305370410b848ef0f" +checksum = "c0220073ce504f12a70efc4e7cdaea9e9b1b324872e7ad96a208056d7a638b81" dependencies = [ - "darling_core 0.12.1", + "darling_core 0.12.2", "quote 1.0.9", "syn 1.0.60", ] @@ -1218,7 +1217,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" dependencies = [ - "generic-array 0.12.3", + "generic-array 0.12.4", ] [[package]] @@ -1338,12 +1337,12 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[package]] name = "fantoccini" -version = "0.17.2" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b29a87bffc0ae78d88bedd8b1a9cb885ce84382ed3457429f35d480269ae17c2" +checksum = "e49ba709e57aca51a97b5f6a81e5be479dff7739422f10f7292d3aaaa5d2a7a9" dependencies = [ "base64 0.13.0", - "cookie 0.14.3", + "cookie 0.14.4", "futures-core", "futures-util", "http", @@ -1474,9 +1473,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7e4c2612746b0df8fed4ce0c69156021b704c9aefa360311c04e6e9e002eed" +checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" @@ -1537,7 +1536,7 @@ dependencies = [ "futures-io", "memchr", "parking", - "pin-project-lite 0.2.4", + "pin-project-lite 0.2.5", "waker-fn", ] @@ -1577,7 +1576,7 @@ version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1812c7ab8aedf8d6f2701a43e1243acdbcc2b36ab26e2ad421eb99ac963d96d1" dependencies = [ - "futures 0.1.30", + "futures 0.1.31", "futures-channel", "futures-core", "futures-io", @@ -1585,7 +1584,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.4", + "pin-project-lite 0.2.5", "pin-utils", "proc-macro-hack", "proc-macro-nested", @@ -1607,24 +1606,11 @@ version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" -[[package]] -name = "generator" -version = "0.6.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9fed24fd1e18827652b4d55652899a1e9da8e54d91624dc3437a5bc3a9f9a9c" -dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "winapi 0.3.9", -] - [[package]] name = "generic-array" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" dependencies = [ "typenum", ] @@ -1754,9 +1740,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b67e66362108efccd8ac053abafc8b7a8d86a37e6e48fc4f6f7485eb5e9e6a5" +checksum = "d832b01df74254fe364568d6ddc294443f61cbec82816b60904303af87efae78" dependencies = [ "bytes 1.0.1", "fnv", @@ -1769,7 +1755,6 @@ dependencies = [ "tokio 1.2.0", "tokio-util 0.6.3", "tracing", - "tracing-futures", ] [[package]] @@ -1925,7 +1910,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.3.0", + "h2 0.3.1", "http", "http-body 0.4.0", "httparse", @@ -1975,7 +1960,7 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b287fb45c60bb826a0dc68ff08742b9d88a2fea13d6e0c286b3172065aaf878c" dependencies = [ - "crossbeam-utils 0.8.2", + "crossbeam-utils 0.8.3", "globset", "lazy_static", "log", @@ -2087,9 +2072,9 @@ checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "js-sys" -version = "0.3.47" +version = "0.3.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cfb73131c35423a367daf8cbd24100af0d077668c8c2943f0e7dd775fef0f65" +checksum = "dc9f84f9b115ce7843d60706df1422a916680bfdfcbdb0447c5614ff9d7e4d78" dependencies = [ "wasm-bindgen", ] @@ -2140,9 +2125,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" +checksum = "265d751d31d6780a3f956bb5b8022feba2d94eeee5a84ba64f4212eedca42213" [[package]] name = "linked-hash-map" @@ -2179,17 +2164,6 @@ dependencies = [ "value-bag", ] -[[package]] -name = "loom" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d44c73b4636e497b4917eb21c33539efa3816741a2d3ff26c6316f1b529481a4" -dependencies = [ - "cfg-if 1.0.0", - "generator", - "scoped-tls", -] - [[package]] name = "lru-cache" version = "0.1.2" @@ -2492,9 +2466,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", "autocfg", @@ -2709,9 +2683,9 @@ checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4" [[package]] name = "once_cell" -version = "1.6.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad167a2f54e832b82dbe003a046280dceffe5227b5f79e08e363a29638cfddd" +checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" [[package]] name = "opaque-debug" @@ -2880,15 +2854,15 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" +checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827" +checksum = "0cf491442e4b033ed1c722cb9f0df5fcfcf4de682466c46469c36bc47dc5548a" [[package]] name = "pin-utils" @@ -3222,7 +3196,7 @@ dependencies = [ "futures-util", "itoa", "percent-encoding", - "pin-project-lite 0.1.11", + "pin-project-lite 0.1.12", "sha1", "tokio 0.2.25", "tokio-util 0.3.1", @@ -3297,7 +3271,7 @@ dependencies = [ "mime", "native-tls", "percent-encoding", - "pin-project-lite 0.2.4", + "pin-project-lite 0.2.5", "serde 1.0.123", "serde_json", "serde_urlencoded 0.7.0", @@ -3416,9 +3390,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "security-framework" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1759c2e3c8580017a484a7ac56d3abc5a6c1feadf88db2f3633f12ae4268c69" +checksum = "2dfd318104249865096c8da1dfabf09ddbb6d0330ea176812a62ec75e40c4166" dependencies = [ "bitflags", "core-foundation", @@ -3429,9 +3403,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f99b9d5e26d2a71633cc4f2ebae7cc9f874044e0c351a27e17892d76dce5678b" +checksum = "dee48cdde5ed250b0d3252818f646e174ab414036edb884dde62d80a3ac6082d" dependencies = [ "core-foundation-sys", "libc", @@ -3493,9 +3467,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.62" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" dependencies = [ "itoa", "ryu", @@ -3551,7 +3525,7 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48b35457e9d855d3dc05ef32a73e0df1e2c0fd72c38796a4ee909160c8eeec2" dependencies = [ - "darling 0.12.1", + "darling 0.12.2", "proc-macro2 1.0.24", "quote 1.0.9", "syn 1.0.60", @@ -4063,7 +4037,7 @@ dependencies = [ "mio 0.6.23", "mio-uds", "num_cpus", - "pin-project-lite 0.1.11", + "pin-project-lite 0.1.12", "signal-hook-registry", "slab", "tokio-macros 0.2.6", @@ -4082,7 +4056,7 @@ dependencies = [ "memchr", "mio 0.7.9", "num_cpus", - "pin-project-lite 0.2.4", + "pin-project-lite 0.2.5", "tokio-macros 1.1.0", ] @@ -4129,7 +4103,7 @@ dependencies = [ "futures-io", "futures-sink", "log", - "pin-project-lite 0.1.11", + "pin-project-lite 0.1.12", "tokio 0.2.25", ] @@ -4143,7 +4117,7 @@ dependencies = [ "futures-core", "futures-sink", "log", - "pin-project-lite 0.2.4", + "pin-project-lite 0.2.5", "tokio 1.2.0", ] @@ -4387,13 +4361,13 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f77d3842f76ca899ff2dbcf231c5c65813dea431301d6eb686279c15c4464f12" +checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f" dependencies = [ "cfg-if 1.0.0", "log", - "pin-project-lite 0.2.4", + "pin-project-lite 0.2.5", "tracing-attributes", "tracing-core", ] @@ -4675,9 +4649,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" -version = "0.2.70" +version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be" +checksum = "7ee1280240b7c461d6a0071313e08f34a60b0365f14260362e5a2b17d1d31aa7" dependencies = [ "cfg-if 1.0.0", "serde 1.0.123", @@ -4687,9 +4661,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.70" +version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bc45447f0d4573f3d65720f636bbcc3dd6ce920ed704670118650bcd47764c7" +checksum = "5b7d8b6942b8bb3a9b0e73fc79b98095a27de6fa247615e59d096754a3bc2aa8" dependencies = [ "bumpalo", "lazy_static", @@ -4702,9 +4676,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3de431a2910c86679c34283a33f66f4e4abd7e0aec27b6669060148872aadf94" +checksum = "8e67a5806118af01f0d9045915676b22aaebecf4178ae7021bc171dab0b897ab" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -4714,9 +4688,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.70" +version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b8853882eef39593ad4174dd26fc9865a64e84026d223f63bb2c42affcbba2c" +checksum = "e5ac38da8ef716661f0f36c0d8320b89028efe10c7c0afde65baffb496ce0d3b" dependencies = [ "quote 1.0.9", "wasm-bindgen-macro-support", @@ -4724,9 +4698,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.70" +version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4133b5e7f2a531fa413b3a1695e925038a05a71cf67e87dafa295cb645a01385" +checksum = "cc053ec74d454df287b9374ee8abb36ffd5acb95ba87da3ba5b7d3fe20eb401e" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.9", @@ -4737,15 +4711,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.70" +version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4945e4943ae02d15c13962b38a5b1e81eadd4b71214eee75af64a4d6a4fd64" +checksum = "7d6f8ec44822dd71f5f221a5847fb34acd9060535c1211b70a05844c0f6383b1" [[package]] name = "wasm-bindgen-test" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d4da138503a4cf86801b94d95781ee3619faa8feca830569cc6b54997b8b5c" +checksum = "2ea9e4f0050d5498a160e6b9d278a9699598e445b51dacd05598da55114c801a" dependencies = [ "console_error_panic_hook", "js-sys", @@ -4757,9 +4731,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3199c33f06500c731d5544664c24d0c2b742b98debc6b1c6f0c6d6e8fb7c19b" +checksum = "43f40402f495d92df6cdd0d329e7cc2580c8f99bcd74faff0e468923a764b7d4" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.9", @@ -4778,9 +4752,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.47" +version = "0.3.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c40dc691fc48003eba817c38da7113c15698142da971298003cac3ef175680b3" +checksum = "ec600b26223b2948cedfde2a0aa6756dcf1fef616f43d7b3097aaf53a6c4d92b" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Makefile b/Makefile index 7e1ab3581..002a7e1b8 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ IMAGE_NAME := $(strip \ RUST_VER := 1.50 CHROME_VERSION := 88.0 -FIREFOX_VERSION := 85.0.2 +FIREFOX_VERSION := 86.0 crate-dir = . ifeq ($(crate),medea-jason) diff --git a/jason/demo/index.html b/jason/demo/index.html index 4128b7a0b..d48c4a5c0 100644 --- a/jason/demo/index.html +++ b/jason/demo/index.html @@ -534,7 +534,7 @@ console.error('Name: "' + err.name() + '";\nMessage: "' + err.message() + '";'); } } else if (name === 'ErroredException') { - alert('Fatal error occured while MediaStreamSettings update.'); + alert('Fatal error occurred while MediaStreamSettings update.'); } } }; diff --git a/jason/e2e-demo/js/index.js b/jason/e2e-demo/js/index.js index 1b9ad8002..cbbd53e9f 100644 --- a/jason/e2e-demo/js/index.js +++ b/jason/e2e-demo/js/index.js @@ -776,7 +776,7 @@ window.onload = async function() { console.error('Name: "' + err.name() + '";\nMessage: "' + err.message() + '";'); } } else if (name === 'ErroredException') { - alert('Fatal error occured while MediaStreamSettings update.'); + alert('Fatal error occurred while MediaStreamSettings update.'); } console.error("Changing video source failed: " + name); } diff --git a/jason/src/api/mod.rs b/jason/src/api/mod.rs index aca93c3e3..bed53b2c7 100644 --- a/jason/src/api/mod.rs +++ b/jason/src/api/mod.rs @@ -1,151 +1,88 @@ -//! External Jason API accessible from JS. - -mod connection; -mod room; - -use std::{cell::RefCell, rc::Rc}; - -use futures::FutureExt as _; -use wasm_bindgen::prelude::*; -use wasm_bindgen_futures::spawn_local; - -use crate::{ - media::{MediaManager, MediaManagerHandle}, - rpc::{ - ClientDisconnect, RpcSession, RpcTransport, WebSocketRpcClient, - WebSocketRpcSession, WebSocketRpcTransport, - }, - set_panic_hook, -}; - -#[doc(inline)] -pub use self::{ - connection::{Connection, ConnectionHandle, Connections}, - room::{ - ConstraintsUpdateException, Room, RoomCloseReason, RoomError, - RoomHandle, WeakRoom, +//! External [`Jason`] API. + +pub mod wasm; + +use crate::media; + +pub use self::wasm::{ + connection_handle::ConnectionHandle, + constraints_update_exception::ConstraintsUpdateException, + input_device_info::InputDeviceInfo, + jason::Jason, + jason_error::JasonError, + local_media_track::LocalMediaTrack, + media_manager_handle::MediaManagerHandle, + media_stream_settings::{ + AudioTrackConstraints, DeviceVideoTrackConstraints, + DisplayVideoTrackConstraints, MediaStreamSettings, }, + reconnect_handle::ReconnectHandle, + remote_media_track::RemoteMediaTrack, + room_close_reason::RoomCloseReason, + room_handle::RoomHandle, + FacingMode, MediaKind, MediaSourceKind, }; -/// General library interface. -/// -/// Responsible for managing shared transports, local media -/// and room initialization. -#[wasm_bindgen] -pub struct Jason(Rc>); - -struct Inner { - /// [`Jason`]s [`MediaManager`]. It's shared across [`Room`]s since - /// [`MediaManager`] contains media tracks that can be used by multiple - /// [`Room`]s. - media_manager: Rc, - - /// [`Room`]s maintained by this [`Jason`] instance. - rooms: Vec, - - /// Connection with Media Server. Only one [`WebSocketRpcClient`] is - /// supported at the moment. - rpc: Rc, -} - -#[wasm_bindgen] -impl Jason { - /// Instantiates new [`Jason`] interface to interact with this library. - #[wasm_bindgen(constructor)] - pub fn new() -> Self { - set_panic_hook(); - wasm_logger::init(wasm_logger::Config::default()); - - Self::with_rpc_client(Rc::new(WebSocketRpcClient::new(Box::new( - |url| { - Box::pin(async move { - let ws = WebSocketRpcTransport::new(url) - .await - .map_err(|e| tracerr::new!(e))?; - Ok(Rc::new(ws) as Rc) - }) - }, - )))) - } - - /// Creates new [`Room`] and returns its [`RoomHandle`]. - pub fn init_room(&self) -> RoomHandle { - let rpc = Rc::clone(&self.0.borrow().rpc); - self.inner_init_room(WebSocketRpcSession::new(rpc)) - } - - /// Returns [`MediaManagerHandle`]. - pub fn media_manager(&self) -> MediaManagerHandle { - self.0.borrow().media_manager.new_handle() +impl From for MediaKind { + #[inline] + fn from(that: media::MediaKind) -> Self { + match that { + media::MediaKind::Audio => Self::Audio, + media::MediaKind::Video => Self::Video, + } } +} - /// Closes the provided [`RoomHandle`]. - #[allow(clippy::needless_pass_by_value)] - pub fn close_room(&self, room_to_delete: RoomHandle) { - self.0.borrow_mut().rooms.retain(|room| { - let should_be_closed = room.inner_ptr_eq(&room_to_delete); - if should_be_closed { - room.set_close_reason(ClientDisconnect::RoomClosed.into()); - } - - !should_be_closed - }); +impl From for media::MediaKind { + #[inline] + fn from(that: MediaKind) -> Self { + match that { + MediaKind::Audio => Self::Audio, + MediaKind::Video => Self::Video, + } } +} - /// Drops [`Jason`] API object, so all related objects (rooms, connections, - /// streams etc.) respectively. All objects related to this [`Jason`] API - /// object will be detached (you will still hold them, but unable to use). - pub fn dispose(self) { - self.0.borrow_mut().rooms.drain(..).for_each(|room| { - room.close(ClientDisconnect::RoomClosed.into()); - }); +impl From for MediaSourceKind { + #[inline] + fn from(that: media::MediaSourceKind) -> Self { + match that { + media::MediaSourceKind::Device => Self::Device, + media::MediaSourceKind::Display => Self::Display, + } } } -impl Jason { - /// Returns new [`Jason`] with the provided [`WebSocketRpcClient`]. +impl From for media::MediaSourceKind { #[inline] - pub fn with_rpc_client(rpc: Rc) -> Self { - Self(Rc::new(RefCell::new(Inner { - rpc, - rooms: Vec::new(), - media_manager: Rc::new(MediaManager::default()), - }))) + fn from(that: MediaSourceKind) -> Self { + match that { + MediaSourceKind::Device => Self::Device, + MediaSourceKind::Display => Self::Display, + } } +} - /// Returns [`RoomHandle`] for [`Room`]. - pub fn inner_init_room(&self, rpc: Rc) -> RoomHandle { - let on_normal_close = rpc.on_normal_close(); - let room = Room::new(rpc, Rc::clone(&self.0.borrow().media_manager)); - - let weak_room = room.downgrade(); - let weak_inner = Rc::downgrade(&self.0); - spawn_local(on_normal_close.map(move |reason| { - (|| { - let room = weak_room.upgrade()?; - let inner = weak_inner.upgrade()?; - let mut inner = inner.borrow_mut(); - let index = inner.rooms.iter().position(|r| r.ptr_eq(&room)); - if let Some(index) = index { - inner.rooms.remove(index).close(reason); - } - if inner.rooms.is_empty() { - inner.media_manager = Rc::default(); - } - - Some(()) - })(); - })); - - let handle = room.new_handle(); - self.0.borrow_mut().rooms.push(room); - handle +impl From for FacingMode { + #[inline] + fn from(that: media::FacingMode) -> Self { + match that { + media::FacingMode::User => Self::User, + media::FacingMode::Environment => Self::Environment, + media::FacingMode::Left => Self::Left, + media::FacingMode::Right => Self::Right, + } } } -impl Default for Jason { +impl From for media::FacingMode { #[inline] - fn default() -> Self { - Self::new() + fn from(val: FacingMode) -> Self { + match val { + FacingMode::User => Self::User, + FacingMode::Environment => Self::Environment, + FacingMode::Left => Self::Left, + FacingMode::Right => Self::Right, + } } } diff --git a/jason/src/api/wasm/connection_handle.rs b/jason/src/api/wasm/connection_handle.rs new file mode 100644 index 000000000..08198f137 --- /dev/null +++ b/jason/src/api/wasm/connection_handle.rs @@ -0,0 +1,63 @@ +//! Connection with a specific remote `Member` used on JS side. + +use derive_more::From; +use wasm_bindgen::prelude::*; + +use crate::{api::JasonError, connection}; + +/// Connection with a specific remote `Member`, that is used on JS side. +/// +/// Like all the handles it contains a weak reference to the object that is +/// managed by Rust, so its methods will fail if a weak reference could not be +/// upgraded. +#[wasm_bindgen] +#[derive(From)] +pub struct ConnectionHandle(connection::ConnectionHandle); + +#[wasm_bindgen] +impl ConnectionHandle { + /// Sets callback, invoked when this [`Connection`] is closed. + /// + /// [`Connection`]: connection::Connection + pub fn on_close(&self, cb: js_sys::Function) -> Result<(), JsValue> { + self.0 + .on_close(cb.into()) + .map_err(JasonError::from) + .map_err(JsValue::from) + } + + /// Returns ID of the remote `Member`. + pub fn get_remote_member_id(&self) -> Result { + self.0 + .get_remote_member_id() + .map_err(JasonError::from) + .map_err(JsValue::from) + } + + /// Sets callback, invoked when a new [`RemoteMediaTrack`] is added to this + /// [`Connection`]. + /// + /// [`Connection`]: connection::Connection + /// [`RemoteMediaTrack`]: crate::api::RemoteMediaTrack + pub fn on_remote_track_added( + &self, + cb: js_sys::Function, + ) -> Result<(), JsValue> { + self.0 + .on_remote_track_added(cb.into()) + .map_err(JasonError::from) + .map_err(JsValue::from) + } + + /// Sets callback, invoked when connection quality score is updated by a + /// server. + pub fn on_quality_score_update( + &self, + cb: js_sys::Function, + ) -> Result<(), JsValue> { + self.0 + .on_quality_score_update(cb.into()) + .map_err(JasonError::from) + .map_err(JsValue::from) + } +} diff --git a/jason/src/api/wasm/constraints_update_exception.rs b/jason/src/api/wasm/constraints_update_exception.rs new file mode 100644 index 000000000..74e78deae --- /dev/null +++ b/jason/src/api/wasm/constraints_update_exception.rs @@ -0,0 +1,55 @@ +//! Exception returned from [`RoomHandle::set_local_media_settings()`][1]. +//! +//! [1]: crate::api::RoomHandle::set_local_media_settings + +use std::iter::FromIterator as _; + +use derive_more::From; +use wasm_bindgen::prelude::*; + +use crate::{api::JasonError, room}; + +/// Exception returned from [`RoomHandle::set_local_media_settings()`][1]. +/// +/// [1]: crate::api::RoomHandle::set_local_media_settings +#[wasm_bindgen] +#[derive(Debug, From)] +#[from(forward)] +pub struct ConstraintsUpdateException(room::ConstraintsUpdateException); + +#[wasm_bindgen] +impl ConstraintsUpdateException { + /// Returns name of this [`ConstraintsUpdateException`]. + pub fn name(&self) -> String { + self.0.name() + } + + /// Returns [`JasonError`] if this [`ConstraintsUpdateException`] represents + /// a `RecoveredException` or a `RecoverFailedException`. + /// + /// Returns `undefined` otherwise. + pub fn recover_reason(&self) -> Option { + self.0.recover_reason().map(Into::into) + } + + /// Returns [`js_sys::Array`] with the [`JasonError`]s if this + /// [`ConstraintsUpdateException`] represents a `RecoverFailedException`. + pub fn recover_fail_reasons(&self) -> JsValue { + js_sys::Array::from_iter( + self.0 + .recover_fail_reasons() + .into_iter() + .map(JasonError::from) + .map(JsValue::from), + ) + .into() + } + + /// Returns [`JasonError`] if this [`ConstraintsUpdateException`] represents + /// an `ErroredException`. + /// + /// Returns `undefined` otherwise. + pub fn error(&self) -> Option { + self.0.error().map(Into::into) + } +} diff --git a/jason/src/api/wasm/input_device_info.rs b/jason/src/api/wasm/input_device_info.rs new file mode 100644 index 000000000..be29c59b4 --- /dev/null +++ b/jason/src/api/wasm/input_device_info.rs @@ -0,0 +1,52 @@ +//! Representation of a [MediaDeviceInfo][1]. +//! +//! [1]: https://w3.org/TR/mediacapture-streams/#device-info + +use derive_more::From; +use wasm_bindgen::prelude::*; + +use crate::{api::MediaKind, platform}; + +/// Representation of a [MediaDeviceInfo][1]. +/// +/// [1]: https://w3.org/TR/mediacapture-streams/#device-info +#[wasm_bindgen] +#[derive(From)] +pub struct InputDeviceInfo(platform::InputDeviceInfo); + +#[wasm_bindgen] +impl InputDeviceInfo { + /// Returns a unique identifier for the represented device. + pub fn device_id(&self) -> String { + self.0.device_id() + } + + /// Returns a kind of the represented device. + /// + /// This representation of [MediaDeviceInfo][1] is for input device ONLY. + /// + /// [1]: https://w3.org/TR/mediacapture-streams/#device-info + pub fn kind(&self) -> MediaKind { + self.0.kind().into() + } + + /// Returns label describing the represented device (for example "External + /// USB Webcam"). + /// + /// If the device has no associated label, then returns an empty string. + pub fn label(&self) -> String { + self.0.label() + } + + /// Returns a group identifier of the represented device. + /// + /// Two devices have the same group identifier if they belong to the same + /// physical device. For example, the audio input and output devices + /// representing the speaker and microphone of the same headset have the + /// same [groupId][1]. + /// + /// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediadeviceinfo-groupid + pub fn group_id(&self) -> String { + self.0.group_id() + } +} diff --git a/jason/src/api/wasm/jason.rs b/jason/src/api/wasm/jason.rs new file mode 100644 index 000000000..5545c2b75 --- /dev/null +++ b/jason/src/api/wasm/jason.rs @@ -0,0 +1,56 @@ +//! General JS side library interface. + +#![allow(clippy::new_without_default)] + +use derive_more::From; +use wasm_bindgen::prelude::*; + +use crate::{ + api::{MediaManagerHandle, RoomHandle}, + jason, + platform::{init_logger, set_panic_hook}, +}; + +/// General JS side library interface. +/// +/// Responsible for managing shared transports, local media and room +/// initialization. +#[wasm_bindgen] +#[derive(From)] +pub struct Jason(jason::Jason); + +#[wasm_bindgen] +impl Jason { + /// Instantiates a new [`Jason`] interface to interact with this library. + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + set_panic_hook(); + init_logger(); + + Self(jason::Jason::new()) + } + + /// Creates a new `Room` and returns its [`RoomHandle`]. + pub fn init_room(&self) -> RoomHandle { + self.0.init_room().into() + } + + /// Returns a [`MediaManagerHandle`]. + pub fn media_manager(&self) -> MediaManagerHandle { + self.0.media_manager().into() + } + + /// Closes the provided [`RoomHandle`]. + #[allow(clippy::needless_pass_by_value)] + pub fn close_room(&self, room_to_delete: RoomHandle) { + self.0.close_room(room_to_delete.into()); + } + + /// Drops [`Jason`] API object, so all the related objects (rooms, + /// connections, streams etc.) respectively. All objects related to this + /// [`Jason`] API object will be detached (you will still hold them, but + /// unable to use). + pub fn dispose(self) { + self.0.dispose(); + } +} diff --git a/jason/src/api/wasm/jason_error.rs b/jason/src/api/wasm/jason_error.rs new file mode 100644 index 000000000..f79f4fb1f --- /dev/null +++ b/jason/src/api/wasm/jason_error.rs @@ -0,0 +1,36 @@ +//! App error exported to JS side. + +use derive_more::From; +use wasm_bindgen::prelude::*; + +use crate::utils; + +/// Representation of an app error exported to JS side. +/// +/// Contains JS side error if it's the cause, and a trace information. +#[wasm_bindgen] +#[derive(From)] +pub struct JasonError(utils::JasonError); + +#[wasm_bindgen] +impl JasonError { + /// Returns a name of this error. + pub fn name(&self) -> String { + self.0.name() + } + + /// Returns a message of this errors. + pub fn message(&self) -> String { + self.0.message() + } + + /// Returns a trace information of this error. + pub fn trace(&self) -> String { + self.0.trace() + } + + /// Returns a JS side error if it's the cause. + pub fn source(&self) -> Option { + self.0.source().and_then(|a| a.sys_cause) + } +} diff --git a/jason/src/api/wasm/local_media_track.rs b/jason/src/api/wasm/local_media_track.rs new file mode 100644 index 000000000..592df70d0 --- /dev/null +++ b/jason/src/api/wasm/local_media_track.rs @@ -0,0 +1,47 @@ +//! Wrapper around a local [MediaStreamTrack][1]. +//! +//! [1]: https://w3.org/TR/mediacapture-streams/#dom-mediastreamtrack + +use derive_more::From; +use wasm_bindgen::prelude::*; + +use crate::{ + api::{MediaKind, MediaSourceKind}, + media::track::local, +}; + +/// Wrapper around a local [MediaStreamTrack][1]. +/// +/// Backed by a strong reference to the actual track implementing auto stop on +/// dropping. Can be manually dropped with a `free()` call. +/// +/// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediastreamtrack +#[wasm_bindgen] +#[derive(From)] +pub struct LocalMediaTrack(local::LocalMediaTrack); + +#[wasm_bindgen] +impl LocalMediaTrack { + /// Returns the underlying [MediaStreamTrack][1]. + /// + /// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediastreamtrack + pub fn get_track(&self) -> web_sys::MediaStreamTrack { + Clone::clone(&self.0.get_track().as_ref()) + } + + /// Returns a [`MediaKind::Audio`] if this [`LocalMediaTrack`] represents an + /// audio track, or a [`MediaKind::Video`] if it represents a video track. + pub fn kind(&self) -> MediaKind { + self.0.kind().into() + } + + /// Returns a [`MediaSourceKind::Device`] if this [`LocalMediaTrack`] is + /// sourced from some device (webcam/microphone), or a + /// [`MediaSourceKind::Display`] if it's captured via + /// [MediaDevices.getDisplayMedia()][1]. + /// + /// [1]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia + pub fn media_source_kind(&self) -> MediaSourceKind { + self.0.media_source_kind().into() + } +} diff --git a/jason/src/api/wasm/media_manager_handle.rs b/jason/src/api/wasm/media_manager_handle.rs new file mode 100644 index 000000000..2314ef24d --- /dev/null +++ b/jason/src/api/wasm/media_manager_handle.rs @@ -0,0 +1,88 @@ +//! Weak reference to a [`MediaManager`]. +//! +//! [`MediaManager`]: media::MediaManager + +use derive_more::From; +use js_sys::Promise; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::future_to_promise; + +use crate::{ + api::{InputDeviceInfo, JasonError, LocalMediaTrack, MediaStreamSettings}, + media, +}; + +/// [`MediaManagerHandle`] is a weak reference to a [`MediaManager`]. +/// +/// [`MediaManager`] performs all the media acquisition requests +/// ([getUserMedia()][1]/[getDisplayMedia()][2]) and stores all the received +/// tracks for further re-usage. +/// +/// [`MediaManager`] stores weak references to [`LocalMediaTrack`]s, so if there +/// are no strong references to some track, then this track is stopped and +/// removed from [`MediaManager`]. +/// +/// Like all the handles it contains a weak reference to the object that is +/// managed by Rust, so its methods will fail if a weak reference could not be +/// upgraded. +/// +/// [`MediaManager`]: media::MediaManager +/// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediadevices-getusermedia +/// [2]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia +#[wasm_bindgen] +#[derive(From)] +pub struct MediaManagerHandle(media::MediaManagerHandle); + +#[wasm_bindgen] +#[allow(clippy::unused_self)] +impl MediaManagerHandle { + /// Returns a list of [`InputDeviceInfo`] objects representing available + /// media input and output devices, such as microphones, cameras, and so + /// forth. + pub fn enumerate_devices(&self) -> Promise { + let this = self.0.clone(); + + future_to_promise(async move { + this.enumerate_devices() + .await + .map(|devices| { + devices + .into_iter() + .fold(js_sys::Array::new(), |devices_info, info| { + devices_info.push(&JsValue::from( + InputDeviceInfo::from(info), + )); + devices_info + }) + .into() + }) + .map_err(JasonError::from) + .map_err(JsValue::from) + }) + } + + /// Returns [`LocalMediaTrack`]s objects, built from the provided + /// [`MediaStreamSettings`]. + pub fn init_local_tracks(&self, caps: &MediaStreamSettings) -> Promise { + let this = self.0.clone(); + let caps = caps.clone(); + + future_to_promise(async move { + this.init_local_tracks(caps.into()) + .await + .map(|tracks| { + tracks + .into_iter() + .fold(js_sys::Array::new(), |tracks, track| { + tracks.push(&JsValue::from(LocalMediaTrack::from( + track, + ))); + tracks + }) + .into() + }) + .map_err(JasonError::from) + .map_err(JsValue::from) + }) + } +} diff --git a/jason/src/api/wasm/media_stream_settings.rs b/jason/src/api/wasm/media_stream_settings.rs new file mode 100644 index 000000000..cbfddda0c --- /dev/null +++ b/jason/src/api/wasm/media_stream_settings.rs @@ -0,0 +1,160 @@ +//! [MediaStreamConstraints][1] wrapper. +//! +//! [1]: https://w3.org/TR/mediacapture-streams/#dom-mediastreamconstraints + +#![allow(clippy::new_without_default)] + +use derive_more::{From, Into}; +use wasm_bindgen::prelude::*; + +use crate::{api::FacingMode, media}; + +/// [MediaStreamConstraints][1] wrapper. +/// +/// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediastreamconstraints +#[wasm_bindgen] +#[derive(Clone, From, Into)] +pub struct MediaStreamSettings(media::MediaStreamSettings); + +#[wasm_bindgen] +impl MediaStreamSettings { + /// Creates new [`MediaStreamSettings`] with none constraints configured. + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + media::MediaStreamSettings::new().into() + } + + /// Specifies the nature and settings of an audio [MediaStreamTrack][1]. + /// + /// [1]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack + pub fn audio(&mut self, constraints: AudioTrackConstraints) { + self.0.audio(constraints.into()) + } + + /// Set constraints that will be used to obtain a local video sourced from + /// a media device. + pub fn device_video(&mut self, constraints: DeviceVideoTrackConstraints) { + self.0.device_video(constraints.into()); + } + + /// Set constraints that will be used to capture a local video from a user's + /// display. + pub fn display_video(&mut self, constraints: DisplayVideoTrackConstraints) { + self.0.display_video(constraints.into()); + } +} + +/// Constraints applicable to audio tracks. +#[wasm_bindgen] +#[derive(From, Into)] +pub struct AudioTrackConstraints(media::AudioTrackConstraints); + +#[wasm_bindgen] +impl AudioTrackConstraints { + /// Creates new [`AudioTrackConstraints`] with none constraints configured. + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + media::AudioTrackConstraints::new().into() + } + + /// Sets an exact [deviceId][1] constraint. + /// + /// [1]: https://w3.org/TR/mediacapture-streams/#def-constraint-deviceId + pub fn device_id(&mut self, device_id: String) { + self.0.device_id(device_id); + } +} + +/// Constraints applicable to video tracks that are sourced from some media +/// device. +#[wasm_bindgen] +#[derive(From, Into)] +pub struct DeviceVideoTrackConstraints(media::DeviceVideoTrackConstraints); + +#[wasm_bindgen] +impl DeviceVideoTrackConstraints { + /// Creates new [`DeviceVideoTrackConstraints`] with none constraints + /// configured. + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + media::DeviceVideoTrackConstraints::new().into() + } + + /// Sets an exact [deviceId][1] constraint. + /// + /// [1]: https://w3.org/TR/mediacapture-streams/#def-constraint-deviceId + pub fn device_id(&mut self, device_id: String) { + self.0.device_id(device_id); + } + + /// Sets an exact [facingMode][1] constraint. + /// + /// [1]: https://w3.org/TR/mediacapture-streams/#dom-constraindomstring + pub fn exact_facing_mode(&mut self, facing_mode: FacingMode) { + self.0.exact_facing_mode(facing_mode.into()); + } + + /// Sets an ideal [facingMode][1] constraint. + /// + /// [1]: https://w3.org/TR/mediacapture-streams/#dom-constraindomstring + pub fn ideal_facing_mode(&mut self, facing_mode: FacingMode) { + self.0.ideal_facing_mode(facing_mode.into()) + } + + /// Sets an exact [`height`][1] constraint. + /// + /// [1]: https://tinyurl.com/w3-streams#def-constraint-height + pub fn exact_height(&mut self, height: u32) { + self.0.exact_height(height) + } + + /// Sets an ideal [`height`][1] constraint. + /// + /// [1]: https://tinyurl.com/w3-streams#def-constraint-height + pub fn ideal_height(&mut self, height: u32) { + self.0.ideal_height(height); + } + + /// Sets a range of a [`height`][1] constraint. + /// + /// [1]: https://tinyurl.com/w3-streams#def-constraint-height + pub fn height_in_range(&mut self, min: u32, max: u32) { + self.0.height_in_range(min, max); + } + + /// Sets an exact [`width`][1] constraint. + /// + /// [1]: https://tinyurl.com/w3-streams#def-constraint-width + pub fn exact_width(&mut self, width: u32) { + self.0.exact_width(width); + } + + /// Sets an ideal [`width`][1] constraint. + /// + /// [1]: https://tinyurl.com/w3-streams#def-constraint-width + pub fn ideal_width(&mut self, width: u32) { + self.0.ideal_width(width); + } + + /// Sets a range of a [`width`][1] constraint. + /// + /// [1]: https://tinyurl.com/w3-streams#def-constraint-width + pub fn width_in_range(&mut self, min: u32, max: u32) { + self.0.width_in_range(min, max); + } +} + +/// Constraints applicable to video tracks sourced from a screen capturing. +#[wasm_bindgen] +#[derive(From, Into)] +pub struct DisplayVideoTrackConstraints(media::DisplayVideoTrackConstraints); + +#[wasm_bindgen] +impl DisplayVideoTrackConstraints { + /// Creates new [`DisplayVideoTrackConstraints`] with none constraints + /// configured. + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + media::DisplayVideoTrackConstraints::new().into() + } +} diff --git a/jason/src/api/wasm/mod.rs b/jason/src/api/wasm/mod.rs new file mode 100644 index 000000000..6481556de --- /dev/null +++ b/jason/src/api/wasm/mod.rs @@ -0,0 +1,64 @@ +//! External [`Jason`] API for `wasm32-unknown-unknown` target, designed to be +//! used in a web environment with JavaScript. +//! +//! [`Jason`]: crate::api::Jason + +use derive_more::Display; +use wasm_bindgen::prelude::*; + +pub mod connection_handle; +pub mod constraints_update_exception; +pub mod input_device_info; +pub mod jason; +pub mod jason_error; +pub mod local_media_track; +pub mod media_manager_handle; +pub mod media_stream_settings; +pub mod reconnect_handle; +pub mod remote_media_track; +pub mod room_close_reason; +pub mod room_handle; + +/// [MediaStreamTrack.kind][1] representation. +/// +/// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediastreamtrack-kind +#[wasm_bindgen] +#[derive(Clone, Copy, Debug, Display, Eq, PartialEq)] +pub enum MediaKind { + /// Audio track. + Audio, + + /// Video track. + Video, +} + +/// Media source type. +#[wasm_bindgen] +#[derive(Clone, Copy, Debug, Display, Eq, PartialEq)] +pub enum MediaSourceKind { + /// Media is sourced from some media device (webcam or microphone). + Device, + + /// Media is obtained via screen capturing. + Display, +} + +/// Describes directions that a camera can face, as seen from a user's +/// perspective. Representation of a [VideoFacingModeEnum][1]. +/// +/// [1]: https://w3.org/TR/mediacapture-streams/#dom-videofacingmodeenum +#[wasm_bindgen] +#[derive(Clone, Copy, Debug, Display, Eq, PartialEq)] +pub enum FacingMode { + /// Facing towards a user (a self-view camera). + User, + + /// Facing away from a user (viewing the environment). + Environment, + + /// Facing to the left of a user. + Left, + + /// Facing to the right of a user. + Right, +} diff --git a/jason/src/api/wasm/reconnect_handle.rs b/jason/src/api/wasm/reconnect_handle.rs new file mode 100644 index 000000000..b748f0882 --- /dev/null +++ b/jason/src/api/wasm/reconnect_handle.rs @@ -0,0 +1,81 @@ +//! JS side handle for reconnections with a media server. + +use derive_more::From; +use js_sys::Promise; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::future_to_promise; + +use crate::{api::JasonError, rpc}; + +/// Handle that JS side can reconnect to a media server with when a connection +/// is lost. +/// +/// This handle is passed into a [`RoomHandle.on_connection_loss`] callback. +/// +/// Like all the handles it contains a weak reference to the object that is +/// managed by Rust, so its methods will fail if a weak reference could not be +/// upgraded. +/// +/// [`RoomHandle.on_connection_loss`]: crate::api::RoomHandle.on_connection_loss +#[wasm_bindgen] +#[derive(Clone, From)] +pub struct ReconnectHandle(rpc::ReconnectHandle); + +#[wasm_bindgen] +impl ReconnectHandle { + /// Tries to reconnect after the provided delay in milliseconds. + /// + /// If [`RpcSession`] is already reconnecting then a new reconnection + /// attempt won't be performed. Instead, it will wait for the first + /// reconnection attempt result and use it. + /// + /// [`RpcSession`]: rpc::RpcSession + pub fn reconnect_with_delay(&self, delay_ms: u32) -> Promise { + let this = self.0.clone(); + future_to_promise(async move { + this.reconnect_with_delay(delay_ms) + .await + .map_err(JasonError::from)?; + Ok(JsValue::UNDEFINED) + }) + } + + /// Tries to reconnect a [`RpcSession`] in a loop with a growing backoff + /// delay. + /// + /// The first attempt to reconnect is guaranteed to happen no earlier than + /// `starting_delay_ms`. + /// + /// Also, it guarantees that delay between reconnection attempts won't be + /// greater than `max_delay_ms`. + /// + /// After each reconnection attempt, delay between reconnections will be + /// multiplied by the given `multiplier` until it reaches `max_delay_ms`. + /// + /// If [`RpcSession`] is already reconnecting then new reconnection attempt + /// won't be performed. Instead, it will wait for the first reconnection + /// attempt result and use it here. + /// + /// If `multiplier` is negative number than `multiplier` will be considered + /// as `0.0`. + /// + /// [`RpcSession`]: rpc::RpcSession + pub fn reconnect_with_backoff( + &self, + starting_delay_ms: u32, + multiplier: f32, + max_delay: u32, + ) -> Promise { + let this = self.0.clone(); + future_to_promise(async move { + this.reconnect_with_backoff( + starting_delay_ms, + multiplier, + max_delay, + ) + .await + .map_err(JasonError::from)?; + Ok(JsValue::UNDEFINED) + }) + } +} diff --git a/jason/src/api/wasm/remote_media_track.rs b/jason/src/api/wasm/remote_media_track.rs new file mode 100644 index 000000000..dbe855d3d --- /dev/null +++ b/jason/src/api/wasm/remote_media_track.rs @@ -0,0 +1,60 @@ +//! Wrapper around a received remote [MediaStreamTrack][1]. +//! +//! [1]: https://w3.org/TR/mediacapture-streams/#dom-mediastreamtrack + +use derive_more::{From, Into}; +use wasm_bindgen::prelude::*; + +use crate::{ + api::{MediaKind, MediaSourceKind}, + media::track::remote, +}; + +/// Wrapper around a received remote [MediaStreamTrack][1]. +/// +/// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediastreamtrack +#[wasm_bindgen] +#[derive(Clone, From, Into)] +pub struct RemoteMediaTrack(remote::Track); + +#[wasm_bindgen] +impl RemoteMediaTrack { + /// Returns the underlying [MediaStreamTrack][1]. + /// + /// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediastreamtrack + pub fn get_track(&self) -> web_sys::MediaStreamTrack { + Clone::clone(self.0.get_track().as_ref()) + } + + /// Indicates whether this [`RemoteMediaTrack`] is enabled. + pub fn enabled(&self) -> bool { + self.0.enabled() + } + + /// Sets callback, invoked when this [`RemoteMediaTrack`] is enabled. + pub fn on_enabled(&self, cb: js_sys::Function) { + self.0.on_enabled(cb.into()) + } + + /// Sets callback, invoked when this [`RemoteMediaTrack`] is disabled. + pub fn on_disabled(&self, cb: js_sys::Function) { + self.0.on_disabled(cb.into()) + } + + /// Returns a [`MediaKind::Audio`] if this [`RemoteMediaTrack`] represents + /// an audio track, or a [`MediaKind::Video`] if it represents a video + /// track. + pub fn kind(&self) -> MediaKind { + self.0.kind().into() + } + + /// Returns a [`MediaSourceKind::Device`] if this [`RemoteMediaTrack`] is + /// sourced from some device (webcam/microphone), or a + /// [`MediaSourceKind::Display`] if it's captured via + /// [MediaDevices.getDisplayMedia()][1]. + /// + /// [1]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia + pub fn media_source_kind(&self) -> MediaSourceKind { + self.0.media_source_kind().into() + } +} diff --git a/jason/src/api/wasm/room_close_reason.rs b/jason/src/api/wasm/room_close_reason.rs new file mode 100644 index 000000000..a9131e92c --- /dev/null +++ b/jason/src/api/wasm/room_close_reason.rs @@ -0,0 +1,42 @@ +//! Reason of a [`Room`] closing. +//! +//! [`Room`]: room::Room + +use derive_more::From; +use wasm_bindgen::prelude::*; + +use crate::room; + +/// Reason of why a [`Room`] is closed. +/// +/// This struct is passed to a [`RoomHandle::on_close`] JS side callback. +/// +/// [`Room`]: room::Room +/// [`RoomHandle::on_close`]: crate::api::RoomHandle::on_close +#[wasm_bindgen] +#[derive(From)] +pub struct RoomCloseReason(room::RoomCloseReason); + +#[wasm_bindgen] +impl RoomCloseReason { + /// Returns the [`Room`]'s close reason. + /// + /// [`Room`]: room::Room + pub fn reason(&self) -> String { + self.0.reason() + } + + /// Indicates whether the [`Room`] was closed by server. + /// + /// [`Room`]: room::Room + pub fn is_closed_by_server(&self) -> bool { + self.0.is_closed_by_server() + } + + /// Indicates whether the [`Room`] close reason is considered as an error. + /// + /// [`Room`]: room::Room + pub fn is_err(&self) -> bool { + self.0.is_err() + } +} diff --git a/jason/src/api/wasm/room_handle.rs b/jason/src/api/wasm/room_handle.rs new file mode 100644 index 000000000..2d0b3f593 --- /dev/null +++ b/jason/src/api/wasm/room_handle.rs @@ -0,0 +1,337 @@ +//! JS side handle to a [`Room`]. +//! +//! [`Room`]: room::Room + +use derive_more::{From, Into}; +use js_sys::Promise; +use wasm_bindgen::{prelude::*, JsValue}; +use wasm_bindgen_futures::future_to_promise; + +use crate::{ + api::{ + ConstraintsUpdateException, JasonError, MediaSourceKind, + MediaStreamSettings, + }, + room, +}; + +/// JS side handle to a [`Room`] where all the media happens. +/// +/// Like all handles it contains a weak reference to the object that is managed +/// by Rust, so its methods will fail if a weak reference could not be upgraded. +/// +/// [`Room`]: room::Room +#[wasm_bindgen] +#[derive(From, Into)] +pub struct RoomHandle(room::RoomHandle); + +#[wasm_bindgen] +impl RoomHandle { + /// Connects to a media server and joins a [`Room`] with the provided + /// authorization `token`. + /// + /// Authorization token has a fixed format: + /// `{{ Host URL }}/{{ Room ID }}/{{ Member ID }}?token={{ Auth Token }}` + /// (e.g. `wss://medea.com/MyConf1/Alice?token=777`). + /// + /// Establishes connection with media server (if it doesn't exist already). + /// + /// Effectively returns `Result<(), JasonError>`. + /// + /// # Errors + /// + /// - When `on_failed_local_media` callback is not set. + /// - When `on_connection_loss` callback is not set. + /// - When unable to connect to a media server. + /// + /// [`Room`]: room::Room + pub fn join(&self, token: String) -> Promise { + let this = self.0.clone(); + + future_to_promise(async move { + this.join(token).await.map_err(JasonError::from)?; + Ok(JsValue::UNDEFINED) + }) + } + + /// Sets callback, invoked when a new [`Connection`] with some remote + /// `Member` is established. + /// + /// [`Connection`]: crate::connection::Connection + pub fn on_new_connection( + &self, + cb: js_sys::Function, + ) -> Result<(), JsValue> { + self.0 + .on_new_connection(cb.into()) + .map_err(JasonError::from) + .map_err(JsValue::from) + } + + /// Sets `on_close` callback, invoked when this [`Room`] is closed, + /// providing a [`RoomCloseReason`]. + /// + /// [`Room`]: room::Room + /// [`RoomCloseReason`]: room::RoomCloseReason + pub fn on_close(&self, cb: js_sys::Function) -> Result<(), JsValue> { + self.0 + .on_close(cb.into()) + .map_err(JasonError::from) + .map_err(JsValue::from) + } + + /// Sets callback, invoked when a new [`LocalMediaTrack`] is added to this + /// [`Room`]. + /// + /// This might happen in such cases: + /// 1. Media server initiates a media request. + /// 2. `disable_audio`/`enable_video` is called. + /// 3. [`MediaStreamSettings`] is updated via `set_local_media_settings`. + /// + /// [`Room`]: room::Room + /// [`LocalMediaTrack`]: crate::api::LocalMediaTrack + pub fn on_local_track(&self, cb: js_sys::Function) -> Result<(), JsValue> { + self.0 + .on_local_track(cb.into()) + .map_err(JasonError::from) + .map_err(JsValue::from) + } + + /// Sets `on_failed_local_media` callback, invoked on local media + /// acquisition failures. + pub fn on_failed_local_media( + &self, + cb: js_sys::Function, + ) -> Result<(), JsValue> { + self.0 + .on_failed_local_media(cb.into()) + .map_err(JasonError::from) + .map_err(JsValue::from) + } + + /// Sets `on_connection_loss` callback, invoked when a connection with a + /// server is lost. + pub fn on_connection_loss( + &self, + cb: js_sys::Function, + ) -> Result<(), JsValue> { + self.0 + .on_connection_loss(cb.into()) + .map_err(JasonError::from) + .map_err(JsValue::from) + } + + /// Updates this [`Room`]s [`MediaStreamSettings`]. This affects all + /// [`PeerConnection`]s in this [`Room`]. If [`MediaStreamSettings`] is + /// configured for some [`Room`], then this [`Room`] can only send media + /// tracks that correspond to this settings. [`MediaStreamSettings`] + /// update will change media tracks in all sending peers, so that might + /// cause new [getUserMedia()][1] request. + /// + /// Media obtaining/injection errors are additionally fired to + /// `on_failed_local_media` callback. + /// + /// If `stop_first` set to `true` then affected [`LocalMediaTrack`]s will be + /// dropped before new [`MediaStreamSettings`] is applied. This is usually + /// required when changing video source device due to hardware limitations, + /// e.g. having an active track sourced from device `A` may hinder + /// [getUserMedia()][1] requests to device `B`. + /// + /// `rollback_on_fail` option configures [`MediaStreamSettings`] update + /// request to automatically rollback to previous settings if new settings + /// cannot be applied. + /// + /// If recovering from fail state isn't possible then affected media types + /// will be disabled. + /// + /// [`Room`]: room::Room + /// [`PeerConnection`]: crate::peer::PeerConnection + /// [`LocalMediaTrack`]: crate::api::LocalMediaTrack + /// [1]: https://tinyurl.com/w3-streams#dom-mediadevices-getusermedia + pub fn set_local_media_settings( + &self, + settings: &MediaStreamSettings, + stop_first: bool, + rollback_on_fail: bool, + ) -> Promise { + let this = self.0.clone(); + let settings = settings.clone(); + + future_to_promise(async move { + this.set_local_media_settings( + settings.into(), + stop_first, + rollback_on_fail, + ) + .await + .map_err(ConstraintsUpdateException::from)?; + Ok(JsValue::UNDEFINED) + }) + } + + /// Mutes outbound audio in this [`Room`]. + /// + /// [`Room`]: room::Room + pub fn mute_audio(&self) -> Promise { + let this = self.0.clone(); + + future_to_promise(async move { + this.mute_audio().await.map_err(JasonError::from)?; + Ok(JsValue::UNDEFINED) + }) + } + + /// Unmutes outbound audio in this [`Room`]. + /// + /// [`Room`]: room::Room + pub fn unmute_audio(&self) -> Promise { + let this = self.0.clone(); + + future_to_promise(async move { + this.unmute_audio().await.map_err(JasonError::from)?; + Ok(JsValue::UNDEFINED) + }) + } + + /// Mutes outbound video in this [`Room`]. + /// + /// [`Room`]: room::Room + pub fn mute_video(&self, source_kind: Option) -> Promise { + let this = self.0.clone(); + + future_to_promise(async move { + this.mute_video(source_kind.map(Into::into)) + .await + .map_err(JasonError::from)?; + Ok(JsValue::UNDEFINED) + }) + } + + /// Unmutes outbound video in this [`Room`]. + /// + /// [`Room`]: room::Room + pub fn unmute_video( + &self, + source_kind: Option, + ) -> Promise { + let this = self.0.clone(); + + future_to_promise(async move { + this.unmute_video(source_kind.map(Into::into)) + .await + .map_err(JasonError::from)?; + Ok(JsValue::UNDEFINED) + }) + } + + /// Disables outbound audio in this [`Room`]. + /// + /// [`Room`]: room::Room + pub fn disable_audio(&self) -> Promise { + let this = self.0.clone(); + + future_to_promise(async move { + this.disable_audio().await.map_err(JasonError::from)?; + Ok(JsValue::UNDEFINED) + }) + } + + /// Enables outbound audio in this [`Room`]. + /// + /// [`Room`]: room::Room + pub fn enable_audio(&self) -> Promise { + let this = self.0.clone(); + + future_to_promise(async move { + this.enable_audio().await.map_err(JasonError::from)?; + Ok(JsValue::UNDEFINED) + }) + } + + /// Disables outbound video. + /// + /// Affects only video with a specific [`MediaSourceKind`] if specified. + pub fn disable_video( + &self, + source_kind: Option, + ) -> Promise { + let this = self.0.clone(); + + future_to_promise(async move { + this.disable_video(source_kind.map(Into::into)) + .await + .map_err(JasonError::from) + .map_err(JasonError::from)?; + Ok(JsValue::UNDEFINED) + }) + } + + /// Enables outbound video. + /// + /// Affects only video with a specific [`MediaSourceKind`] if specified. + pub fn enable_video( + &self, + source_kind: Option, + ) -> Promise { + let this = self.0.clone(); + + future_to_promise(async move { + this.enable_video(source_kind.map(Into::into)) + .await + .map_err(JasonError::from)?; + Ok(JsValue::UNDEFINED) + }) + } + + /// Disables inbound audio in this [`Room`]. + /// + /// [`Room`]: room::Room + pub fn disable_remote_audio(&self) -> Promise { + let this = self.0.clone(); + + future_to_promise(async move { + this.disable_remote_audio() + .await + .map_err(JasonError::from)?; + Ok(JsValue::UNDEFINED) + }) + } + + /// Disables inbound video in this [`Room`]. + /// + /// [`Room`]: room::Room + pub fn disable_remote_video(&self) -> Promise { + let this = self.0.clone(); + + future_to_promise(async move { + this.disable_remote_video() + .await + .map_err(JasonError::from)?; + Ok(JsValue::UNDEFINED) + }) + } + + /// Enables inbound audio in this [`Room`]. + /// + /// [`Room`]: room::Room + pub fn enable_remote_audio(&self) -> Promise { + let this = self.0.clone(); + + future_to_promise(async move { + this.enable_remote_audio().await.map_err(JasonError::from)?; + Ok(JsValue::UNDEFINED) + }) + } + + /// Enables inbound video in this [`Room`]. + /// + /// [`Room`]: room::Room + pub fn enable_remote_video(&self) -> Promise { + let this = self.0.clone(); + + future_to_promise(async move { + this.enable_remote_video().await.map_err(JasonError::from)?; + Ok(JsValue::UNDEFINED) + }) + } +} diff --git a/jason/src/api/connection.rs b/jason/src/connection.rs similarity index 65% rename from jason/src/api/connection.rs rename to jason/src/connection.rs index 52b1952fe..f6c24814b 100644 --- a/jason/src/api/connection.rs +++ b/jason/src/connection.rs @@ -1,4 +1,7 @@ -//! Connection with specific remote `Member`. +//! [`Connection`] with a specific remote `Member`. + +// TODO: Remove when moving `JasonError` to `api::wasm`. +#![allow(clippy::missing_errors_doc)] use std::{ cell::{Cell, RefCell}, @@ -7,14 +10,15 @@ use std::{ }; use medea_client_api_proto::{ConnectionQualityScore, MemberId, PeerId}; -use wasm_bindgen::prelude::*; use crate::{ + api, media::track::remote, - utils::{Callback0, Callback1, HandlerDetachedError}, + platform, + utils::{HandlerDetachedError, JasonError}, }; -/// Service which manages [`Connection`]s with the remote `Member`s. +/// Service which manages [`Connection`]s with remote `Member`s. #[derive(Default)] pub struct Connections { /// Local [`PeerId`] to remote [`MemberId`]. @@ -23,15 +27,17 @@ pub struct Connections { /// Remote [`MemberId`] to [`Connection`] with that `Member`. connections: RefCell>, - /// Callback from JS side which will be invoked on remote `Member` media - /// stream arrival. - on_new_connection: Callback1, + /// Callback invoked on remote `Member` media arrival. + on_new_connection: platform::Callback, } impl Connections { /// Sets callback, which will be invoked when new [`Connection`] is /// established. - pub fn on_new_connection(&self, f: js_sys::Function) { + pub fn on_new_connection( + &self, + f: platform::Function, + ) { self.on_new_connection.set_func(f); } @@ -46,7 +52,7 @@ impl Connections { let is_new = !self.connections.borrow().contains_key(remote_member_id); if is_new { let con = Connection::new(remote_member_id.clone()); - self.on_new_connection.call(con.new_handle()); + self.on_new_connection.call1(con.new_handle()); self.connections .borrow_mut() .insert(remote_member_id.clone(), con); @@ -77,23 +83,21 @@ impl Connections { // `on_close` callback is invoked here and not in `Drop` // implementation so `ConnectionHandle` is available during // callback invocation. - connection.0.on_close.call(); + connection.0.on_close.call0(); } } } } } -/// Connection with a specific remote `Member`, that is used on JS side. +/// External handler to a [`Connection`] with a remote `Member`. /// /// Actually, represents a [`Weak`]-based handle to `InnerConnection`. -#[wasm_bindgen] pub struct ConnectionHandle(Weak); /// Actual data of a connection with a specific remote `Member`. /// -/// Shared between JS side ([`ConnectionHandle`]) and Rust side -/// ([`Connection`]). +/// Shared between external [`ConnectionHandle`] and Rust side [`Connection`]. struct InnerConnection { /// Remote `Member` ID. remote_id: MemberId, @@ -101,46 +105,46 @@ struct InnerConnection { /// Current [`ConnectionQualityScore`] of this [`Connection`]. quality_score: Cell>, - /// JS callback, that will be invoked when [`remote::Track`] is - /// received. - on_remote_track_added: Callback1, + /// Callback invoked when a [`remote::Track`] is received. + on_remote_track_added: platform::Callback, - /// JS callback, that will be invoked when [`ConnectionQualityScore`] will - /// be updated. - on_quality_score_update: Callback1, + /// Callback invoked when a [`ConnectionQualityScore`] is updated. + on_quality_score_update: platform::Callback, - /// JS callback, that will be invoked when this connection is closed. - on_close: Callback0, + /// Callback invoked when this [`Connection`] is closed. + on_close: platform::Callback<()>, } -#[wasm_bindgen] impl ConnectionHandle { - /// Sets callback, which will be invoked when this `Connection` will close. - pub fn on_close(&self, f: js_sys::Function) -> Result<(), JsValue> { + /// Sets callback, invoked when this `Connection` will close. + pub fn on_close( + &self, + f: platform::Function<()>, + ) -> Result<(), JasonError> { upgrade_or_detached!(self.0).map(|inner| inner.on_close.set_func(f)) } /// Returns remote `Member` ID. - pub fn get_remote_member_id(&self) -> Result { + pub fn get_remote_member_id(&self) -> Result { upgrade_or_detached!(self.0).map(|inner| inner.remote_id.0.clone()) } - /// Sets callback, which will be invoked when new [`remote::Track`] will be - /// added to this [`Connection`]. + /// Sets callback, invoked when a new [`remote::Track`] will is added to + /// this [`Connection`]. pub fn on_remote_track_added( &self, - f: js_sys::Function, - ) -> Result<(), JsValue> { + f: platform::Function, + ) -> Result<(), JasonError> { upgrade_or_detached!(self.0) .map(|inner| inner.on_remote_track_added.set_func(f)) } - /// Sets callback, which will be invoked when connection quality score will - /// be updated by server. + /// Sets callback, invoked when a connection quality score is updated by + /// a server. pub fn on_quality_score_update( &self, - f: js_sys::Function, - ) -> Result<(), JsValue> { + f: platform::Function, + ) -> Result<(), JasonError> { upgrade_or_detached!(self.0) .map(|inner| inner.on_quality_score_update.set_func(f)) } @@ -157,19 +161,19 @@ impl Connection { Self(Rc::new(InnerConnection { remote_id, quality_score: Cell::default(), - on_quality_score_update: Callback1::default(), - on_close: Callback0::default(), - on_remote_track_added: Callback1::default(), + on_quality_score_update: platform::Callback::default(), + on_close: platform::Callback::default(), + on_remote_track_added: platform::Callback::default(), })) } - /// Invokes `on_remote_track_added` JS callback with the provided + /// Invokes `on_remote_track_added` callback with the provided /// [`remote::Track`]. pub fn add_remote_track(&self, track: remote::Track) { - self.0.on_remote_track_added.call(track); + self.0.on_remote_track_added.call1(track); } - /// Creates new [`ConnectionHandle`] for using [`Connection`] on JS side. + /// Creates a new external handle to this [`Connection`]. #[inline] pub fn new_handle(&self) -> ConnectionHandle { ConnectionHandle(Rc::downgrade(&self.0)) @@ -178,7 +182,7 @@ impl Connection { /// Updates [`ConnectionQualityScore`] of this [`Connection`]. pub fn update_quality_score(&self, score: ConnectionQualityScore) { if self.0.quality_score.replace(Some(score)) != Some(score) { - self.0.on_quality_score_update.call(score as u8); + self.0.on_quality_score_update.call1(score as u8); } } } diff --git a/jason/src/jason.rs b/jason/src/jason.rs new file mode 100644 index 000000000..a451fe208 --- /dev/null +++ b/jason/src/jason.rs @@ -0,0 +1,137 @@ +//! General library interface. + +use futures::FutureExt as _; +use std::{cell::RefCell, rc::Rc}; + +use crate::platform; + +use crate::{ + media::{MediaManager, MediaManagerHandle}, + room::{Room, RoomHandle}, + rpc::{ + ClientDisconnect, RpcSession, WebSocketRpcClient, WebSocketRpcSession, + }, +}; + +/// General library interface. +/// +/// Responsible for managing shared transports, local media and room +/// initialization. +pub struct Jason(Rc>); + +/// Inner representation if a [`Jason`]. +struct Inner { + /// [`Jason`]s [`MediaManager`]. + /// + /// It's shared across [`Room`]s since [`MediaManager`] contains media + /// tracks that can be used by multiple [`Room`]s. + media_manager: Rc, + + /// [`Room`]s maintained by this [`Jason`] instance. + rooms: Vec, + + /// Connection with a media server. + /// + /// Only one [`WebSocketRpcClient`] is supported at the moment. + rpc: Rc, +} + +impl Jason { + /// Instantiates a new [`Jason`] interface to interact with this library. + pub fn new() -> Self { + Self::with_rpc_client(Rc::new(WebSocketRpcClient::new(Box::new( + |url| { + Box::pin(async move { + let ws = platform::WebSocketRpcTransport::new(url) + .await + .map_err(|e| tracerr::new!(e))?; + Ok(Rc::new(ws) as Rc) + }) + }, + )))) + } + + /// Creates a new [`Room`] and returns its [`RoomHandle`]. + #[inline] + #[must_use] + pub fn init_room(&self) -> RoomHandle { + let rpc = Rc::clone(&self.0.borrow().rpc); + self.inner_init_room(WebSocketRpcSession::new(rpc)) + } + + /// Returns a [`MediaManagerHandle`]. + #[inline] + #[must_use] + pub fn media_manager(&self) -> MediaManagerHandle { + self.0.borrow().media_manager.new_handle() + } + + /// Closes the provided [`RoomHandle`]. + #[allow(clippy::needless_pass_by_value)] + pub fn close_room(&self, room_to_delete: RoomHandle) { + self.0.borrow_mut().rooms.retain(|room| { + let should_be_closed = room.inner_ptr_eq(&room_to_delete); + if should_be_closed { + room.set_close_reason(ClientDisconnect::RoomClosed.into()); + } + + !should_be_closed + }); + } + + /// Drops this [`Jason`] API object, so all the related objects (rooms, + /// connections, streams, etc.) respectively. All objects related to this + /// [`Jason`] API object will be detached (you will still hold them, but + /// unable to use). + pub fn dispose(self) { + self.0.borrow_mut().rooms.drain(..).for_each(|room| { + room.close(ClientDisconnect::RoomClosed.into()); + }); + } + + /// Returns a new [`Jason`] with the provided [`WebSocketRpcClient`]. + #[inline] + pub fn with_rpc_client(rpc: Rc) -> Self { + Self(Rc::new(RefCell::new(Inner { + rpc, + rooms: Vec::new(), + media_manager: Rc::new(MediaManager::default()), + }))) + } + + /// Returns a [`RoomHandle`] for an initialized [`Room`]. + pub fn inner_init_room(&self, rpc: Rc) -> RoomHandle { + let on_normal_close = rpc.on_normal_close(); + let room = Room::new(rpc, Rc::clone(&self.0.borrow().media_manager)); + + let weak_room = room.downgrade(); + let weak_inner = Rc::downgrade(&self.0); + platform::spawn(on_normal_close.map(move |reason| { + (|| { + let room = weak_room.upgrade()?; + let inner = weak_inner.upgrade()?; + let mut inner = inner.borrow_mut(); + let index = inner.rooms.iter().position(|r| r.ptr_eq(&room)); + if let Some(index) = index { + inner.rooms.remove(index).close(reason); + } + if inner.rooms.is_empty() { + inner.media_manager = Rc::default(); + } + + Some(()) + })(); + })); + + let handle = room.new_handle(); + self.0.borrow_mut().rooms.push(room); + handle + } +} + +impl Default for Jason { + #[inline] + fn default() -> Self { + Self::new() + } +} diff --git a/jason/src/lib.rs b/jason/src/lib.rs index dd441b60c..a27341251 100644 --- a/jason/src/lib.rs +++ b/jason/src/lib.rs @@ -1,4 +1,4 @@ -//! Client library for Medea media server. +//! Client library for [Medea] media server. //! //! [Medea]: https://github.com/instrumentisto/medea @@ -11,34 +11,11 @@ #[macro_use] pub mod utils; - pub mod api; +pub mod connection; +pub mod jason; pub mod media; pub mod peer; +pub mod platform; +pub mod room; pub mod rpc; - -// When the `console_error_panic_hook` feature is enabled, we can call the -// `set_panic_hook` function at least once during initialization, and then -// we will get better error messages if our code ever panics. -// -// For more details see: -// https://github.com/rustwasm/console_error_panic_hook#readme -#[cfg(feature = "console_error_panic_hook")] -pub use console_error_panic_hook::set_once as set_panic_hook; - -#[doc(inline)] -pub use self::{ - api::{ConnectionHandle, Jason, RoomCloseReason, RoomHandle}, - media::{ - track::{local::JsTrack, remote::Track}, - AudioTrackConstraints, DeviceVideoTrackConstraints, - DisplayVideoTrackConstraints, FacingMode, JsMediaSourceKind, MediaKind, - MediaStreamSettings, - }, -}; - -// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global -// allocator. -#[cfg(feature = "wee_alloc")] -#[global_allocator] -static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; diff --git a/jason/src/media/constraints.rs b/jason/src/media/constraints.rs index b434ab024..2db251dee 100644 --- a/jason/src/media/constraints.rs +++ b/jason/src/media/constraints.rs @@ -1,26 +1,44 @@ -// TODO: Split to multiple modules. +//! Media tracks and streams constraints functionality. use std::{ cell::{Cell, RefCell}, rc::Rc, }; -use derive_more::{AsRef, Into}; use medea_client_api_proto::{ AudioSettings as ProtoAudioConstraints, MediaSourceKind, MediaType as ProtoTrackConstraints, MediaType, VideoSettings, }; -use wasm_bindgen::prelude::*; -use web_sys as sys; use crate::{ - media::MediaKind, + media::{track::MediaStreamTrackState, MediaKind}, peer::{ media_exchange_state, mute_state, LocalStreamUpdateCriteria, MediaState, }, - utils::get_property_by_name, + platform, }; +/// Describes directions that a camera can face, as seen from a user's +/// perspective. +/// +/// Representation of a [VideoFacingModeEnum][1]. +/// +/// [1]: https://w3.org/TR/mediacapture-streams/#dom-videofacingmodeenum +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum FacingMode { + /// Facing towards a user (a self-view camera). + User, + + /// Facing away from a user (viewing an environment). + Environment, + + /// Facing to the left of a user. + Left, + + /// Facing to the right of a user. + Right, +} + /// Local media stream for injecting into new created [`PeerConnection`]s. /// /// [`PeerConnection`]: crate::peer::PeerConnection @@ -188,12 +206,15 @@ impl Default for AudioMediaTracksSettings { } } -/// Returns `true` if provided [`sys::MediaStreamTrack`] basically satisfies any -/// constraints with a provided [`MediaKind`]. +/// Indicates whether the provided [`platform::MediaStreamTrack`] satisfies any +/// constraints with the provided [`MediaKind`]. #[inline] -fn satisfies_track(track: &sys::MediaStreamTrack, kind: MediaKind) -> bool { - track.kind() == kind.as_str() - && track.ready_state() == sys::MediaStreamTrackState::Live +#[must_use] +fn satisfies_track( + track: &platform::MediaStreamTrack, + kind: MediaKind, +) -> bool { + track.kind() == kind && track.ready_state() == MediaStreamTrackState::Live } /// [MediaStreamConstraints][1] for the video media type. @@ -214,7 +235,7 @@ pub struct VideoTrackConstraints { /// actions by [`Room`]. This flag can't be changed by /// [`MediaStreamSettings`] updating. /// - /// [`Room`]: crate::api::Room + /// [`Room`]: crate::room::Room enabled: bool, /// Indicator whether video should be muted. @@ -235,7 +256,7 @@ impl VideoTrackConstraints { /// Returns `true` if this [`VideoTrackConstraints`] are enabled by the /// [`Room`] and constrained with [`VideoTrackConstraints::constraints`]. /// - /// [`Room`]: crate::api::Room + /// [`Room`]: crate::room::Room #[inline] fn enabled(&self) -> bool { self.enabled && self.is_constrained() @@ -270,11 +291,16 @@ impl VideoTrackConstraints { } impl VideoTrackConstraints { - /// Indicates whether the provided [`sys::MediaStreamTrack`] satisfies + /// Indicates whether the provided [`platform::MediaStreamTrack`] satisfies /// device [`VideoTrackConstraints::constraints`]. /// /// Returns `false` if [`VideoTrackConstraints::constraints`] is not set. - fn satisfies(&self, track: &sys::MediaStreamTrack) -> bool { + #[inline] + #[must_use] + pub fn satisfies>( + &self, + track: T, + ) -> bool { self.constraints .as_ref() .filter(|_| self.enabled()) @@ -283,11 +309,16 @@ impl VideoTrackConstraints { } impl VideoTrackConstraints { - /// Indicates whether the provided [`sys::MediaStreamTrack`] satisfies + /// Indicates whether the provided [`platform::MediaStreamTrack`] satisfies /// device [`VideoTrackConstraints::constraints`]. /// /// Returns `false` if [`VideoTrackConstraints::constraints`] is not set. - fn satisfies(&self, track: &sys::MediaStreamTrack) -> bool { + #[inline] + #[must_use] + pub fn satisfies>( + &self, + track: T, + ) -> bool { self.constraints .as_ref() .filter(|_| self.enabled()) @@ -298,7 +329,6 @@ impl VideoTrackConstraints { /// [MediaStreamConstraints][1] wrapper. /// /// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediastreamconstraints -#[wasm_bindgen] #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct MediaStreamSettings { /// [MediaStreamConstraints][1] for the audio media type. @@ -317,10 +347,10 @@ pub struct MediaStreamSettings { display_video: VideoTrackConstraints, } -#[wasm_bindgen] impl MediaStreamSettings { /// Creates new [`MediaStreamSettings`] with none constraints configured. - #[wasm_bindgen(constructor)] + #[inline] + #[must_use] pub fn new() -> Self { Self { audio: AudioMediaTracksSettings { @@ -341,9 +371,9 @@ impl MediaStreamSettings { } } - /// Specifies the nature and settings of the audio [MediaStreamTrack][1]. - /// - /// [1]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack + /// Specifies the nature and settings of the audio + /// [`platform::MediaStreamTrack`]. + #[inline] pub fn audio(&mut self, constraints: AudioTrackConstraints) { self.audio.enabled = true; self.audio.constraints = constraints; @@ -351,33 +381,35 @@ impl MediaStreamSettings { /// Set constraints that will be used to obtain local video sourced from /// media device. + #[inline] pub fn device_video(&mut self, constraints: DeviceVideoTrackConstraints) { self.device_video.set(constraints); } /// Set constraints that will be used to capture local video from user /// display. + #[inline] pub fn display_video(&mut self, constraints: DisplayVideoTrackConstraints) { self.display_video.set(constraints); } } impl MediaStreamSettings { - /// Returns `true` if provided [`sys::MediaStreamTrack`] satisfies some of - /// the [`VideoTrackConstraints`] from this [`MediaStreamSettings`]. + /// Indicates whether the provided [`platform::MediaStreamTrack`] satisfies + /// some of the [`VideoTrackConstraints`] from this [`MediaStreamSettings`]. /// /// Unconstrains [`VideoTrackConstraints`] which this - /// [`sys::MediaStreamTrack`] satisfies by calling - /// [`VideoTrackConstraints::unconstrain`]. + /// [`platform::MediaStreamTrack`] satisfies by calling a + /// [`VideoTrackConstraints::unconstrain()`]. + #[must_use] pub fn unconstrain_if_satisfies_video(&mut self, track: T) -> bool where - T: AsRef, + T: AsRef, { - let track = track.as_ref(); - if self.device_video.satisfies(track.as_ref()) { + if self.device_video.satisfies(&track) { self.device_video.unconstrain(); true - } else if self.display_video.satisfies(track.as_ref()) { + } else if self.display_video.satisfies(&track) { self.display_video.unconstrain(); true } else { @@ -638,20 +670,20 @@ pub enum MultiSourceTracksConstraints { /// Only [getUserMedia()][1] request is required. /// /// [1]: https://tinyurl.com/w3-streams#dom-mediadevices-getusermedia - Device(sys::MediaStreamConstraints), + Device(platform::MediaStreamConstraints), /// Only [getDisplayMedia()][1] request is required. /// /// [1]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia - Display(sys::DisplayMediaStreamConstraints), + Display(platform::DisplayMediaStreamConstraints), /// Both [getUserMedia()][1] and [getDisplayMedia()][2] are required. /// /// [1]: https://tinyurl.com/w3-streams#dom-mediadevices-getusermedia /// [2]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia DeviceAndDisplay( - sys::MediaStreamConstraints, - sys::DisplayMediaStreamConstraints, + platform::MediaStreamConstraints, + platform::DisplayMediaStreamConstraints, ), } @@ -669,11 +701,8 @@ impl From for Option { constraints.device_video.constraints { device_cons - .get_or_insert_with(sys::MediaStreamConstraints::new) - .video( - &sys::MediaTrackConstraints::from(device_video_cons) - .into(), - ); + .get_or_insert_with(platform::MediaStreamConstraints::new) + .video(device_video_cons); } } if is_display_video_enabled { @@ -681,22 +710,16 @@ impl From for Option { constraints.display_video.constraints { display_cons - .get_or_insert_with(sys::DisplayMediaStreamConstraints::new) - .video( - &sys::MediaTrackConstraints::from(display_video_cons) - .into(), - ); + .get_or_insert_with( + platform::DisplayMediaStreamConstraints::new, + ) + .video(display_video_cons); } } if is_device_audio_enabled { device_cons - .get_or_insert_with(sys::MediaStreamConstraints::new) - .audio( - &sys::MediaTrackConstraints::from( - constraints.audio.constraints, - ) - .into(), - ); + .get_or_insert_with(platform::MediaStreamConstraints::new) + .audio(constraints.audio.constraints); } match (device_cons, display_cons) { @@ -734,7 +757,7 @@ pub enum VideoSource { } impl VideoSource { - /// Returns importance of this [`VideoSource`]. + /// Returns an importance of this [`VideoSource`]. /// /// If this [`VideoSource`] is important then without this [`VideoSource`] /// call session can't be started. @@ -746,12 +769,14 @@ impl VideoSource { } } - /// Checks if provided [MediaStreamTrack][1] satisfies this [`VideoSource`]. - /// - /// [1]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack + /// Checks whether the provided [`platform::MediaStreamTrack`] satisfies + /// this [`VideoSource`]. #[inline] - pub fn satisfies>(&self, track: T) -> bool { - let track = track.as_ref(); + #[must_use] + pub fn satisfies>( + &self, + track: T, + ) -> bool { match self { VideoSource::Display(display) => display.satisfies(&track), VideoSource::Device(device) => device.satisfies(track), @@ -792,21 +817,26 @@ pub enum TrackConstraints { } impl TrackConstraints { - /// Checks if provided [MediaStreamTrack][1] satisfies this - /// [`TrackConstraints`]. - /// - /// [1]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack - pub fn satisfies>(&self, track: T) -> bool { + /// Checks whether the provided [`platform::MediaStreamTrack`] satisfies + /// these [`TrackConstraints`]. + #[inline] + #[must_use] + pub fn satisfies>( + &self, + track: T, + ) -> bool { match self { Self::Audio(audio) => audio.satisfies(&track), Self::Video(video) => video.satisfies(&track), } } - /// Returns importance of this [`TrackConstraints`]. + /// Returns an importance of these [`TrackConstraints`]. /// - /// If this [`TrackConstraints`] is important then without this - /// [`TrackConstraints`] call session can't be started. + /// If these [`TrackConstraints`] are important then without them a session + /// call can't be started. + #[inline] + #[must_use] pub fn required(&self) -> bool { match self { TrackConstraints::Video(video) => video.required(), @@ -814,7 +844,9 @@ impl TrackConstraints { } } - /// Returns this [`TrackConstraints`] media source kind. + /// Returns these [`TrackConstraints`] media source kind. + #[inline] + #[must_use] pub fn media_source_kind(&self) -> MediaSourceKind { match &self { TrackConstraints::Audio(_) => MediaSourceKind::Device, @@ -828,6 +860,8 @@ impl TrackConstraints { } /// Returns [`MediaKind`] of these [`TrackConstraints`]. + #[inline] + #[must_use] pub fn media_kind(&self) -> MediaKind { match &self { TrackConstraints::Audio(_) => MediaKind::Audio, @@ -837,6 +871,7 @@ impl TrackConstraints { } impl From for TrackConstraints { + #[inline] fn from(caps: ProtoTrackConstraints) -> Self { match caps { ProtoTrackConstraints::Audio(audio) => Self::Audio(audio.into()), @@ -845,20 +880,11 @@ impl From for TrackConstraints { } } -// TODO: Its gonna be a nightmare if we will add all possible constraints, -// especially if we will support all that `exact`/`min`/`max`/`ideal` -// stuff, will need major refactoring then. -// TODO: Using reflection to get fields values is pure evil, but there are no -// getters for WebIDL's dictionaries, should be wrapped or improved in -// wasm-bindgen. - /// Constraints applicable to audio tracks. -#[wasm_bindgen] #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct AudioTrackConstraints { - /// The identifier of the device generating the content for the media - /// track. - device_id: Option>, + /// Identifier of the device generating the content for the media track. + pub device_id: Option>, /// Importance of this [`AudioTrackConstraints`]. /// @@ -867,37 +893,40 @@ pub struct AudioTrackConstraints { required: bool, } -#[wasm_bindgen] impl AudioTrackConstraints { /// Creates new [`AudioTrackConstraints`] with none constraints configured. - #[wasm_bindgen(constructor)] + #[inline] + #[must_use] pub fn new() -> Self { Self::default() } - /// Sets exact [deviceId][1] constraint. + /// Sets an exact [deviceId][1] constraint. /// /// [1]: https://w3.org/TR/mediacapture-streams/#def-constraint-deviceId + #[inline] pub fn device_id(&mut self, device_id: String) { - self.device_id = Some(ConstrainString::Exact(DeviceId(device_id))); + self.device_id = Some(ConstrainString::Exact(device_id)); } -} -impl AudioTrackConstraints { - /// Checks if provided [MediaStreamTrack][1] satisfies constraints - /// contained. - /// - /// [1]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack - pub fn satisfies>(&self, track: T) -> bool { + /// Checks whether the provided [`platform::MediaStreamTrack`] satisfies + /// contained constraints. + #[inline] + #[must_use] + pub fn satisfies>( + &self, + track: T, + ) -> bool { let track = track.as_ref(); satisfies_track(track, MediaKind::Audio) - && ConstrainString::satisfies(&self.device_id, track) + && ConstrainString::satisfies(&self.device_id, &track.device_id()) // TODO returns Result } - /// Merges this [`AudioTrackConstraints`] with `another` one, meaning that - /// if some constraint is not set on this one, then it will be applied from - /// `another`. + /// Merges these [`AudioTrackConstraints`] with `another` ones, meaning that + /// if some constraints are not set on these ones, then they will be applied + /// from `another`. + #[inline] pub fn merge(&mut self, another: AudioTrackConstraints) { if self.device_id.is_none() && another.device_id.is_some() { self.device_id = another.device_id; @@ -907,10 +936,12 @@ impl AudioTrackConstraints { } } - /// Returns importance of this [`AudioTrackConstraints`]. + /// Returns an importance of these [`AudioTrackConstraints`]. /// - /// If this [`AudioTrackConstraints`] is important then without this - /// [`AudioTrackConstraints`] call session can't be started. + /// If these [`AudioTrackConstraints`] are important then without them a + /// session call can't be started. + #[inline] + #[must_use] pub fn required(&self) -> bool { self.required } @@ -926,81 +957,8 @@ impl From for AudioTrackConstraints { } } -impl From for sys::MediaTrackConstraints { - fn from(track_constraints: AudioTrackConstraints) -> Self { - let mut constraints = Self::new(); - - if let Some(device_id) = track_constraints.device_id { - constraints.device_id(&sys::ConstrainDomStringParameters::from( - &device_id, - )); - } - - constraints - } -} - -/// Constraints applicable to [MediaStreamTrack][1]. -/// -/// Constraints provide a general control surface that allows applications to -/// both select an appropriate source for a track and, once selected, to -/// influence how a source operates. -/// -/// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -trait Constraint { - /// Constrained parameter field name. - const TRACK_SETTINGS_FIELD_NAME: &'static str; -} - -/// The identifier of the device generating the content of the -/// [MediaStreamTrack][1]. -/// -/// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -#[derive(AsRef, Clone, Debug, Eq, PartialEq)] -#[as_ref(forward)] -struct DeviceId(String); - -impl Constraint for DeviceId { - const TRACK_SETTINGS_FIELD_NAME: &'static str = "deviceId"; -} - -/// Height, in pixels, of the video. -#[derive(Clone, Copy, Debug, Eq, Into, PartialEq)] -struct Height(u32); - -impl Constraint for Height { - const TRACK_SETTINGS_FIELD_NAME: &'static str = "height"; -} - -/// Width, in pixels, of the video. -#[derive(Clone, Copy, Debug, Eq, Into, PartialEq)] -struct Width(u32); - -impl Constraint for Width { - const TRACK_SETTINGS_FIELD_NAME: &'static str = "width"; -} - -/// Describes the directions that the camera can face, as seen from the user's -/// perspective. Representation of [VideoFacingModeEnum][1]. -/// -/// [1]: https://w3.org/TR/mediacapture-streams/#dom-videofacingmodeenum -#[wasm_bindgen] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum FacingMode { - /// Facing toward the user (a self-view camera). - User, - - /// Facing away from the user (viewing the environment). - Environment, - - /// Facing to the left of the user. - Left, - - /// Facing to the right of the user. - Right, -} - impl AsRef for FacingMode { + #[inline] fn as_ref(&self) -> &str { match self { FacingMode::User => "user", @@ -1011,69 +969,37 @@ impl AsRef for FacingMode { } } -impl Constraint for FacingMode { - const TRACK_SETTINGS_FIELD_NAME: &'static str = "facingMode"; -} - -/// Representation of the [ConstrainULong][1]. Underlying value must fit in -/// `[0, 4294967295]` range. +/// Representation of a [ConstrainULong][1]. +/// +/// Underlying value must fit in a `[0, 4294967295]` range. /// /// [1]: https://tinyurl.com/w3-streams#dom-constrainulong #[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum ConstrainU32 { +pub enum ConstrainU32 { /// Must be the parameter's value. - Exact(T), + Exact(u32), /// Should be used if possible. - Ideal(T), + Ideal(u32), /// Parameter's value must be in this range. - Range(T, T), + Range(u32, u32), } -impl> ConstrainU32 { +impl ConstrainU32 { // It's up to `::TRACK_SETTINGS_FIELD_NAME` to guarantee // that such casts are safe. - #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] - fn satisfies(this: Option, track: &sys::MediaStreamTrack) -> bool { + #[must_use] + fn satisfies(this: Option, setting: Option) -> bool { match this { None | Some(ConstrainU32::Ideal(_)) => true, - Some(ConstrainU32::Exact(exact)) => get_property_by_name( - &track.get_settings(), - T::TRACK_SETTINGS_FIELD_NAME, - |v| v.as_f64().map(|v| v as u32), - ) - .map_or(false, |val| val == exact.into()), - Some(ConstrainU32::Range(start, end)) => get_property_by_name( - &track.get_settings(), - T::TRACK_SETTINGS_FIELD_NAME, - |v| v.as_f64().map(|v| v as u32), - ) - .map_or(false, |val| val >= start.into() && val <= end.into()), - } - } -} - -impl> From> - for sys::ConstrainDoubleRange -{ - fn from(from: ConstrainU32) -> Self { - let mut constraint = sys::ConstrainDoubleRange::new(); - match from { - ConstrainU32::Exact(val) => { - constraint.exact(f64::from(val.into())); + Some(ConstrainU32::Exact(exact)) => { + setting.map_or(false, |val| val == exact) } - ConstrainU32::Ideal(val) => { - constraint.ideal(f64::from(val.into())); - } - ConstrainU32::Range(min, max) => { - constraint - .min(f64::from(min.into())) - .max(f64::from(max.into())); + Some(ConstrainU32::Range(start, end)) => { + setting.map_or(false, |val| val >= start && val <= end) } } - - constraint } } @@ -1084,46 +1010,28 @@ impl> From> /// /// [1]: https://w3.org/TR/mediacapture-streams/#dom-constraindomstring #[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum ConstrainString { +pub enum ConstrainString { + /// Exact value required for this property. Exact(T), + + /// Ideal (target) value for this property. Ideal(T), } -impl> ConstrainString { - fn satisfies(this: &Option, track: &sys::MediaStreamTrack) -> bool { +impl> ConstrainString { + #[must_use] + fn satisfies(this: &Option, setting: &Option) -> bool { match this { None | Some(ConstrainString::Ideal(_)) => true, - Some(ConstrainString::Exact(constrain)) => get_property_by_name( - &track.get_settings(), - T::TRACK_SETTINGS_FIELD_NAME, - |v| v.as_string(), - ) - .map_or(false, |id| id.as_str() == constrain.as_ref()), + Some(ConstrainString::Exact(constrain)) => setting + .as_ref() + .map_or(false, |val| val.as_ref() == constrain.as_ref()), } } } -impl> From<&ConstrainString> - for sys::ConstrainDomStringParameters -{ - fn from(from: &ConstrainString) -> Self { - let mut constraint = sys::ConstrainDomStringParameters::new(); - match from { - ConstrainString::Exact(val) => { - constraint.exact(&JsValue::from_str(val.as_ref())) - } - ConstrainString::Ideal(val) => { - constraint.ideal(&JsValue::from_str(val.as_ref())) - } - }; - - constraint - } -} - /// Constraints applicable to video tracks that are sourced from some media /// device. -#[wasm_bindgen] #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct DeviceVideoTrackConstraints { /// Importance of this [`DeviceVideoTrackConstraints`]. @@ -1132,71 +1040,26 @@ pub struct DeviceVideoTrackConstraints { /// session can't be started. required: bool, - /// The identifier of the device generating the content for the media - /// track. - device_id: Option>, + /// Identifier of the device generating the content for the media track. + pub device_id: Option>, /// Describes the directions that the camera can face, as seen from the /// user's perspective. - facing_mode: Option>, + pub facing_mode: Option>, /// Height of the video in pixels. - height: Option>, + pub height: Option, /// Width of the video in pixels. - width: Option>, -} - -impl DeviceVideoTrackConstraints { - /// Checks if provided [MediaStreamTrack][1] satisfies - /// [`DeviceVideoTrackConstraints`] contained. - /// - /// [1]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack - pub fn satisfies(&self, track: &sys::MediaStreamTrack) -> bool { - satisfies_track(track, MediaKind::Video) - && ConstrainString::satisfies(&self.device_id, track) - && ConstrainString::satisfies(&self.facing_mode, track) - && ConstrainU32::satisfies(self.height, track) - && ConstrainU32::satisfies(self.width, track) - && !guess_is_from_display(&track) - } - - /// Merges this [`DeviceVideoTrackConstraints`] with `another` one , meaning - /// that if some constraint is not set on this one, then it will be applied - /// from `another`. - pub fn merge(&mut self, another: DeviceVideoTrackConstraints) { - if self.device_id.is_none() && another.device_id.is_some() { - self.device_id = another.device_id; - } - if !self.required && another.required { - self.required = another.required; - } - if self.facing_mode.is_none() && another.facing_mode.is_some() { - self.facing_mode = another.facing_mode; - } - if self.height.is_none() && another.height.is_some() { - self.height = another.height; - } - if self.width.is_none() && another.width.is_some() { - self.width = another.width; - } - } - - /// Returns importance of this [`DeviceVideoTrackConstraints`]. - /// - /// If this [`DeviceVideoTrackConstraints`] is important then without this - /// [`DeviceVideoTrackConstraints`] call session can't be started. - pub fn required(&self) -> bool { - self.required - } + pub width: Option, } /// Constraints applicable to video tracks that are sourced from screen-capture. -#[wasm_bindgen] impl DeviceVideoTrackConstraints { /// Creates new [`DeviceVideoTrackConstraints`] with none constraints /// configured. - #[wasm_bindgen(constructor)] + #[inline] + #[must_use] pub fn new() -> Self { Self::default() } @@ -1204,13 +1067,15 @@ impl DeviceVideoTrackConstraints { /// Sets exact [deviceId][1] constraint. /// /// [1]: https://w3.org/TR/mediacapture-streams/#def-constraint-deviceId + #[inline] pub fn device_id(&mut self, device_id: String) { - self.device_id = Some(ConstrainString::Exact(DeviceId(device_id))); + self.device_id = Some(ConstrainString::Exact(device_id)); } /// Sets exact [facingMode][1] constraint. /// /// [1]: https://w3.org/TR/mediacapture-streams/#dom-constraindomstring + #[inline] pub fn exact_facing_mode(&mut self, facing_mode: FacingMode) { self.facing_mode = Some(ConstrainString::Exact(facing_mode)); } @@ -1218,6 +1083,7 @@ impl DeviceVideoTrackConstraints { /// Sets ideal [facingMode][1] constraint. /// /// [1]: https://w3.org/TR/mediacapture-streams/#dom-constraindomstring + #[inline] pub fn ideal_facing_mode(&mut self, facing_mode: FacingMode) { self.facing_mode = Some(ConstrainString::Ideal(facing_mode)); } @@ -1225,147 +1091,151 @@ impl DeviceVideoTrackConstraints { /// Sets exact [`height`][1] constraint. /// /// [1]: https://tinyurl.com/w3-streams#def-constraint-height + #[inline] pub fn exact_height(&mut self, height: u32) { - self.height = Some(ConstrainU32::Exact(Height(height))); + self.height = Some(ConstrainU32::Exact(height)); } /// Sets ideal [`height`][1] constraint. /// /// [1]: https://tinyurl.com/w3-streams#def-constraint-height + #[inline] pub fn ideal_height(&mut self, height: u32) { - self.height = Some(ConstrainU32::Ideal(Height(height))); + self.height = Some(ConstrainU32::Ideal(height)); } /// Sets range of [`height`][1] constraint. /// /// [1]: https://tinyurl.com/w3-streams#def-constraint-height + #[inline] pub fn height_in_range(&mut self, min: u32, max: u32) { - self.height = Some(ConstrainU32::Range(Height(min), Height(max))); + self.height = Some(ConstrainU32::Range(min, max)); } /// Sets exact [`width`][1] constraint. /// /// [1]: https://tinyurl.com/w3-streams#def-constraint-width + #[inline] pub fn exact_width(&mut self, width: u32) { - self.width = Some(ConstrainU32::Exact(Width(width))); + self.width = Some(ConstrainU32::Exact(width)); } /// Sets ideal [`width`][1] constraint. /// /// [1]: https://tinyurl.com/w3-streams#def-constraint-width + #[inline] pub fn ideal_width(&mut self, width: u32) { - self.width = Some(ConstrainU32::Ideal(Width(width))); + self.width = Some(ConstrainU32::Ideal(width)); } /// Sets range of [`width`][1] constraint. /// /// [1]: https://tinyurl.com/w3-streams#def-constraint-width + #[inline] pub fn width_in_range(&mut self, min: u32, max: u32) { - self.width = Some(ConstrainU32::Range(Width(min), Width(max))); + self.width = Some(ConstrainU32::Range(min, max)); } -} - -/// Constraints applicable to video tracks sourced from screen capture. -#[wasm_bindgen] -#[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct DisplayVideoTrackConstraints { - /// Importance of this [`DisplayVideoTrackConstraints`]. - /// - /// If `true` then without this [`DisplayVideoTrackConstraints`] call - /// session can't be started. - required: bool, -} -impl DisplayVideoTrackConstraints { - /// Checks if provided [MediaStreamTrack][1] satisfies - /// [`DisplayVideoTrackConstraints`] contained. - /// - /// [1]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack - #[allow(clippy::unused_self)] - #[inline] - pub fn satisfies(&self, track: &sys::MediaStreamTrack) -> bool { + /// Checks whether the provided [`platform::MediaStreamTrack`] satisfies + /// contained [`DeviceVideoTrackConstraints`]. + #[must_use] + pub fn satisfies>( + &self, + track: T, + ) -> bool { + let track = track.as_ref(); satisfies_track(track, MediaKind::Video) - && guess_is_from_display(&track) + && ConstrainString::satisfies(&self.device_id, &track.device_id()) + && ConstrainString::satisfies( + &self.facing_mode, + &track.facing_mode(), + ) + && ConstrainU32::satisfies(self.height, track.height()) + && ConstrainU32::satisfies(self.width, track.width()) + && !track.guess_is_from_display() } - /// Merges this [`DisplayVideoTrackConstraints`] with `another` one, - /// meaning that if some constraint is not set on this one, then it will be - /// applied from `another`. - #[inline] - pub fn merge(&mut self, another: &Self) { + /// Merges these [`DeviceVideoTrackConstraints`] with `another` ones, + /// meaning that if some constraints are not set on these ones, then they + /// will be applied from `another`. + pub fn merge(&mut self, another: DeviceVideoTrackConstraints) { + if self.device_id.is_none() && another.device_id.is_some() { + self.device_id = another.device_id; + } if !self.required && another.required { self.required = another.required; } + if self.facing_mode.is_none() && another.facing_mode.is_some() { + self.facing_mode = another.facing_mode; + } + if self.height.is_none() && another.height.is_some() { + self.height = another.height; + } + if self.width.is_none() && another.width.is_some() { + self.width = another.width; + } } - /// Returns importance of this [`DisplayVideoTrackConstraints`]. + /// Returns an importance of these [`DeviceVideoTrackConstraints`]. /// - /// If this [`DisplayVideoTrackConstraints`] is important then without this - /// [`DisplayVideoTrackConstraints`] call session can't be started. + /// If these [`DeviceVideoTrackConstraints`] are important then without them + /// a session call can't be started. #[inline] + #[must_use] pub fn required(&self) -> bool { self.required } } -#[wasm_bindgen] +/// Constraints applicable to video tracks sourced from a screen capturing. +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct DisplayVideoTrackConstraints { + /// Importance of this [`DisplayVideoTrackConstraints`]. + /// + /// If `true` then without these [`DisplayVideoTrackConstraints`] a session + /// call can't be started. + required: bool, +} + impl DisplayVideoTrackConstraints { /// Creates new [`DisplayVideoTrackConstraints`] with none constraints /// configured. - #[wasm_bindgen(constructor)] + #[inline] + #[must_use] pub fn new() -> Self { Self::default() } -} -/// Detects if video track captured from display searching [specific fields][1] -/// in its settings. Only works in Chrome atm. -/// -/// [1]: https://w3.org/TR/screen-capture/#extensions-to-mediatracksettings -fn guess_is_from_display(track: &sys::MediaStreamTrack) -> bool { - let settings = track.get_settings(); - - let has_display_surface = - get_property_by_name(&settings, "displaySurface", |val| { - val.as_string() - }) - .is_some(); - - if has_display_surface { - true - } else { - get_property_by_name(&settings, "logicalSurface", |val| val.as_string()) - .is_some() + /// Checks whether the provided [`platform::MediaStreamTrack`] satisfies + /// contained [`DisplayVideoTrackConstraints`]. + #[allow(clippy::unused_self)] + #[inline] + pub fn satisfies>( + &self, + track: T, + ) -> bool { + let track = track.as_ref(); + satisfies_track(track, MediaKind::Video) + && track.guess_is_from_display() } -} - -impl From for sys::MediaTrackConstraints { - fn from(track_constraints: DeviceVideoTrackConstraints) -> Self { - let mut constraints = Self::new(); - if let Some(device_id) = track_constraints.device_id { - constraints.device_id(&sys::ConstrainDomStringParameters::from( - &device_id, - )); - } - if let Some(facing_mode) = track_constraints.facing_mode { - constraints.facing_mode(&sys::ConstrainDomStringParameters::from( - &facing_mode, - )); - } - if let Some(width) = track_constraints.width { - constraints.width(&sys::ConstrainDoubleRange::from(width)); - } - if let Some(height) = track_constraints.height { - constraints.height(&sys::ConstrainDoubleRange::from(height)); + /// Merges these [`DisplayVideoTrackConstraints`] with `another` ones, + /// meaning that if some constraints are not set on these ones, then they + /// will be applied from `another`. + #[inline] + pub fn merge(&mut self, another: &Self) { + if !self.required && another.required { + self.required = another.required; } - - constraints } -} -impl From for sys::MediaTrackConstraints { - fn from(_: DisplayVideoTrackConstraints) -> Self { - Self::new() + /// Returns an importance of this [`DisplayVideoTrackConstraints`]. + /// + /// If these [`DisplayVideoTrackConstraints`] are important then without + /// them a session call can't be started. + #[inline] + #[must_use] + pub fn required(&self) -> bool { + self.required } } diff --git a/jason/src/media/manager.rs b/jason/src/media/manager.rs index 0d930c8fe..2b484561c 100644 --- a/jason/src/media/manager.rs +++ b/jason/src/media/manager.rs @@ -1,54 +1,56 @@ //! Acquiring and storing [`local::Track`]s. +// TODO: Remove when moving `JasonError` to `api::wasm`. +#![allow(clippy::missing_errors_doc)] + use std::{ cell::RefCell, collections::HashMap, - convert::TryFrom, rc::{Rc, Weak}, }; use derive_more::Display; -use js_sys::Promise; use medea_client_api_proto::MediaSourceKind; use tracerr::Traced; -use wasm_bindgen::{prelude::*, JsValue}; -use wasm_bindgen_futures::{future_to_promise, JsFuture}; -use web_sys as sys; use crate::{ - media::{MediaStreamSettings, MultiSourceTracksConstraints}, - utils::{window, HandlerDetachedError, JasonError, JsCaused, JsError}, - MediaKind, + media::{ + track::MediaStreamTrackState, MediaKind, MediaStreamSettings, + MultiSourceTracksConstraints, + }, + platform, + utils::{HandlerDetachedError, JasonError, JsCaused}, }; -use super::{track::local, InputDeviceInfo}; +use super::track::local; /// Errors that may occur in a [`MediaManager`]. #[derive(Clone, Debug, Display, JsCaused)] +#[js(error = "platform::Error")] pub enum MediaManagerError { /// Occurs when cannot get access to [MediaDevices][1] object. /// /// [1]: https://w3.org/TR/mediacapture-streams/#mediadevices #[display(fmt = "Navigator.mediaDevices() failed: {}", _0)] - CouldNotGetMediaDevices(JsError), + CouldNotGetMediaDevices(platform::Error), /// Occurs if the [getUserMedia][1] request failed. /// /// [1]: https://tinyurl.com/w3-streams#dom-mediadevices-getusermedia #[display(fmt = "MediaDevices.getUserMedia() failed: {}", _0)] - GetUserMediaFailed(JsError), + GetUserMediaFailed(platform::Error), /// Occurs if the [getDisplayMedia()][1] request failed. /// /// [1]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia #[display(fmt = "MediaDevices.getDisplayMedia() failed: {}", _0)] - GetDisplayMediaFailed(JsError), + GetDisplayMediaFailed(platform::Error), /// Occurs when cannot get info about connected [MediaDevices][1]. /// /// [1]: https://w3.org/TR/mediacapture-streams/#mediadevices #[display(fmt = "MediaDevices.enumerateDevices() failed: {}", _0)] - EnumerateDevicesFailed(JsError), + EnumerateDevicesFailed(platform::Error), /// Occurs when local track is [`ended`][1] right after [getUserMedia()][2] /// or [getDisplayMedia()][3] request. @@ -83,38 +85,10 @@ struct InnerMediaManager { } impl InnerMediaManager { - /// Returns the vector of [`InputDeviceInfo`] objects. - async fn enumerate_devices() -> Result> { - use MediaManagerError::{ - CouldNotGetMediaDevices, EnumerateDevicesFailed, - }; - - let devices = window() - .navigator() - .media_devices() - .map_err(JsError::from) - .map_err(CouldNotGetMediaDevices) - .map_err(tracerr::from_and_wrap!())?; - let devices = JsFuture::from( - devices - .enumerate_devices() - .map_err(JsError::from) - .map_err(EnumerateDevicesFailed) - .map_err(tracerr::from_and_wrap!())?, - ) - .await - .map_err(JsError::from) - .map_err(EnumerateDevicesFailed) - .map_err(tracerr::from_and_wrap!())?; - - Ok(js_sys::Array::from(&devices) - .values() - .into_iter() - .filter_map(|info| { - let info = web_sys::MediaDeviceInfo::from(info.unwrap()); - InputDeviceInfo::try_from(info).ok() - }) - .collect()) + /// Returns a list of [`platform::InputDeviceInfo`] objects. + #[inline] + async fn enumerate_devices() -> Result> { + platform::enumerate_devices().await } /// Obtains [`local::Track`]s based on a provided @@ -207,7 +181,7 @@ impl InnerMediaManager { if caps.is_audio_enabled() { let track = storage .iter() - .find(|track| caps.get_audio().satisfies(track.sys_track())) + .find(|&track| caps.get_audio().satisfies(track.as_ref())) .cloned(); if let Some(track) = track { @@ -219,8 +193,8 @@ impl InnerMediaManager { tracks.extend( storage .iter() - .filter(|track| { - caps.unconstrain_if_satisfies_video(track.sys_track()) + .filter(|&track| { + caps.unconstrain_if_satisfies_video(track.as_ref()) }) .cloned(), ); @@ -228,85 +202,41 @@ impl InnerMediaManager { tracks } - /// Obtains new [MediaStream][1] making [getUserMedia()][2] call, saves + /// Obtains new [`local::Track`]s making [getUserMedia()][1] call, saves /// received tracks weak refs to storage, returns list of tracks strong /// refs. /// - /// [1]: https://w3.org/TR/mediacapture-streams/#mediastream - /// [2]: https://tinyurl.com/w3-streams#dom-mediadevices-getusermedia + /// [1]: https://tinyurl.com/w3-streams#dom-mediadevices-getusermedia async fn get_user_media( &self, - caps: sys::MediaStreamConstraints, + caps: platform::MediaStreamConstraints, ) -> Result>> { - use MediaManagerError::{CouldNotGetMediaDevices, GetUserMediaFailed}; - - let media_devices = window() - .navigator() - .media_devices() - .map_err(JsError::from) - .map_err(CouldNotGetMediaDevices) - .map_err(tracerr::from_and_wrap!())?; - - let stream = JsFuture::from( - media_devices - .get_user_media_with_constraints(&caps) - .map_err(JsError::from) - .map_err(GetUserMediaFailed) - .map_err(tracerr::from_and_wrap!())?, - ) - .await - .map(sys::MediaStream::from) - .map_err(JsError::from) - .map_err(GetUserMediaFailed) - .map_err(tracerr::from_and_wrap!())?; - - Ok(self.parse_and_save_tracks(stream, MediaSourceKind::Device)?) + let tracks = platform::get_user_media(caps).await?; + + Ok(self.parse_and_save_tracks(tracks, MediaSourceKind::Device)?) } - /// Obtains new [MediaStream][1] making [getDisplayMedia()][2] call, saves + /// Obtains [`local::Track`]s making [getDisplayMedia()][1] call, saves /// received tracks weak refs to storage, returns list of tracks strong /// refs. /// - /// [1]: https://w3.org/TR/mediacapture-streams/#mediastream - /// [2]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia + /// [1]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia async fn get_display_media( &self, - caps: sys::DisplayMediaStreamConstraints, + caps: platform::DisplayMediaStreamConstraints, ) -> Result>> { - use MediaManagerError::{ - CouldNotGetMediaDevices, GetDisplayMediaFailed, GetUserMediaFailed, - }; - - let media_devices = window() - .navigator() - .media_devices() - .map_err(JsError::from) - .map_err(CouldNotGetMediaDevices) - .map_err(tracerr::from_and_wrap!())?; - - let stream = JsFuture::from( - media_devices - .get_display_media_with_constraints(&caps) - .map_err(JsError::from) - .map_err(GetDisplayMediaFailed) - .map_err(tracerr::from_and_wrap!())?, - ) - .await - .map(sys::MediaStream::from) - .map_err(JsError::from) - .map_err(GetUserMediaFailed) - .map_err(tracerr::from_and_wrap!())?; - - Ok(self.parse_and_save_tracks(stream, MediaSourceKind::Display)?) + let tracks = platform::get_display_media(caps).await?; + + Ok(self.parse_and_save_tracks(tracks, MediaSourceKind::Display)?) } - /// Retrieves tracks from provided [`sys::MediaStream`], saves tracks weak - /// references in [`MediaManager`] tracks storage. + /// Retrieves tracks from provided [`platform::MediaStreamTrack`]s, saves + /// tracks weak references in [`MediaManager`] tracks storage. /// /// # Errors /// /// With [`MediaManagerError::LocalTrackIsEnded`] if at least one track from - /// the provided [`sys::MediaStream`] is in [`ended`][1] state. + /// the provided [`platform::MediaStreamTrack`]s is in [`ended`][1] state. /// /// In case of error all tracks are stopped and are not saved in /// [`MediaManager`]'s tracks storage. @@ -315,32 +245,29 @@ impl InnerMediaManager { #[allow(clippy::needless_pass_by_value)] fn parse_and_save_tracks( &self, - stream: sys::MediaStream, + tracks: Vec, kind: MediaSourceKind, ) -> Result>> { use MediaManagerError::LocalTrackIsEnded; let mut storage = self.tracks.borrow_mut(); - let tracks: Vec<_> = js_sys::try_iter(&stream.get_tracks()) - .unwrap() - .unwrap() - .map(|tr| Rc::new(local::Track::new(tr.unwrap().into(), kind))) - .collect(); // Tracks returned by getDisplayMedia()/getUserMedia() request should be // `live`. Otherwise, we should err without caching tracks in // `MediaManager`. Tracks will be stopped on `Drop`. for track in &tracks { - if track.sys_track().ready_state() - != sys::MediaStreamTrackState::Live - { + if track.ready_state() != MediaStreamTrackState::Live { return Err(tracerr::new!(LocalTrackIsEnded(track.kind()))); } } - for track in &tracks { - storage.insert(track.id(), Rc::downgrade(&track)); - } + let tracks = tracks + .into_iter() + .map(|tr| Rc::new(local::Track::new(tr, kind))) + .inspect(|track| { + storage.insert(track.id(), Rc::downgrade(track)); + }) + .collect(); Ok(tracks) } @@ -368,14 +295,14 @@ impl MediaManager { self.0.get_tracks(caps.into()).await } - /// Instantiates new [`MediaManagerHandle`] for use on JS side. + /// Instantiates a new [`MediaManagerHandle`] for external usage. #[inline] pub fn new_handle(&self) -> MediaManagerHandle { MediaManagerHandle(Rc::downgrade(&self.0)) } } -/// JS side handle to [`MediaManager`]. +/// External handle to a [`MediaManager`]. /// /// [`MediaManager`] performs all media acquisition requests /// ([getUserMedia()][1]/[getDisplayMedia()][2]) and stores all received tracks @@ -387,52 +314,39 @@ impl MediaManager { /// /// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediadevices-getusermedia /// [2]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia -#[wasm_bindgen] +#[derive(Clone)] pub struct MediaManagerHandle(Weak); -#[wasm_bindgen] #[allow(clippy::unused_self)] impl MediaManagerHandle { - /// Returns array of [`InputDeviceInfo`] objects, which represent available - /// media input and output devices, such as microphones, cameras, and so - /// forth. - pub fn enumerate_devices(&self) -> Promise { - future_to_promise(async { - InnerMediaManager::enumerate_devices() - .await - .map(|devices| { - devices - .into_iter() - .fold(js_sys::Array::new(), |devices_info, info| { - devices_info.push(&JsValue::from(info)); - devices_info - }) - .into() - }) - .map_err(tracerr::wrap!(=> MediaManagerError)) - .map_err(|e| JasonError::from(e).into()) - }) + /// Returns a list of [`platform::InputDeviceInfo`] objects representing + /// available media input and output devices, such as microphones, cameras, + /// and so forth. + pub async fn enumerate_devices( + &self, + ) -> std::result::Result, JasonError> { + InnerMediaManager::enumerate_devices() + .await + .map_err(tracerr::wrap!(=> MediaManagerError)) + .map_err(JasonError::from) } - /// Returns [`local::JsTrack`]s objects, built from provided + /// Returns [`local::LocalMediaTrack`]s objects, built from the provided /// [`MediaStreamSettings`]. - pub fn init_local_tracks(&self, caps: &MediaStreamSettings) -> Promise { - let inner = upgrade_or_detached!(self.0, JasonError); - let caps = caps.clone(); - future_to_promise(async move { - inner? - .get_tracks(caps) - .await - .map(|tracks| { - tracks - .into_iter() - .map(|(t, _)| local::JsTrack::new(t)) - .map(JsValue::from) - .collect::() - .into() - }) - .map_err(tracerr::wrap!(=> MediaManagerError)) - .map_err(|e| JasonError::from(e).into()) - }) + pub async fn init_local_tracks( + &self, + caps: MediaStreamSettings, + ) -> std::result::Result, JasonError> { + upgrade_or_detached!(self.0, JasonError)? + .get_tracks(caps) + .await + .map(|tracks| { + tracks + .into_iter() + .map(|(t, _)| local::LocalMediaTrack::new(t)) + .collect::>() + }) + .map_err(tracerr::wrap!(=> MediaManagerError)) + .map_err(JasonError::from) } } diff --git a/jason/src/media/mod.rs b/jason/src/media/mod.rs index b88615f5c..6325d8fd6 100644 --- a/jason/src/media/mod.rs +++ b/jason/src/media/mod.rs @@ -2,13 +2,11 @@ //! //! [1]: https://w3.org/TR/mediacapture-streams -mod constraints; -mod device_info; +pub mod constraints; mod manager; pub mod track; use derive_more::Display; -use wasm_bindgen::prelude::*; #[doc(inline)] pub use self::{ @@ -19,15 +17,13 @@ pub use self::{ MultiSourceTracksConstraints, RecvConstraints, TrackConstraints, VideoSource, VideoTrackConstraints, }, - device_info::InputDeviceInfo, manager::{MediaManager, MediaManagerError, MediaManagerHandle}, - track::JsMediaSourceKind, + track::MediaSourceKind, }; /// [MediaStreamTrack.kind][1] representation. /// /// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediastreamtrack-kind -#[wasm_bindgen] #[derive(Clone, Copy, Debug, Display, Eq, PartialEq)] pub enum MediaKind { /// Audio track. @@ -41,6 +37,8 @@ pub enum MediaKind { impl MediaKind { /// Returns string representation of a [`MediaKind`]. + #[inline] + #[must_use] pub fn as_str(self) -> &'static str { match self { Self::Audio => "audio", diff --git a/jason/src/media/track/local.rs b/jason/src/media/track/local.rs index fea609f3c..b991c613d 100644 --- a/jason/src/media/track/local.rs +++ b/jason/src/media/track/local.rs @@ -1,4 +1,4 @@ -//! Wrapper around [`sys::MediaStreamTrack`] received from +//! Wrapper around a [`platform::MediaStreamTrack`] received from a //! [getUserMedia()][1]/[getDisplayMedia()][2] request. //! //! [1]: https://w3.org/TR/mediacapture-streams/#dom-mediadevices-getusermedia @@ -6,30 +6,30 @@ use std::rc::Rc; -use medea_client_api_proto::MediaSourceKind; -use wasm_bindgen::prelude::*; -use web_sys as sys; +use derive_more::AsRef; +use medea_client_api_proto as proto; -use crate::{media::MediaKind, JsMediaSourceKind}; +use crate::{ + media::{MediaKind, MediaSourceKind}, + platform, +}; -/// Wrapper around [`sys::MediaStreamTrack`] received from from +/// Wrapper around a [`platform::MediaStreamTrack`] received from a /// [getUserMedia()][1]/[getDisplayMedia()][2] request. /// -/// Underlying [`sys::MediaStreamTrack`] is stopped on this [`Track`]'s +/// Underlying [`platform::MediaStreamTrack`] is stopped on this [`Track`]'s /// [`Drop`]. /// /// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediadevices-getusermedia /// [2]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia -#[derive(Debug)] +#[derive(AsRef, Debug)] pub struct Track { - /// Actual [`sys::MediaStreamTrack`]. - track: sys::MediaStreamTrack, + /// Actual [`platform::MediaStreamTrack`]. + #[as_ref] + track: platform::MediaStreamTrack, - /// Underlying [`sys::MediaStreamTrack`] source kind. - source_kind: MediaSourceKind, - - /// Underlying [`sys::MediaStreamTrack`] kind. - kind: MediaKind, + /// Underlying [`platform::MediaStreamTrack`] source kind. + source_kind: proto::MediaSourceKind, /// Reference to the parent [`Track`]. /// @@ -41,22 +41,17 @@ pub struct Track { } impl Track { - /// Builds new [`Track`] from the provided [`sys::MediaStreamTrack`] and - /// [`MediaSourceKind`]. + /// Builds a new [`Track`] from the provided [`platform::MediaStreamTrack`] + /// and [`proto::MediaSourceKind`]. + #[inline] #[must_use] pub fn new( - track: sys::MediaStreamTrack, - source_kind: MediaSourceKind, + track: platform::MediaStreamTrack, + source_kind: proto::MediaSourceKind, ) -> Self { - let kind = match track.kind().as_ref() { - "audio" => MediaKind::Audio, - "video" => MediaKind::Video, - _ => unreachable!(), - }; Self { track, source_kind, - kind, _parent: None, } } @@ -84,7 +79,7 @@ impl Track { /// Returns this [`Track`]'s media source kind. #[inline] #[must_use] - pub fn media_source_kind(&self) -> MediaSourceKind { + pub fn media_source_kind(&self) -> proto::MediaSourceKind { self.source_kind } @@ -92,13 +87,13 @@ impl Track { #[inline] #[must_use] pub fn kind(&self) -> MediaKind { - self.kind + self.track.kind() } /// Forks this [`Track`]. /// - /// Creates new [`sys::MediaStreamTrack`] from this [`Track`]'s - /// [`sys::MediaStreamTrack`] using [`clone()`][1] method. + /// Creates new a [`platform::MediaStreamTrack`] from this [`Track`]'s + /// [`platform::MediaStreamTrack`] using a [`clone()`][1] method. /// /// Forked [`Track`] will hold a strong reference to this [`Track`]. /// @@ -106,22 +101,13 @@ impl Track { #[must_use] pub fn fork(self: &Rc) -> Self { let parent = Rc::clone(self); - let track = sys::MediaStreamTrack::clone(&self.track); + let track = platform::MediaStreamTrack::clone(&self.track); Self { track, - kind: self.kind, source_kind: self.source_kind, _parent: Some(parent), } } - - /// Returns reference to the underlying [`sys::MediaStreamTrack`] of this - /// [`Track`]. - #[inline] - #[must_use] - pub fn sys_track(&self) -> &sys::MediaStreamTrack { - &self.track - } } impl Drop for Track { @@ -131,38 +117,46 @@ impl Drop for Track { } } -/// Wrapper around strongly referenced [`Track`] for JS side. -#[wasm_bindgen(js_name = LocalMediaTrack)] -pub struct JsTrack(Rc); +/// Strongly referenced [`Track`] received from a +/// [getUserMedia()][1]/[getDisplayMedia()][2] request. +/// +/// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediadevices-getusermedia +/// [2]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia +pub struct LocalMediaTrack(Rc); -impl JsTrack { - /// Creates new [`JsTrack`] from the provided [`Track`]. +impl LocalMediaTrack { + /// Createsa new [`LocalMediaTrack`] from the provided [`Track`]. #[inline] #[must_use] pub fn new(track: Rc) -> Self { - JsTrack(track) + LocalMediaTrack(track) } -} -#[wasm_bindgen(js_class = LocalMediaTrack)] -impl JsTrack { - /// Returns the underlying [`sys::MediaStreamTrack`] of this [`JsTrack`]. - pub fn get_track(&self) -> sys::MediaStreamTrack { - Clone::clone(self.0.track.as_ref()) + /// Returns the underlying [`platform::MediaStreamTrack`] of this + /// [`LocalMediaTrack`]. + #[inline] + #[must_use] + pub fn get_track(&self) -> &platform::MediaStreamTrack { + &self.0.track } - /// Returns [`MediaKind::Audio`] if this [`JsTrack`] represents an audio - /// track, or [`MediaKind::Video`] if it represents a video track. + /// Returns a [`MediaKind::Audio`] if this [`LocalMediaTrack`] represents an + /// audio track, or a [`MediaKind::Video`] if it represents a video track. + #[inline] + #[must_use] pub fn kind(&self) -> MediaKind { self.0.kind() } - /// Returns [`JsMediaSourceKind::Device`] if this [`JsTrack`] is sourced - /// from some device (webcam/microphone), or [`JsMediaSourceKind::Display`] - /// if ot is captured via [MediaDevices.getDisplayMedia()][1]. + /// Returns a [`MediaSourceKind::Device`] if this [`LocalMediaTrack`] is + /// sourced from some device (webcam/microphone), or + /// a [`MediaSourceKind::Display`] if it's captured via + /// [MediaDevices.getDisplayMedia()][1]. /// /// [1]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia - pub fn media_source_kind(&self) -> JsMediaSourceKind { + #[inline] + #[must_use] + pub fn media_source_kind(&self) -> MediaSourceKind { self.0.media_source_kind().into() } } diff --git a/jason/src/media/track/mod.rs b/jason/src/media/track/mod.rs index 1353d6748..5e862ae52 100644 --- a/jason/src/media/track/mod.rs +++ b/jason/src/media/track/mod.rs @@ -5,13 +5,27 @@ pub mod local; pub mod remote; -use medea_client_api_proto::MediaSourceKind; -use wasm_bindgen::prelude::*; +use medea_client_api_proto as proto; + +/// Liveness state of a [MediaStreamTrack][1] . +/// +/// [1]: crate::platform::MediaStreamTrack +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum MediaStreamTrackState { + /// Active track (the track's underlying media source is making a + /// best-effort attempt to provide a data in real time). + Live, + + /// Ended track (the track's underlying media source is no longer providing + /// any data, and will never provide more data for this track). + /// + /// This is a final state. + Ended, +} /// Media source type. -#[wasm_bindgen(js_name = MediaSourceKind)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum JsMediaSourceKind { +pub enum MediaSourceKind { /// Media is sourced from some media device (webcam or microphone). Device, @@ -19,22 +33,22 @@ pub enum JsMediaSourceKind { Display, } -impl From for MediaSourceKind { +impl From for proto::MediaSourceKind { #[inline] - fn from(val: JsMediaSourceKind) -> Self { + fn from(val: MediaSourceKind) -> Self { match val { - JsMediaSourceKind::Device => Self::Device, - JsMediaSourceKind::Display => Self::Display, + MediaSourceKind::Device => Self::Device, + MediaSourceKind::Display => Self::Display, } } } -impl From for JsMediaSourceKind { +impl From for MediaSourceKind { #[inline] - fn from(val: MediaSourceKind) -> Self { + fn from(val: proto::MediaSourceKind) -> Self { match val { - MediaSourceKind::Device => Self::Device, - MediaSourceKind::Display => Self::Display, + proto::MediaSourceKind::Device => Self::Device, + proto::MediaSourceKind::Display => Self::Display, } } } diff --git a/jason/src/media/track/remote.rs b/jason/src/media/track/remote.rs index 89ccf297e..168ec5782 100644 --- a/jason/src/media/track/remote.rs +++ b/jason/src/media/track/remote.rs @@ -1,76 +1,65 @@ -//! Wrapper around [`sys::MediaStreamTrack`] received from the remote. +//! Wrapper around a received remote [`platform::MediaStreamTrack`]. use std::rc::Rc; use futures::StreamExt; -use medea_client_api_proto::MediaSourceKind; +use medea_client_api_proto as proto; use medea_reactive::ObservableCell; -use wasm_bindgen::prelude::*; -use wasm_bindgen_futures::spawn_local; -use web_sys as sys; -use crate::{media::MediaKind, utils::Callback0, JsMediaSourceKind}; +use crate::{ + media::{MediaKind, MediaSourceKind}, + platform, +}; -/// Inner reference-counted data of [`Track`]. +/// Inner reference-counted data of a [`Track`]. struct Inner { - /// Underlying JS-side [`sys::MediaStreamTrack`]. - track: sys::MediaStreamTrack, + /// Underlying platform-specific [`platform::MediaStreamTrack`]. + track: platform::MediaStreamTrack, - /// Underlying [`sys::MediaStreamTrack`] kind. - kind: MediaKind, + /// Underlying [`platform::MediaStreamTrack`] source kind. + media_source_kind: proto::MediaSourceKind, - /// Underlying [`sys::MediaStreamTrack`] source kind. - media_source_kind: MediaSourceKind, + /// Callback invoked when this [`Track`] is enabled. + on_enabled: platform::Callback<()>, - /// Callback to be invoked when this [`Track`] is enabled. - on_enabled: Callback0, + /// Callback invoked when this [`Track`] is disabled. + on_disabled: platform::Callback<()>, - /// Callback to be invoked when this [`Track`] is disabled. - on_disabled: Callback0, - - /// [`enabled`][1] property of [MediaStreamTrack][2]. + /// [`enabled`][1] property of this [MediaStreamTrack][2]. /// /// [1]: https://tinyurl.com/w3-streams#dom-mediastreamtrack-enabled /// [2]: https://w3.org/TR/mediacapture-streams/#dom-mediastreamtrack enabled: ObservableCell, } -/// Wrapper around [MediaStreamTrack][1] received from the remote. +/// Wrapper around a received remote [MediaStreamTrack][1]. /// /// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -#[wasm_bindgen(js_name = RemoteMediaTrack)] #[derive(Clone)] pub struct Track(Rc); impl Track { - /// Creates new [`Track`] spawning a listener for its [`enabled`][1] + /// Creates a new [`Track`] spawning a listener for its [`enabled`][1] /// property changes. /// /// [1]: https://tinyurl.com/w3-streams#dom-mediastreamtrack-enabled #[must_use] - pub fn new(track: T, media_source_kind: MediaSourceKind) -> Self + pub fn new(track: T, media_source_kind: proto::MediaSourceKind) -> Self where - sys::MediaStreamTrack: From, + platform::MediaStreamTrack: From, { - let track = sys::MediaStreamTrack::from(track); - let kind = match track.kind().as_ref() { - "audio" => MediaKind::Audio, - "video" => MediaKind::Video, - _ => unreachable!(), - }; - + let track = platform::MediaStreamTrack::from(track); let track = Track(Rc::new(Inner { enabled: ObservableCell::new(track.enabled()), - on_enabled: Callback0::default(), - on_disabled: Callback0::default(), + on_enabled: platform::Callback::default(), + on_disabled: platform::Callback::default(), media_source_kind, - kind, track, })); let mut track_enabled_state_changes = - track.enabled().subscribe().skip(1); - spawn_local({ + track.0.enabled.subscribe().skip(1); + platform::spawn({ let weak_inner = Rc::downgrade(&track.0); async move { while let Some(enabled) = @@ -78,9 +67,9 @@ impl Track { { if let Some(track) = weak_inner.upgrade() { if enabled { - track.on_enabled.call(); + track.on_enabled.call0(); } else { - track.on_disabled.call(); + track.on_disabled.call0(); } } else { break; @@ -92,17 +81,10 @@ impl Track { track } - /// Indicates whether this [`Track`] is enabled. - #[inline] - #[must_use] - pub fn enabled(&self) -> &ObservableCell { - &self.0.enabled - } - /// Sets [`Track::enabled`] to the provided value. /// /// Updates [`enabled`][1] property in the underlying - /// [`sys::MediaStreamTrack`]. + /// [`platform::MediaStreamTrack`]. /// /// [1]: https://tinyurl.com/w3-streams#dom-mediastreamtrack-enabled #[inline] @@ -111,8 +93,8 @@ impl Track { self.0.track.set_enabled(enabled); } - /// Returns [`id`][1] of underlying [`sys::MediaStreamTrack`] of this - /// [`Track`]. + /// Returns [`id`][1] of the underlying [`platform::MediaStreamTrack`] of + /// this [`Track`]. /// /// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediastreamtrack-id #[inline] @@ -125,54 +107,39 @@ impl Track { #[inline] #[must_use] pub fn kind(&self) -> MediaKind { - self.0.kind + self.0.track.kind() } /// Returns this [`Track`]'s media source kind. #[inline] #[must_use] pub fn media_source_kind(&self) -> MediaSourceKind { - self.0.media_source_kind + self.0.media_source_kind.into() } -} -#[wasm_bindgen(js_class = RemoteMediaTrack)] -impl Track { - /// Returns the underlying [`sys::MediaStreamTrack`] of this [`Track`]. - pub fn get_track(&self) -> sys::MediaStreamTrack { - Clone::clone(&self.0.track) + /// Returns the underlying [`platform::MediaStreamTrack`] of this [`Track`]. + #[inline] + #[must_use] + pub fn get_track(&self) -> &platform::MediaStreamTrack { + &self.0.track } - /// Indicate whether this [`Track`] is enabled. - #[wasm_bindgen(js_name = enabled)] - pub fn js_enabled(&self) -> bool { + /// Indicates whether this [`Track`] is enabled. + #[inline] + #[must_use] + pub fn enabled(&self) -> bool { self.0.enabled.get() } - /// Sets callback to invoke when this [`Track`] is enabled. - pub fn on_enabled(&self, callback: js_sys::Function) { + /// Sets callback, invoked when this [`Track`] is enabled. + #[inline] + pub fn on_enabled(&self, callback: platform::Function<()>) { self.0.on_enabled.set_func(callback); } - /// Sets callback to invoke when this [`Track`] is disabled. - pub fn on_disabled(&self, callback: js_sys::Function) { + /// Sets callback, invoked when this [`Track`] is disabled. + #[inline] + pub fn on_disabled(&self, callback: platform::Function<()>) { self.0.on_disabled.set_func(callback); } - - /// Returns [`MediaKind::Audio`] if this [`Track`] represents an audio - /// track, or [`MediaKind::Video`] if it represents a video track. - #[wasm_bindgen(js_name = kind)] - pub fn js_kind(&self) -> MediaKind { - self.kind() - } - - /// Returns [`JsMediaSourceKind::Device`] if this [`Track`] is sourced from - /// some device (webcam/microphone), or [`JsMediaSourceKind::Display`] if - /// it's captured via [MediaDevices.getDisplayMedia()][1]. - /// - /// [1]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia - #[wasm_bindgen(js_name = media_source_kind)] - pub fn js_media_source_kind(&self) -> JsMediaSourceKind { - self.0.media_source_kind.into() - } } diff --git a/jason/src/peer/component/ice_candidates.rs b/jason/src/peer/component/ice_candidates.rs index c0599362c..f3fdd86de 100644 --- a/jason/src/peer/component/ice_candidates.rs +++ b/jason/src/peer/component/ice_candidates.rs @@ -11,7 +11,9 @@ use crate::{ utils::{AsProtoState, SynchronizableState}, }; -/// Store of all the [`IceCandidate`]s of a [`PeerComponent`]. +/// Store of all the [`IceCandidate`]s of a [`peer::Component`]. +/// +/// [`peer::Component`]: super::Component #[derive(Debug)] pub struct IceCandidates(RefCell>); diff --git a/jason/src/peer/component/local_sdp.rs b/jason/src/peer/component/local_sdp.rs index 6d2d23f8f..6694b1144 100644 --- a/jason/src/peer/component/local_sdp.rs +++ b/jason/src/peer/component/local_sdp.rs @@ -13,9 +13,11 @@ use futures::{ StreamExt as _, }; use medea_reactive::ObservableCell; -use wasm_bindgen_futures::spawn_local; -use crate::utils::{resettable_delay_for, ResettableDelayHandle}; +use crate::{ + platform, + utils::{resettable_delay_for, ResettableDelayHandle}, +}; const DESCRIPTION_APPROVE_TIMEOUT: Duration = Duration::from_secs(10); @@ -55,7 +57,7 @@ impl LocalSdp { /// Returns [`Stream`] into which `()` will be sent on every SDP offer /// approve. /// - /// [`Stream`]: futures::stream::Stream + /// [`Stream`]: futures::Stream #[inline] pub fn on_approve(&self) -> LocalBoxStream<'static, ()> { Box::pin(self.0.approved.subscribe().filter_map(|approved| { @@ -150,7 +152,7 @@ impl LocalSdp { DESCRIPTION_APPROVE_TIMEOUT, self.0.is_rollback_timeout_stopped.get(), ); - spawn_local({ + platform::spawn({ let this = self.clone(); async move { if let Either::Right(_) = diff --git a/jason/src/peer/component/tracks_repository.rs b/jason/src/peer/component/tracks_repository.rs index b515411c1..a6f9f44f5 100644 --- a/jason/src/peer/component/tracks_repository.rs +++ b/jason/src/peer/component/tracks_repository.rs @@ -29,6 +29,7 @@ use super::sender; /// [`Component`]. /// /// [`Component`]: super::Component +/// [`receiver::State`]: super::receiver::State #[derive(Debug, From)] pub struct TracksRepository( RefCell>>, @@ -43,6 +44,8 @@ impl TracksRepository { } /// Returns [`Future`] resolving once all inserts/removes are processed. + /// + /// [`Future`]: std::future::Future #[inline] pub fn when_all_processed(&self) -> AllProcessed<'static> { self.0.borrow().when_all_processed() @@ -62,6 +65,8 @@ impl TracksRepository { } /// Returns a [`Stream`] streaming the all [`TracksRepository::insert`]ions. + /// + /// [`Stream`]: futures::Stream #[inline] pub fn on_insert( &self, @@ -157,6 +162,8 @@ where /// Returns [`Future`] resolving once all tracks from this /// [`TracksRepository`] will be stabilized meaning that all track's /// components won't contain any pending state change transitions. + /// + /// [`Future`]: std::future::Future fn when_stabilized(&self) -> AllProcessed<'static> { let when_futs: Vec<_> = self .0 diff --git a/jason/src/peer/component/watchers.rs b/jason/src/peer/component/watchers.rs index 1a1ef5803..d68346abf 100644 --- a/jason/src/peer/component/watchers.rs +++ b/jason/src/peer/component/watchers.rs @@ -81,16 +81,18 @@ impl Component { /// Watcher for the [`State::senders`] insert update. /// - /// Waits until [`ReceiverComponent`]s creation is finished. + /// Waits until [`receiver::Component`]s creation is finished. /// /// Waits for a remote SDP offer apply if the current [`NegotiationRole`] is /// an [`Answerer`]. /// - /// Creates a new [`SenderComponent`], creates a new [`Connection`] with all - /// [`sender::State::receivers`] by calling - /// [`Connections::create_connection()`]. + /// Creates a new [`sender::Component`], creates a new [`Connection`] with + /// all [`sender::State::receivers`] by calling a + /// [`Connections::create_connection()`][1]. /// /// [`Answerer`]: NegotiationRole::Answerer + /// [`Connection`]: crate::connection::Connection + /// [1]: crate::connection::Connections::create_connection #[watch(self.senders.on_insert())] async fn sender_added( peer: Rc, @@ -136,9 +138,12 @@ impl Component { /// Watcher for the [`State::receivers`] insert update. /// - /// Creates a new [`ReceiverComponent`], creates a new [`Connection`] with a - /// [`receiver::State::sender_id`] by calling - /// [`Connections::create_connection()`]. + /// Creates a new [`receiver::Component`], creates a new [`Connection`] with + /// a [`receiver::State::sender_id`] by calling a + /// [`Connections::create_connection()`][1]. + /// + /// [`Connection`]: crate::connection::Connections + /// [1]: crate::connection::Connections::create_connection #[watch(self.receivers.on_insert())] async fn receiver_added( peer: Rc, @@ -164,15 +169,15 @@ impl Component { /// Watcher for the [`State::local_sdp`] updates. /// /// Sets [`PeerConnection`]'s SDP offer to the provided one and sends - /// a [`Command::MakeSdpOffer`] if the [`Sdp`] is a [`Sdp::Offer`] and - /// the [`NegotiationRole`] is an [`Offerer`]. + /// a [`PeerEvent::NewSdpOffer`] if [`NegotiationRole`] is + /// [`NegotiationRole::Offerer`]. /// /// Sets [`PeerConnection`]'s SDP answer to the provided one and sends - /// a [`Command::MakeSdpAnswer`] if the [`Sdp`] is a [`Sdp::Offer`] and - /// the [`NegotiationRole`] is an [`Answerer`]. + /// a [`PeerEvent::NewSdpAnswer`] if [`NegotiationRole`] is + /// [`NegotiationRole::Answerer`]. /// - /// Rollbacks [`PeerConnection`] to a stable state if the [`Sdp`] is a - /// [`Sdp::Rollback`] and the [`NegotiationRole`] is [`Some`]. + /// Rollbacks [`PeerConnection`] to a stable state if [`PeerConnection`] is + /// marked for rollback and [`NegotiationRole`] is [`Some`]. /// /// [`Answerer`]: NegotiationRole::Answerer /// [`Offerer`]: NegotiationRole::Offerer @@ -245,11 +250,11 @@ impl Component { /// Watcher for the SDP offer approving by server. /// - /// If the current [`NegotiationRole`] is an [`Offerer`] then - /// [`NegotiationState`] will transit to a [`WaitRemoteSdp`]. + /// If the current [`NegotiationRole`] is an [`NegotiationRole::Offerer`] + /// then [`NegotiationState`] will transit to a [`WaitRemoteSdp`]. /// - /// If the current [`NegotiationRole`] is an [`Answerer`] then - /// [`NegotiationState`] will transit to a [`Stable`]. + /// If the current [`NegotiationRole`] is an [`NegotiationRole::Answerer`] + /// then [`NegotiationState`] will transit to a [`Stable`]. /// /// [`Offerer`]: NegotiationRole::Offerer /// [`Stable`]: NegotiationState::Stable @@ -333,7 +338,7 @@ impl Component { /// Watcher for the [`State::negotiation_role`] updates. /// - /// Waits for [`SenderComponent`]s' and [`ReceiverComponent`]s' + /// Waits for [`sender::Component`]s' and [`receiver::Component`]s' /// creation/update, updates local `MediaStream` (if required) and /// renegotiates [`PeerConnection`]. #[watch(self.negotiation_role.subscribe().filter_map(future::ready))] diff --git a/jason/src/peer/media/mod.rs b/jason/src/peer/media/mod.rs index f1f392e08..98c8937b1 100644 --- a/jason/src/peer/media/mod.rs +++ b/jason/src/peer/media/mod.rs @@ -16,25 +16,28 @@ use medea_client_api_proto::{MediaType, MemberId}; use medea_reactive::DroppedError; use proto::{MediaSourceKind, TrackId}; use tracerr::Traced; -use web_sys::RtcTrackEvent; #[cfg(feature = "mockable")] use crate::media::{LocalTracksConstraints, RecvConstraints}; use crate::{ media::{track::local, MediaKind}, - peer::{ - transceiver::Transceiver, LocalStreamUpdateCriteria, PeerEvent, - TrackEvent, TransceiverDirection, - }, - utils::{JsCaused, JsError}, + peer::{LocalStreamUpdateCriteria, PeerEvent}, + platform, + utils::JsCaused, }; -use super::{conn::RtcPeerConnection, tracks_request::TracksRequest}; - -pub use self::transitable_state::{ - media_exchange_state, mute_state, InStable, InTransition, - MediaExchangeState, MediaExchangeStateController, MediaState, MuteState, - MuteStateController, TransitableState, TransitableStateController, +use super::tracks_request::TracksRequest; + +#[doc(inline)] +pub use self::{ + receiver::Receiver, + sender::Sender, + transitable_state::{ + media_exchange_state, mute_state, InStable, InTransition, + MediaExchangeState, MediaExchangeStateController, MediaState, + MuteState, MuteStateController, TransitableState, + TransitableStateController, + }, }; /// Transceiver's sending ([`Sender`]) or receiving ([`Receiver`]) side. @@ -112,7 +115,7 @@ pub trait MediaStateControllable { /// Indicates whether [`Room`] should subscribe to the [`MediaState`] update /// when updating [`MediaStateControllable`] to the provided [`MediaState`]. /// - /// [`Room`]: crate::api::Room + /// [`Room`]: crate::room::Room #[must_use] fn is_subscription_needed(&self, desired_state: MediaState) -> bool { match desired_state { @@ -140,7 +143,7 @@ pub trait MediaStateControllable { /// [`MediaState`]. /// /// [`TrackPatchCommand`]: medea_client_api_proto::TrackPatchCommand - /// [`Room`]: crate::api::Room + /// [`Room`]: crate::room::Room #[must_use] fn is_track_patch_needed(&self, desired_state: MediaState) -> bool { match desired_state { @@ -206,16 +209,18 @@ pub enum TrackDirection { /// Errors that may occur in [`MediaConnections`] storage. #[derive(Clone, Debug, Display, JsCaused)] +#[js(error = "platform::Error")] pub enum MediaConnectionsError { /// Occurs when the provided [`local::Track`] cannot be inserted into /// provided [`Sender`]s transceiver. /// /// [`Sender`]: self::sender::Sender #[display(fmt = "Failed to insert Track to a sender: {}", _0)] - CouldNotInsertLocalTrack(JsError), + CouldNotInsertLocalTrack(platform::Error), - /// Occurs when [`remote::Track`] discovered by [`RtcPeerConnection`] could - /// not be inserted into [`Receiver`]. + /// Occurs when [`remote::Track`] discovered by a + /// [`platform::RtcPeerConnection`] couldn't be inserted into a + /// [`Receiver`]. /// /// [`remote::Track`]: crate::media::track::remote::Track /// [`Receiver`]: self::receiver::Receiver @@ -226,9 +231,7 @@ pub enum MediaConnectionsError { )] CouldNotInsertRemoteTrack(String), - /// Could not find [`RtcRtpTransceiver`] by `mid`. - /// - /// [`RtcRtpTransceiver`]: web_sys::RtcRtpTransceiver + /// Could not find a [`platform::Transceiver`] by `mid`. #[display(fmt = "Unable to find Transceiver with provided mid: {}", _0)] TransceiverNotFound(String), @@ -292,12 +295,13 @@ type Result = std::result::Result>; /// Actual data of [`MediaConnections`] storage. struct InnerMediaConnections { - /// Ref to parent [`RtcPeerConnection`]. Used to generate transceivers for - /// [`Sender`]s and [`Receiver`]s. + /// Reference to the parent [`platform::RtcPeerConnection`]. + /// + /// Used to generate transceivers for [`Sender`]s and [`Receiver`]s. /// /// [`Sender`]: self::sender::Sender /// [`Receiver`]: self::receiver::Receiver - peer: Rc, + peer: Rc, /// [`PeerEvent`]s tx. peer_events_sender: mpsc::UnboundedSender, @@ -359,33 +363,39 @@ impl InnerMediaConnections { } } - /// Creates [`Transceiver`] and adds it to the [`RtcPeerConnection`]. + /// Creates a [`platform::Transceiver`] and adds it to the + /// [`platform::RtcPeerConnection`]. fn add_transceiver( &self, kind: MediaKind, - direction: TransceiverDirection, - ) -> Transceiver { - Transceiver::from(self.peer.add_transceiver(kind, direction)) + direction: platform::TransceiverDirection, + ) -> platform::Transceiver { + platform::Transceiver::from(self.peer.add_transceiver(kind, direction)) } - /// Lookups [`Transceiver`] by the provided [`mid`]. + /// Lookups a [`platform::Transceiver`] by the provided [`mid`]. /// /// [`mid`]: https://w3.org/TR/webrtc/#dom-rtptransceiver-mid - fn get_transceiver_by_mid(&self, mid: &str) -> Option { - self.peer.get_transceiver_by_mid(mid).map(Transceiver::from) + fn get_transceiver_by_mid( + &self, + mid: &str, + ) -> Option { + self.peer + .get_transceiver_by_mid(mid) + .map(platform::Transceiver::from) } } -/// Storage of [`RtcPeerConnection`]'s [`sender::Component`] and +/// Storage of [`platform::RtcPeerConnection`]'s [`sender::Component`] and /// [`receiver::Component`]. pub struct MediaConnections(RefCell); impl MediaConnections { - /// Instantiates new [`MediaConnections`] storage for a given - /// [`RtcPeerConnection`]. + /// Instantiates a new [`MediaConnections`] storage for the given + /// [`platform::RtcPeerConnection`]. #[inline] pub fn new( - peer: Rc, + peer: Rc, peer_events_sender: mpsc::UnboundedSender, ) -> Self { Self(RefCell::new(InnerMediaConnections { @@ -454,7 +464,7 @@ impl MediaConnections { } /// Returns mapping from a [`proto::Track`] ID to a `mid` of this track's - /// [`RtcRtpTransceiver`]. + /// [`platform::Transceiver`]. /// /// # Errors /// @@ -466,7 +476,6 @@ impl MediaConnections { /// /// [`Sender`]: self::sender::Sender /// [`Receiver`]: self::receiver::Receiver - /// [`RtcRtpTransceiver`]: web_sys::RtcRtpTransceiver /// [mid]: /// https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiver/mid pub fn get_mids(&self) -> Result> { @@ -494,8 +503,8 @@ impl MediaConnections { Ok(mids) } - /// Returns activity statuses of the all [`Sender`]s and [`Receiver`]s from - /// this [`MediaConnections`]. + /// Returns activity statuses of the all the [`Sender`]s and [`Receiver`]s + /// from these [`MediaConnections`]. /// /// [`Sender`]: self::sender::Sender /// [`Receiver`]: self::receiver::Receiver @@ -578,8 +587,9 @@ impl MediaConnections { /// Inserts provided tracks into [`Sender`]s based on track IDs. /// - /// [`local::Track`]s are inserted into [`Sender`]'s [`RtcRtpTransceiver`]s - /// via [`replaceTrack` method][1], changing its direction to `sendonly`. + /// [`local::Track`]s are inserted into [`Sender`]'s + /// [`platform::Transceiver`]s via a [`replaceTrack` method][1], changing + /// its direction to `sendonly`. /// /// Returns [`HashMap`] with [`media_exchange_state::Stable`]s updates for /// the [`Sender`]s. @@ -598,7 +608,6 @@ impl MediaConnections { /// transceiver. /// /// [`Sender`]: self::sender::Sender - /// [`RtcRtpTransceiver`]: web_sys::RtcRtpTransceiver /// [1]: https://w3.org/TR/webrtc/#dom-rtcrtpsender-replacetrack pub async fn insert_local_tracks( &self, @@ -610,7 +619,7 @@ impl MediaConnections { let mut media_exchange_state_updates = HashMap::new(); for sender in self.0.borrow().senders.values() { if let Some(track) = tracks.get(&sender.state().id()).cloned() { - if sender.caps().satisfies(track.sys_track()) { + if sender.caps().satisfies(track.as_ref()) { media_exchange_state_updates.insert( sender.state().id(), media_exchange_state::Stable::Enabled, @@ -644,11 +653,7 @@ impl MediaConnections { Ok(media_exchange_state_updates) } - /// Handles [`RtcTrackEvent`] by adding new track to the corresponding - /// [`Receiver`]. - /// - /// Returns ID of associated [`Sender`] and provided track [`TrackId`], if - /// any. + /// Adds a new track to the corresponding [`Receiver`]. /// /// # Errors /// @@ -657,10 +662,12 @@ impl MediaConnections { /// /// [`Sender`]: self::sender::Sender /// [`Receiver`]: self::receiver::Receiver - pub fn add_remote_track(&self, track_event: &RtcTrackEvent) -> Result<()> { + pub fn add_remote_track( + &self, + track: platform::MediaStreamTrack, + transceiver: platform::Transceiver, + ) -> Result<()> { let inner = self.0.borrow(); - let transceiver = Transceiver::from(track_event.transceiver()); - let track = track_event.track(); // Cannot fail, since transceiver is guaranteed to be negotiated at this // point. let mid = transceiver.mid().unwrap(); @@ -679,11 +686,12 @@ impl MediaConnections { } /// Iterates over all [`Receiver`]s with [`mid`] and without - /// [`Transceiver`], trying to find the corresponding [`Transceiver`] in - /// [`RtcPeerConnection`] and to insert it into the [`Receiver`]. + /// [`platform::Transceiver`], trying to find the corresponding + /// [`platform::Transceiver`] in the [`platform::RtcPeerConnection`] and to + /// insert it into the [`Receiver`]. /// - /// [`mid`]: https://w3.org/TR/webrtc/#dom-rtptransceiver-mid /// [`Receiver`]: self::receiver::Receiver + /// [`mid`]: https://w3.org/TR/webrtc/#dom-rtptransceiver-mid pub fn sync_receivers(&self) { let inner = self.0.borrow(); for receiver in inner diff --git a/jason/src/peer/media/receiver/component.rs b/jason/src/peer/media/receiver/component.rs index dd1077fe9..b064e017f 100644 --- a/jason/src/peer/media/receiver/component.rs +++ b/jason/src/peer/media/receiver/component.rs @@ -14,18 +14,17 @@ use medea_reactive::{ }; use crate::{ - media::LocalTracksConstraints, + media::{LocalTracksConstraints, MediaKind}, peer::{ component::SyncState, media::{ transitable_state::media_exchange_state, InTransition, Result, }, MediaExchangeState, MediaExchangeStateController, - MediaStateControllable, MuteStateController, TransceiverDirection, - TransceiverSide, + MediaStateControllable, MuteStateController, TransceiverSide, }, + platform, utils::{component, AsProtoState, SynchronizableState, Updatable}, - MediaKind, }; use super::Receiver; @@ -248,11 +247,13 @@ impl State { #[watchers] impl Component { - /// Watcher for the [`State::general_media_exchange_state`] update. + /// Watcher for the [`State::enabled_general`] updates. /// /// Updates [`Receiver`]'s general media exchange state. Adds or removes - /// [`TransceiverDirection::RECV`] from the [`Transceiver`] of the + /// [`TransceiverDirection::RECV`] from the [`platform::Transceiver`] of the /// [`Receiver`]. + /// + /// [`TransceiverDirection::RECV`]: platform::TransceiverDirection::RECV #[watch(self.enabled_general.subscribe())] async fn general_media_exchange_state_changed( receiver: Rc, @@ -269,7 +270,7 @@ impl Component { track.set_enabled(false); } if let Some(trnscvr) = receiver.transceiver.borrow().as_ref() { - trnscvr.sub_direction(TransceiverDirection::RECV); + trnscvr.sub_direction(platform::TransceiverDirection::RECV); } } media_exchange_state::Stable::Enabled => { @@ -277,7 +278,7 @@ impl Component { track.set_enabled(true); } if let Some(trnscvr) = receiver.transceiver.borrow().as_ref() { - trnscvr.add_direction(TransceiverDirection::RECV); + trnscvr.add_direction(platform::TransceiverDirection::RECV); } } } @@ -286,7 +287,8 @@ impl Component { Ok(()) } - /// Watcher for [`MediaExchangeState::Stable`] update. + /// Watcher for [`media_exchange_state::Stable`] media exchange state + /// updates. /// /// Updates [`Receiver::enabled_individual`] to the new state. #[inline] @@ -302,10 +304,13 @@ impl Component { Ok(()) } - /// Watcher for [`MediaExchangeState::Transition`] update. + /// Watcher for media exchange state [`media_exchange_state::Transition`] + /// updates. /// - /// Sends [`TrackEvent::MediaExchangeIntention`] with the provided + /// Sends [`TrackEvent::MediaExchangeIntention`][1] with the provided /// [`media_exchange_state`]. + /// + /// [1]: crate::peer::TrackEvent::MediaExchangeIntention #[inline] #[watch(self.enabled_individual.subscribe_transition())] async fn enabled_individual_transition_started( @@ -402,8 +407,6 @@ impl TransceiverSide for State { impl State { /// Stabilizes [`MediaExchangeState`] of this [`State`]. pub fn stabilize(&self) { - use crate::peer::media::InTransition as _; - if let crate::peer::MediaExchangeState::Transition(transition) = self.enabled_individual.state() { diff --git a/jason/src/peer/media/receiver/mod.rs b/jason/src/peer/media/receiver/mod.rs index 601855b43..70e8b8888 100644 --- a/jason/src/peer/media/receiver/mod.rs +++ b/jason/src/peer/media/receiver/mod.rs @@ -5,35 +5,33 @@ mod component; use std::cell::{Cell, RefCell}; use futures::channel::mpsc; -use medea_client_api_proto as proto; -use medea_client_api_proto::{MediaType, MemberId}; +use medea_client_api_proto::{self as proto, MediaType, MemberId}; use proto::TrackId; -use web_sys as sys; use crate::{ media::{track::remote, MediaKind, RecvConstraints, TrackConstraints}, peer::{ - media::{media_exchange_state, TrackEvent}, - transceiver::Transceiver, - MediaConnections, MediaStateControllable, PeerEvent, - TransceiverDirection, + media::media_exchange_state, MediaConnections, MediaStateControllable, + PeerEvent, TrackEvent, }, + platform, }; use super::TransceiverSide; +#[doc(inline)] pub use self::component::{Component, State}; /// Representation of a remote [`remote::Track`] that is being received from /// some remote peer. It may have two states: `waiting` and `receiving`. /// -/// We can save related [`Transceiver`] and the actual +/// We can save related [`platform::Transceiver`] and the actual /// [`remote::Track`] only when [`remote::Track`] data arrives. pub struct Receiver { track_id: TrackId, caps: TrackConstraints, sender_id: MemberId, - transceiver: RefCell>, + transceiver: RefCell>, mid: RefCell>, track: RefCell>, is_track_notified: Cell, @@ -44,16 +42,19 @@ pub struct Receiver { } impl Receiver { - /// Creates new [`Transceiver`] if provided `mid` is `None`, otherwise - /// creates [`Receiver`] without [`Transceiver`]. It will be injected - /// when [`remote::Track`] arrives. + /// Creates a new [`platform::Transceiver`] if provided `mid` is [`None`], + /// otherwise creates a [`Receiver`] without a [`platform::Transceiver`]. It + /// will be injected when a [`remote::Track`] will arrive. /// - /// Created [`Transceiver`] direction is set to - /// [`TransceiverDirection::INACTIVE`] if `enabled_individual` is `false`. + /// Created [`platform::Transceiver`] direction is set to + /// [`TransceiverDirection::INACTIVE`][1] if `enabled_individual` is + /// `false`. /// /// `track` field in the created [`Receiver`] will be `None`, since /// [`Receiver`] must be created before the actual [`remote::Track`] data /// arrives. + /// + /// [1]: platform::TransceiverDirection::INACTIVE pub fn new( state: &State, media_connections: &MediaConnections, @@ -64,9 +65,9 @@ impl Receiver { let caps = TrackConstraints::from(state.media_type().clone()); let kind = MediaKind::from(&caps); let transceiver_direction = if state.enabled_individual() { - TransceiverDirection::RECV + platform::TransceiverDirection::RECV } else { - TransceiverDirection::INACTIVE + platform::TransceiverDirection::INACTIVE }; let transceiver = if state.mid().is_none() { @@ -144,7 +145,7 @@ impl Receiver { pub fn is_receiving(&self) -> bool { let is_recv_direction = self.transceiver.borrow().as_ref().map_or(false, |trnsvr| { - trnsvr.has_direction(TransceiverDirection::RECV) + trnsvr.has_direction(platform::TransceiverDirection::RECV) }); self.enabled_individual.get() && is_recv_direction @@ -167,15 +168,15 @@ impl Receiver { ); } - /// Adds provided [`sys::MediaStreamTrack`] and [`Transceiver`] to this - /// [`Receiver`]. + /// Adds the provided [`platform::MediaStreamTrack`] and + /// [`platform::Transceiver`] to this [`Receiver`]. /// - /// Sets [`sys::MediaStreamTrack::enabled`] same as [`Receiver::enabled`] of - /// this [`Receiver`]. + /// Sets [`platform::MediaStreamTrack::enabled`] same as + /// [`Receiver::enabled`] of this [`Receiver`]. pub fn set_remote_track( &self, - transceiver: Transceiver, - new_track: sys::MediaStreamTrack, + transceiver: platform::Transceiver, + new_track: platform::MediaStreamTrack, ) { if let Some(old_track) = self.track.borrow().as_ref() { if old_track.id() == new_track.id() { @@ -187,9 +188,9 @@ impl Receiver { remote::Track::new(new_track, self.caps.media_source_kind()); if self.enabled() { - transceiver.add_direction(TransceiverDirection::RECV); + transceiver.add_direction(platform::TransceiverDirection::RECV); } else { - transceiver.sub_direction(TransceiverDirection::RECV); + transceiver.sub_direction(platform::TransceiverDirection::RECV); } new_track.set_enabled(self.enabled()); @@ -198,23 +199,26 @@ impl Receiver { self.maybe_notify_track(); } - /// Replaces [`Receiver`]'s [`Transceiver`] with a provided [`Transceiver`]. + /// Replaces [`Receiver`]'s [`platform::Transceiver`] with the provided + /// [`platform::Transceiver`]. /// - /// Doesn't update [`TransceiverDirection`] of the [`Transceiver`]. + /// Doesn't update [`platform::TransceiverDirection`] of the + /// [`platform::Transceiver`]. /// - /// No-op if provided with the same [`Transceiver`] as already exists in - /// this [`Receiver`]. - pub fn replace_transceiver(&self, transceiver: Transceiver) { + /// No-op if provided with the same [`platform::Transceiver`] as already + /// exists in this [`Receiver`]. + pub fn replace_transceiver(&self, transceiver: platform::Transceiver) { if self.mid.borrow().as_ref() == transceiver.mid().as_ref() { self.transceiver.replace(Some(transceiver)); } } - /// Returns [`Transceiver`] of this [`Receiver`]. + /// Returns a [`platform::Transceiver`] of this [`Receiver`]. /// - /// Returns [`None`] if this [`Receiver`] doesn't have [`Transceiver`]. + /// Returns [`None`] if this [`Receiver`] doesn't have a + /// [`platform::Transceiver`]. #[inline] - pub fn transceiver(&self) -> Option { + pub fn transceiver(&self) -> Option { self.transceiver.borrow().clone() } diff --git a/jason/src/peer/media/sender/component.rs b/jason/src/peer/media/sender/component.rs index da7485713..33635d877 100644 --- a/jason/src/peer/media/sender/component.rs +++ b/jason/src/peer/media/sender/component.rs @@ -12,7 +12,7 @@ use medea_reactive::{AllProcessed, Guarded, ObservableCell, ProgressableCell}; use tracerr::Traced; use crate::{ - media::{LocalTracksConstraints, TrackConstraints, VideoSource}, + media::{LocalTracksConstraints, MediaKind, TrackConstraints, VideoSource}, peer::{ self, component::SyncState, @@ -22,10 +22,10 @@ use crate::{ }, MediaConnectionsError, MediaExchangeStateController, MediaState, MediaStateControllable, MuteStateController, PeerError, - TransceiverDirection, TransceiverSide, + TransceiverSide, }, + platform, utils::{component, AsProtoState, SynchronizableState, Updatable}, - MediaKind, }; use super::Sender; @@ -34,12 +34,18 @@ use super::Sender; /// /// [`PartialEq`] implementation of this state ignores /// [`LocalTrackState::Failed`] content. +/// +/// [`local::Track`]: crate::media::track::local::Track #[derive(Debug, Clone)] enum LocalTrackState { /// Indicates that [`Sender`] is new, or [`local::Track`] is set. + /// + /// [`local::Track`]: crate::media::track::local::Track Stable, /// Indicates that [`Sender`] needs a new [`local::Track`]. + /// + /// [`local::Track`]: crate::media::track::local::Track NeedUpdate, /// Indicates that new [`local::Track`] getting is failed. @@ -47,6 +53,7 @@ enum LocalTrackState { /// Contains [`PeerError`] with which /// [getUserMedia()][1]/[getDisplayMedia()][2] request was failed. /// + /// [`local::Track`]: crate::media::track::local::Track /// [1]: https://tinyurl.com/w3-streams#dom-mediadevices-getusermedia /// [2]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia Failed(Traced), @@ -392,10 +399,13 @@ impl State { #[watchers] impl Component { - /// Watcher for [`MediaExchangeState::Transition`] update. + /// Watcher for media exchange state [`media_exchange_state::Transition`] + /// updates. /// - /// Sends [`TrackEvent::MediaExchangeIntention`] with the provided + /// Sends [`TrackEvent::MediaExchangeIntention`][1] with the provided /// [`media_exchange_state`]. + /// + /// [1]: crate::peer::TrackEvent::MediaExchangeIntention #[watch(self.enabled_individual.subscribe_transition())] async fn enabled_individual_transition_started( sender: Rc, @@ -406,10 +416,12 @@ impl Component { Ok(()) } - /// Watcher for [`MuteState::Transition`] update. + /// Watcher for mute state [`mute_state::Transition`] updates. /// - /// Sends [`TrackEvent::MuteUpdateIntention`] with the provided + /// Sends [`TrackEvent::MuteUpdateIntention`][1] with the provided /// [`mute_state`]. + /// + /// [1]: crate::peer::TrackEvent::MuteUpdateIntention #[watch(self.mute_state.subscribe_transition())] async fn mute_state_transition_watcher( sender: Rc, @@ -423,8 +435,10 @@ impl Component { /// Watcher for the [`State::enabled_general`] update. /// /// Updates [`Sender`]'s general media exchange state. Adds or removes - /// [`TransceiverDirection::SEND`] from the [`Transceiver`] of the - /// [`Receiver`]. + /// [`TransceiverDirection::SEND`] from the [`platform::Transceiver`] of + /// this [`Sender`]. + /// + /// [`TransceiverDirection::SEND`]: platform::TransceiverDirection::SEND #[watch(self.enabled_general.subscribe())] async fn enabled_general_state_changed( sender: Rc, @@ -432,7 +446,6 @@ impl Component { new_state: Guarded, ) -> Result<()> { let (new_state, _guard) = new_state.into_parts(); - sender .enabled_general .set(new_state == media_exchange_state::Stable::Enabled); @@ -441,26 +454,29 @@ impl Component { if sender.enabled_in_cons() { sender .transceiver - .add_direction(TransceiverDirection::SEND); + .add_direction(platform::TransceiverDirection::SEND); } } media_exchange_state::Stable::Disabled => { - sender.transceiver.sub_direction(TransceiverDirection::SEND); + sender + .transceiver + .sub_direction(platform::TransceiverDirection::SEND); } } Ok(()) } - /// Watcher for the [`MediaExchangeState::Stable`] update. + /// Watcher for [`media_exchange_state::Stable`] media exchange state + /// updates. /// - /// Updates [`Receiver::enabled_individual`] to the `new_state`. + /// Updates [`Sender::enabled_individual`] to the `new_state`. /// - /// Removes `MediaTrack` from [`Transceiver`] if `new_state` is + /// Removes `MediaTrack` from [`platform::Transceiver`] if `new_state` is /// [`media_exchange_state::Stable::Disabled`]. /// - /// Sets [`State::need_local_stream_update`] to the `true` if `new_state` is - /// [`media_exchange_state::Stable::Enabled`]. + /// Marks [`State::local_track_state`] as [`LocalTrackState::NeedUpdate`] if + /// `new_state` is [`media_exchange_state::Stable::Enabled`]. #[watch(self.enabled_individual.subscribe_stable())] async fn enabled_individual_stable_state_changed( sender: Rc, @@ -481,11 +497,12 @@ impl Component { Ok(()) } - /// Watcher for the [`MuteState::Stable`] update. + /// Watcher for the [`mute_state::Stable`] updates. /// /// Updates [`Sender`]'s mute state. /// - /// Updates [`Sender`]'s [`Transceiver`] `MediaTrack.enabled` property. + /// Updates [`Sender`]'s [`platform::Transceiver`] `MediaTrack.enabled` + /// property. #[watch(self.mute_state.subscribe_stable())] async fn mute_state_stable_watcher( sender: Rc, diff --git a/jason/src/peer/media/sender/mod.rs b/jason/src/peer/media/sender/mod.rs index cdca10665..07db74ecc 100644 --- a/jason/src/peer/media/sender/mod.rs +++ b/jason/src/peer/media/sender/mod.rs @@ -11,10 +11,8 @@ use crate::{ media::{ track::local, LocalTracksConstraints, MediaKind, TrackConstraints, }, - peer::{ - transceiver::{Transceiver, TransceiverDirection}, - TrackEvent, - }, + peer::TrackEvent, + platform, }; use super::{ @@ -22,13 +20,14 @@ use super::{ MediaStateControllable, Result, }; +#[doc(inline)] pub use self::component::{Component, State}; /// Representation of a [`local::Track`] that is being sent to some remote peer. pub struct Sender { track_id: TrackId, caps: TrackConstraints, - transceiver: Transceiver, + transceiver: platform::Transceiver, muted: Cell, enabled_individual: Cell, enabled_general: Cell, @@ -37,10 +36,9 @@ pub struct Sender { } impl Sender { - /// Creates new [`Transceiver`] if provided `mid` is [`None`], otherwise - /// retrieves existing [`Transceiver`] via provided `mid` from a - /// provided [`MediaConnections`]. Errors if [`Transceiver`] lookup - /// fails. + /// Creates a new [`platform::Transceiver`] if the provided `mid` is + /// [`None`], otherwise retrieves an existing [`platform::Transceiver`] via + /// the provided `mid` from the provided [`MediaConnections`]. /// /// # Errors /// @@ -82,8 +80,10 @@ impl Sender { }) .and_then(|rcvr| rcvr.transceiver()) .unwrap_or_else(|| { - connections - .add_transceiver(kind, TransceiverDirection::INACTIVE) + connections.add_transceiver( + kind, + platform::TransceiverDirection::INACTIVE, + ) }), Some(mid) => connections .get_transceiver_by_mid(mid) @@ -128,7 +128,8 @@ impl Sender { #[inline] #[must_use] pub fn is_publishing(&self) -> bool { - self.transceiver.has_direction(TransceiverDirection::SEND) + self.transceiver + .has_direction(platform::TransceiverDirection::SEND) } /// Drops [`local::Track`] used by this [`Sender`]. Sets track used by @@ -174,10 +175,10 @@ impl Sender { Ok(()) } - /// Returns [`Transceiver`] of this [`Sender`]. + /// Returns [`platform::Transceiver`] of this [`Sender`]. #[inline] #[must_use] - pub fn transceiver(&self) -> Transceiver { + pub fn transceiver(&self) -> platform::Transceiver { self.transceiver.clone() } diff --git a/jason/src/peer/media/transitable_state/controller.rs b/jason/src/peer/media/transitable_state/controller.rs index 346c2f56f..1680041ed 100644 --- a/jason/src/peer/media/transitable_state/controller.rs +++ b/jason/src/peer/media/transitable_state/controller.rs @@ -11,7 +11,6 @@ use futures::{ StreamExt as _, }; use medea_reactive::{Processed, ProgressableCell}; -use wasm_bindgen_futures::spawn_local; use crate::{ peer::media::{ @@ -20,6 +19,7 @@ use crate::{ }, MediaConnectionsError, Result, }, + platform, utils::{resettable_delay_for, ResettableDelayHandle}, }; @@ -81,13 +81,13 @@ where // at that moment. let mut state_changes = self.state.subscribe().skip(1); let weak_this = Rc::downgrade(&self); - spawn_local(async move { + platform::spawn(async move { while let Some(state) = state_changes.next().await { let (state, _guard) = state.into_parts(); if let Some(this) = weak_this.upgrade() { if let TransitableState::Transition(_) = state { let weak_this = Rc::downgrade(&this); - spawn_local(async move { + platform::spawn(async move { let mut states = this.state.subscribe().skip(1); let (timeout, timeout_handle) = resettable_delay_for( @@ -126,7 +126,7 @@ where /// Returns [`Stream`] into which the [`TransitableState::Stable`] updates /// will be emitted. /// - /// [`Stream`]: futures::stream::Stream + /// [`Stream`]: futures::Stream pub fn subscribe_stable(&self) -> LocalBoxStream<'static, S> { self.state .subscribe() @@ -144,7 +144,7 @@ where /// Returns [`Stream`] into which the [`TransitableState::Transition`] /// updates will be emitted. /// - /// [`Stream`]: futures::stream::Stream + /// [`Stream`]: futures::Stream pub fn subscribe_transition(&self) -> LocalBoxStream<'static, T> { self.state .subscribe() diff --git a/jason/src/peer/media/transitable_state/mod.rs b/jason/src/peer/media/transitable_state/mod.rs index 1d2b2ff6d..429e680c8 100644 --- a/jason/src/peer/media/transitable_state/mod.rs +++ b/jason/src/peer/media/transitable_state/mod.rs @@ -9,6 +9,7 @@ pub mod mute_state; use derive_more::From; use medea_client_api_proto::{TrackId, TrackPatchCommand}; +#[doc(inline)] pub use self::controller::{ MediaExchangeStateController, MuteStateController, TransitableStateController, @@ -86,6 +87,7 @@ impl MediaState { /// [`TransitableState::Stable`] variant of the [`TransitableState`]. pub trait InStable: Clone + Copy + PartialEq { + /// Transition invariants of this [`InStable`]. type Transition: InTransition; /// Converts this [`InStable`] into [`InStable::Transition`]. @@ -95,6 +97,7 @@ pub trait InStable: Clone + Copy + PartialEq { /// [`TransitableState::Transition`] variant of the [`TransitableState`]. pub trait InTransition: Clone + Copy + PartialEq { + /// Stable invariants of this [`InTransition`]. type Stable: InStable; /// Returns intention which this state indicates. diff --git a/jason/src/peer/mod.rs b/jason/src/peer/mod.rs index a0816ebcb..e5a5941cd 100644 --- a/jason/src/peer/mod.rs +++ b/jason/src/peer/mod.rs @@ -3,14 +3,10 @@ //! [1]: https://w3.org/TR/webrtc/#rtcpeerconnection-interface mod component; -mod conn; -mod ice_server; -mod media; +pub mod media; pub mod repo; -mod stats; mod stream_update_criteria; mod tracks_request; -mod transceiver; use std::{ cell::{Cell, RefCell}, @@ -28,24 +24,21 @@ use medea_client_api_proto::{ }; use medea_macro::dispatchable; use tracerr::Traced; -use wasm_bindgen_futures::spawn_local; -use web_sys::{RtcIceConnectionState, RtcTrackEvent}; use crate::{ - api::Connections, + connection::Connections, media::{ track::{local, remote}, LocalTracksConstraints, MediaKind, MediaManager, MediaManagerError, - RecvConstraints, + MediaStreamSettings, RecvConstraints, }, - utils::{JasonError, JsCaused, JsError}, - MediaStreamSettings, + platform, + utils::{JasonError, JsCaused}, }; #[doc(inline)] pub use self::{ component::{Component, State}, - conn::{IceCandidate, RTCPeerConnectionError, RtcPeerConnection, SdpType}, media::{ media_exchange_state, mute_state, receiver, sender, MediaConnections, MediaConnectionsError, MediaExchangeState, @@ -53,16 +46,15 @@ pub use self::{ MuteState, MuteStateController, TrackDirection, TransceiverSide, TransitableState, TransitableStateController, }, - stats::RtcStats, stream_update_criteria::LocalStreamUpdateCriteria, tracks_request::{SimpleTracksRequest, TracksRequest, TracksRequestError}, - transceiver::{Transceiver, TransceiverDirection}, }; /// Errors that may occur in [RTCPeerConnection][1]. /// /// [1]: https://w3.org/TR/webrtc/#rtcpeerconnection-interface #[derive(Clone, Debug, Display, From, JsCaused)] +#[js(error = "platform::Error")] pub enum PeerError { /// Errors that may occur in [`MediaConnections`] storage. #[display(fmt = "{}", _0)] @@ -77,7 +69,7 @@ pub enum PeerError { /// /// [1]: https://w3.org/TR/webrtc/#dom-rtcpeerconnection #[display(fmt = "{}", _0)] - RtcPeerConnection(#[js(cause)] RTCPeerConnectionError), + RtcPeerConnection(#[js(cause)] platform::RTCPeerConnectionError), /// Errors that may occur when validating [`TracksRequest`] or parsing /// [`local::Track`]s. @@ -112,11 +104,11 @@ pub enum TrackEvent { }, } +/// Events emitted from [`platform::RtcPeerConnection`]. #[dispatchable(self: &Self, async_trait(?Send))] #[derive(Clone)] -/// Events emitted from [`RtcPeerConnection`]. pub enum PeerEvent { - /// [`RtcPeerConnection`] discovered new ICE candidate. + /// [`platform::RtcPeerConnection`] discovered new ICE candidate. /// /// Wrapper around [RTCPeerConnectionIceEvent][1]. /// @@ -144,8 +136,8 @@ pub enum PeerEvent { sdp_mid: Option, }, - /// [`RtcPeerConnection`] received new [`remote::Track`] from remote - /// sender. + /// [`platform::RtcPeerConnection`] received a new [`remote::Track`] from + /// a remote sender. NewRemoteTrack { /// Remote `Member` ID. sender_id: MemberId, @@ -154,13 +146,13 @@ pub enum PeerEvent { track: remote::Track, }, - /// [`RtcPeerConnection`] sent new local track to remote members. + /// [`platform::RtcPeerConnection`] sent new local track to remote members. NewLocalTrack { /// Local [`local::Track`] that is sent to remote members. local_track: Rc, }, - /// [`RtcPeerConnection`]'s [ICE connection][1] state changed. + /// [`platform::RtcPeerConnection`]'s [ICE connection][1] state changed. /// /// [1]: https://w3.org/TR/webrtc/#dfn-ice-connection-state IceConnectionStateChanged { @@ -174,7 +166,7 @@ pub enum PeerEvent { ice_connection_state: IceConnectionState, }, - /// [`RtcPeerConnection`]'s [connection][1] state changed. + /// [`platform::RtcPeerConnection`]'s [connection][1] state changed. /// /// [1]: https://w3.org/TR/webrtc/#dfn-ice-connection-state ConnectionStateChanged { @@ -188,13 +180,14 @@ pub enum PeerEvent { peer_connection_state: PeerConnectionState, }, - /// [`RtcPeerConnection`]'s [`RtcStats`] update. + /// [`platform::RtcPeerConnection`]'s [`platform::RtcStats`] update. StatsUpdate { - /// ID of the [`PeerConnection`] for which [`RtcStats`] was sent. + /// ID of the [`PeerConnection`] for which [` platform::RtcStats`] was + /// sent. peer_id: Id, - /// [`RtcStats`] of this [`PeerConnection`]. - stats: RtcStats, + /// [` platform::RtcStats`] of this [`PeerConnection`]. + stats: platform::RtcStats, }, /// [`PeerConnection::update_local_stream`] was failed, so @@ -243,16 +236,16 @@ pub enum PeerEvent { }, } -/// High-level wrapper around [`RtcPeerConnection`]. +/// High-level wrapper around a [`platform::RtcPeerConnection`]. pub struct PeerConnection { /// Unique ID of [`PeerConnection`]. id: Id, - /// Underlying [`RtcPeerConnection`]. - peer: Rc, + /// Underlying [`platform::RtcPeerConnection`]. + peer: Rc, /// [`sender::Component`]s and [`receiver::Component`]s of this - /// [`RtcPeerConnection`]. + /// [`platform::RtcPeerConnection`]. media_connections: Rc, /// [`MediaManager`] that will be used to acquire [`local::Track`]s. @@ -261,15 +254,16 @@ pub struct PeerConnection { /// [`PeerEvent`]s tx. peer_events_sender: mpsc::UnboundedSender, - /// Indicates if underlying [`RtcPeerConnection`] has remote description. + /// Indicator whether the underlying [`platform::RtcPeerConnection`] has a + /// remote description. has_remote_description: Cell, - /// Stores [`IceCandidate`]s received before remote description for - /// underlying [`RtcPeerConnection`]. - ice_candidates_buffer: RefCell>, + /// Buffer of [`platform::IceCandidate`]s received before a remote + /// description for the underlying [`platform::RtcPeerConnection`]. + ice_candidates_buffer: RefCell>, - /// Last hashes of the all [`RtcStats`] which was already sent to the - /// server, so we won't duplicate stats that were already sent. + /// Last hashes of all the [`platform::RtcStats`] which were already sent + /// to a server, so we won't duplicate stats that were already sent. /// /// Stores precomputed hashes, since we don't need access to actual stats /// values. @@ -280,7 +274,7 @@ pub struct PeerConnection { /// Collection of [`Connection`]s with a remote `Member`s. /// - /// [`Connection`]: crate::api::Connection + /// [`Connection`]: crate::connection::Connection connections: Rc, /// Sender for the [`TrackEvent`]s which should be processed by this @@ -298,15 +292,16 @@ impl PeerConnection { /// Provided `peer_events_sender` will be used to emit [`PeerEvent`]s from /// this peer. /// - /// Provided `ice_servers` will be used by created [`RtcPeerConnection`]. + /// Provided `ice_servers` will be used by the created + /// [`platform::RtcPeerConnection`]. /// /// # Errors /// - /// Errors with [`PeerError::RtcPeerConnection`] if [`RtcPeerConnection`] - /// creating fails. + /// Errors with [`PeerError::RtcPeerConnection`] if + /// [`platform::RtcPeerConnection`] creating fails. /// /// Errors with [`PeerError::RtcPeerConnection`] if some callback of - /// [`RtcPeerConnection`] can't be set. + /// [`platform::RtcPeerConnection`] can't be set. pub fn new( state: &State, peer_events_sender: mpsc::UnboundedSender, @@ -316,7 +311,7 @@ impl PeerConnection { recv_constraints: Rc, ) -> Result> { let peer = Rc::new( - RtcPeerConnection::new( + platform::RtcPeerConnection::new( state.ice_servers().clone(), state.force_relay(), ) @@ -328,7 +323,7 @@ impl PeerConnection { peer_events_sender.clone(), )); - spawn_local({ + platform::spawn({ let peer_events_sender = peer_events_sender.clone(); let peer_id = state.id(); @@ -357,58 +352,57 @@ impl PeerConnection { // Bind to `icecandidate` event. let id = peer.id; let sender = peer.peer_events_sender.clone(); - peer.peer - .on_ice_candidate(Some(move |candidate| { - Self::on_ice_candidate(id, &sender, candidate); - })) - .map_err(tracerr::map_from_and_wrap!())?; + peer.peer.on_ice_candidate(Some(move |candidate| { + Self::on_ice_candidate(id, &sender, candidate); + })); // Bind to `iceconnectionstatechange` event. let id = peer.id; let sender = peer.peer_events_sender.clone(); - peer.peer - .on_ice_connection_state_change(Some(move |ice_connection_state| { + peer.peer.on_ice_connection_state_change(Some( + move |ice_connection_state| { Self::on_ice_connection_state_changed( id, &sender, ice_connection_state, ); - })) - .map_err(tracerr::map_from_and_wrap!())?; + }, + )); // Bind to `connectionstatechange` event. let id = peer.id; let sender = peer.peer_events_sender.clone(); - peer.peer - .on_connection_state_change(Some(move |peer_connection_state| { + peer.peer.on_connection_state_change(Some( + move |peer_connection_state| { Self::on_connection_state_changed( id, &sender, peer_connection_state, ) - })) - .map_err(tracerr::map_from_and_wrap!())?; + }, + )); // Bind to `track` event. let media_connections = Rc::clone(&peer.media_connections); - peer.peer - .on_track(Some(move |track_event| { - if let Err(err) = - media_connections.add_remote_track(&track_event) - { - JasonError::from(err).print(); - }; - })) - .map_err(tracerr::map_from_and_wrap!())?; + peer.peer.on_track(Some(move |track, transceiver| { + if let Err(err) = + media_connections.add_remote_track(track, transceiver) + { + JasonError::from(err).print(); + }; + })); Ok(Rc::new(peer)) } - /// Handler [`TrackEvent`]s emitted from [`Sender`] or [`Receiver`]. + /// Handles [`TrackEvent`]s emitted from a [`Sender`] or a [`Receiver`]. + /// + /// Sends a [`PeerEvent::MediaUpdateCommand`] with a + /// [`Command::UpdateTracks`] on [`TrackEvent::MediaExchangeIntention`] and + /// [`TrackEvent::MuteUpdateIntention`]. /// - /// Sends [`PeerEvent::SendIntention`] with a [`Command::UpdateTracks`] on - /// [`TrackEvent::MediaExchangeIntention`] and - /// [`TrackEvent::MuteStateIntention`]. + /// [`Sender`]: sender::Sender + /// [`Receiver`]: receiver::Receiver fn handle_track_event( peer_id: PeerId, peer_events_sender: &mpsc::UnboundedSender, @@ -463,12 +457,12 @@ impl PeerConnection { self.media_connections.drop_send_tracks(kinds).await } - /// Filters out already sent stats, and send new statss from - /// provided [`RtcStats`]. + /// Filters out already sent stats, and send new stats from the provided + /// [`platform::RtcStats`]. #[allow(clippy::option_if_let_else)] - pub fn send_peer_stats(&self, stats: RtcStats) { + pub fn send_peer_stats(&self, stats: platform::RtcStats) { let mut stats_cache = self.sent_stats_cache.borrow_mut(); - let stats = RtcStats( + let stats = platform::RtcStats( stats .0 .into_iter() @@ -502,7 +496,8 @@ impl PeerConnection { } } - /// Sends [`RtcStats`] update of this [`PeerConnection`] to the server. + /// Sends [`platform::RtcStats`] update of this [`PeerConnection`] to a + /// server. pub async fn scrape_and_send_peer_stats(&self) { match self.peer.get_stats().await { Ok(stats) => self.send_peer_stats(stats), @@ -544,7 +539,7 @@ impl PeerConnection { fn on_ice_candidate( id: Id, sender: &mpsc::UnboundedSender, - candidate: IceCandidate, + candidate: platform::IceCandidate, ) { let _ = sender.unbounded_send(PeerEvent::IceCandidateDiscovered { peer_id: id, @@ -560,24 +555,8 @@ impl PeerConnection { fn on_ice_connection_state_changed( peer_id: Id, sender: &mpsc::UnboundedSender, - ice_connection_state: RtcIceConnectionState, + ice_connection_state: IceConnectionState, ) { - use RtcIceConnectionState as S; - - let ice_connection_state = match ice_connection_state { - S::New => IceConnectionState::New, - S::Checking => IceConnectionState::Checking, - S::Connected => IceConnectionState::Connected, - S::Completed => IceConnectionState::Completed, - S::Failed => IceConnectionState::Failed, - S::Disconnected => IceConnectionState::Disconnected, - S::Closed => IceConnectionState::Closed, - S::__Nonexhaustive => { - log::error!("Unknown ICE connection state"); - return; - } - }; - let _ = sender.unbounded_send(PeerEvent::IceConnectionStateChanged { peer_id, ice_connection_state, @@ -642,9 +621,9 @@ impl PeerConnection { } /// Track id to mid relations of all send tracks of this - /// [`RtcPeerConnection`]. mid is id of [`m= section`][1]. mids are received - /// directly from registered [`RTCRtpTransceiver`][2]s, and are being - /// allocated on sdp update. + /// [`platform::RtcPeerConnection`]. mid is id of [`m= section`][1]. mids + /// are received directly from registered [`RTCRtpTransceiver`][2]s, and + /// are being allocated on SDP update. /// /// # Errors /// @@ -689,7 +668,7 @@ impl PeerConnection { /// Constraints being used when requesting stream from [`MediaManager`] are /// a result of merging constraints received from this [`PeerConnection`] /// [`Sender`]s, which are configured by server during signalling, and - /// [`LocalTracksConstraints`], that are optionally configured by JS-side. + /// [`LocalTracksConstraints`]. /// /// Returns [`HashMap`] with [`media_exchange_state::Stable`]s updates for /// the [`Sender`]s. @@ -767,7 +746,7 @@ impl PeerConnection { /// /// # Errors /// - /// Errors with [`Media::TracksRequest`] if failed to create or merge + /// Errors with [`PeerError::TracksRequest`] if failed to create or merge /// [`SimpleTracksRequest`]. fn get_simple_tracks_request( &self, @@ -846,13 +825,14 @@ impl PeerConnection { /// /// # Errors /// - /// With [`RTCPeerConnectionError::SetRemoteDescriptionFailed`] if + /// With [`RTCPeerConnectionError::SetRemoteDescriptionFailed`][3] if /// [RTCPeerConnection.setRemoteDescription()][2] fails. /// /// [1]: https://w3.org/TR/webrtc/#rtcpeerconnection-interface /// [2]: https://w3.org/TR/webrtc/#dom-peerconnection-setremotedescription + /// [3]: platform::RTCPeerConnectionError::SetRemoteDescriptionFailed async fn set_remote_answer(&self, answer: String) -> Result<()> { - self.set_remote_description(SdpType::Answer(answer)) + self.set_remote_description(platform::SdpType::Answer(answer)) .await .map_err(tracerr::wrap!()) } @@ -861,13 +841,13 @@ impl PeerConnection { /// /// # Errors /// - /// With [`RTCPeerConnectionError::SetRemoteDescriptionFailed`] if + /// With [`platform::RTCPeerConnectionError::SetRemoteDescriptionFailed`] if /// [RTCPeerConnection.setRemoteDescription()][2] fails. /// /// [1]: https://w3.org/TR/webrtc/#rtcpeerconnection-interface /// [2]: https://w3.org/TR/webrtc/#dom-peerconnection-setremotedescription async fn set_remote_offer(&self, offer: String) -> Result<()> { - self.set_remote_description(SdpType::Offer(offer)) + self.set_remote_description(platform::SdpType::Offer(offer)) .await .map_err(tracerr::wrap!()) } @@ -877,17 +857,20 @@ impl PeerConnection { /// /// # Errors /// - /// With [`RTCPeerConnectionError::SetRemoteDescriptionFailed`] if + /// With [`platform::RTCPeerConnectionError::SetRemoteDescriptionFailed`] if /// [RTCPeerConnection.setRemoteDescription()][2] fails. /// - /// With [`RTCPeerConnectionError::AddIceCandidateFailed`] if + /// With [`platform::RTCPeerConnectionError::AddIceCandidateFailed`] if /// [RtcPeerConnection.addIceCandidate()][3] fails when adding buffered ICE /// candidates. /// /// [1]: https://w3.org/TR/webrtc/#rtcpeerconnection-interface /// [2]: https://w3.org/TR/webrtc/#dom-peerconnection-setremotedescription /// [3]: https://w3.org/TR/webrtc/#dom-peerconnection-addicecandidate - async fn set_remote_description(&self, desc: SdpType) -> Result<()> { + async fn set_remote_description( + &self, + desc: platform::SdpType, + ) -> Result<()> { self.peer .set_remote_description(desc) .await @@ -921,12 +904,13 @@ impl PeerConnection { /// /// # Errors /// - /// With [`RTCPeerConnectionError::AddIceCandidateFailed`] if - /// [RtcPeerConnection.addIceCandidate()][2] fails to add buffered + /// With [`RTCPeerConnectionError::AddIceCandidateFailed`][2] if + /// [RtcPeerConnection.addIceCandidate()][3] fails to add buffered /// [ICE candidates][1]. /// /// [1]: https://tools.ietf.org/html/rfc5245#section-2 - /// [2]: https://w3.org/TR/webrtc/#dom-peerconnection-addicecandidate + /// [2]: platform::RTCPeerConnectionError::AddIceCandidateFailed + /// [3]: https://w3.org/TR/webrtc/#dom-peerconnection-addicecandidate pub async fn add_ice_candidate( &self, candidate: String, @@ -939,11 +923,13 @@ impl PeerConnection { .await .map_err(tracerr::map_from_and_wrap!())?; } else { - self.ice_candidates_buffer.borrow_mut().push(IceCandidate { - candidate, - sdp_m_line_index, - sdp_mid, - }); + self.ice_candidates_buffer.borrow_mut().push( + platform::IceCandidate { + candidate, + sdp_m_line_index, + sdp_mid, + }, + ); } Ok(()) } @@ -957,7 +943,7 @@ impl PeerConnection { /// /// Errors with [`PeerError::RtcPeerConnection`] if failed to get /// [`RtcStats`]. - pub async fn get_stats(&self) -> Result { + pub async fn get_stats(&self) -> Result { self.peer .get_stats() .await @@ -1066,9 +1052,10 @@ impl Drop for PeerConnection { /// Drops `on_track` and `on_ice_candidate` callbacks to prevent possible /// leaks. fn drop(&mut self) { - let _ = self.peer.on_track::>(None); - let _ = self - .peer - .on_ice_candidate::>(None); + self.peer.on_track::>(None); + self.peer + .on_ice_candidate::>(None); } } diff --git a/jason/src/peer/repo.rs b/jason/src/peer/repo.rs index 5ea984da3..44cc344a8 100644 --- a/jason/src/peer/repo.rs +++ b/jason/src/peer/repo.rs @@ -7,14 +7,14 @@ use medea_client_api_proto::{self as proto, PeerId}; use medea_macro::watchers; use medea_reactive::ObservableHashMap; use tracerr::Traced; -use wasm_bindgen_futures::spawn_local; use crate::{ - api::{Connections, RoomError}, + connection::Connections, media::{LocalTracksConstraints, MediaManager, RecvConstraints}, - peer, + peer, platform, + room::RoomError, utils::{ - component, delay_for, AsProtoState, SynchronizableState, TaskHandle, + component, AsProtoState, SynchronizableState, TaskHandle, Updatable as _, }, }; @@ -102,21 +102,19 @@ pub struct Repository { /// Channel for sending events produced by [`PeerConnection`] to [`Room`]. /// - /// [`PeerConnection`]: crate::peer::PeerConnection - /// [`Room`]: crate::api::Room + /// [`Room`]: crate::room::Room peer_event_sender: mpsc::UnboundedSender, /// Constraints to local [`local::Track`]s that are being published by /// [`PeerConnection`]s from this [`Repository`]. /// - /// [`PeerConnection`]: crate::peer::PeerConnection - /// [`Room`]: crate::api::Room + /// [`Room`]: crate::room::Room /// [`local::Track`]: crate::media::track::local::Track send_constraints: LocalTracksConstraints, /// Collection of [`Connection`]s with a remote `Member`s. /// - /// [`Connection`]: crate::api::Connection + /// [`Connection`]: crate::connection::Connection connections: Rc, /// Constraints to the [`remote::Track`] received by [`PeerConnection`]s @@ -129,11 +127,9 @@ pub struct Repository { } impl Repository { - /// Returns new empty [`Repository`]. + /// Returns new empty [`platform::RtcStats`]. /// - /// Spawns [`RtcStats`] scrape task. - /// - /// [`RtcStats`]: crate::peer::RtcStats + /// Spawns a task for scraping [`platform::RtcStats`]. #[must_use] pub fn new( media_manager: Rc, @@ -156,19 +152,17 @@ impl Repository { } } - /// Spawns task which will call [`PeerConnection::send_peer_stats`] of - /// all [`PeerConnection`]s every second and send updated [`RtcStats`] - /// to the server. - /// - /// Returns [`TaskHandle`] which will stop this task on [`Drop::drop()`]. + /// Spawns a task which will call [`PeerConnection::send_peer_stats()`] of + /// all [`PeerConnection`]s every second and send updated + /// [`platform::RtcStats`] to a server. /// - /// [`RtcStats`]: crate::peer::RtcStats + /// Returns [`TaskHandle`] which will stop this task on its [`Drop`]. fn spawn_peers_stats_scrape_task( peers: Rc>>, ) -> TaskHandle { let (fut, abort) = future::abortable(async move { loop { - delay_for(Duration::from_secs(1).into()).await; + platform::delay_for(Duration::from_secs(1)).await; let peers = peers .borrow() @@ -182,7 +176,7 @@ impl Repository { } }); - spawn_local(async move { + platform::spawn(async move { fut.await.ok(); }); @@ -260,6 +254,8 @@ impl Component { /// /// Removes [`peer::Component`] and closes [`Connection`] by calling /// [`Connections::close_connection()`]. + /// + /// [`Connection`]: crate::connection::Connection #[inline] #[watch(self.0.borrow().on_remove())] async fn peer_removed( diff --git a/jason/src/peer/stream_update_criteria.rs b/jason/src/peer/stream_update_criteria.rs index 15be7c176..1489784f4 100644 --- a/jason/src/peer/stream_update_criteria.rs +++ b/jason/src/peer/stream_update_criteria.rs @@ -4,7 +4,7 @@ use std::ops::BitOrAssign; use medea_client_api_proto::{Direction, MediaSourceKind, MediaType, Track}; -use crate::MediaKind; +use crate::media::MediaKind; bitflags::bitflags! { pub struct Inner: u8 { diff --git a/jason/src/peer/tracks_request.rs b/jason/src/peer/tracks_request.rs index 1c1db90fb..27efb957c 100644 --- a/jason/src/peer/tracks_request.rs +++ b/jason/src/peer/tracks_request.rs @@ -10,16 +10,18 @@ use tracerr::Traced; use crate::{ media::{ - track::local, AudioTrackConstraints, MediaKind, MediaStreamSettings, + track::local, AudioTrackConstraints, DeviceVideoTrackConstraints, + DisplayVideoTrackConstraints, MediaKind, MediaStreamSettings, TrackConstraints, VideoSource, }, - utils::{JsCaused, JsError}, - DeviceVideoTrackConstraints, DisplayVideoTrackConstraints, + platform, + utils::JsCaused, }; /// Errors that may occur when validating [`TracksRequest`] or /// parsing [`local::Track`]s. #[derive(Clone, Debug, Display, JsCaused)] +#[js(error = "platform::Error")] pub enum TracksRequestError { /// [`TracksRequest`] contains multiple [`AudioTrackConstraints`]. #[display(fmt = "only one audio track is allowed in SimpleTracksRequest")] @@ -164,7 +166,7 @@ impl SimpleTracksRequest { if let Some((id, audio)) = &self.audio { if let Some(track) = audio_tracks.into_iter().next() { - if audio.satisfies(track.sys_track()) { + if audio.satisfies(track.as_ref()) { parsed_tracks.insert(*id, track); } else { return Err(tracerr::new!(InvalidAudioTrack)); @@ -173,7 +175,7 @@ impl SimpleTracksRequest { } if let Some((id, device_video)) = &self.device_video { if let Some(track) = device_video_tracks.into_iter().next() { - if device_video.satisfies(track.sys_track()) { + if device_video.satisfies(track.as_ref()) { parsed_tracks.insert(*id, track); } else { return Err(tracerr::new!(InvalidVideoTrack)); @@ -182,7 +184,7 @@ impl SimpleTracksRequest { } if let Some((id, display_video)) = &self.display_video { if let Some(track) = display_video_tracks.into_iter().next() { - if display_video.satisfies(track.sys_track()) { + if display_video.satisfies(track.as_ref()) { parsed_tracks.insert(*id, track); } else { return Err(tracerr::new!(InvalidVideoTrack)); diff --git a/jason/src/platform/mod.rs b/jason/src/platform/mod.rs new file mode 100644 index 000000000..8e9eae9b9 --- /dev/null +++ b/jason/src/platform/mod.rs @@ -0,0 +1,31 @@ +//! Platform-specific functionality. + +pub mod peer_connection; +pub mod rtc_stats; +pub mod transport; +pub mod wasm; + +pub use self::{ + peer_connection::{IceCandidate, RTCPeerConnectionError, SdpType}, + rtc_stats::RtcStatsError, + transport::{ + RpcTransport, TransportError, TransportState, WebSocketRpcTransport, + }, + wasm::{ + constraints::{DisplayMediaStreamConstraints, MediaStreamConstraints}, + delay_for, + error::Error, + get_property_by_name, init_logger, + input_device_info::InputDeviceInfo, + media_devices::{enumerate_devices, get_display_media, get_user_media}, + media_track::MediaStreamTrack, + peer_connection::RtcPeerConnection, + rtc_stats::RtcStats, + set_panic_hook, spawn, + transceiver::{Transceiver, TransceiverDirection}, + utils::{Callback, Function}, + }, +}; + +#[cfg(feature = "mockable")] +pub use self::transport::MockRpcTransport; diff --git a/jason/src/platform/peer_connection.rs b/jason/src/platform/peer_connection.rs new file mode 100644 index 000000000..46ca510e2 --- /dev/null +++ b/jason/src/platform/peer_connection.rs @@ -0,0 +1,103 @@ +//! Platform-agnostic functionality of [`platform::RtcPeerConnection`]. + +use derive_more::{Display, From}; + +use crate::{ + platform::{self, RtcStatsError}, + utils::JsCaused, +}; + +/// Representation of [RTCSdpType]. +/// +/// [RTCSdpType]: https://w3.org/TR/webrtc/#dom-rtcsdptype +pub enum SdpType { + /// [`offer` type][1] of SDP. + /// + /// [1]: https://w3.org/TR/webrtc/#dom-rtcsdptype-offer + Offer(String), + + /// [`answer` type][1] of SDP. + /// + /// [1]: https://w3.org/TR/webrtc/#dom-rtcsdptype-answer + Answer(String), +} + +/// [RTCIceCandidate][1] representation. +/// +/// [1]: https://w3.org/TR/webrtc/#rtcicecandidate-interface +pub struct IceCandidate { + /// [`candidate` field][2] of the discovered [RTCIceCandidate][1]. + /// + /// [1]: https://w3.org/TR/webrtc/#dom-rtcicecandidate + /// [2]: https://w3.org/TR/webrtc/#dom-rtcicecandidate-candidate + pub candidate: String, + + /// [`sdpMLineIndex` field][2] of the discovered [RTCIceCandidate][1]. + /// + /// [1]: https://w3.org/TR/webrtc/#dom-rtcicecandidate + /// [2]: https://w3.org/TR/webrtc/#dom-rtcicecandidate-sdpmlineindex + pub sdp_m_line_index: Option, + + /// [`sdpMid` field][2] of the discovered [RTCIceCandidate][1]. + /// + /// [1]: https://w3.org/TR/webrtc/#dom-rtcicecandidate + /// [2]: https://w3.org/TR/webrtc/#dom-rtcicecandidate-sdpmid + pub sdp_mid: Option, +} + +/// Errors that may occur during signaling between this and remote +/// [RTCPeerConnection][1] and event handlers setting errors. +/// +/// [1]: https://w3.org/TR/webrtc/#dom-rtcpeerconnection +#[derive(Clone, Debug, Display, From, JsCaused)] +#[js(error = "platform::Error")] +pub enum RTCPeerConnectionError { + /// Occurs when cannot adds new remote candidate to the + /// [RTCPeerConnection][1]'s remote description. + /// + /// [1]: https://w3.org/TR/webrtc/#dom-rtcpeerconnection + #[display(fmt = "Failed to add ICE candidate: {}", _0)] + #[from(ignore)] + AddIceCandidateFailed(platform::Error), + + /// Occurs when cannot obtains [SDP answer][`SdpType::Answer`] from + /// the underlying [`platform::RtcPeerConnection`]. + #[display(fmt = "Failed to create SDP answer: {}", _0)] + #[from(ignore)] + CreateAnswerFailed(platform::Error), + + /// Occurs when a new [`platform::RtcPeerConnection`] cannot be created. + #[display(fmt = "Failed to create PeerConnection: {}", _0)] + #[from(ignore)] + PeerCreationError(platform::Error), + + /// Occurs when cannot obtains [SDP offer][`SdpType::Offer`] from + /// the underlying [`platform::RtcPeerConnection`]. + #[display(fmt = "Failed to create SDP offer: {}", _0)] + #[from(ignore)] + CreateOfferFailed(platform::Error), + + /// Occurs while getting and parsing [`platform::RtcStats`] of + /// [`platform::RtcPeerConnection`]. + #[display(fmt = "Failed to get RTCStats: {}", _0)] + RtcStatsError(#[js(cause)] RtcStatsError), + + /// [PeerConnection.getStats][1] promise thrown exception. + /// + /// [1]: https://tinyurl.com/w6hmt5f + #[display(fmt = "PeerConnection.getStats() failed with error: {}", _0)] + #[from(ignore)] + GetStatsException(platform::Error), + + /// Occurs if the local description associated with the + /// [`platform::RtcPeerConnection`] cannot be changed. + #[display(fmt = "Failed to set local SDP description: {}", _0)] + #[from(ignore)] + SetLocalDescriptionFailed(platform::Error), + + /// Occurs if the description of the remote end of the + /// [`platform::RtcPeerConnection`] cannot be changed. + #[display(fmt = "Failed to set remote SDP description: {}", _0)] + #[from(ignore)] + SetRemoteDescriptionFailed(platform::Error), +} diff --git a/jason/src/platform/rtc_stats.rs b/jason/src/platform/rtc_stats.rs new file mode 100644 index 000000000..b734f05b3 --- /dev/null +++ b/jason/src/platform/rtc_stats.rs @@ -0,0 +1,38 @@ +//! Platform-agnostic functionality of [`platform::RtcStats`]. + +use std::rc::Rc; + +use derive_more::{Display, From}; + +use crate::{platform, utils::JsCaused}; + +/// Errors which can occur during deserialization of a [`RtcStatsType`]. +/// +/// [`RtcStatsType`]: medea_client_api_proto::stats::RtcStatsType +#[derive(Clone, Debug, Display, From, JsCaused)] +#[js(error = "platform::Error")] +pub enum RtcStatsError { + /// [RTCStats.id][1] is undefined. + /// + /// [1]: https://w3.org/TR/webrtc/#dom-rtcstats-id + #[display(fmt = "RTCStats.id is undefined")] + UndefinedId, + + /// [RTCStats.stats] are undefined. + /// + /// [1]: https://w3.org/TR/webrtc-stats/#dfn-stats-object + #[display(fmt = "RTCStats.stats are undefined")] + UndefinedStats, + + /// Some platform error occurred. + #[display(fmt = "Unexpected platform error: {}", _0)] + Platform(platform::Error), + + /// `RTCStats.entries` are undefined. + #[display(fmt = "RTCStats.entries are undefined")] + UndefinedEntries, + + /// [`platform::RtcStats`] deserialization error. + #[display(fmt = "Failed to deserialize into RtcStats: {}", _0)] + ParseError(Rc), +} diff --git a/jason/src/platform/transport.rs b/jason/src/platform/transport.rs new file mode 100644 index 000000000..e08229921 --- /dev/null +++ b/jason/src/platform/transport.rs @@ -0,0 +1,96 @@ +//! Platform-agnostic functionality of RPC transport. + +use derive_more::Display; +use futures::stream::LocalBoxStream; +use medea_client_api_proto::{ClientMsg, ServerMsg}; +use tracerr::Traced; + +use crate::{ + platform, + rpc::{ClientDisconnect, CloseMsg}, + utils::{JsCaused, JsonParseError}, +}; + +pub use super::wasm::transport::WebSocketRpcTransport; + +/// Possible states of a [`RpcTransport`]. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum TransportState { + /// Socket has been created. The connection is not opened yet. + Connecting, + + /// The connection is opened and ready to communicate. + Open, + + /// The connection is in the process of closing. + Closing, + + /// The connection is closed or couldn't be opened. + /// + /// [`CloseMsg`] is the reason of why [`RpcTransport`] went into this + /// [`TransportState`]. + Closed(CloseMsg), +} + +impl TransportState { + /// Indicates whether the socket can be closed. + #[inline] + #[must_use] + pub fn can_close(self) -> bool { + matches!(self, Self::Connecting | Self::Open) + } +} + +/// RPC transport between a client and a server. +#[cfg_attr(feature = "mockable", mockall::automock)] +pub trait RpcTransport { + /// Returns [`LocalBoxStream`] of all messages received by this transport. + fn on_message(&self) -> LocalBoxStream<'static, ServerMsg>; + + /// Sets reason, that will be sent to remote server when this transport will + /// be dropped. + fn set_close_reason(&self, reason: ClientDisconnect); + + /// Sends given [`ClientMsg`] to a server. + /// + /// # Errors + /// + /// Errors if sending [`ClientMsg`] fails. + fn send(&self, msg: &ClientMsg) -> Result<(), Traced>; + + /// Subscribes to a [`RpcTransport`]'s [`TransportState`] changes. + fn on_state_change(&self) -> LocalBoxStream<'static, TransportState>; +} + +/// Errors that may occur when working with a [`RpcTransport`]. +#[derive(Clone, Debug, Display, JsCaused, PartialEq)] +#[js(error = "platform::Error")] +pub enum TransportError { + /// Error encountered when trying to establish connection. + #[display(fmt = "Failed to create WebSocket: {}", _0)] + CreateSocket(platform::Error), + + /// Connection was closed before becoming active. + #[display(fmt = "Failed to init WebSocket")] + InitSocket, + + /// Occurs when [`ClientMsg`] cannot be parsed. + #[display(fmt = "Failed to parse client message: {}", _0)] + ParseClientMessage(JsonParseError), + + /// Occurs when [`ServerMsg`] cannot be parsed. + #[display(fmt = "Failed to parse server message: {}", _0)] + ParseServerMessage(JsonParseError), + + /// Occurs if the parsed message is not string. + #[display(fmt = "Message is not a string")] + MessageNotString, + + /// Occurs when a message cannot be send to server. + #[display(fmt = "Failed to send message: {}", _0)] + SendMessage(platform::Error), + + /// Occurs when message is sent to a closed socket. + #[display(fmt = "Underlying socket is closed")] + ClosedSocket, +} diff --git a/jason/src/platform/wasm/constraints.rs b/jason/src/platform/wasm/constraints.rs new file mode 100644 index 000000000..c20ca9491 --- /dev/null +++ b/jason/src/platform/wasm/constraints.rs @@ -0,0 +1,160 @@ +//! Media tracks and streams constraints functionality. + +use crate::media::{ + constraints::{ConstrainString, ConstrainU32}, + AudioTrackConstraints, DeviceVideoTrackConstraints, + DisplayVideoTrackConstraints, +}; +use derive_more::{AsRef, Into}; +use web_sys::{ + ConstrainDomStringParameters, ConstrainDoubleRange, MediaTrackConstraints, +}; + +/// [MediaStreamConstraints][1] wrapper. +/// +/// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediastreamconstraints +#[derive(AsRef, Debug, Into)] +pub struct MediaStreamConstraints(web_sys::MediaStreamConstraints); + +impl MediaStreamConstraints { + /// Creates new [`MediaStreamConstraints`] with none constraints configured. + #[inline] + #[must_use] + pub fn new() -> Self { + Self(web_sys::MediaStreamConstraints::new()) + } + + /// Specifies the nature and settings of the `audio` [MediaStreamTrack][1]. + /// + /// [1]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack + #[inline] + pub fn audio(&mut self, audio: AudioTrackConstraints) { + self.0.audio(&MediaTrackConstraints::from(audio).into()); + } + + /// Specifies the nature and settings of the `video` [MediaStreamTrack][1]. + /// + /// [1]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack + #[inline] + pub fn video(&mut self, video: DeviceVideoTrackConstraints) { + self.0.video(&MediaTrackConstraints::from(video).into()); + } +} + +impl Default for MediaStreamConstraints { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl From for MediaTrackConstraints { + fn from(track_constraints: AudioTrackConstraints) -> Self { + let mut constraints = Self::new(); + + if let Some(device_id) = track_constraints.device_id { + constraints + .device_id(&ConstrainDomStringParameters::from(&device_id)); + } + + constraints + } +} + +impl From for MediaTrackConstraints { + fn from(track_constraints: DeviceVideoTrackConstraints) -> Self { + let mut constraints = Self::new(); + + if let Some(device_id) = track_constraints.device_id { + constraints + .device_id(&ConstrainDomStringParameters::from(&device_id)); + } + if let Some(facing_mode) = track_constraints.facing_mode { + constraints + .facing_mode(&ConstrainDomStringParameters::from(&facing_mode)); + } + if let Some(width) = track_constraints.width { + constraints.width(&ConstrainDoubleRange::from(width)); + } + if let Some(height) = track_constraints.height { + constraints.height(&ConstrainDoubleRange::from(height)); + } + + constraints + } +} + +impl From for ConstrainDoubleRange { + fn from(from: ConstrainU32) -> Self { + let mut constraint = ConstrainDoubleRange::new(); + match from { + ConstrainU32::Exact(val) => { + constraint.exact(f64::from(val)); + } + ConstrainU32::Ideal(val) => { + constraint.ideal(f64::from(val)); + } + ConstrainU32::Range(min, max) => { + constraint.min(f64::from(min)).max(f64::from(max)); + } + } + + constraint + } +} + +impl> From<&ConstrainString> for ConstrainDomStringParameters { + fn from(from: &ConstrainString) -> Self { + let mut constraint = ConstrainDomStringParameters::new(); + match from { + ConstrainString::Exact(val) => { + constraint.exact(&wasm_bindgen::JsValue::from_str(val.as_ref())) + } + ConstrainString::Ideal(val) => { + constraint.ideal(&wasm_bindgen::JsValue::from_str(val.as_ref())) + } + }; + + constraint + } +} + +/// [DisplayMediaStreamConstraints][1] wrapper. +/// +/// [1]: https://w3.org/TR/screen-capture/#dom-displaymediastreamconstraints +#[derive(AsRef, Debug, Into)] +pub struct DisplayMediaStreamConstraints( + web_sys::DisplayMediaStreamConstraints, +); + +impl Default for DisplayMediaStreamConstraints { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl DisplayMediaStreamConstraints { + /// Creates a new [`DisplayMediaStreamConstraints`] with none constraints + /// configured. + #[inline] + #[must_use] + pub fn new() -> Self { + Self(web_sys::DisplayMediaStreamConstraints::new()) + } + + /// Specifies the nature and settings of the `video` [MediaStreamTrack][1]. + /// + /// [1]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack + #[inline] + pub fn video(&mut self, video: DisplayVideoTrackConstraints) { + self.0.video(&MediaTrackConstraints::from(video).into()); + } +} + +impl From for MediaTrackConstraints { + #[inline] + fn from(_: DisplayVideoTrackConstraints) -> Self { + Self::new() + } +} diff --git a/jason/src/platform/wasm/error.rs b/jason/src/platform/wasm/error.rs new file mode 100644 index 000000000..c2834858d --- /dev/null +++ b/jason/src/platform/wasm/error.rs @@ -0,0 +1,46 @@ +//! More convenient wrapper for [`js_sys::Error`]. + +use std::borrow::Cow; + +use derive_more::Display; +use wasm_bindgen::{JsCast, JsValue}; + +use crate::platform; + +/// Wrapper for JS value which returned from JS side as error. +#[derive(Clone, Debug, Display, PartialEq)] +#[display(fmt = "{}: {}", name, message)] +pub struct Error { + /// Name of JS error. + pub name: Cow<'static, str>, + + /// Message of JS error. + pub message: Cow<'static, str>, + + /// Original JS error. + pub sys_cause: Option, +} + +impl From for platform::Error { + fn from(val: JsValue) -> Self { + match val.dyn_into::() { + Ok(err) => Self { + name: Cow::Owned(err.name().into()), + message: Cow::Owned(err.message().into()), + sys_cause: Some(err), + }, + Err(val) => match val.as_string() { + Some(reason) => Self { + name: "Unknown JS error".into(), + message: reason.into(), + sys_cause: None, + }, + None => Self { + name: "Unknown JS error".into(), + message: format!("{:?}", val).into(), + sys_cause: None, + }, + }, + } + } +} diff --git a/jason/src/peer/ice_server.rs b/jason/src/platform/wasm/ice_server.rs similarity index 88% rename from jason/src/peer/ice_server.rs rename to jason/src/platform/wasm/ice_server.rs index ae9847256..e06b44e5c 100644 --- a/jason/src/peer/ice_server.rs +++ b/jason/src/platform/wasm/ice_server.rs @@ -2,8 +2,7 @@ //! //! [1]: https://w3.org/TR/webrtc/#rtciceserver-dictionary -use std::ops::Deref; - +use derive_more::Deref; use js_sys::Array as JsArray; use medea_client_api_proto::IceServer; use wasm_bindgen::JsValue; @@ -12,6 +11,7 @@ use web_sys::RtcIceServer; /// Collection of [`RtcIceServer`]s (see [RTCIceServer][1]). /// /// [1]: https://w3.org/TR/webrtc/#rtciceserver-dictionary +#[derive(Debug, Deref)] pub struct RtcIceServers(JsArray); impl From for RtcIceServers @@ -44,11 +44,3 @@ where Self(inner) } } - -impl Deref for RtcIceServers { - type Target = JsArray; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} diff --git a/jason/src/media/device_info.rs b/jason/src/platform/wasm/input_device_info.rs similarity index 78% rename from jason/src/media/device_info.rs rename to jason/src/platform/wasm/input_device_info.rs index 32a64d694..54508364c 100644 --- a/jason/src/media/device_info.rs +++ b/jason/src/platform/wasm/input_device_info.rs @@ -5,8 +5,7 @@ use std::convert::TryFrom; use derive_more::Display; -use wasm_bindgen::prelude::*; -use web_sys::{MediaDeviceInfo, MediaDeviceKind}; +use web_sys as sys; use crate::media::MediaKind; @@ -23,31 +22,32 @@ pub enum Error { /// Representation of [MediaDeviceInfo][1]. /// /// [1]: https://w3.org/TR/mediacapture-streams/#device-info -#[wasm_bindgen] pub struct InputDeviceInfo { media_kind: MediaKind, /// Actual underlying [MediaDeviceInfo][1] object. /// /// [1]: https://w3.org/TR/mediacapture-streams/#device-info - info: MediaDeviceInfo, + info: sys::MediaDeviceInfo, } -impl TryFrom for MediaKind { +impl TryFrom for MediaKind { type Error = Error; - fn try_from(value: MediaDeviceKind) -> Result { + #[inline] + fn try_from(value: sys::MediaDeviceKind) -> Result { match value { - MediaDeviceKind::Audioinput => Ok(Self::Audio), - MediaDeviceKind::Videoinput => Ok(Self::Video), + sys::MediaDeviceKind::Audioinput => Ok(Self::Audio), + sys::MediaDeviceKind::Videoinput => Ok(Self::Video), _ => Err(Error::NotInputDevice), } } } -#[wasm_bindgen] impl InputDeviceInfo { /// Returns unique identifier for the represented device. + #[inline] + #[must_use] pub fn device_id(&self) -> String { self.info.device_id() } @@ -57,6 +57,8 @@ impl InputDeviceInfo { /// This representation of [MediaDeviceInfo][1] ONLY for input device. /// /// [1]: https://w3.org/TR/mediacapture-streams/#device-info + #[inline] + #[must_use] pub fn kind(&self) -> MediaKind { self.media_kind } @@ -64,6 +66,8 @@ impl InputDeviceInfo { /// Returns label describing the represented device (for example /// "External USB Webcam"). /// If the device has no associated label, then returns an empty string. + #[inline] + #[must_use] pub fn label(&self) -> String { self.info.label() } @@ -76,15 +80,18 @@ impl InputDeviceInfo { /// same [groupId][1]. /// /// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediadeviceinfo-groupid + #[inline] + #[must_use] pub fn group_id(&self) -> String { self.info.group_id() } } -impl TryFrom for InputDeviceInfo { +impl TryFrom for InputDeviceInfo { type Error = Error; - fn try_from(info: MediaDeviceInfo) -> Result { + #[inline] + fn try_from(info: sys::MediaDeviceInfo) -> Result { Ok(Self { media_kind: MediaKind::try_from(info.kind())?, info, diff --git a/jason/src/platform/wasm/media_devices.rs b/jason/src/platform/wasm/media_devices.rs new file mode 100644 index 000000000..946648733 --- /dev/null +++ b/jason/src/platform/wasm/media_devices.rs @@ -0,0 +1,161 @@ +//! [MediaDevices][1] functionality. +//! +//! [1]: https://tinyurl.com/w3-streams/#mediadevices + +use std::convert::TryFrom as _; +use wasm_bindgen_futures::JsFuture; + +use tracerr::Traced; + +use crate::{ + media::MediaManagerError, + platform::{ + DisplayMediaStreamConstraints, Error, InputDeviceInfo, + MediaStreamConstraints, MediaStreamTrack, + }, +}; + +use super::window; + +/// Collects information about the User Agent's available media input devices. +/// +/// Adapter for a [MediaDevices.enumerateDevices()][1] function. +/// +/// # Errors +/// +/// With [`MediaManagerError::CouldNotGetMediaDevices`] if couldn't get +/// [MediaDevices][2]. +/// +/// With [`MediaManagerError::EnumerateDevicesFailed`] if +/// [MediaDevices.enumerateDevices()][1] returns error. +/// +/// [1]: https://tinyurl.com/w3-streams/#dom-mediadevices-enumeratedevices +/// [2]: https://tinyurl.com/w3-streams/#mediadevices +pub async fn enumerate_devices( +) -> Result, Traced> { + use MediaManagerError::{CouldNotGetMediaDevices, EnumerateDevicesFailed}; + + let devices = window() + .navigator() + .media_devices() + .map_err(Error::from) + .map_err(CouldNotGetMediaDevices) + .map_err(tracerr::from_and_wrap!())?; + let devices = JsFuture::from( + devices + .enumerate_devices() + .map_err(Error::from) + .map_err(EnumerateDevicesFailed) + .map_err(tracerr::from_and_wrap!())?, + ) + .await + .map_err(Error::from) + .map_err(EnumerateDevicesFailed) + .map_err(tracerr::from_and_wrap!())?; + + Ok(js_sys::Array::from(&devices) + .values() + .into_iter() + .filter_map(|info| { + let info = web_sys::MediaDeviceInfo::from(info.unwrap()); + InputDeviceInfo::try_from(info).ok() + }) + .collect()) +} + +/// Prompts a user for a permission to use a media input which produces vector +/// of [`MediaStreamTrack`]s containing the requested types of media. +/// +/// Adapter for a [MediaDevices.getUserMedia()][1] function. +/// +/// # Errors +/// +/// With [`MediaManagerError::CouldNotGetMediaDevices`] if couldn't get +/// [MediaDevices][2]. +/// +/// With [`MediaManagerError::GetUserMediaFailed`] if +/// [MediaDevices.getUserMedia()][1] returns error. +/// +/// [1]: https://tinyurl.com/w3-streams/#dom-mediadevices-getusermedia +/// [2]: https://tinyurl.com/w3-streams/#mediadevices +pub async fn get_user_media( + caps: MediaStreamConstraints, +) -> Result, Traced> { + use MediaManagerError::{CouldNotGetMediaDevices, GetUserMediaFailed}; + + let media_devices = window() + .navigator() + .media_devices() + .map_err(Error::from) + .map_err(CouldNotGetMediaDevices) + .map_err(tracerr::from_and_wrap!())?; + + let stream = JsFuture::from( + media_devices + .get_user_media_with_constraints(&caps.into()) + .map_err(Error::from) + .map_err(GetUserMediaFailed) + .map_err(tracerr::from_and_wrap!())?, + ) + .await + .map(web_sys::MediaStream::from) + .map_err(Error::from) + .map_err(GetUserMediaFailed) + .map_err(tracerr::from_and_wrap!())?; + + Ok(js_sys::try_iter(&stream.get_tracks()) + .unwrap() + .unwrap() + .map(|tr| MediaStreamTrack::from(tr.unwrap())) + .collect()) +} + +/// Prompts a user to select and grant a permission to capture contents of a +/// display or portion thereof (such as a single window) as vector of +/// [`MediaStreamTrack`]. +/// +/// Adapter for a [MediaDevices.getDisplayMedia()][1] function. +/// +/// # Errors +/// +/// With [`MediaManagerError::CouldNotGetMediaDevices`] if couldn't get +/// [MediaDevices][2]. +/// +/// With [`MediaManagerError::GetUserMediaFailed`] if +/// [MediaDevices.getDisplayMedia()][1] returns error. +/// +/// [1]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia +/// [2]: https://tinyurl.com/w3-streams/#mediadevices +pub async fn get_display_media( + caps: DisplayMediaStreamConstraints, +) -> Result, Traced> { + use MediaManagerError::{ + CouldNotGetMediaDevices, GetDisplayMediaFailed, GetUserMediaFailed, + }; + + let media_devices = window() + .navigator() + .media_devices() + .map_err(Error::from) + .map_err(CouldNotGetMediaDevices) + .map_err(tracerr::from_and_wrap!())?; + + let stream = JsFuture::from( + media_devices + .get_display_media_with_constraints(&caps.into()) + .map_err(Error::from) + .map_err(GetDisplayMediaFailed) + .map_err(tracerr::from_and_wrap!())?, + ) + .await + .map(web_sys::MediaStream::from) + .map_err(Error::from) + .map_err(GetUserMediaFailed) + .map_err(tracerr::from_and_wrap!())?; + + Ok(js_sys::try_iter(&stream.get_tracks()) + .unwrap() + .unwrap() + .map(|tr| MediaStreamTrack::from(tr.unwrap())) + .collect()) +} diff --git a/jason/src/platform/wasm/media_track.rs b/jason/src/platform/wasm/media_track.rs new file mode 100644 index 000000000..16dfd9426 --- /dev/null +++ b/jason/src/platform/wasm/media_track.rs @@ -0,0 +1,196 @@ +//! Wrapper around [MediaStreamTrack][1]. +//! +//! [1]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack + +use derive_more::AsRef; + +use crate::{ + media::{track::MediaStreamTrackState, FacingMode, MediaKind}, + platform::get_property_by_name, +}; + +/// Wrapper around [MediaStreamTrack][1] received from a +/// [getUserMedia()][2]/[getDisplayMedia()][3] request. +/// +/// [1]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack +/// [2]: https://w3.org/TR/mediacapture-streams/#dom-mediadevices-getusermedia +/// [3]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia +#[derive(AsRef, Clone, Debug)] +pub struct MediaStreamTrack { + #[as_ref] + sys_track: web_sys::MediaStreamTrack, + kind: MediaKind, +} + +impl From for MediaStreamTrack +where + web_sys::MediaStreamTrack: From, +{ + #[inline] + fn from(from: T) -> MediaStreamTrack { + let sys_track = web_sys::MediaStreamTrack::from(from); + let kind = match sys_track.kind().as_ref() { + "audio" => MediaKind::Audio, + "video" => MediaKind::Video, + _ => unreachable!(), + }; + MediaStreamTrack { sys_track, kind } + } +} + +impl MediaStreamTrack { + /// Returns [`id`] of the underlying [MediaStreamTrack][2]. + /// + /// [`id`]: https://w3.org/TR/mediacapture-streams/#dom-mediastreamtrack-id + /// [2]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack + #[inline] + #[must_use] + pub fn id(&self) -> String { + self.sys_track.id() + } + + /// Returns this [`MediaStreamTrack`]'s kind (audio/video). + #[inline] + #[must_use] + pub fn kind(&self) -> MediaKind { + self.kind + } + + /// Returns [MediaStreamTrackState][1] of the underlying + /// [MediaStreamTrack][2]. + /// + /// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediastreamtrackstate + /// [2]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack + #[must_use] + pub fn ready_state(&self) -> MediaStreamTrackState { + let state = self.sys_track.ready_state(); + match state { + web_sys::MediaStreamTrackState::Live => MediaStreamTrackState::Live, + web_sys::MediaStreamTrackState::Ended => { + MediaStreamTrackState::Ended + } + web_sys::MediaStreamTrackState::__Nonexhaustive => { + unreachable!("Unknown MediaStreamTrackState::{:?}", state) + } + } + } + + /// Returns a [`deviceId`][1] of the underlying [MediaStreamTrack][2]. + /// + /// [1]: https://tinyurl.com/w3-streams/#dom-mediatracksettings-deviceid + /// [2]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack + #[inline] + #[must_use] + pub fn device_id(&self) -> Option { + get_property_by_name(&self.sys_track.get_settings(), "deviceId", |v| { + v.as_string() + }) + } + + /// Return a [`facingMode`][1] of the underlying [MediaStreamTrack][2]. + /// + /// [1]: https://tinyurl.com/w3-streams/#dom-mediatracksettings-facingmode + /// [2]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack + #[must_use] + pub fn facing_mode(&self) -> Option { + let facing_mode = get_property_by_name( + &self.sys_track.get_settings(), + "facingMode", + |v| v.as_string(), + ); + facing_mode.and_then(|facing_mode| match facing_mode.as_ref() { + "user" => Some(FacingMode::User), + "environment" => Some(FacingMode::Environment), + "left" => Some(FacingMode::Left), + "right" => Some(FacingMode::Right), + _ => { + log::error!("Unknown FacingMode: {}", facing_mode); + None + } + }) + } + + /// Returns a [`height`][1] of the underlying [MediaStreamTrack][2]. + /// + /// [1]: https://tinyurl.com/w3-streams/#dom-mediatracksettings-height + /// [2]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack + #[inline] + #[must_use] + pub fn height(&self) -> Option { + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + get_property_by_name(&self.sys_track.get_settings(), "height", |v| { + v.as_f64().map(|v| v as u32) + }) + } + + /// Return a [`width`][1] of the underlying [MediaStreamTrack][2]. + /// + /// [1]: https://tinyurl.com/w3-streams/#dom-mediatracksettings-width + /// [2]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack + #[inline] + #[must_use] + pub fn width(&self) -> Option { + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + get_property_by_name(&self.sys_track.get_settings(), "width", |v| { + v.as_f64().map(|v| v as u32) + }) + } + + /// Changes an [`enabled`][1] attribute in the underlying + /// [MediaStreamTrack][2]. + /// + /// [1]: https://tinyurl.com/w3-streams#dom-mediastreamtrack-enabled + /// [2]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack + #[inline] + pub fn set_enabled(&self, enabled: bool) { + self.sys_track.set_enabled(enabled); + } + + /// Changes a [`readyState`][1] attribute in the underlying + /// [MediaStreamTrack][2] to [`ended`][3]. + /// + /// [1]: https://tinyurl.com/w3-streams#dom-mediastreamtrack-readystate + /// [2]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack + /// [3]: https://tinyurl.com/w3-streams#idl-def-MediaStreamTrackState.ended + #[inline] + pub fn stop(&self) { + self.sys_track.stop() + } + + /// Returns an [`enabled`][1] attribute of the underlying + /// [MediaStreamTrack][2]. + /// + /// [1]: https://tinyurl.com/w3-streams#dom-mediastreamtrack-enabled + /// [2]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack + #[inline] + #[must_use] + pub fn enabled(&self) -> bool { + self.sys_track.enabled() + } + + /// Detects whether a video track captured from display searching + /// [specific fields][1] in its settings. + /// + /// Only works in Chrome browser at the moment. + /// + /// [1]: https://w3.org/TR/screen-capture/#extensions-to-mediatracksettings + #[must_use] + pub fn guess_is_from_display(&self) -> bool { + let settings = self.sys_track.get_settings(); + + let has_display_surface = + get_property_by_name(&settings, "displaySurface", |val| { + val.as_string() + }) + .is_some(); + + if has_display_surface { + true + } else { + get_property_by_name(&settings, "logicalSurface", |val| { + val.as_string() + }) + .is_some() + } + } +} diff --git a/jason/src/platform/wasm/mod.rs b/jason/src/platform/wasm/mod.rs new file mode 100644 index 000000000..442cb5e22 --- /dev/null +++ b/jason/src/platform/wasm/mod.rs @@ -0,0 +1,105 @@ +//! `wasm32`-platform-specific functionality. + +use std::{convert::TryInto as _, time::Duration}; + +pub mod constraints; +pub mod error; +pub mod ice_server; +pub mod input_device_info; +pub mod media_devices; +pub mod media_track; +pub mod peer_connection; +pub mod rtc_stats; +pub mod transceiver; +pub mod transport; +pub mod utils; + +use futures::Future; +use js_sys::{Promise, Reflect}; +use wasm_bindgen::JsValue; +use wasm_bindgen_futures::JsFuture; +use web_sys::Window; + +// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global +// allocator. +#[cfg(feature = "wee_alloc")] +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + +// When the `console_error_panic_hook` feature is enabled, we can call the +// `set_panic_hook` function at least once during initialization, and then +// we will get better error messages if our code ever panics. +// +// For more details see: +// https://github.com/rustwasm/console_error_panic_hook#readme +#[cfg(feature = "console_error_panic_hook")] +pub use console_error_panic_hook::set_once as set_panic_hook; + +/// Initialize [`wasm_logger`] as default application logger. +/// +/// [`wasm_logger`]: https://docs.rs/wasm-logger +pub fn init_logger() { + wasm_logger::init(wasm_logger::Config::default()); +} + +/// Runs a Rust [`Future`] on the current thread. +#[inline] +pub fn spawn(task: F) +where + F: Future + 'static, +{ + wasm_bindgen_futures::spawn_local(task); +} + +/// [`Future`] which resolves after the provided [`Duration`]. +/// +/// [`Future`]: std::future::Future +pub async fn delay_for(delay: Duration) { + let delay_ms = delay.as_millis().try_into().unwrap_or(i32::max_value()); + JsFuture::from(Promise::new(&mut |yes, _| { + window() + .set_timeout_with_callback_and_timeout_and_arguments_0( + &yes, delay_ms, + ) + .unwrap(); + })) + .await + .unwrap(); +} + +/// Returns property of JS object by name if its defined. +/// Converts the value with a given predicate. +pub fn get_property_by_name( + value: &T, + name: &str, + into: F, +) -> Option +where + T: AsRef, + F: Fn(wasm_bindgen::JsValue) -> Option, +{ + Reflect::get(value.as_ref(), &JsValue::from_str(name)) + .ok() + .map_or_else(|| None, into) +} + +/// Returns [`Window`] object. +/// +/// # Panics +/// +/// When global [`Window`] object is inaccessible. +pub fn window() -> Window { + // Cannot use `lazy_static` since `window` is `!Sync`. + // Safe to unwrap. + web_sys::window().unwrap() +} + +/// Wrapper around interval timer ID. +pub struct IntervalHandle(pub i32); + +impl Drop for IntervalHandle { + /// Clears interval with provided ID. + fn drop(&mut self) { + window().clear_interval_with_handle(self.0); + } +} diff --git a/jason/src/peer/conn.rs b/jason/src/platform/wasm/peer_connection.rs similarity index 78% rename from jason/src/peer/conn.rs rename to jason/src/platform/wasm/peer_connection.rs index fe627a465..8071770e4 100644 --- a/jason/src/peer/conn.rs +++ b/jason/src/platform/wasm/peer_connection.rs @@ -1,11 +1,16 @@ +//! Wrapper around [RTCPeerConnection][1]. +//! +//! [1]: https://w3.org/TR/webrtc/#dom-rtcpeerconnection + use std::{ cell::{Cell, RefCell}, convert::TryFrom as _, rc::Rc, }; -use derive_more::{Display, From}; -use medea_client_api_proto::{IceServer, PeerConnectionState}; +use medea_client_api_proto::{ + IceConnectionState, IceServer, PeerConnectionState, +}; use tracerr::Traced; use wasm_bindgen_futures::JsFuture; use web_sys::{ @@ -18,37 +23,14 @@ use web_sys::{ use crate::{ media::{MediaKind, TrackConstraints}, - peer::stats::{RtcStats, RtcStatsError}, - utils::{ - get_property_by_name, EventListener, EventListenerBindError, JsCaused, - JsError, + platform::{ + self, get_property_by_name, wasm::utils::EventListener, IceCandidate, + MediaStreamTrack, RTCPeerConnectionError, RtcStats, SdpType, + Transceiver, TransceiverDirection, }, }; -use super::{ice_server::RtcIceServers, TransceiverDirection}; - -/// [RTCIceCandidate][1] representation. -/// -/// [1]: https://w3.org/TR/webrtc/#rtcicecandidate-interface -pub struct IceCandidate { - /// [`candidate` field][2] of the discovered [RTCIceCandidate][1]. - /// - /// [1]: https://w3.org/TR/webrtc/#dom-rtcicecandidate - /// [2]: https://w3.org/TR/webrtc/#dom-rtcicecandidate-candidate - pub candidate: String, - - /// [`sdpMLineIndex` field][2] of the discovered [RTCIceCandidate][1]. - /// - /// [1]: https://w3.org/TR/webrtc/#dom-rtcicecandidate - /// [2]: https://w3.org/TR/webrtc/#dom-rtcicecandidate-sdpmlineindex - pub sdp_m_line_index: Option, - - /// [`sdpMid` field][2] of the discovered [RTCIceCandidate][1]. - /// - /// [1]: https://w3.org/TR/webrtc/#dom-rtcicecandidate - /// [2]: https://w3.org/TR/webrtc/#dom-rtcicecandidate-sdpmid - pub sdp_mid: Option, -} +use super::ice_server::RtcIceServers; impl From<&TrackConstraints> for MediaKind { fn from(media_type: &TrackConstraints) -> Self { @@ -59,83 +41,6 @@ impl From<&TrackConstraints> for MediaKind { } } -/// Representation of [RTCSdpType]. -/// -/// [RTCSdpType]: https://w3.org/TR/webrtc/#dom-rtcsdptype -pub enum SdpType { - /// [`offer` type][1] of SDP. - /// - /// [1]: https://w3.org/TR/webrtc/#dom-rtcsdptype-offer - Offer(String), - - /// [`answer` type][1] of SDP. - /// - /// [1]: https://w3.org/TR/webrtc/#dom-rtcsdptype-answer - Answer(String), -} - -/// Errors that may occur during signaling between this and remote -/// [RTCPeerConnection][1] and event handlers setting errors. -/// -/// [1]: https://w3.org/TR/webrtc/#dom-rtcpeerconnection -#[derive(Clone, Debug, Display, From, JsCaused)] -pub enum RTCPeerConnectionError { - /// Occurs when cannot adds new remote candidate to the - /// [RTCPeerConnection][1]'s remote description. - /// - /// [1]: https://w3.org/TR/webrtc/#dom-rtcpeerconnection - #[display(fmt = "Failed to add ICE candidate: {}", _0)] - #[from(ignore)] - AddIceCandidateFailed(JsError), - - /// Occurs when cannot obtains [SDP answer][`SdpType::Answer`] from - /// the underlying [RTCPeerConnection][`SysRtcPeerConnection`]. - #[display(fmt = "Failed to create SDP answer: {}", _0)] - #[from(ignore)] - CreateAnswerFailed(JsError), - - /// Occurs when a new [`RtcPeerConnection`] cannot be created. - #[display(fmt = "Failed to create PeerConnection: {}", _0)] - #[from(ignore)] - PeerCreationError(JsError), - - /// Occurs when cannot obtains [SDP offer][`SdpType::Offer`] from - /// the underlying [RTCPeerConnection][`SysRtcPeerConnection`] - #[display(fmt = "Failed to create SDP offer: {}", _0)] - #[from(ignore)] - CreateOfferFailed(JsError), - - /// Occurs when handler failed to bind to some [`RtcPeerConnection`] event. - /// Not really supposed to ever happen. - #[display(fmt = "Failed to bind to RTCPeerConnection event: {}", _0)] - PeerConnectionEventBindFailed(EventListenerBindError), - - /// Occurs while getting and parsing [`RtcStats`] of [`PeerConnection`]. - /// - /// [`PeerConnection`]: super::PeerConnection - #[display(fmt = "Failed to get RTCStats: {}", _0)] - RtcStatsError(#[js(cause)] RtcStatsError), - - /// [PeerConnection.getStats][1] promise thrown exception. - /// - /// [1]: https://tinyurl.com/w6hmt5f - #[display(fmt = "PeerConnection.getStats() failed with error: {}", _0)] - #[from(ignore)] - GetStatsException(JsError), - - /// Occurs if the local description associated with the - /// [`RtcPeerConnection`] cannot be changed. - #[display(fmt = "Failed to set local SDP description: {}", _0)] - #[from(ignore)] - SetLocalDescriptionFailed(JsError), - - /// Occurs if the description of the remote end of the - /// [`RtcPeerConnection`] cannot be changed. - #[display(fmt = "Failed to set remote SDP description: {}", _0)] - #[from(ignore)] - SetRemoteDescriptionFailed(JsError), -} - type Result = std::result::Result>; /// Representation of [RTCPeerConnection][1]. @@ -233,7 +138,7 @@ impl RtcPeerConnection { }) } - /// Returns [`RtcStats`] of this [`PeerConnection`]. + /// Returns [`RtcStats`] of this [`RtcPeerConnection`]. /// /// # Errors /// @@ -243,13 +148,12 @@ impl RtcPeerConnection { /// Errors with [`RTCPeerConnectionError::GetStatsException`] when /// [PeerConnection.getStats][1] promise throws exception. /// - /// [`PeerConnection`]: super::PeerConnection /// [1]: https://tinyurl.com/w6hmt5f pub async fn get_stats(&self) -> Result { let js_stats = JsFuture::from(self.peer.get_stats()).await.map_err(|e| { tracerr::new!(RTCPeerConnectionError::GetStatsException( - JsError::from(e) + platform::Error::from(e) )) })?; @@ -259,16 +163,11 @@ impl RtcPeerConnection { /// Sets handler for [`RtcTrackEvent`] event (see [RTCTrackEvent][1] and /// [`ontrack` callback][2]). /// - /// # Errors - /// - /// Errors with [`RTCPeerConnectionError::PeerConnectionEventBindFailed`] if - /// [`EventListener`] binding fails. - /// /// [1]: https://w3.org/TR/webrtc/#rtctrackevent /// [2]: https://w3.org/TR/webrtc/#dom-rtcpeerconnection-ontrack - pub fn on_track(&self, f: Option) -> Result<()> + pub fn on_track(&self, f: Option) where - F: 'static + FnMut(RtcTrackEvent), + F: 'static + FnMut(MediaStreamTrack, Transceiver), { let mut on_track = self.on_track.borrow_mut(); match f { @@ -277,31 +176,30 @@ impl RtcPeerConnection { } Some(mut f) => { on_track.replace( + // Unwrapping is OK here, because this function shouldn't + // error ever. EventListener::new_mut( Rc::clone(&self.peer), "track", move |msg: RtcTrackEvent| { - f(msg); + f( + MediaStreamTrack::from(msg.track()), + Transceiver::from(msg.transceiver()), + ); }, ) - .map_err(tracerr::map_from_and_wrap!())?, + .unwrap(), ); } } - Ok(()) } /// Sets handler for [`RtcPeerConnectionIceEvent`] event /// (see [RTCPeerConnectionIceEvent][1] and [`onicecandidate` callback][2]). /// - /// # Errors - /// - /// Errors with [`RTCPeerConnectionError::PeerConnectionEventBindFailed`] if - /// [`EventListener`] binding fails. - /// /// [1]: https://w3.org/TR/webrtc/#dom-rtcpeerconnectioniceevent /// [2]: https://w3.org/TR/webrtc/#dom-rtcpeerconnection-onicecandidate - pub fn on_ice_candidate(&self, f: Option) -> Result<()> + pub fn on_ice_candidate(&self, f: Option) where F: 'static + FnMut(IceCandidate), { @@ -312,6 +210,8 @@ impl RtcPeerConnection { } Some(mut f) => { on_ice_candidate.replace( + // Unwrapping is OK here, because this function shouldn't + // error ever. EventListener::new_mut( Rc::clone(&self.peer), "icecandidate", @@ -329,18 +229,17 @@ impl RtcPeerConnection { } }, ) - .map_err(tracerr::map_from_and_wrap!())?, + .unwrap(), ); } } - Ok(()) } /// Returns [`RtcIceConnectionState`] of this [`RtcPeerConnection`]. #[inline] #[must_use] - pub fn ice_connection_state(&self) -> RtcIceConnectionState { - self.peer.ice_connection_state() + pub fn ice_connection_state(&self) -> IceConnectionState { + parse_ice_connection_state(self.peer.ice_connection_state()) } /// Returns [`PeerConnectionState`] of this [`RtcPeerConnection`]. @@ -354,15 +253,10 @@ impl RtcPeerConnection { /// Sets handler for [`iceconnectionstatechange`][1] event. /// - /// # Errors - /// - /// Will return [`RTCPeerConnectionError::PeerConnectionEventBindFailed`] if - /// [`EventListener`] binding fails. - /// /// [1]: https://w3.org/TR/webrtc/#event-iceconnectionstatechange - pub fn on_ice_connection_state_change(&self, f: Option) -> Result<()> + pub fn on_ice_connection_state_change(&self, f: Option) where - F: 'static + FnMut(RtcIceConnectionState), + F: 'static + FnMut(IceConnectionState), { let mut on_ice_connection_state_changed = self.on_ice_connection_state_changed.borrow_mut(); @@ -373,31 +267,27 @@ impl RtcPeerConnection { Some(mut f) => { let peer = Rc::clone(&self.peer); on_ice_connection_state_changed.replace( + // Unwrapping is OK here, because this function shouldn't + // error ever. EventListener::new_mut( Rc::clone(&self.peer), "iceconnectionstatechange", move |_| { - f(peer.ice_connection_state()); + f(parse_ice_connection_state( + peer.ice_connection_state(), + )); }, ) - .map_err(tracerr::map_from_and_wrap!())?, + .unwrap(), ); } } - Ok(()) } /// Sets handler for [`connectionstatechange`][1] event. /// - /// # Errors - /// - /// Will return [`RTCPeerConnectionError::PeerConnectionEventBindFailed`] if - /// [`EventListener`] binding fails. - /// This error can be ignored, since this event is currently implemented - /// only in Chrome and Safari. - /// /// [1]: https://w3.org/TR/webrtc/#event-connectionstatechange - pub fn on_connection_state_change(&self, f: Option) -> Result<()> + pub fn on_connection_state_change(&self, f: Option) where F: 'static + FnMut(PeerConnectionState), { @@ -410,6 +300,8 @@ impl RtcPeerConnection { Some(mut f) => { let peer = Rc::clone(&self.peer); on_connection_state_changed.replace( + // Unwrapping is OK here, because this function shouldn't + // error ever. EventListener::new_mut( Rc::clone(&self.peer), "connectionstatechange", @@ -438,11 +330,10 @@ impl RtcPeerConnection { } }, ) - .map_err(tracerr::map_from_and_wrap!())?, + .unwrap(), ); } } - Ok(()) } /// Adds remote [RTCPeerConnection][1]'s [ICE candidate][2] to this @@ -483,6 +374,7 @@ impl RtcPeerConnection { /// After this function returns, the offer returned by the next call to /// [`RtcPeerConnection::create_offer`] is automatically configured /// to trigger ICE restart. + #[inline] pub fn restart_ice(&self) { self.ice_restart.set(true); } @@ -736,3 +628,24 @@ fn get_peer_connection_state( } })) } + +/// Parses a [`IceConnectionState`] out of the given [`RtcIceConnectionState`]. +#[must_use] +fn parse_ice_connection_state( + state: RtcIceConnectionState, +) -> IceConnectionState { + use RtcIceConnectionState as S; + + match state { + S::New => IceConnectionState::New, + S::Checking => IceConnectionState::Checking, + S::Connected => IceConnectionState::Connected, + S::Completed => IceConnectionState::Completed, + S::Failed => IceConnectionState::Failed, + S::Disconnected => IceConnectionState::Disconnected, + S::Closed => IceConnectionState::Closed, + S::__Nonexhaustive => { + unreachable!("Unknown ICE connection state {:?}", state); + } + } +} diff --git a/jason/src/peer/stats.rs b/jason/src/platform/wasm/rtc_stats.rs similarity index 56% rename from jason/src/peer/stats.rs rename to jason/src/platform/wasm/rtc_stats.rs index 6a6f0e97f..2151c92f0 100644 --- a/jason/src/peer/stats.rs +++ b/jason/src/platform/wasm/rtc_stats.rs @@ -1,10 +1,9 @@ -//! Deserialization of the [`RtcStats`] from the [`SysRtcStats`]. +//! Deserialization of [`RtcStats`] from [`SysRtcStats`]. //! //! [`SysRtcStats`]: web_sys::RtcStats use std::{convert::TryFrom, rc::Rc}; -use derive_more::{Display, From}; use js_sys::{ Array as JsArray, Function as JsFunction, Iterator as JsIterator, JsString, }; @@ -12,42 +11,9 @@ use medea_client_api_proto::stats::{RtcStat, RtcStatsType}; use tracerr::Traced; use wasm_bindgen::{prelude::*, JsCast}; -use crate::utils::{get_property_by_name, JsCaused, JsError}; +use crate::platform::{self, get_property_by_name, RtcStatsError}; -/// Entry of the JS RTC stats dictionary. -struct RtcStatsReportEntry(JsString, JsValue); - -impl TryFrom for RtcStatsReportEntry { - type Error = Traced; - - fn try_from(value: JsArray) -> Result { - use RtcStatsError::{Js, UndefinedId, UndefinedStats}; - - let id = value.get(0); - let stats = value.get(1); - - if id.is_undefined() { - return Err(tracerr::new!(UndefinedId)); - } - - if stats.is_undefined() { - return Err(tracerr::new!(UndefinedStats)); - } - - let id = id - .dyn_into::() - .map_err(|e| tracerr::new!(Js(JsError::from(e))))?; - let stats = stats - .dyn_into::() - .map_err(|e| tracerr::new!(Js(JsError::from(e))))?; - - Ok(RtcStatsReportEntry(id, stats)) - } -} - -/// All available [`RtcStatsType`] of [`PeerConnection`]. -/// -/// [`PeerConnection`]: crate::peer::PeerConnection +/// All available [`RtcStatsType`]s of a [`platform::RtcPeerConnection`]. #[derive(Clone, Debug)] pub struct RtcStats(pub Vec); @@ -55,7 +21,7 @@ impl TryFrom<&JsValue> for RtcStats { type Error = Traced; fn try_from(stats: &JsValue) -> Result { - use RtcStatsError::{Js, UndefinedEntries}; + use RtcStatsError::{Platform, UndefinedEntries}; let entries_fn = get_property_by_name(&stats, "entries", |func: JsValue| { @@ -65,13 +31,15 @@ impl TryFrom<&JsValue> for RtcStats { let iterator = entries_fn .call0(stats.as_ref()) - .map_err(|e| tracerr::new!(Js(JsError::from(e))))? + .map_err(|e| tracerr::new!(Platform(platform::Error::from(e))))? .unchecked_into::(); let mut stats = Vec::new(); for stat in iterator { - let stat = stat.map_err(|e| tracerr::new!(Js(JsError::from(e))))?; + let stat = stat.map_err(|e| { + tracerr::new!(Platform(platform::Error::from(e))) + })?; let stat = stat.unchecked_into::(); let stat = RtcStatsReportEntry::try_from(stat) .map_err(tracerr::map_from_and_wrap!())?; @@ -90,31 +58,33 @@ impl TryFrom<&JsValue> for RtcStats { Ok(RtcStats(stats)) } } +/// Entry of a JS RTC stats dictionary. +struct RtcStatsReportEntry(JsString, JsValue); + +impl TryFrom for RtcStatsReportEntry { + type Error = Traced; + + fn try_from(value: JsArray) -> Result { + use RtcStatsError::{Platform, UndefinedId, UndefinedStats}; -/// Errors which can occur during deserialization of the [`RtcStatsType`]. -#[derive(Clone, Debug, Display, From, JsCaused)] -pub enum RtcStatsError { - /// [RTCStats.id][1] is undefined. - /// - /// [1]: https://w3.org/TR/webrtc/#dom-rtcstats-id - #[display(fmt = "RTCStats.id is undefined")] - UndefinedId, - - /// [RTCStats.stats] is undefined. - /// - /// [1]: https://w3.org/TR/webrtc-stats/#dfn-stats-object - #[display(fmt = "RTCStats.stats is undefined")] - UndefinedStats, - - /// Some JS error occurred. - #[display(fmt = "Unexpected JS side error: {}", _0)] - Js(JsError), - - /// `RTCStats.entries` is undefined. - #[display(fmt = "RTCStats.entries is undefined")] - UndefinedEntries, - - /// Error of [`RtcStats`] deserialization. - #[display(fmt = "Failed to deserialize into RtcStats: {}", _0)] - ParseError(Rc), + let id = value.get(0); + let stats = value.get(1); + + if id.is_undefined() { + return Err(tracerr::new!(UndefinedId)); + } + + if stats.is_undefined() { + return Err(tracerr::new!(UndefinedStats)); + } + + let id = id + .dyn_into::() + .map_err(|e| tracerr::new!(Platform(platform::Error::from(e))))?; + let stats = stats + .dyn_into::() + .map_err(|e| tracerr::new!(Platform(platform::Error::from(e))))?; + + Ok(RtcStatsReportEntry(id, stats)) + } } diff --git a/jason/src/peer/transceiver.rs b/jason/src/platform/wasm/transceiver.rs similarity index 88% rename from jason/src/peer/transceiver.rs rename to jason/src/platform/wasm/transceiver.rs index a97da4e7c..86364e594 100644 --- a/jason/src/peer/transceiver.rs +++ b/jason/src/platform/wasm/transceiver.rs @@ -8,7 +8,7 @@ use wasm_bindgen::JsValue; use wasm_bindgen_futures::JsFuture; use web_sys::{RtcRtpTransceiver, RtcRtpTransceiverDirection}; -use crate::media::track::local; +use crate::{media::track::local, platform::MediaStreamTrack}; /// Wrapper around [`RtcRtpTransceiver`] which provides handy methods for /// direction changes. @@ -20,11 +20,14 @@ pub struct Transceiver { impl Transceiver { /// Returns current [`TransceiverDirection`] of this [`Transceiver`]. + #[inline] + #[must_use] fn current_direction(&self) -> TransceiverDirection { TransceiverDirection::from(self.transceiver.direction()) } /// Disables provided [`TransceiverDirection`] of this [`Transceiver`]. + #[inline] pub fn sub_direction(&self, disabled_direction: TransceiverDirection) { self.transceiver.set_direction( (self.current_direction() - disabled_direction).into(), @@ -32,6 +35,7 @@ impl Transceiver { } /// Enables provided [`TransceiverDirection`] of this [`Transceiver`]. + #[inline] pub fn add_direction(&self, enabled_direction: TransceiverDirection) { self.transceiver.set_direction( (self.current_direction() | enabled_direction).into(), @@ -40,6 +44,8 @@ impl Transceiver { /// Indicates whether the provided [`TransceiverDirection`] is enabled for /// this [`Transceiver`]. + #[inline] + #[must_use] pub fn has_direction(&self, direction: TransceiverDirection) -> bool { self.current_direction().contains(direction) } @@ -59,22 +65,31 @@ impl Transceiver { if new_track.is_none() { self.send_track.replace(None); } - let sys_track = new_track.as_ref().map(|t| t.sys_track()); - JsFuture::from(self.transceiver.sender().replace_track(sys_track)) - .await - .map(|_| { - self.send_track.replace(new_track); - }) + let sys_track: Option<&MediaStreamTrack> = + new_track.as_ref().map(|t| (**t).as_ref()); + JsFuture::from( + self.transceiver + .sender() + .replace_track(sys_track.map(AsRef::as_ref)), + ) + .await + .map(|_| { + self.send_track.replace(new_track); + }) } /// Returns [`mid`] of this [`Transceiver`]. /// /// [`mid`]: https://w3.org/TR/webrtc/#dom-rtptransceiver-mid + #[inline] + #[must_use] pub fn mid(&self) -> Option { self.transceiver.mid() } /// Returns [`local::Track`] that is being send to remote, if any. + #[inline] + #[must_use] pub fn send_track(&self) -> Option> { self.send_track.borrow().clone() } @@ -88,6 +103,7 @@ impl Transceiver { /// Sets the underlying [`local::Track`]'s `enabled` field to the provided /// value, if any. + #[inline] pub fn set_send_track_enabled(&self, enabled: bool) { if let Some(track) = self.send_track.borrow().as_ref() { track.set_enabled(enabled); @@ -96,6 +112,7 @@ impl Transceiver { } impl From for Transceiver { + #[inline] fn from(transceiver: RtcRtpTransceiver) -> Self { Transceiver { send_track: RefCell::new(None), @@ -206,32 +223,32 @@ mod tests { } #[test] - fn from_trnsvr_direction_to_sys() { + fn from_trnscvr_direction_to_sys() { use RtcRtpTransceiverDirection as S; use TransceiverDirection as D; - for (trnsv_dir, sys_dir) in &[ + for (trnscvr_dir, sys_dir) in &[ (D::SEND, S::Sendonly), (D::RECV, S::Recvonly), (D::all(), S::Sendrecv), (D::INACTIVE, S::Inactive), ] { - assert_eq!(S::from(*trnsv_dir), *sys_dir); + assert_eq!(S::from(*trnscvr_dir), *sys_dir); } } #[test] - fn from_sys_direction_to_trnsvr() { + fn from_sys_direction_to_trnscvr() { use RtcRtpTransceiverDirection as S; use TransceiverDirection as D; - for (sys_dir, trnsv_dir) in &[ + for (sys_dir, trnscvr_dir) in &[ (S::Sendonly, D::SEND), (S::Recvonly, D::RECV), (S::Sendrecv, D::all()), (S::Inactive, D::INACTIVE), ] { - assert_eq!(D::from(*sys_dir), *trnsv_dir); + assert_eq!(D::from(*sys_dir), *trnscvr_dir); } } } diff --git a/jason/src/rpc/websocket/transport.rs b/jason/src/platform/wasm/transport.rs similarity index 65% rename from jason/src/rpc/websocket/transport.rs rename to jason/src/platform/wasm/transport.rs index 8723c0448..807b9b2dd 100644 --- a/jason/src/rpc/websocket/transport.rs +++ b/jason/src/platform/wasm/transport.rs @@ -4,95 +4,27 @@ use std::{cell::RefCell, convert::TryFrom, rc::Rc}; -use derive_more::{Display, From, Into}; +use derive_more::{From, Into}; use futures::{channel::mpsc, stream::LocalBoxStream, StreamExt}; -use medea_client_api_proto::{ClientMsg, ServerMsg}; +use medea_client_api_proto::{ClientMsg, CloseDescription, ServerMsg}; use medea_reactive::ObservableCell; use tracerr::Traced; use web_sys::{CloseEvent, Event, MessageEvent, WebSocket as SysWebSocket}; use crate::{ - rpc::{websocket::client::ClientDisconnect, ApiUrl, CloseMsg}, - utils::{ - EventListener, EventListenerBindError, JasonError, JsCaused, JsError, - JsonParseError, + platform::{ + transport::{RpcTransport, TransportError, TransportState}, + wasm::utils::EventListener, }, + rpc::{websocket::ClientDisconnect, ApiUrl, CloseMsg}, + utils::JasonError, }; -/// RPC transport between a client and a server. -#[cfg_attr(feature = "mockable", mockall::automock)] -pub trait RpcTransport { - /// Returns [`LocalBoxStream`] of all messages received by this transport. - fn on_message(&self) -> LocalBoxStream<'static, ServerMsg>; - - /// Sets reason, that will be sent to remote server when this transport will - /// be dropped. - fn set_close_reason(&self, reason: ClientDisconnect); - - /// Sends given [`ClientMsg`] to a server. - /// - /// # Errors - /// - /// Errors if sending [`ClientMsg`] fails. - fn send(&self, msg: &ClientMsg) -> Result<(), Traced>; - - /// Subscribes to a [`RpcTransport`]'s [`TransportState`] changes. - fn on_state_change(&self) -> LocalBoxStream<'static, TransportState>; -} - -/// Errors that may occur when working with [`WebSocketRpcClient`]. -/// -/// [`WebSocketRpcClient`]: super::WebSocketRpcClient -#[derive(Clone, Debug, Display, JsCaused, PartialEq)] -pub enum TransportError { - /// Occurs when the port to which the connection is being attempted - /// is being blocked. - #[display(fmt = "Failed to create WebSocket: {}", _0)] - CreateSocket(JsError), - - /// Occurs when the connection close before becomes state active. - #[display(fmt = "Failed to init WebSocket")] - InitSocket, - - /// Occurs when [`ClientMsg`] cannot be parsed. - #[display(fmt = "Failed to parse client message: {}", _0)] - ParseClientMessage(JsonParseError), - - /// Occurs when [`ServerMsg`] cannot be parsed. - #[display(fmt = "Failed to parse server message: {}", _0)] - ParseServerMessage(JsonParseError), - - /// Occurs if the parsed message is not string. - #[display(fmt = "Message is not a string")] - MessageNotString, - - /// Occurs when a message cannot be send to server. - #[display(fmt = "Failed to send message: {}", _0)] - SendMessage(JsError), - - /// Occurs when handler failed to bind to some [WebSocket] event. Not - /// really supposed to ever happen. - /// - /// [WebSocket]: https://developer.mozilla.org/ru/docs/WebSockets - #[display(fmt = "Failed to bind to WebSocket event: {}", _0)] - WebSocketEventBindError(EventListenerBindError), - - /// Occurs when message is sent to a closed socket. - #[display(fmt = "Underlying socket is closed")] - ClosedSocket, -} - -impl From for TransportError { - fn from(err: EventListenerBindError) -> Self { - Self::WebSocketEventBindError(err) - } -} - /// Wrapper for help to get [`ServerMsg`] from Websocket [MessageEvent][1]. /// /// [1]: https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent #[derive(Clone, From, Into)] -pub struct ServerMessage(ServerMsg); +struct ServerMessage(ServerMsg); impl TryFrom<&MessageEvent> for ServerMessage { type Error = TransportError; @@ -108,48 +40,6 @@ impl TryFrom<&MessageEvent> for ServerMessage { } } -/// [`RpcTransport`] states. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum TransportState { - /// Socket has been created. The connection is not open yet. - /// - /// Reflects `CONNECTING` state from JS side [`WebSocket.readyState`][1]. - /// - /// [1]: https://developer.mozilla.org/docs/Web/API/WebSocket/readyState - Connecting, - - /// The connection is open and ready to communicate. - /// - /// Reflects `OPEN` state from JS side [`WebSocket.readyState`][1]. - /// - /// [1]: https://developer.mozilla.org/docs/Web/API/WebSocket/readyState - Open, - - /// The connection is in the process of closing. - /// - /// Reflects `CLOSING` state from JS side [`WebSocket.readyState`][1]. - /// - /// [1]: https://developer.mozilla.org/docs/Web/API/WebSocket/readyState - Closing, - - /// The connection is closed or couldn't be opened. - /// - /// Reflects `CLOSED` state from JS side [`WebSocket.readyState`][1]. - /// - /// [`CloseMsg`] is the reason of why [`RpcTransport`] went into - /// this [`TransportState`]. - /// - /// [1]: https://developer.mozilla.org/docs/Web/API/WebSocket/readyState - Closed(CloseMsg), -} - -impl TransportState { - /// Returns `true` if socket can be closed. - pub fn can_close(self) -> bool { - matches!(self, Self::Connecting | Self::Open) - } -} - type Result> = std::result::Result; struct InnerSocket { @@ -245,7 +135,7 @@ impl WebSocketRpcTransport { /// [1]: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/onclose /// [2]: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/onopen pub async fn new(url: ApiUrl) -> Result { - let socket = Rc::new(RefCell::new(InnerSocket::new(url.0.as_str())?)); + let socket = Rc::new(RefCell::new(InnerSocket::new(url.as_ref())?)); { let mut socket_mut = socket.borrow_mut(); let inner = Rc::clone(&socket); @@ -260,7 +150,7 @@ impl WebSocketRpcTransport { .set(TransportState::Closed(CloseMsg::from(&msg))); }, ) - .map_err(tracerr::map_from_and_wrap!())?, + .unwrap(), ); let inner = Rc::clone(&socket); @@ -272,7 +162,7 @@ impl WebSocketRpcTransport { inner.borrow().socket_state.set(TransportState::Open); }, ) - .map_err(tracerr::map_from_and_wrap!(=> TransportError))?, + .unwrap(), ); } @@ -281,8 +171,8 @@ impl WebSocketRpcTransport { if let Some(TransportState::Open) = state { let this = Self(socket); - this.set_on_close_listener()?; - this.set_on_message_listener()?; + this.set_on_close_listener(); + this.set_on_message_listener(); Ok(this) } else { @@ -292,7 +182,7 @@ impl WebSocketRpcTransport { /// Sets [`InnerSocket::on_close_listener`] which will update /// [`RpcTransport`]'s [`TransportState`] to [`TransportState::Closed`]. - fn set_on_close_listener(&self) -> Result<()> { + fn set_on_close_listener(&self) { let this = Rc::clone(&self.0); let on_close = EventListener::new_once( Rc::clone(&self.0.borrow().socket), @@ -303,15 +193,13 @@ impl WebSocketRpcTransport { .set(TransportState::Closed(CloseMsg::from(&msg))); }, ) - .map_err(tracerr::map_from_and_wrap!(=> TransportError))?; + .unwrap(); self.0.borrow_mut().on_close_listener = Some(on_close); - - Ok(()) } /// Sets [`InnerSocket::on_message_listener`] which will send /// [`ServerMessage`]s to [`WebSocketRpcTransport::on_message`] subscribers. - fn set_on_message_listener(&self) -> Result<()> { + fn set_on_message_listener(&self) { let this = Rc::clone(&self.0); let on_message = EventListener::new_mut( Rc::clone(&self.0.borrow().socket), @@ -334,15 +222,14 @@ impl WebSocketRpcTransport { }); }, ) - .map_err(tracerr::map_from_and_wrap!(=> TransportError))?; + .unwrap(); self.0.borrow_mut().on_message_listener = Some(on_message); - - Ok(()) } } impl RpcTransport for WebSocketRpcTransport { + #[inline] fn on_message(&self) -> LocalBoxStream<'static, ServerMsg> { let (tx, rx) = mpsc::unbounded(); self.0.borrow_mut().on_message_subs.push(tx); @@ -350,6 +237,7 @@ impl RpcTransport for WebSocketRpcTransport { Box::pin(rx) } + #[inline] fn set_close_reason(&self, close_reason: ClientDisconnect) { self.0.borrow_mut().close_reason = close_reason; } @@ -372,6 +260,7 @@ impl RpcTransport for WebSocketRpcTransport { } } + #[inline] fn on_state_change(&self) -> LocalBoxStream<'static, TransportState> { self.0.borrow().socket_state.subscribe() } @@ -387,3 +276,21 @@ impl Drop for WebSocketRpcTransport { inner.on_close_listener.take(); } } + +impl From<&CloseEvent> for CloseMsg { + fn from(event: &CloseEvent) -> Self { + let code: u16 = event.code(); + match code { + 1000 => { + if let Ok(description) = + serde_json::from_str::(&event.reason()) + { + Self::Normal(code, description.reason) + } else { + Self::Abnormal(code) + } + } + _ => Self::Abnormal(code), + } + } +} diff --git a/jason/src/platform/wasm/utils/callback.rs b/jason/src/platform/wasm/utils/callback.rs new file mode 100644 index 000000000..695381ace --- /dev/null +++ b/jason/src/platform/wasm/utils/callback.rs @@ -0,0 +1,87 @@ +//! Somewhat convenient wrappers around JS functions used as callbacks. + +use std::{cell::RefCell, marker::PhantomData}; + +use wasm_bindgen::JsValue; + +/// Wrapper for a single argument JS function. +pub struct Callback(RefCell>>); + +impl Callback { + /// Sets an inner JS function. + #[inline] + pub fn set_func(&self, f: Function) { + self.0.borrow_mut().replace(f); + } + + /// Indicates whether this [`Callback`] is set. + #[inline] + #[must_use] + pub fn is_set(&self) -> bool { + self.0.borrow().as_ref().is_some() + } +} + +impl Callback<()> { + /// Invokes its JS function (if any) passing no arguments to it. + #[inline] + pub fn call0(&self) { + if let Some(f) = self.0.borrow().as_ref() { + f.call0() + }; + } +} + +impl Default for Callback { + #[inline] + fn default() -> Self { + Self(RefCell::new(None)) + } +} + +impl> Callback { + /// Invokes JS function (if any) passing the single provided `arg`ument to + /// it. + #[inline] + pub fn call1>(&self, arg: T) { + if let Some(f) = self.0.borrow().as_ref() { + f.call1(arg.into()) + }; + } +} + +/// Typed wrapper for a [`js_sys::Function`] with a single argument and no +/// result. +pub struct Function { + /// [`js_sys::Function`] itself. + inner: js_sys::Function, + + /// Type of the function argument. + _arg: PhantomData, +} + +impl Function<()> { + /// Invokes a JS function passing no arguments to it. + #[inline] + pub fn call0(&self) { + drop(self.inner.call0(&JsValue::NULL)); + } +} + +impl> Function { + /// Invokes a JS function passing the provided single `arg`ument to it. + #[inline] + pub fn call1(&self, arg: T) { + drop(self.inner.call1(&JsValue::NULL, &arg.into())); + } +} + +impl From for Function { + #[inline] + fn from(func: js_sys::Function) -> Self { + Self { + inner: func, + _arg: PhantomData, + } + } +} diff --git a/jason/src/utils/event_listener.rs b/jason/src/platform/wasm/utils/event_listener.rs similarity index 87% rename from jason/src/utils/event_listener.rs rename to jason/src/platform/wasm/utils/event_listener.rs index bcca5ff24..2317225f2 100644 --- a/jason/src/utils/event_listener.rs +++ b/jason/src/platform/wasm/utils/event_listener.rs @@ -3,20 +3,22 @@ use std::{ops::Deref, rc::Rc}; use derive_more::{Display, From}; use tracerr::Traced; use wasm_bindgen::{closure::Closure, convert::FromWasmAbi, JsCast}; -use web_sys::EventTarget; -use super::{errors::JsCaused, JsError}; +use crate::{platform, utils::JsCaused}; /// Failed to bind to [`EventTarget`][1] event. /// /// [1]: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget #[derive(Clone, Debug, Display, From, JsCaused, PartialEq)] -pub struct EventListenerBindError(JsError); +#[js(error = "platform::Error")] +pub struct EventListenerBindError(platform::Error); /// Wrapper for closure that handles some [`EventTarget`] event. +/// +/// [`EventTarget`]: web_sys::EventTarget pub struct EventListener where - T: Deref, + T: Deref, { event_name: &'static str, target: Rc, @@ -25,7 +27,7 @@ where impl EventListener where - T: Deref, + T: Deref, A: FromWasmAbi + 'static, { /// Creates new [`EventListener`] from a given [`FnMut`] `closure`. @@ -48,7 +50,7 @@ where event_name, closure.as_ref().unchecked_ref(), ) - .map_err(JsError::from) + .map_err(platform::Error::from) .map_err(EventListenerBindError::from) .map_err(tracerr::wrap!())?; @@ -79,7 +81,7 @@ where event_name, closure.as_ref().unchecked_ref(), ) - .map_err(JsError::from) + .map_err(platform::Error::from) .map_err(EventListenerBindError::from) .map_err(tracerr::wrap!())?; @@ -93,7 +95,7 @@ where impl Drop for EventListener where - T: Deref, + T: Deref, { /// Drops [`EventListener`]'s closure and unregisters appropriate event /// handler. diff --git a/jason/src/platform/wasm/utils/mod.rs b/jason/src/platform/wasm/utils/mod.rs new file mode 100644 index 000000000..d7bcc03a0 --- /dev/null +++ b/jason/src/platform/wasm/utils/mod.rs @@ -0,0 +1,10 @@ +//! `wasm32`-specific utility structs and functions. + +mod callback; +mod event_listener; + +#[doc(inline)] +pub use self::{ + callback::{Callback, Function}, + event_listener::{EventListener, EventListenerBindError}, +}; diff --git a/jason/src/api/room.rs b/jason/src/room.rs similarity index 81% rename from jason/src/api/room.rs rename to jason/src/room.rs index 7156920dc..22f7e186e 100644 --- a/jason/src/api/room.rs +++ b/jason/src/room.rs @@ -1,4 +1,7 @@ -//! Medea room. +//! Medea [`Room`]. + +// TODO: Remove when moving `JasonError` to `api::wasm`. +#![allow(clippy::missing_errors_doc)] use std::{ cell::RefCell, @@ -13,44 +16,38 @@ use derive_more::{Display, From}; use futures::{ channel::mpsc, future, FutureExt as _, StreamExt as _, TryFutureExt as _, }; -use js_sys::Promise; use medea_client_api_proto::{ self as proto, Command, ConnectionQualityScore, Event as RpcEvent, - EventHandler, IceCandidate, IceConnectionState, IceServer, MediaSourceKind, - MemberId, NegotiationRole, PeerConnectionState, PeerId, PeerMetrics, Track, - TrackId, TrackUpdate, + EventHandler, IceCandidate, IceConnectionState, IceServer, MemberId, + NegotiationRole, PeerConnectionState, PeerId, PeerMetrics, Track, TrackId, + TrackUpdate, }; use tracerr::Traced; -use wasm_bindgen::{prelude::*, JsValue}; -use wasm_bindgen_futures::{future_to_promise, spawn_local}; use crate::{ - api::connection::Connections, + api, + connection::Connections, media::{ track::{local, remote}, LocalTracksConstraints, MediaKind, MediaManager, MediaManagerError, - MediaStreamSettings, RecvConstraints, + MediaSourceKind, MediaStreamSettings, RecvConstraints, }, peer::{ self, media_exchange_state, mute_state, LocalStreamUpdateCriteria, MediaConnectionsError, MediaState, PeerConnection, PeerError, - PeerEvent, PeerEventHandler, RtcStats, TrackDirection, + PeerEvent, PeerEventHandler, TrackDirection, }, + platform, rpc::{ ClientDisconnect, CloseReason, ConnectionInfo, ConnectionInfoParseError, ReconnectHandle, RpcSession, SessionError, }, - utils::{ - AsProtoState, Callback1, HandlerDetachedError, JasonError, JsCaused, - JsError, - }, - JsMediaSourceKind, + utils::{AsProtoState, HandlerDetachedError, JasonError, JsCaused}, }; /// Reason of why [`Room`] has been closed. /// -/// This struct is passed into `on_close_by_server` JS side callback. -#[wasm_bindgen] +/// This struct is passed into [`RoomHandle::on_close`] callback. pub struct RoomCloseReason { /// Indicator if [`Room`] is closed by server. /// @@ -67,8 +64,7 @@ pub struct RoomCloseReason { } impl RoomCloseReason { - /// Creates new [`RoomCloseReason`] with provided [`CloseReason`] - /// converted into [`String`]. + /// Creates a new [`RoomCloseReason`] with the provided [`CloseReason`]. /// /// `is_err` may be `true` only on closing by client. /// @@ -87,22 +83,24 @@ impl RoomCloseReason { }, } } -} -#[wasm_bindgen] -impl RoomCloseReason { - /// `wasm_bindgen` getter for [`RoomCloseReason::reason`] field. + /// Returns a close reason of the [`Room`]. + #[inline] + #[must_use] pub fn reason(&self) -> String { self.reason.clone() } - /// `wasm_bindgen` getter for [`RoomCloseReason::is_closed_by_server`] - /// field. + /// Indicates whether the [`Room`] was closed by server. + #[inline] + #[must_use] pub fn is_closed_by_server(&self) -> bool { self.is_closed_by_server } - /// `wasm_bindgen` getter for [`RoomCloseReason::is_err`] field. + /// Indicates whether the [`Room`]'s close reason is considered as an error. + #[inline] + #[must_use] pub fn is_err(&self) -> bool { self.is_err } @@ -110,6 +108,7 @@ impl RoomCloseReason { /// Errors that may occur in a [`Room`]. #[derive(Clone, Debug, Display, From, JsCaused)] +#[js(error = "platform::Error")] pub enum RoomError { /// Returned if the mandatory callback wasn't set. #[display(fmt = "`{}` callback isn't set.", _0)] @@ -124,8 +123,6 @@ pub enum RoomError { /// Returned if [`PeerConnection`] cannot receive the local tracks from /// [`MediaManager`]. - /// - /// [`MediaManager`]: crate::media::MediaManager #[display(fmt = "Failed to get local tracks: {}", _0)] #[from(ignore)] CouldNotGetLocalMedia(#[js(cause)] PeerError), @@ -187,24 +184,27 @@ impl From for RoomError { } } -/// JS side handle to `Room` where all the media happens. -/// -/// Actually, represents a [`Weak`]-based handle to `InnerRoom`. -/// -/// For using [`RoomHandle`] on Rust side, consider the `Room`. -#[wasm_bindgen] +/// External handle to a [`Room`]. +#[derive(Clone)] pub struct RoomHandle(Weak); impl RoomHandle { - /// Implements externally visible `RoomHandle::join`. + /// Connects to a media server and joins the [`Room`] with the provided + /// authorization `token`. /// - /// # Errors + /// Authorization token has a fixed format: + /// `{{ Host URL }}/{{ Room ID }}/{{ Member ID }}?token={{ Auth Token }}` + /// (e.g. `wss://medea.com/MyConf1/Alice?token=777`). /// - /// With [`RoomError::CallbackNotSet`] if `on_failed_local_media` or - /// `on_connection_loss` callbacks are not set. + /// Establishes connection with a media server (if it doesn't exist + /// already ). /// - /// With [`RoomError::SessionError`] if cannot connect to media server. - pub async fn inner_join(&self, url: String) -> Result<(), JasonError> { + /// # Errors + /// + /// - When `on_failed_local_media` callback is not set. + /// - When `on_connection_loss` callback is not set. + /// - When unable to connect to a media server. + pub async fn join(&self, url: String) -> Result<(), JasonError> { let inner = upgrade_or_detached!(self.0, JasonError)?; let connection_info: ConnectionInfo = url.parse().map_err( @@ -238,7 +238,7 @@ impl RoomHandle { new_state: MediaState, kind: MediaKind, direction: TrackDirection, - source_kind: Option, + source_kind: Option, ) -> Result<(), JasonError> { let inner = upgrade_or_detached!(self.0, JasonError)?; inner.set_constraints_media_state( @@ -254,8 +254,8 @@ impl RoomHandle { MediaState::MediaExchange(media_exchange_state::Stable::Enabled) ); - // Perform `getUuserMedia()`/`getDisplayMedia()` right away, so we can - // fail fast without touching senders' states and starting all required + // Perform `getUserMedia()`/`getDisplayMedia()` right away, so we can + // fail fast without touching senders states and starting all required // messaging. // Hold tracks through all process, to ensure that they will be reused // without additional requests. @@ -300,81 +300,63 @@ impl RoomHandle { } Ok(()) } -} -#[wasm_bindgen] -impl RoomHandle { - /// Sets callback, which will be invoked when new [`Connection`] with some - /// remote `Peer` is established. + /// Sets callback, invoked when a new [`Connection`] with some remote `Peer` + /// is established. /// - /// [`Connection`]: crate::api::Connection + /// [`Connection`]: crate::connection::Connection pub fn on_new_connection( &self, - f: js_sys::Function, - ) -> Result<(), JsValue> { + f: platform::Function, + ) -> Result<(), JasonError> { upgrade_or_detached!(self.0) .map(|inner| inner.connections.on_new_connection(f)) } - /// Sets `on_close` callback, which will be invoked on [`Room`] close, - /// providing [`RoomCloseReason`]. - pub fn on_close(&mut self, f: js_sys::Function) -> Result<(), JsValue> { + /// Sets `on_close` callback, invoked on this [`Room`] close, providing a + /// [`RoomCloseReason`]. + pub fn on_close( + &self, + f: platform::Function, + ) -> Result<(), JasonError> { upgrade_or_detached!(self.0).map(|inner| inner.on_close.set_func(f)) } - /// Sets callback, which will be invoked when new [`local::Track`] will be - /// added to this [`Room`]. + /// Sets callback, invoked when a new [`local::Track`] is added to this + /// [`Room`]. + /// /// This might happen in such cases: - /// 1. Media server initiates media request. + /// 1. Media server initiates a media request. /// 2. `disable_audio`/`enable_video` is called. /// 3. [`MediaStreamSettings`] updated via `set_local_media_settings`. - pub fn on_local_track(&self, f: js_sys::Function) -> Result<(), JsValue> { + pub fn on_local_track( + &self, + f: platform::Function, + ) -> Result<(), JasonError> { upgrade_or_detached!(self.0) .map(|inner| inner.on_local_track.set_func(f)) } - /// Sets `on_failed_local_media` callback, which will be invoked on local - /// media acquisition failures. + /// Sets `on_failed_local_media` callback, invoked on a local media + /// acquisition failures. pub fn on_failed_local_media( &self, - f: js_sys::Function, - ) -> Result<(), JsValue> { + f: platform::Function, + ) -> Result<(), JasonError> { upgrade_or_detached!(self.0) .map(|inner| inner.on_failed_local_media.set_func(f)) } - /// Sets `on_connection_loss` callback, which will be invoked on connection - /// with server loss. + /// Sets `on_connection_loss` callback, invoked when a connection with + /// server is lost. pub fn on_connection_loss( &self, - f: js_sys::Function, - ) -> Result<(), JsValue> { + f: platform::Function, + ) -> Result<(), JasonError> { upgrade_or_detached!(self.0) .map(|inner| inner.on_connection_loss.set_func(f)) } - /// Connects media server and enters [`Room`] with provided authorization - /// `token`. - /// - /// Authorization token has fixed format: - /// `{{ Host URL }}/{{ Room ID }}/{{ Member ID }}?token={{ Auth Token }}` - /// (e.g. `wss://medea.com/MyConf1/Alice?token=777`). - /// - /// Establishes connection with media server (if it doesn't already exist). - /// Fails if: - /// - `on_failed_local_media` callback is not set - /// - `on_connection_loss` callback is not set - /// - unable to connect to media server. - /// - /// Effectively returns `Result<(), JasonError>`. - pub fn join(&self, token: String) -> Promise { - let this = Self(self.0.clone()); - future_to_promise(async move { - this.inner_join(token).await?; - Ok(JsValue::undefined()) - }) - } - /// Updates this [`Room`]s [`MediaStreamSettings`]. This affects all /// [`PeerConnection`]s in this [`Room`]. If [`MediaStreamSettings`] is /// configured for some [`Room`], then this [`Room`] can only send media @@ -399,190 +381,207 @@ impl RoomHandle { /// will be disabled. /// /// [1]: https://tinyurl.com/w3-streams#dom-mediadevices-getusermedia - pub fn set_local_media_settings( + pub async fn set_local_media_settings( &self, - settings: &MediaStreamSettings, + settings: MediaStreamSettings, stop_first: bool, rollback_on_fail: bool, - ) -> Promise { - let inner = upgrade_or_detached!(self.0, JasonError); - let settings = settings.clone(); - future_to_promise(async move { - inner? - .set_local_media_settings( - settings, - stop_first, - rollback_on_fail, - ) - .await - .map_err(ConstraintsUpdateException::from)?; - Ok(JsValue::UNDEFINED) - }) + ) -> Result<(), ConstraintsUpdateException> { + let inner = (self.0).upgrade().ok_or_else(|| { + ConstraintsUpdateException::Errored(new_js_error!( + HandlerDetachedError + )) + })?; + + inner + .set_local_media_settings(settings, stop_first, rollback_on_fail) + .await + .map_err(ConstraintsUpdateException::from) } - /// Returns [`Promise`] which will switch [`MediaState`] of the provided - /// [`MediaKind`], [`TrackDirection`] and [`JsMediaSourceKind`] to the - /// provided [`MediaState`]. + /// Changes a [`MediaState`] of the provided [`MediaKind`], + /// [`TrackDirection`] and [`MediaSourceKind`] to the provided one. /// /// Helper function for all the exported mute/unmute/enable/disable /// audio/video send/receive methods. - fn change_media_state( + #[inline] + async fn change_media_state( &self, media_state: S, kind: MediaKind, direction: TrackDirection, - source_kind: Option, - ) -> Promise + source_kind: Option, + ) -> Result<(), JasonError> where S: Into + 'static, { - let this = Self(self.0.clone()); - future_to_promise(async move { - this.set_track_media_state( - media_state.into(), - kind, - direction, - source_kind.map(Into::into), - ) - .await?; - Ok(JsValue::UNDEFINED) - }) + self.set_track_media_state( + media_state.into(), + kind, + direction, + source_kind.map(Into::into), + ) + .await } /// Mutes outbound audio in this [`Room`]. - pub fn mute_audio(&self) -> Promise { + #[inline] + pub async fn mute_audio(&self) -> Result<(), JasonError> { self.change_media_state( mute_state::Stable::Muted, MediaKind::Audio, TrackDirection::Send, None, ) + .await } /// Unmutes outbound audio in this [`Room`]. - pub fn unmute_audio(&self) -> Promise { + #[inline] + pub async fn unmute_audio(&self) -> Result<(), JasonError> { self.change_media_state( mute_state::Stable::Unmuted, MediaKind::Audio, TrackDirection::Send, None, ) + .await } /// Mutes outbound video in this [`Room`]. - pub fn mute_video( + #[inline] + pub async fn mute_video( &self, - source_kind: Option, - ) -> Promise { + source_kind: Option, + ) -> Result<(), JasonError> { self.change_media_state( mute_state::Stable::Muted, MediaKind::Video, TrackDirection::Send, source_kind, ) + .await } /// Unmutes outbound video in this [`Room`]. - pub fn unmute_video( + #[inline] + pub async fn unmute_video( &self, - source_kind: Option, - ) -> Promise { + source_kind: Option, + ) -> Result<(), JasonError> { self.change_media_state( mute_state::Stable::Unmuted, MediaKind::Video, TrackDirection::Send, source_kind, ) + .await } /// Disables outbound audio in this [`Room`]. - pub fn disable_audio(&self) -> Promise { + #[inline] + pub async fn disable_audio(&self) -> Result<(), JasonError> { self.change_media_state( media_exchange_state::Stable::Disabled, MediaKind::Audio, TrackDirection::Send, None, ) + .await } /// Enables outbound audio in this [`Room`]. - pub fn enable_audio(&self) -> Promise { + #[inline] + pub async fn enable_audio(&self) -> Result<(), JasonError> { self.change_media_state( media_exchange_state::Stable::Enabled, MediaKind::Audio, TrackDirection::Send, None, ) + .await } /// Disables outbound video. /// - /// Affects only video with specific [`JsMediaSourceKind`] if specified. - pub fn disable_video( + /// Affects only video with specific [`MediaSourceKind`] if specified. + #[inline] + pub async fn disable_video( &self, - source_kind: Option, - ) -> Promise { + source_kind: Option, + ) -> Result<(), JasonError> { self.change_media_state( media_exchange_state::Stable::Disabled, MediaKind::Video, TrackDirection::Send, source_kind, ) + .await } /// Enables outbound video. /// - /// Affects only video with specific [`JsMediaSourceKind`] if specified. - pub fn enable_video( + /// Affects only video with specific [`MediaSourceKind`] if specified. + #[inline] + pub async fn enable_video( &self, - source_kind: Option, - ) -> Promise { + source_kind: Option, + ) -> Result<(), JasonError> { self.change_media_state( media_exchange_state::Stable::Enabled, MediaKind::Video, TrackDirection::Send, source_kind, ) + .await } /// Disables inbound audio in this [`Room`]. - pub fn disable_remote_audio(&self) -> Promise { + #[inline] + pub async fn disable_remote_audio(&self) -> Result<(), JasonError> { self.change_media_state( media_exchange_state::Stable::Disabled, MediaKind::Audio, TrackDirection::Recv, None, ) + .await } /// Disables inbound video in this [`Room`]. - pub fn disable_remote_video(&self) -> Promise { + #[inline] + pub async fn disable_remote_video(&self) -> Result<(), JasonError> { self.change_media_state( media_exchange_state::Stable::Disabled, MediaKind::Video, TrackDirection::Recv, None, ) + .await } /// Enables inbound audio in this [`Room`]. - pub fn enable_remote_audio(&self) -> Promise { + #[inline] + pub async fn enable_remote_audio(&self) -> Result<(), JasonError> { self.change_media_state( media_exchange_state::Stable::Enabled, MediaKind::Audio, TrackDirection::Recv, None, ) + .await } /// Enables inbound video in this [`Room`]. - pub fn enable_remote_video(&self) -> Promise { + #[inline] + pub async fn enable_remote_video(&self) -> Result<(), JasonError> { self.change_media_state( media_exchange_state::Stable::Enabled, MediaKind::Video, TrackDirection::Recv, None, ) + .await } } @@ -602,8 +601,6 @@ impl WeakRoom { /// [`Room`] where all the media happens (manages concrete [`PeerConnection`]s, /// handles media server events, etc). -/// -/// For using [`Room`] on JS side, consider the [`RoomHandle`]. pub struct Room(Rc); impl Room { @@ -638,7 +635,7 @@ impl Room { let room = Rc::new(InnerRoom::new(rpc, media_manager, tx)); let inner = Rc::downgrade(&room); - spawn_local(async move { + platform::spawn(async move { loop { let event: RoomEvent = futures::select! { event = rpc_events_stream.select_next_some() => event, @@ -695,7 +692,7 @@ impl Room { self.0.set_close_reason(reason); } - /// Creates new [`RoomHandle`] used by JS side. You can create them as many + /// Creates a new external handle to [`Room`]. You can create them as many /// as you need. #[inline] pub fn new_handle(&self) -> RoomHandle { @@ -718,7 +715,7 @@ impl Room { .map_or(false, |handle_inner| Rc::ptr_eq(&self.0, &handle_inner)) } - /// Downgrades this [`Room`] to a [`WeakRoom`] reference. + /// Downgrades this [`Room`] to a weak reference. #[inline] pub fn downgrade(&self) -> WeakRoom { WeakRoom(Rc::downgrade(&self.0)) @@ -727,7 +724,7 @@ impl Room { /// Actual data of a [`Room`]. /// -/// Shared between JS side ([`RoomHandle`]) and Rust side ([`Room`]). +/// Shared between an external [`RoomHandle`] and Rust side ([`Room`]). struct InnerRoom { /// Client to talk with media server via Client API RPC. rpc: Rc, @@ -748,118 +745,112 @@ struct InnerRoom { /// Collection of [`Connection`]s with a remote `Member`s. /// - /// [`Connection`]: crate::api::Connection + /// [`Connection`]: crate::connection::Connection connections: Rc, - /// Callback to be invoked when new local [`local::JsTrack`] will be added - /// to this [`Room`]. - on_local_track: Callback1, + /// Callback invoked when a new local [`local::LocalMediaTrack`] will be + /// added to this [`Room`]. + on_local_track: platform::Callback, - /// Callback to be invoked when failed obtain [`local::Track`]s from + /// Callback invoked when failed obtain [`local::Track`]s from /// [`MediaManager`] or failed inject stream into [`PeerConnection`]. - /// - /// [`MediaManager`]: crate::media::MediaManager - on_failed_local_media: Rc>, + on_failed_local_media: Rc>, - /// Callback to be invoked when [`RpcSession`] loses connection. - on_connection_loss: Callback1, + /// Callback invoked when a [`RpcSession`] loses connection. + on_connection_loss: platform::Callback, - /// JS callback which will be called when this [`Room`] will be closed. - on_close: Rc>, + /// Callback invoked when this [`Room`] is closed. + on_close: Rc>, /// Reason of [`Room`] closing. /// - /// This [`CloseReason`] will be provided into `on_close` JS callback. + /// This [`CloseReason`] will be provided into [`RoomHandle::on_close`] + /// callback. /// /// Note that `None` will be considered as error and `is_err` will be - /// `true` in [`CloseReason`] provided to JS callback. + /// `true` in [`CloseReason`] provided to callback. close_reason: RefCell, } -/// JS exception for the [`RoomHandle::set_local_media_settings`]. -#[wasm_bindgen] -#[derive(Debug, From)] -#[from(forward)] -pub struct ConstraintsUpdateException(JsConstraintsUpdateError); +/// Exception for a [`RoomHandle::set_local_media_settings`]. +#[derive(Debug, Display)] +pub enum ConstraintsUpdateException { + /// New [`MediaStreamSettings`] set failed and state was recovered + /// accordingly to the provided recover policy + /// (`rollback_on_fail`/`stop_first` arguments). + #[display(fmt = "RecoveredException")] + Recovered { + /// [`JasonError`] due to which recovery happened. + recover_reason: JasonError, + }, + + /// New [`MediaStreamSettings`] set failed and state recovering also + /// failed. + #[display(fmt = "RecoverFailedException")] + RecoverFailed { + /// [`JasonError`] due to which recovery happened. + recover_reason: JasonError, + + /// Vector of [`JasonError`]s due to which recovery failed. + recover_fail_reasons: Vec, + }, + + /// Some other error occurred. + #[display(fmt = "ErroredException")] + Errored(JasonError), +} -#[wasm_bindgen] impl ConstraintsUpdateException { /// Returns name of this [`ConstraintsUpdateException`]. + #[inline] + #[must_use] pub fn name(&self) -> String { - self.0.to_string() + self.to_string() } /// Returns [`JasonError`] if this [`ConstraintsUpdateException`] represents - /// `RecoveredException` or `RecoverFailedException`. + /// a `RecoveredException` or a `RecoverFailedException`. /// /// Returns `undefined` otherwise. - pub fn recover_reason(&self) -> JsValue { - use JsConstraintsUpdateError as E; - match &self.0 { - E::RecoverFailed { recover_reason, .. } - | E::Recovered { recover_reason, .. } => recover_reason.clone(), - _ => JsValue::UNDEFINED, + #[inline] + #[must_use] + pub fn recover_reason(&self) -> Option { + match &self { + Self::RecoverFailed { recover_reason, .. } + | Self::Recovered { recover_reason, .. } => { + Some(recover_reason.clone()) + } + _ => None, } } - /// Returns [`js_sys::Array`] with the [`JasonError`]s if this - /// [`ConstraintsUpdateException`] represents `RecoverFailedException`. - /// - /// Returns `undefined` otherwise. - pub fn recover_fail_reasons(&self) -> JsValue { - match &self.0 { - JsConstraintsUpdateError::RecoverFailed { + /// Returns a list of [`JasonError`]s due to which a recovery has failed. + #[inline] + #[must_use] + pub fn recover_fail_reasons(&self) -> Vec { + match &self { + Self::RecoverFailed { recover_fail_reasons, .. } => recover_fail_reasons.clone(), - _ => JsValue::UNDEFINED, + _ => Vec::new(), } } /// Returns [`JasonError`] if this [`ConstraintsUpdateException`] represents - /// `ErroredException`. + /// an `ErroredException`. /// /// Returns `undefined` otherwise. - pub fn error(&self) -> JsValue { - match &self.0 { - JsConstraintsUpdateError::Errored { reason } => reason.clone(), - _ => JsValue::UNDEFINED, + #[inline] + #[must_use] + pub fn error(&self) -> Option { + match &self { + Self::Errored(reason) => Some(reason.clone()), + _ => None, } } } -/// [`ConstraintsUpdateError`] for JS side. -/// -/// Should be wrapped to [`ConstraintsUpdateException`] before returning to the -/// JS side. -#[derive(Debug, Display)] -pub enum JsConstraintsUpdateError { - /// New [`MediaStreamSettings`] set failed and state was recovered - /// accordingly to the provided recover policy - /// (`rollback_on_fail`/`stop_first` arguments). - #[display(fmt = "RecoveredException")] - Recovered { - /// [`JasonError`] due to which recovery happened. - recover_reason: JsValue, - }, - - /// New [`MediaStreamSettings`] set failed and state recovering also - /// failed. - #[display(fmt = "RecoverFailedException")] - RecoverFailed { - /// [`JasonError`] due to which recovery happened. - recover_reason: JsValue, - - /// [`js_sys::Array`] with a [`JasonError`]s due to which recovery - /// failed. - recover_fail_reasons: JsValue, - }, - - /// Some another error occurred. - #[display(fmt = "ErroredException")] - Errored { reason: JsValue }, -} - /// Constraints errors which are can occur while updating /// [`MediaStreamSettings`] by [`InnerRoom::set_local_media_settings`] call. #[derive(Debug)] @@ -924,36 +915,34 @@ impl ConstraintsUpdateError { } } -impl From for JsConstraintsUpdateError { +impl From for ConstraintsUpdateException { fn from(from: ConstraintsUpdateError) -> Self { use ConstraintsUpdateError as E; match from { E::Recovered { recover_reason } => Self::Recovered { - recover_reason: JasonError::from(recover_reason).into(), + recover_reason: JasonError::from(recover_reason), }, E::RecoverFailed { recover_reason, recover_fail_reasons, } => Self::RecoverFailed { - recover_reason: JasonError::from(recover_reason).into(), + recover_reason: JasonError::from(recover_reason), recover_fail_reasons: { - let arr = js_sys::Array::new(); - for e in recover_fail_reasons { - arr.push(&JasonError::from(e).into()); - } - - arr.into() + recover_fail_reasons + .into_iter() + .map(JasonError::from) + .collect() }, }, - E::Errored { error: reason } => Self::Errored { - reason: JasonError::from(reason).into(), - }, + E::Errored { error: reason } => { + Self::Errored(JasonError::from(reason)) + } } } } impl InnerRoom { - /// Creates new [`InnerRoom`]. + /// Creates a new [`InnerRoom`]. #[inline] fn new( rpc: Rc, @@ -979,10 +968,10 @@ impl InnerRoom { send_constraints, recv_constraints, connections, - on_connection_loss: Callback1::default(), - on_failed_local_media: Rc::new(Callback1::default()), - on_local_track: Callback1::default(), - on_close: Rc::new(Callback1::default()), + on_connection_loss: platform::Callback::default(), + on_failed_local_media: Rc::new(platform::Callback::default()), + on_local_track: platform::Callback::default(), + on_close: Rc::new(platform::Callback::default()), close_reason: RefCell::new(CloseReason::ByClient { reason: ClientDisconnect::RoomUnexpectedlyDropped, is_err: true, @@ -992,13 +981,14 @@ impl InnerRoom { /// Toggles [`InnerRoom::recv_constraints`] or /// [`InnerRoom::send_constraints`] media exchange status based on the - /// provided [`TrackDirection`], [`MediaKind`] and [`MediaSourceKind`]. + /// provided [`TrackDirection`], [`MediaKind`] and + /// [`proto::MediaSourceKind`]. fn set_constraints_media_state( &self, state: MediaState, kind: MediaKind, direction: TrackDirection, - source_kind: Option, + source_kind: Option, ) { use media_exchange_state::Stable::Enabled; use MediaState::{MediaExchange, Mute}; @@ -1018,16 +1008,16 @@ impl InnerRoom { } } - /// Sets `close_reason` of [`InnerRoom`]. + /// Sets `close_reason` of this [`InnerRoom`]. /// - /// [`Drop`] implementation of [`InnerRoom`] is supposed - /// to be triggered after this function call. + /// [`Drop`] implementation of [`InnerRoom`] is supposed to be triggered + /// after this function call. fn set_close_reason(&self, reason: CloseReason) { self.close_reason.replace(reason); } - /// Toggles [`TransceiverSide`]s [`MediaState`] by provided - /// [`MediaKind`] in all [`PeerConnection`]s in this [`Room`]. + /// Toggles [`TransceiverSide`]s [`MediaState`] by the provided + /// [`MediaKind`] in all [`PeerConnection`]s of this [`Room`]. /// /// [`TransceiverSide`]: crate::peer::TransceiverSide #[allow(clippy::filter_map)] @@ -1036,7 +1026,7 @@ impl InnerRoom { state: MediaState, kind: MediaKind, direction: TrackDirection, - source_kind: Option, + source_kind: Option, ) -> Result<(), Traced> { let disable_tracks: HashMap<_, _> = self .peers @@ -1056,9 +1046,8 @@ impl InnerRoom { self.update_media_states(disable_tracks).await } - /// Updates [`MediaState`]s of the [`TransceiverSide`] with a - /// provided [`PeerId`] and [`TrackId`] to a provided - /// [`MediaState`]s. + /// Updates [`MediaState`]s of the [`TransceiverSide`] with the provided + /// [`PeerId`] and [`TrackId`] to the provided [`MediaState`]s. /// /// [`TransceiverSide`]: crate::peer::TransceiverSide #[allow(clippy::filter_map)] @@ -1143,23 +1132,21 @@ impl InnerRoom { } /// Returns [`local::Track`]s for the provided [`MediaKind`] and - /// [`MediaSourceKind`]. + /// [`proto::MediaSourceKind`]. /// - /// If [`MediaSourceKind`] is [`None`] then [`local::TrackHandle`]s for all - /// needed [`MediaSourceKind`]s will be returned. + /// If [`proto::MediaSourceKind`] is [`None`] then [`local::Track`]s for all + /// needed [`proto::MediaSourceKind`]s will be returned. /// /// # Errors /// - /// - [`RoomError::MediaManagerError`] if failed to obtain - /// [`local::TrackHandle`] from the [`MediaManager`]. + /// - [`RoomError::MediaManagerError`] if failed to obtain a + /// [`local::Track`] from the [`MediaManager`]. /// - [`RoomError::PeerConnectionError`] if failed to get /// [`MediaStreamSettings`]. - /// - /// [`MediaStreamSettings`]: crate::MediaStreamSettings async fn get_local_tracks( &self, kind: MediaKind, - source_kind: Option, + source_kind: Option, ) -> Result>, Traced> { let requests: Vec<_> = self .peers @@ -1178,14 +1165,14 @@ impl InnerRoom { .map_err(tracerr::map_from_and_wrap!()) .map_err(|e| { self.on_failed_local_media - .call(JasonError::from(e.clone())); + .call1(JasonError::from(e.clone())); e })?; for (track, is_new) in tracks { if is_new { self.on_local_track - .call(local::JsTrack::new(Rc::clone(&track))); + .call1(local::LocalMediaTrack::new(Rc::clone(&track))); } result.push(track); } @@ -1195,16 +1182,16 @@ impl InnerRoom { } /// Returns `true` if all [`Sender`]s or [`Receiver`]s with a provided - /// [`MediaKind`] and [`MediaSourceKind`] of this [`Room`] are in the + /// [`MediaKind`] and [`proto::MediaSourceKind`] of this [`Room`] are in the /// provided [`MediaState`]. /// - /// [`Sender`]: crate::peer::Sender - /// [`Receiver`]: crate::peer::Receiver + /// [`Sender`]: peer::media::Sender + /// [`Receiver`]: peer::media::Receiver pub fn is_all_peers_in_media_state( &self, kind: MediaKind, direction: TrackDirection, - source_kind: Option, + source_kind: Option, state: MediaState, ) -> bool { self.peers @@ -1224,7 +1211,7 @@ impl InnerRoom { /// Updates [`MediaState`]s to the provided `states_update` and disables all /// [`Sender`]s which are doesn't have [`local::Track`]. /// - /// [`Sender`]: crate::peer::Sender + /// [`Sender`]: peer::media::Sender async fn disable_senders_without_tracks( &self, peer: &Rc, @@ -1275,8 +1262,8 @@ impl InnerRoom { /// If recovering from fail state isn't possible and `stop_first` set to /// `true` then affected media types will be disabled. /// + /// [`Sender`]: peer::media::Sender /// [1]: https://tinyurl.com/rnxcavf - /// [`Sender`]: crate::peer::Sender #[async_recursion(?Send)] async fn set_local_media_settings( &self, @@ -1369,7 +1356,7 @@ impl InnerRoom { fn handle_rpc_connection_lost(&self) { self.peers.connection_lost(); self.on_connection_loss - .call(ReconnectHandle::new(Rc::downgrade(&self.rpc))); + .call1(ReconnectHandle::new(Rc::downgrade(&self.rpc))); } /// Sends [`Command::SynchronizeMe`] with a current Client state to the @@ -1396,7 +1383,7 @@ impl EventHandler for InnerRoom { /// If provided `sdp_offer` is `Some`, then offer is applied to a created /// peer, and [`Command::MakeSdpAnswer`] is emitted back to the RPC server. /// - /// [`Connection`]: crate::api::Connection + /// [`Connection`]: crate::connection::Connection async fn on_peer_created( &self, peer_id: PeerId, @@ -1416,7 +1403,7 @@ impl EventHandler for InnerRoom { .insert_track(track, self.send_constraints.clone()) .map_err(|e| { self.on_failed_local_media - .call(JasonError::from(e.clone())); + .call1(JasonError::from(e.clone())); tracerr::map_from_and_new!(e) })?; } @@ -1488,8 +1475,8 @@ impl EventHandler for InnerRoom { /// Will start (re)negotiation process if `Some` [`NegotiationRole`] is /// provided. /// - /// [`Receiver`]: crate::peer::Receiver - /// [`Sender`]: crate::peer::Sender + /// [`Receiver`]: peer::media::Receiver + /// [`Sender`]: peer::media::Sender async fn on_tracks_applied( &self, peer_id: PeerId, @@ -1508,7 +1495,7 @@ impl EventHandler for InnerRoom { .insert_track(&track, self.send_constraints.clone()) .map_err(|e| { self.on_failed_local_media - .call(JasonError::from(e.clone())); + .call1(JasonError::from(e.clone())); tracerr::map_from_and_new!(e) })?, TrackUpdate::Updated(track_patch) => { @@ -1529,8 +1516,8 @@ impl EventHandler for InnerRoom { /// Updates [`Connection`]'s [`ConnectionQualityScore`] by calling /// [`Connection::update_quality_score()`][1]. /// - /// [`Connection`]: crate::api::Connection - /// [1]: crate::api::Connection::update_quality_score + /// [`Connection`]: crate::connection::Connection + /// [1]: crate::connection::Connection::update_quality_score async fn on_connection_quality_updated( &self, partner_member_id: MemberId, @@ -1594,7 +1581,7 @@ impl PeerEventHandler for InnerRoom { /// Handles [`PeerEvent::NewRemoteTrack`] event and passes received /// [`remote::Track`] to the related [`Connection`]. /// - /// [`Connection`]: crate::api::Connection + /// [`Connection`]: crate::connection::Connection /// [`Stream`]: futures::Stream async fn on_new_remote_track( &self, @@ -1615,7 +1602,8 @@ impl PeerEventHandler for InnerRoom { &self, track: Rc, ) -> Self::Output { - self.on_local_track.call(local::JsTrack::new(track)); + self.on_local_track + .call1(local::LocalMediaTrack::new(track)); Ok(()) } @@ -1658,7 +1646,7 @@ impl PeerEventHandler for InnerRoom { async fn on_stats_update( &self, peer_id: PeerId, - stats: RtcStats, + stats: platform::RtcStats, ) -> Self::Output { self.rpc.send_command(Command::AddPeerConnectionMetrics { peer_id, @@ -1670,7 +1658,7 @@ impl PeerEventHandler for InnerRoom { /// Handles [`PeerEvent::FailedLocalMedia`] event by invoking /// `on_failed_local_media` [`Room`]'s callback. async fn on_failed_local_media(&self, error: JasonError) -> Self::Output { - self.on_failed_local_media.call(error); + self.on_failed_local_media.call1(error); Ok(()) } @@ -1708,7 +1696,7 @@ impl PeerEventHandler for InnerRoom { Ok(()) } - /// Handles [`PeerEvent::SendIntention`] event by sending the provided + /// Handles [`PeerEvent::MediaUpdateCommand`] event by sending the provided /// [`Command`] to Media Server. async fn on_media_update_command( &self, @@ -1728,12 +1716,8 @@ impl Drop for InnerRoom { self.rpc.close_with_reason(reason); }; - if let Some(Err(e)) = self - .on_close - .call(RoomCloseReason::new(*self.close_reason.borrow())) - { - log::error!("Failed to call Room::on_close callback: {:?}", e); - } + self.on_close + .call1(RoomCloseReason::new(*self.close_reason.borrow())); } } diff --git a/jason/src/rpc/backoff_delayer.rs b/jason/src/rpc/backoff_delayer.rs index cfd622cb4..eeac0b14c 100644 --- a/jason/src/rpc/backoff_delayer.rs +++ b/jason/src/rpc/backoff_delayer.rs @@ -1,6 +1,8 @@ //! Delayer that increases delay time by provided multiplier on each call. -use crate::utils::{delay_for, JsDuration}; +use std::time::Duration; + +use crate::platform; /// Delayer that increases delay time by provided multiplier on each call. /// @@ -13,10 +15,10 @@ pub struct BackoffDelayer { /// Delay of next [`BackoffDelayer::delay`] call. /// /// Will be increased by [`BackoffDelayer::delay`] call. - current_interval: JsDuration, + current_interval: Duration, /// Maximum delay for which this [`BackoffDelayer`] may delay. - max_interval: JsDuration, + max_interval: Duration, /// The multiplier by which [`BackoffDelayer::current_interval`] will be /// multiplied on [`BackoffDelayer::delay`] call. @@ -26,14 +28,14 @@ pub struct BackoffDelayer { impl BackoffDelayer { /// Creates and returns new [`BackoffDelayer`]. pub fn new( - starting_interval: JsDuration, + starting_interval: Duration, interval_multiplier: f32, - max_interval: JsDuration, + max_interval: Duration, ) -> Self { Self { current_interval: starting_interval, max_interval, - interval_multiplier, + interval_multiplier: interval_multiplier.max(0_f32), } } @@ -44,17 +46,18 @@ impl BackoffDelayer { /// [`BackoffDelayer::interval_multiplier`] milliseconds, /// until [`BackoffDelayer::max_interval`] is reached. pub async fn delay(&mut self) { - delay_for(self.get_delay()).await; + platform::delay_for(self.get_delay()).await; } /// Returns current interval and increases it for next call. - fn get_delay(&mut self) -> JsDuration { + fn get_delay(&mut self) -> Duration { if self.is_max_interval_reached() { self.max_interval } else { let delay = self.current_interval; - self.current_interval = - self.current_interval * self.interval_multiplier; + self.current_interval = Duration::from_secs_f32( + self.current_interval.as_secs_f32() * self.interval_multiplier, + ); delay } } diff --git a/jason/src/rpc/heartbeat.rs b/jason/src/rpc/heartbeat.rs index b1f11d997..bd16c8fc1 100644 --- a/jason/src/rpc/heartbeat.rs +++ b/jason/src/rpc/heartbeat.rs @@ -1,42 +1,42 @@ //! Connection loss detection via ping/pong mechanism. -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, rc::Rc, time::Duration}; use derive_more::{Display, From, Mul}; use futures::{channel::mpsc, future, stream::LocalBoxStream, StreamExt as _}; use medea_client_api_proto::{ClientMsg, ServerMsg}; -use wasm_bindgen_futures::spawn_local; use crate::{ - rpc::{RpcTransport, TransportError}, - utils::{delay_for, JsCaused, JsDuration, JsError, TaskHandle}, + platform, + utils::{JsCaused, TaskHandle}, }; /// Errors that may occur in [`Heartbeat`]. #[derive(Clone, Debug, Display, From, JsCaused)] -pub struct HeartbeatError(TransportError); +#[js(error = "platform::Error")] +pub struct HeartbeatError(platform::TransportError); /// Idle timeout of [`WebSocketRpcClient`]. /// /// [`WebSocketRpcClient`]: super::WebSocketRpcClient #[derive(Debug, Copy, Clone)] -pub struct IdleTimeout(pub JsDuration); +pub struct IdleTimeout(pub Duration); /// Ping interval of [`WebSocketRpcClient`]. /// /// [`WebSocketRpcClient`]: super::WebSocketRpcClient #[derive(Debug, Copy, Clone, Mul)] -pub struct PingInterval(pub JsDuration); +pub struct PingInterval(pub Duration); /// Inner data of [`Heartbeat`]. struct Inner { - /// [`RpcTransport`] which heartbeats. - transport: Rc, + /// [`platform::RpcTransport`] which heartbeats. + transport: Rc, - /// Idle timeout of [`RpcTransport`]. + /// Idle timeout of the [`platform::RpcTransport`]. idle_timeout: IdleTimeout, - /// Ping interval of [`RpcTransport`]. + /// Ping interval of the [`platform::RpcTransport`]. ping_interval: PingInterval, /// [`TaskHandle`] for [`Future`] which sends [`ClientMsg::Pong`] on @@ -62,7 +62,7 @@ impl Inner { fn send_pong(&self, n: u32) { self.transport .send(&ClientMsg::Pong(n)) - .map_err(tracerr::wrap!(=> TransportError)) + .map_err(tracerr::wrap!(=> platform::TransportError)) .map_err(|e| log::error!("Failed to send pong: {}", e)) .ok(); } @@ -72,10 +72,11 @@ impl Inner { pub struct Heartbeat(Rc>); impl Heartbeat { - /// Start this [`Heartbeat`] for the provided [`RpcTransport`] with - /// the provided `idle_timeout` and `ping_interval`. + /// Starts this [`Heartbeat`] for the provided [`platform::RpcTransport`] + /// with the provided `idle_timeout` and `ping_interval`. + #[must_use] pub fn start( - transport: Rc, + transport: Rc, ping_interval: PingInterval, idle_timeout: IdleTimeout, ) -> Self { @@ -109,7 +110,7 @@ impl Heartbeat { } /// Returns [`LocalBoxStream`] to which will sent `()` when [`Heartbeat`] - /// considers that [`RpcTransport`] is idle. + /// considers that [`platform::RpcTransport`] is idle. pub fn on_idle(&self) -> LocalBoxStream<'static, ()> { let (on_idle_tx, on_idle_rx) = mpsc::unbounded(); self.0.borrow_mut().on_idle_subs.push(on_idle_tx); @@ -129,19 +130,19 @@ fn spawn_idle_watchdog_task(this: Rc>) -> TaskHandle { let (idle_watchdog_fut, idle_watchdog_handle) = future::abortable(async move { let wait_for_ping = this.borrow().ping_interval * 2; - delay_for(wait_for_ping.0).await; + platform::delay_for(wait_for_ping.0).await; let last_ping_num = this.borrow().last_ping_num; this.borrow().send_pong(last_ping_num + 1); let idle_timeout = this.borrow().idle_timeout; - delay_for(idle_timeout.0 - wait_for_ping.0).await; + platform::delay_for(idle_timeout.0 - wait_for_ping.0).await; this.borrow_mut() .on_idle_subs .retain(|sub| sub.unbounded_send(()).is_ok()); }); - spawn_local(async move { + platform::spawn(async move { idle_watchdog_fut.await.ok(); }); @@ -166,7 +167,7 @@ fn spawn_ping_handle_task(this: Rc>) -> TaskHandle { } } }); - spawn_local(async move { + platform::spawn(async move { handle_ping_fut.await.ok(); }); handle_ping_task.into() diff --git a/jason/src/rpc/mod.rs b/jason/src/rpc/mod.rs index c99b45ec3..93ca51550 100644 --- a/jason/src/rpc/mod.rs +++ b/jason/src/rpc/mod.rs @@ -8,16 +8,14 @@ pub mod websocket; use std::str::FromStr; -use derive_more::{Display, From}; +use derive_more::{AsRef, Display, From}; use medea_client_api_proto::{ - CloseDescription, CloseReason as CloseByServerReason, Credential, MemberId, - RoomId, + CloseReason as CloseByServerReason, Credential, MemberId, RoomId, }; use tracerr::Traced; use url::Url; -use web_sys::CloseEvent; -use crate::utils::{JsCaused, JsError}; +use crate::{platform, utils::JsCaused}; #[cfg(feature = "mockable")] pub use self::rpc_session::MockRpcSession; @@ -29,14 +27,12 @@ pub use self::{ rpc_session::{ RpcSession, SessionError, SessionState, WebSocketRpcSession, }, - websocket::{ - ClientDisconnect, RpcTransport, TransportError, WebSocketRpcClient, - WebSocketRpcTransport, - }, + websocket::{ClientDisconnect, RpcEvent, WebSocketRpcClient}, }; /// [`Url`] to which transport layer will connect. -#[derive(Clone, Debug, Eq, From, PartialEq)] +#[derive(AsRef, Clone, Debug, Eq, From, PartialEq)] +#[as_ref(forward)] pub struct ApiUrl(Url); /// Information about [`RpcSession`] connection. @@ -80,6 +76,7 @@ impl ConnectionInfo { /// Errors which can occur while [`ConnectionInfo`] parsing from the [`str`]. #[derive(Debug, JsCaused, Display)] +#[js(error = "platform::Error")] pub enum ConnectionInfoParseError { /// [`Url::parse`] returned error. #[display(fmt = "Failed to parse provided URL: {:?}", _0)] @@ -159,15 +156,15 @@ pub enum CloseReason { }, } -/// The reason of why [`WebSocketRpcClient`]/[`RpcTransport`] went into -/// closed state. +/// The reason of why [`WebSocketRpcClient`]/[`platform::RpcTransport`] went +/// into a closed state. #[derive(Clone, Debug, PartialEq)] pub enum ClosedStateReason { /// Connection with server was lost. ConnectionLost(CloseMsg), /// Error while creating connection between client and server. - ConnectionFailed(TransportError), + ConnectionFailed(platform::TransportError), /// Indicates that connection with server has never been established. NeverConnected, @@ -186,10 +183,11 @@ pub enum ClosedStateReason { /// Errors that may occur in [`WebSocketRpcClient`]. #[derive(Clone, Debug, Display, From, JsCaused)] +#[js(error = "platform::Error")] pub enum RpcClientError { /// Occurs if WebSocket connection to remote media server failed. #[display(fmt = "Connection failed: {}", _0)] - RpcTransportError(#[js(cause)] TransportError), + RpcTransportError(#[js(cause)] platform::TransportError), /// Occurs if the heartbeat cannot be started. #[display(fmt = "Start heartbeat failed: {}", _0)] @@ -227,21 +225,3 @@ pub enum CloseMsg { /// `1000` without reason. Abnormal(u16), } - -impl From<&CloseEvent> for CloseMsg { - fn from(event: &CloseEvent) -> Self { - let code: u16 = event.code(); - match code { - 1000 => { - if let Ok(description) = - serde_json::from_str::(&event.reason()) - { - Self::Normal(code, description.reason) - } else { - Self::Abnormal(code) - } - } - _ => Self::Abnormal(code), - } - } -} diff --git a/jason/src/rpc/reconnect_handle.rs b/jason/src/rpc/reconnect_handle.rs index 18609c886..41ffa0c1f 100644 --- a/jason/src/rpc/reconnect_handle.rs +++ b/jason/src/rpc/reconnect_handle.rs @@ -1,57 +1,54 @@ //! Reconnection for [`RpcSession`]. +// TODO: Remove when moving `JasonError` to `api::wasm`. +#![allow(clippy::missing_errors_doc)] + use std::{rc::Weak, time::Duration}; use derive_more::Display; -use js_sys::Promise; -use wasm_bindgen::prelude::*; -use wasm_bindgen_futures::future_to_promise; use crate::{ + platform, rpc::{BackoffDelayer, RpcSession}, - utils::{delay_for, HandlerDetachedError, JasonError, JsCaused, JsError}, + utils::{HandlerDetachedError, JasonError, JsCaused}, }; /// Error which indicates that [`RpcSession`]'s (which this [`ReconnectHandle`] /// tries to reconnect) token is `None`. #[derive(Debug, Display, JsCaused)] +#[js(error = "platform::Error")] struct NoTokenError; -/// Handle that JS side can reconnect to the Medea media server on -/// a connection loss with. +/// External handle used to reconnect to a media server when connection is lost. /// -/// This handle will be provided into `Room.on_connection_loss` callback. -#[wasm_bindgen] +/// This handle will be passed to a `Room.on_connection_loss` callback. #[derive(Clone)] pub struct ReconnectHandle(Weak); impl ReconnectHandle { /// Instantiates new [`ReconnectHandle`] from the given [`RpcSession`] /// reference. + #[inline] + #[must_use] pub fn new(rpc: Weak) -> Self { Self(rpc) } -} -#[wasm_bindgen] -impl ReconnectHandle { /// Tries to reconnect after the provided delay in milliseconds. /// /// If [`RpcSession`] is already reconnecting then new reconnection attempt /// won't be performed. Instead, it will wait for the first reconnection /// attempt result and use it here. - pub fn reconnect_with_delay(&self, delay_ms: u32) -> Promise { - let rpc = Clone::clone(&self.0); - future_to_promise(async move { - delay_for(Duration::from_millis(u64::from(delay_ms)).into()).await; + pub async fn reconnect_with_delay( + &self, + delay_ms: u32, + ) -> Result<(), JasonError> { + platform::delay_for(Duration::from_millis(u64::from(delay_ms))).await; - let rpc = upgrade_or_detached!(rpc, JsValue)?; - rpc.reconnect() - .await - .map_err(|e| JsValue::from(JasonError::from(e)))?; + let rpc = upgrade_or_detached!(self.0, JasonError)?; + rpc.reconnect().await.map_err(JasonError::from)?; - Ok(JsValue::UNDEFINED) - }) + Ok(()) } /// Tries to reconnect [`RpcSession`] in a loop with a growing backoff @@ -72,29 +69,26 @@ impl ReconnectHandle { /// /// If `multiplier` is negative number than `multiplier` will be considered /// as `0.0`. - pub fn reconnect_with_backoff( + pub async fn reconnect_with_backoff( &self, starting_delay_ms: u32, multiplier: f32, max_delay: u32, - ) -> Promise { - let rpc = self.0.clone(); - future_to_promise(async move { - let mut backoff_delayer = BackoffDelayer::new( - Duration::from_millis(u64::from(starting_delay_ms)).into(), - multiplier, - Duration::from_millis(u64::from(max_delay)).into(), - ); + ) -> Result<(), JasonError> { + let mut backoff_delayer = BackoffDelayer::new( + Duration::from_millis(u64::from(starting_delay_ms)), + multiplier, + Duration::from_millis(u64::from(max_delay)), + ); + backoff_delayer.delay().await; + while upgrade_or_detached!(self.0, JasonError)? + .reconnect() + .await + .is_err() + { backoff_delayer.delay().await; - while upgrade_or_detached!(rpc, JsValue)? - .reconnect() - .await - .is_err() - { - backoff_delayer.delay().await; - } + } - Ok(JsValue::UNDEFINED) - }) + Ok(()) } } diff --git a/jason/src/rpc/rpc_session.rs b/jason/src/rpc/rpc_session.rs index 8b9afb51e..8ded73a56 100644 --- a/jason/src/rpc/rpc_session.rs +++ b/jason/src/rpc/rpc_session.rs @@ -1,3 +1,8 @@ +//! Wrapper around [WebSocket] based transport that implements `Room` +//! management. +//! +//! [WebSocket]: https://developer.mozilla.org/ru/docs/WebSockets + use std::{ cell::{Cell, RefCell}, rc::Rc, @@ -15,18 +20,19 @@ use futures::{ use medea_client_api_proto::{Command, Event, MemberId, RoomId}; use medea_reactive::ObservableCell; use tracerr::Traced; -use wasm_bindgen_futures::spawn_local; use crate::{ + platform, rpc::{ websocket::RpcEventHandler, ClientDisconnect, CloseReason, ConnectionInfo, RpcClientError, WebSocketRpcClient, }, - utils::{JsCaused, JsError}, + utils::JsCaused, }; /// Errors which are can be returned from the [`WebSocketRpcSession`]. #[derive(Clone, Debug, From, JsCaused, Display)] +#[js(error = "platform::Error")] pub enum SessionError { /// [`WebSocketRpcSession`] goes into [`SessionState::Finished`] and can't /// be used. @@ -75,7 +81,7 @@ pub trait RpcSession { /// new RPC connection. /// /// If [`RpcSession`] already in [`SessionState::Connecting`] then this - /// function will not perform one more connection try. It will subsribe + /// function will not perform one more connection try. It will subscribe /// to [`SessionState`] changes and wait for first connection result. /// And based on this result - this function will be resolved. /// @@ -112,14 +118,12 @@ pub trait RpcSession { /// Subscribe to connection loss events. /// - /// Connection loss is any unexpected [`RpcTransport`] close. In case of - /// connection loss, JS side user should select reconnection strategy with - /// [`ReconnectHandle`] (or simply close [`Room`]). + /// Connection loss is any unexpected [`platform::RpcTransport`] close. In + /// case of connection loss, client side user should select reconnection + /// strategy with [`ReconnectHandle`] (or simply close [`Room`]). /// - /// [`ReconnectHandle`]: crate::rpc::RpcTransport - /// [`Room`]: crate::api::Room - /// [`RpcTransport`]: crate::rpc::RpcTransport - /// [`Stream`]: futures::Stream + /// [`ReconnectHandle`]: crate::rpc::ReconnectHandle + /// [`Room`]: crate::room::Room fn on_connection_loss(&self) -> LocalBoxStream<'static, ()>; /// Subscribe to reconnected events. @@ -133,9 +137,9 @@ pub trait RpcSession { /// /// Responsible for [`Room`] authorization and closing. /// -/// [`Room`]: crate::api::Room +/// [`Room`]: crate::room::Room pub struct WebSocketRpcSession { - /// [WebSocket] based Rpc Client used to . + /// [WebSocket] based Rpc Client used to talk with `Medea` server. /// /// [WebSocket]: https://developer.mozilla.org/ru/docs/WebSockets client: Rc, @@ -249,7 +253,7 @@ impl WebSocketRpcSession { let mut state_updates = self.state.subscribe(); let weak_this = Rc::downgrade(self); - spawn_local(async move { + platform::spawn(async move { while let Some(state) = state_updates.next().await { let this = upgrade_or_break!(weak_this); match state { @@ -290,7 +294,7 @@ impl WebSocketRpcSession { let mut client_on_connection_loss = self.client.on_connection_loss(); let weak_this = Rc::downgrade(self); - spawn_local(async move { + platform::spawn(async move { while client_on_connection_loss.next().await.is_some() { let this = upgrade_or_break!(weak_this); @@ -322,7 +326,7 @@ impl WebSocketRpcSession { fn spawn_close_watcher(self: &Rc) { let on_normal_close = self.client.on_normal_close(); let weak_this = Rc::downgrade(self); - spawn_local(async move { + platform::spawn(async move { let reason = on_normal_close.await.unwrap_or_else(|_| { ClientDisconnect::RpcClientUnexpectedlyDropped.into() }); @@ -336,7 +340,7 @@ impl WebSocketRpcSession { fn spawn_server_msg_listener(self: &Rc) { let mut server_msg_rx = self.client.subscribe(); let weak_this = Rc::downgrade(self); - spawn_local(async move { + platform::spawn(async move { while let Some(msg) = server_msg_rx.next().await { let this = upgrade_or_break!(weak_this); msg.dispatch_with(this.as_ref()); diff --git a/jason/src/rpc/websocket/client.rs b/jason/src/rpc/websocket/client.rs index a1e2bdf12..e37f5ad94 100644 --- a/jason/src/rpc/websocket/client.rs +++ b/jason/src/rpc/websocket/client.rs @@ -14,11 +14,10 @@ use medea_macro::dispatchable; use medea_reactive::ObservableCell; use serde::Serialize; use tracerr::Traced; -use wasm_bindgen_futures::spawn_local; use crate::{ + platform, rpc::{ - websocket::transport::{RpcTransport, TransportError, TransportState}, ApiUrl, CloseMsg, CloseReason, ClosedStateReason, Heartbeat, IdleTimeout, PingInterval, RpcClientError, }, @@ -30,22 +29,18 @@ use crate::{ pub enum ClientDisconnect { /// [`Room`] was dropped without any [`CloseReason`]. /// - /// [`Room`]: crate::api::Room + /// [`Room`]: crate::room::Room RoomUnexpectedlyDropped, - /// [`Room`] was normally closed by JS side. + /// [`Room`] was normally closed bu client. /// - /// [`Room`]: crate::api::Room + /// [`Room`]: crate::room::Room RoomClosed, /// [`WebSocketRpcClient`] was unexpectedly dropped. - /// - /// [`WebSocketRpcClient`]: crate::rpc::WebSocketRpcClient RpcClientUnexpectedlyDropped, - /// [`RpcTransport`] was unexpectedly dropped. - /// - /// [`RpcTransport`]: crate::rpc::RpcTransport + /// [`platform::RpcTransport`] was unexpectedly dropped. RpcTransportUnexpectedlyDropped, /// [`WebSocketRpcSession`] was unexpectedly dropped. @@ -79,7 +74,7 @@ impl From for CloseReason { } } -/// State of [`WebSocketRpcClient`] and [`RpcTransport`]. +/// State of a [`WebSocketRpcClient`] and a [`platform::RpcTransport`]. #[derive(Clone, Debug, PartialEq)] pub enum ClientState { /// [`WebSocketRpcClient`] is currently establishing a connection to RPC @@ -96,7 +91,7 @@ pub enum ClientState { /// Inner state of [`WebSocketRpcClient`]. struct Inner { /// Transport connection with remote media server. - sock: Option>, + sock: Option>, /// Connection loss detector via ping/pong mechanism. heartbeat: Option, @@ -110,19 +105,20 @@ struct Inner { /// Reason of [`WebSocketRpcClient`] closing. /// - /// This reason will be provided to the underlying [`RpcTransport`]. + /// This reason will be provided to the underlying + /// [`platform::RpcTransport`]. close_reason: ClientDisconnect, /// Subscribers that will be notified when underlying transport connection /// is lost. on_connection_loss_subs: Vec>, - /// Closure which will create new [`RpcTransport`]s for this + /// Closure which will create new [`platform::RpcTransport`]s for this /// [`WebSocketRpcClient`] on each /// [`WebSocketRpcClient:: establish_connection`] call. rpc_transport_factory: RpcTransportFactory, - /// URL that [`RpcTransport`] will connect to. + /// URL that [`platform::RpcTransport`] will connect to. /// /// [`None`] if this [`WebSocketRpcClient`] has never been connected to /// a sever. @@ -132,14 +128,17 @@ struct Inner { state: ObservableCell, } -/// Factory closure which creates [`RpcTransport`] for -/// [`WebSocketRpcClient::establish_connection`] function. +/// Factory closure which creates a [`platform::RpcTransport`] for a +/// [`WebSocketRpcClient::establish_connection()`] function. pub type RpcTransportFactory = Box< dyn Fn( ApiUrl, ) -> LocalBoxFuture< 'static, - Result, Traced>, + Result< + Rc, + Traced, + >, >, >; @@ -169,11 +168,11 @@ pub enum RpcEvent { /// Notification of the subscribers that [`WebSocketRpcClient`] is joined /// [`Room`] on Media Server. /// - /// [`Room`]: crate::api::Room + /// [`Room`]: crate::room::Room JoinedRoom { /// ID of the joined [`Room`]. /// - /// [`Room`]: crate::api::Room + /// [`Room`]: crate::room::Room room_id: RoomId, /// ID of the joined `Member`. @@ -183,16 +182,16 @@ pub enum RpcEvent { /// Notification of the subscribers that [`WebSocketRpcClient`] left /// [`Room`] on Media Server. /// - /// [`Room`]: crate::api::Room + /// [`Room`]: crate::room::Room LeftRoom { /// ID of the [`Room`] being left. /// - /// [`Room`]: crate::api::Room + /// [`Room`]: crate::room::Room room_id: RoomId, /// Reason of why the [`Room`] has been left. /// - /// [`Room`]: crate::api::Room + /// [`Room`]: crate::room::Room close_reason: CloseReason, }, @@ -200,7 +199,7 @@ pub enum RpcEvent { Event { /// ID of the [`Room`] for that this [`Event`] has been received for. /// - /// [`Room`]: crate::api::Room + /// [`Room`]: crate::room::Room room_id: RoomId, /// Received [`Event`]. @@ -308,14 +307,12 @@ impl WebSocketRpcClient { }, ServerMsg::RpcSettings(settings) => { self.update_settings( - IdleTimeout( - Duration::from_millis(settings.idle_timeout_ms.into()) - .into(), - ), - PingInterval( - Duration::from_millis(settings.ping_interval_ms.into()) - .into(), - ), + IdleTimeout(Duration::from_millis( + settings.idle_timeout_ms.into(), + )), + PingInterval(Duration::from_millis( + settings.ping_interval_ms.into(), + )), ) .map_err(tracerr::wrap!(=> RpcClientError)) .map_err(|e| { @@ -335,25 +332,25 @@ impl WebSocketRpcClient { } /// Starts [`Heartbeat`] with provided [`RpcSettings`] for provided - /// [`RpcTransport`]. + /// [`platform::RpcTransport`]. async fn start_heartbeat( self: Rc, - transport: Rc, + transport: Rc, rpc_settings: RpcSettings, ) -> Result<(), Traced> { - let idle_timeout = IdleTimeout( - Duration::from_millis(rpc_settings.idle_timeout_ms.into()).into(), - ); - let ping_interval = PingInterval( - Duration::from_millis(rpc_settings.ping_interval_ms.into()).into(), - ); + let idle_timeout = IdleTimeout(Duration::from_millis( + rpc_settings.idle_timeout_ms.into(), + )); + let ping_interval = PingInterval(Duration::from_millis( + rpc_settings.ping_interval_ms.into(), + )); let heartbeat = Heartbeat::start(transport, ping_interval, idle_timeout); let mut on_idle = heartbeat.on_idle(); let weak_this = Rc::downgrade(&self); - spawn_local(async move { + platform::spawn(async move { while on_idle.next().await.is_some() { if let Some(this) = weak_this.upgrade() { this.handle_connection_loss(ClosedStateReason::Idle); @@ -412,10 +409,10 @@ impl WebSocketRpcClient { // subscribe to transport close let mut transport_state_changes = transport.on_state_change(); let weak_this = Rc::downgrade(&self); - spawn_local(async move { + platform::spawn(async move { while let Some(state) = transport_state_changes.next().await { if let Some(this) = weak_this.upgrade() { - if let TransportState::Closed(msg) = state { + if let platform::TransportState::Closed(msg) = state { this.handle_close_message(msg); } } @@ -425,7 +422,7 @@ impl WebSocketRpcClient { // subscribe to transport message received let weak_this = Rc::downgrade(&self); let mut on_socket_message = transport.on_message(); - spawn_local(async move { + platform::spawn(async move { while let Some(msg) = on_socket_message.next().await { if let Some(this) = weak_this.upgrade() { this.on_transport_message(msg) @@ -553,12 +550,12 @@ impl WebSocketRpcClient { /// Subscribe to connection loss events. /// - /// Connection loss is any unexpected [`RpcTransport`] close. In case of - /// connection loss, JS side user should select reconnection strategy with - /// [`ReconnectHandle`] (or simply close [`Room`]). + /// Connection loss is any unexpected [`platform::RpcTransport`] close. In + /// case of connection loss, client side user should select reconnection + /// strategy with [`ReconnectHandle`] (or simply close [`Room`]). /// /// [`ReconnectHandle`]: crate::rpc::ReconnectHandle - /// [`Room`]: crate::api::Room + /// [`Room`]: crate::room::Room /// [`Stream`]: futures::Stream pub fn on_connection_loss(&self) -> LocalBoxStream<'static, ()> { let (tx, rx) = mpsc::unbounded(); diff --git a/jason/src/rpc/websocket/mod.rs b/jason/src/rpc/websocket/mod.rs index 51ce61c49..d5d893a4e 100644 --- a/jason/src/rpc/websocket/mod.rs +++ b/jason/src/rpc/websocket/mod.rs @@ -3,17 +3,9 @@ //! [WebSocket]: https://developer.mozilla.org/ru/docs/WebSockets mod client; -mod transport; -#[cfg(feature = "mockable")] -pub use self::transport::MockRpcTransport; #[doc(inline)] -pub use self::{ - client::{ - ClientDisconnect, ClientState, RpcEvent, RpcEventHandler, - RpcTransportFactory, WebSocketRpcClient, - }, - transport::{ - RpcTransport, TransportError, TransportState, WebSocketRpcTransport, - }, +pub use self::client::{ + ClientDisconnect, ClientState, RpcEvent, RpcEventHandler, + RpcTransportFactory, WebSocketRpcClient, }; diff --git a/jason/src/utils/callback.rs b/jason/src/utils/callback.rs deleted file mode 100644 index d1ccd0e4e..000000000 --- a/jason/src/utils/callback.rs +++ /dev/null @@ -1,155 +0,0 @@ -//! Somewhat convenient wrappers around JS functions used as callbacks. - -use std::{cell::RefCell, marker::PhantomData}; - -use js_sys::Function as JsFunction; -use wasm_bindgen::JsValue; - -/// Wrapper for JS function with no arguments. -#[derive(Default)] -pub struct Callback0 { - f: RefCell>, -} - -impl Callback0 { - /// Sets inner JS function. - #[inline] - pub fn set_func(&self, f: JsFunction) { - self.f.borrow_mut().replace(f); - } - - /// Invokes JS function if any. - /// - /// Returns `None` if no callback is set, otherwise returns its invocation - /// result. - pub fn call(&self) -> Option> { - self.f.borrow().as_ref().map(|f| f.call0(&JsValue::NULL)) - } - - /// Indicates whether callback is set. - #[inline] - pub fn is_set(&self) -> bool { - self.f.borrow().as_ref().is_some() - } -} - -/// Wrapper for a single argument JS function. -pub struct Callback1 { - f: RefCell>, - _arg: PhantomData, -} - -impl Default for Callback1 { - #[inline] - fn default() -> Self { - Self { - f: RefCell::new(None), - _arg: PhantomData, - } - } -} - -impl> Callback1 { - /// Sets inner JS function. - #[inline] - pub fn set_func(&self, f: JsFunction) { - self.f.borrow_mut().replace(f); - } - - /// Invokes JS function if any. - /// - /// Returns `None` if no callback is set, otherwise returns its invocation - /// result. - pub fn call(&self, arg: A) -> Option> { - self.f - .borrow() - .as_ref() - .map(|f| f.call1(&JsValue::NULL, &arg.into())) - } - - /// Indicates if callback is set. - #[inline] - pub fn is_set(&self) -> bool { - self.f.borrow().as_ref().is_some() - } -} - -impl From for Callback1 { - #[inline] - fn from(f: JsFunction) -> Self { - Self { - f: RefCell::new(Some(f)), - _arg: PhantomData, - } - } -} - -/// Wrapper for a JS functions with two arguments. -/// -/// Can be used if you need to conditionally invoke function passing one of two -/// args, e.g. first arg in case of success, and second as error. -pub struct Callback2 { - f: RefCell>, - _arg1: PhantomData, - _arg2: PhantomData, -} - -impl Default for Callback2 { - #[inline] - fn default() -> Self { - Self { - f: RefCell::new(None), - _arg1: PhantomData, - _arg2: PhantomData, - } - } -} - -impl, A2: Into> Callback2 { - /// Sets inner JS function. - #[inline] - pub fn set_func(&self, f: JsFunction) { - self.f.borrow_mut().replace(f); - } - - /// Invokes JS function passing both arguments. - /// - /// Returns `None` if no callback is set, otherwise returns its invocation - /// result. - pub fn call( - &self, - arg1: Option, - arg2: Option, - ) -> Option> { - self.f.borrow().as_ref().map(|f| { - f.call2( - &JsValue::NULL, - &arg1.map_or(JsValue::NULL, Into::into), - &arg2.map_or(JsValue::NULL, Into::into), - ) - }) - } - - /// Invokes JS function passing only first argument. - #[inline] - pub fn call1(&self, arg1: A1) -> Option> { - self.call(Some(arg1), None) - } - - /// Invokes JS function passing only second argument. - #[inline] - pub fn call2(&self, arg2: A2) -> Option> { - self.call(None, Some(arg2)) - } -} - -impl From for Callback2 { - #[inline] - fn from(f: JsFunction) -> Self { - Self { - f: RefCell::new(Some(f)), - _arg1: PhantomData, - _arg2: PhantomData, - } - } -} diff --git a/jason/src/utils/component.rs b/jason/src/utils/component.rs index 4b9bd7028..3814dcb9d 100644 --- a/jason/src/utils/component.rs +++ b/jason/src/utils/component.rs @@ -5,10 +5,10 @@ use std::rc::Rc; use derive_more::Deref; use futures::{future, Future, FutureExt as _, Stream, StreamExt}; use medea_reactive::AllProcessed; -use wasm_bindgen_futures::spawn_local; use crate::{ media::LocalTracksConstraints, + platform, utils::{JasonError, TaskHandle}, }; @@ -137,7 +137,7 @@ impl WatchersSpawner { } } }); - spawn_local(fut.map(|_| ())); + platform::spawn(fut.map(|_| ())); self.spawned_watchers.push(handle.into()); } diff --git a/jason/src/utils/errors.rs b/jason/src/utils/errors.rs index 1b06c827c..ab62ecfb2 100644 --- a/jason/src/utils/errors.rs +++ b/jason/src/utils/errors.rs @@ -1,29 +1,16 @@ +//! Helpers for application errors. + use std::{ - borrow::Cow, fmt::{Debug, Display}, rc::Rc, }; use derive_more::{Display, From}; use tracerr::{Trace, Traced}; -use wasm_bindgen::{prelude::*, JsCast}; -pub use medea_macro::JsCaused; +use crate::platform; -/// Prints provided message with [`Console.error()`]. -/// -/// May be useful during development. -/// -/// __Unavailable in the release builds.__ -/// -/// [`Console.error()`]: https://tinyurl.com/psv3wqw -#[cfg(debug_assertions)] -pub fn console_error(msg: M) -where - M: Into, -{ - web_sys::console::error_1(&msg.into()); -} +pub use medea_macro::JsCaused; /// Representation of an error which can caused by error returned from the /// JS side. @@ -38,92 +25,56 @@ pub trait JsCaused { fn js_cause(self) -> Option; } -/// Wrapper for JS value which returned from JS side as error. -#[derive(Clone, Debug, Display, PartialEq)] -#[display(fmt = "{}: {}", name, message)] -pub struct JsError { - /// Name of JS error. - pub name: Cow<'static, str>, - - /// Message of JS error. - pub message: Cow<'static, str>, -} - -impl From for JsError { - fn from(val: JsValue) -> Self { - match val.dyn_into::() { - Ok(err) => Self { - name: Cow::Owned(err.name().into()), - message: Cow::Owned(err.message().into()), - }, - Err(val) => match val.as_string() { - Some(reason) => Self { - name: "Unknown JS error".into(), - message: reason.into(), - }, - None => Self { - name: "Unknown JS error".into(), - message: format!("{:?}", val).into(), - }, - }, - } - } -} - -impl From for js_sys::Error { - fn from(err: JsError) -> Self { - let error = Self::new(&err.message); - error.set_name(&err.name); - error - } -} - -/// Representation of app error exported to JS side. -/// -/// Contains JS side error if it the cause and trace information. -#[wasm_bindgen] +// TODO: Consider moving to `api::wasm`. +/// Abstract application error. #[derive(Clone, Debug, Display)] #[display(fmt = "{}: {}\n{}", name, message, trace)] pub struct JasonError { name: &'static str, message: String, trace: Trace, - source: Option, + source: Option, } impl JasonError { - /// Prints error information to `console.error()`. - pub fn print(&self) { - log::error!("{}", self); - } -} - -#[wasm_bindgen] -impl JasonError { - /// Returns name of error. + /// Returns name of this error. + #[inline] + #[must_use] pub fn name(&self) -> String { String::from(self.name) } - /// Returns message of errors. + /// Returns message of this error. + #[inline] + #[must_use] pub fn message(&self) -> String { self.message.clone() } - /// Returns trace information of error. + /// Returns trace information of this error. + #[inline] + #[must_use] pub fn trace(&self) -> String { self.trace.to_string() } - /// Returns JS side error if it the cause. - pub fn source(&self) -> Option { + /// Returns [`platform::Error`] if it's the cause. + #[inline] + #[must_use] + pub fn source(&self) -> Option { Clone::clone(&self.source) } + + /// Prints error information to default logger with an `ERROR` level. + #[inline] + pub fn print(&self) { + log::error!("{}", self); + } } impl From<(E, Trace)> for JasonError where - E::Error: Into, + E::Error: Into, { fn from((err, trace): (E, Trace)) -> Self { Self { @@ -137,7 +88,7 @@ where impl From> for JasonError where - E::Error: Into, + E::Error: Into, { fn from(traced: Traced) -> Self { Self::from(traced.into_parts()) @@ -147,6 +98,7 @@ where /// Occurs if referenced value was dropped. #[derive(Debug, Display, JsCaused)] #[display(fmt = "Handler is in detached state.")] +#[js(error = "platform::Error")] pub struct HandlerDetachedError; /// Wrapper for [`serde_json::error::Error`] that provides [`Clone`], [`Debug`], diff --git a/jason/src/utils/mod.rs b/jason/src/utils/mod.rs index 5186e5092..a6a666d0f 100644 --- a/jason/src/utils/mod.rs +++ b/jason/src/utils/mod.rs @@ -3,102 +3,25 @@ #[macro_use] mod errors; -mod callback; pub mod component; -mod event_listener; mod resettable_delay; -use std::{convert::TryInto as _, future::Future, ops::Mul, time::Duration}; +use std::future::Future; -use derive_more::{From, Sub}; +use derive_more::From; use futures::future::{self, AbortHandle}; -use js_sys::{Promise, Reflect}; use medea_reactive::Guarded; -use wasm_bindgen::prelude::*; -use wasm_bindgen_futures::JsFuture; -use web_sys::Window; -#[cfg(debug_assertions)] -pub use self::errors::console_error; #[doc(inline)] pub use self::{ - callback::{Callback0, Callback1, Callback2}, component::{AsProtoState, Component, SynchronizableState, Updatable}, - errors::{ - HandlerDetachedError, JasonError, JsCaused, JsError, JsonParseError, - }, - event_listener::{EventListener, EventListenerBindError}, + errors::{HandlerDetachedError, JasonError, JsCaused, JsonParseError}, resettable_delay::{resettable_delay_for, ResettableDelayHandle}, }; -/// Returns [`Window`] object. -/// -/// # Panics -/// -/// When global [`Window`] object is inaccessible. -pub fn window() -> Window { - // Cannot use `lazy_static` since `window` is `!Sync`. - // Safe to unwrap. - web_sys::window().unwrap() -} - -/// Wrapper around [`Duration`] which can be transformed into [`i32`] for JS -/// side timers. -/// -/// Also [`JsDuration`] can be multiplied by [`f32`]. -#[derive(Clone, Copy, Debug, From, PartialEq, PartialOrd, Sub)] -pub struct JsDuration(Duration); - -impl JsDuration { - /// Converts this [`JsDuration`] into `i32` milliseconds. - /// - /// Unfortunately, [`web_sys`] believes that only `i32` can be passed to a - /// `setTimeout`. But it is unlikely we will need a duration of more, - /// than 596 hours, so it was decided to simply truncate the number. If we - /// will need a longer duration in the future, then we can implement this - /// with a few `setTimeout`s. - #[inline] - pub fn into_js_duration(self) -> i32 { - self.0.as_millis().try_into().unwrap_or(i32::max_value()) - } -} - -impl Mul for JsDuration { - type Output = Self; - - #[inline] - fn mul(self, rhs: u32) -> Self::Output { - Self(self.0 * rhs) - } -} - -impl Mul for JsDuration { - type Output = Self; - - #[inline] - fn mul(self, mut rhs: f32) -> Self::Output { - // Emulation of JS side's 'setTimeout' behavior which will be instantly - // resolved if call it with negative number. - if rhs < 0.0 { - rhs = 0.0; - }; - Self(self.0.mul_f64(rhs.into())) - } -} - -/// Wrapper around interval timer ID. -pub struct IntervalHandle(pub i32); - -impl Drop for IntervalHandle { - /// Clears interval with provided ID. - fn drop(&mut self) { - window().clear_interval_with_handle(self.0); - } -} - /// Upgrades provided [`Weak`] reference, mapping it to a [`Result`] with /// [`HandlerDetachedError`] and invokes [`Into::into`] on the error. -/// If the errot type cannot be inferred, then you can provide a concrete type +/// If the error type cannot be inferred, then you can provide a concrete type /// (usually being [`JasonError`] or [`JsValue`]). /// /// [`Weak`]: std::rc::Weak @@ -119,8 +42,8 @@ macro_rules! upgrade_or_detached { /// This macro has two syntaxes: /// - `new_js_error!(DetachedStateError)` - converts provided error wrapped into /// [`JasonError`] with [`Into::into`] automatically; -/// - `new_js_error!(DetachedStateError => JsError)` - annotates explicitly -/// which type conversion is required. +/// - `new_js_error!(DetachedStateError => platform::Error)` - annotates +/// explicitly which type conversion is required. macro_rules! new_js_error { ($e:expr) => { $crate::utils::JasonError::from(tracerr::new!($e)).into() @@ -130,38 +53,6 @@ macro_rules! new_js_error { }; } -/// Returns property of JS object by name if its defined. -/// Converts the value with a given predicate. -pub fn get_property_by_name( - value: &T, - name: &str, - into: F, -) -> Option -where - T: AsRef, - F: Fn(wasm_bindgen::JsValue) -> Option, -{ - Reflect::get(value.as_ref(), &JsValue::from_str(name)) - .ok() - .map_or_else(|| None, into) -} - -/// [`Future`] which resolves after the provided [`JsDuration`]. -/// -/// [`Future`]: std::future::Future -pub async fn delay_for(delay_ms: JsDuration) { - JsFuture::from(Promise::new(&mut |yes, _| { - window() - .set_timeout_with_callback_and_timeout_and_arguments_0( - &yes, - delay_ms.into_js_duration(), - ) - .unwrap(); - })) - .await - .unwrap(); -} - /// Wrapper around [`AbortHandle`] which aborts [`Future`] on [`Drop`]. /// /// [`Future`]: std::future::Future diff --git a/jason/src/utils/resettable_delay.rs b/jason/src/utils/resettable_delay.rs index 3721af2c0..a78ab244b 100644 --- a/jason/src/utils/resettable_delay.rs +++ b/jason/src/utils/resettable_delay.rs @@ -7,9 +7,8 @@ use futures::{ future, future::{AbortHandle, FutureExt}, }; -use wasm_bindgen_futures::spawn_local; -use crate::utils::delay_for; +use crate::platform; type FutureResolver = Rc>>>; @@ -84,12 +83,12 @@ impl ResettableDelayHandle { let future_resolver = self.future_resolver.clone(); let timeout = self.timeout; let (fut, abort) = future::abortable(async move { - delay_for(timeout.into()).await; - if let Some(rslvr) = future_resolver.borrow_mut().take() { - let _ = rslvr.send(()); + platform::delay_for(timeout).await; + if let Some(rsvr) = future_resolver.borrow_mut().take() { + let _ = rsvr.send(()); } }); - spawn_local(fut.map(|_| ())); + platform::spawn(fut.map(|_| ())); self.abort_handle.replace(abort); } diff --git a/jason/tests/media/constraints.rs b/jason/tests/media/constraints.rs index 2df7b2fe8..dd645cdd9 100644 --- a/jason/tests/media/constraints.rs +++ b/jason/tests/media/constraints.rs @@ -3,19 +3,18 @@ use medea_client_api_proto::{MediaSourceKind, VideoSettings}; use medea_jason::{ media::{ - AudioTrackConstraints, DeviceVideoTrackConstraints, MediaKind, - MediaManager, MediaStreamSettings, MultiSourceTracksConstraints, - VideoSource, + AudioTrackConstraints, DeviceVideoTrackConstraints, + DisplayVideoTrackConstraints, MediaKind, MediaManager, + MediaStreamSettings, MultiSourceTracksConstraints, VideoSource, }, - utils::{get_property_by_name, window}, - DisplayVideoTrackConstraints, + platform::get_property_by_name, }; use wasm_bindgen::JsValue; use wasm_bindgen_futures::JsFuture; use wasm_bindgen_test::*; use web_sys::{MediaDeviceInfo, MediaDeviceKind}; -use crate::is_firefox; +use crate::{is_firefox, window}; wasm_bindgen_test_configure!(run_in_browser); @@ -41,7 +40,7 @@ async fn video_constraints_satisfies() { let track = tracks.pop().unwrap().0; assert_eq!(track.kind(), MediaKind::Video); - assert!(track_constraints.satisfies(track.sys_track())); + assert!(track_constraints.satisfies(track.as_ref())); } // 1. Get device id of non default audio device from enumerate_devices(); @@ -66,7 +65,7 @@ async fn audio_constraints_satisfies() { let track = tracks.pop().unwrap().0; assert_eq!(track.kind(), MediaKind::Audio); - assert!(track_constraints.satisfies(track.sys_track())); + assert!(track_constraints.satisfies(track.as_ref())); } // 1. Get device id of non default video device from enumerate_devices(); @@ -115,10 +114,10 @@ async fn both_constraints_satisfies() { let video_track = video.pop().unwrap().0; assert_eq!(audio_track.kind(), MediaKind::Audio); - assert!(audio_constraints.satisfies(audio_track.sys_track())); + assert!(audio_constraints.satisfies(audio_track.as_ref())); assert_eq!(video_track.kind(), MediaKind::Video); - assert!(video_constraints.satisfies(video_track.sys_track())); + assert!(video_constraints.satisfies(video_track.as_ref())); } // 1. Get device id of non default audio and video device from @@ -224,12 +223,18 @@ async fn multi_source_media_stream_constraints_build2() { match constraints { Some(MultiSourceTracksConstraints::Device(constraints)) => { - let has_video = - get_property_by_name(&constraints, "video", js_val_to_option) - .is_some(); - let has_audio = - get_property_by_name(&constraints, "audio", js_val_to_option) - .is_some(); + let has_video = get_property_by_name( + constraints.as_ref(), + "video", + js_val_to_option, + ) + .is_some(); + let has_audio = get_property_by_name( + constraints.as_ref(), + "audio", + js_val_to_option, + ) + .is_some(); assert!(!has_video); assert!(has_audio); @@ -250,12 +255,18 @@ async fn multi_source_media_stream_constraints_build3() { match constraints { Some(MultiSourceTracksConstraints::Device(constraints)) => { - let has_video = - get_property_by_name(&constraints, "video", js_val_to_option) - .is_some(); - let has_audio = - get_property_by_name(&constraints, "audio", js_val_to_option) - .is_some(); + let has_video = get_property_by_name( + constraints.as_ref(), + "video", + js_val_to_option, + ) + .is_some(); + let has_audio = get_property_by_name( + constraints.as_ref(), + "audio", + js_val_to_option, + ) + .is_some(); assert!(has_video); assert!(has_audio); @@ -279,18 +290,30 @@ async fn multi_source_media_stream_constraints_build4() { device, display, )) => { - let device_has_video = - get_property_by_name(&device, "video", js_val_to_option) - .is_some(); - let device_has_audio = - get_property_by_name(&device, "audio", js_val_to_option) - .is_some(); - let display_has_video = - get_property_by_name(&display, "video", js_val_to_option) - .is_some(); - let display_has_audio = - get_property_by_name(&display, "audio", js_val_to_option) - .is_some(); + let device_has_video = get_property_by_name( + device.as_ref(), + "video", + js_val_to_option, + ) + .is_some(); + let device_has_audio = get_property_by_name( + &device.as_ref(), + "audio", + js_val_to_option, + ) + .is_some(); + let display_has_video = get_property_by_name( + display.as_ref(), + "video", + js_val_to_option, + ) + .is_some(); + let display_has_audio = get_property_by_name( + display.as_ref(), + "audio", + js_val_to_option, + ) + .is_some(); assert!(!device_has_video); assert!(device_has_audio); @@ -312,12 +335,18 @@ async fn multi_source_media_stream_constraints_build5() { match constraints { Some(MultiSourceTracksConstraints::Device(constraints)) => { - let has_video = - get_property_by_name(&constraints, "video", js_val_to_option) - .is_some(); - let has_audio = - get_property_by_name(&constraints, "audio", js_val_to_option) - .is_some(); + let has_video = get_property_by_name( + constraints.as_ref(), + "video", + js_val_to_option, + ) + .is_some(); + let has_audio = get_property_by_name( + constraints.as_ref(), + "audio", + js_val_to_option, + ) + .is_some(); assert!(has_video); assert!(!has_audio); @@ -337,12 +366,18 @@ async fn multi_source_media_stream_constraints_build6() { match constraints { Some(MultiSourceTracksConstraints::Display(constraints)) => { - let has_video = - get_property_by_name(&constraints, "video", js_val_to_option) - .is_some(); - let has_audio = - get_property_by_name(&constraints, "audio", js_val_to_option) - .is_some(); + let has_video = get_property_by_name( + constraints.as_ref(), + "video", + js_val_to_option, + ) + .is_some(); + let has_audio = get_property_by_name( + constraints.as_ref(), + "audio", + js_val_to_option, + ) + .is_some(); assert!(has_video); assert!(!has_audio); @@ -373,12 +408,18 @@ async fn multi_source_media_stream_constraints_build7() { match constraints { Some(MultiSourceTracksConstraints::Device(constraints)) => { - let has_video = - get_property_by_name(&constraints, "video", js_val_to_option) - .is_some(); - let has_audio = - get_property_by_name(&constraints, "audio", js_val_to_option) - .is_some(); + let has_video = get_property_by_name( + constraints.as_ref(), + "video", + js_val_to_option, + ) + .is_some(); + let has_audio = get_property_by_name( + constraints.as_ref(), + "audio", + js_val_to_option, + ) + .is_some(); assert!(has_video); assert!(has_audio); @@ -398,12 +439,18 @@ async fn multi_source_media_stream_constraints_build8() { match constraints { Some(MultiSourceTracksConstraints::Device(constraints)) => { - let has_video = - get_property_by_name(&constraints, "video", js_val_to_option) - .is_some(); - let has_audio = - get_property_by_name(&constraints, "audio", js_val_to_option) - .is_some(); + let has_video = get_property_by_name( + constraints.as_ref(), + "video", + js_val_to_option, + ) + .is_some(); + let has_audio = get_property_by_name( + constraints.as_ref(), + "audio", + js_val_to_option, + ) + .is_some(); assert!(has_video); assert!(!has_audio); @@ -530,13 +577,11 @@ async fn simultaneous_device_and_display() { let audio_track = audio.pop().unwrap().0; assert_eq!(audio_track.kind(), MediaKind::Audio); - assert!(audio_constraints.satisfies(audio_track.sys_track())); + assert!(audio_constraints.satisfies(audio_track.as_ref())); let display_video_track = video.pop().unwrap().0; assert_eq!(display_video_track.kind(), MediaKind::Video); - assert!( - display_video_constraints.satisfies(display_video_track.sys_track()) - ); + assert!(display_video_constraints.satisfies(display_video_track.as_ref())); assert_eq!( display_video_track.media_source_kind(), MediaSourceKind::Display @@ -544,7 +589,7 @@ async fn simultaneous_device_and_display() { let device_video_track = video.pop().unwrap().0; assert_eq!(device_video_track.kind(), MediaKind::Video); - assert!(device_video_constraints.satisfies(device_video_track.sys_track())); + assert!(device_video_constraints.satisfies(device_video_track.as_ref())); assert_eq!( device_video_track.media_source_kind(), MediaSourceKind::Device diff --git a/jason/tests/media/manager.rs b/jason/tests/media/manager.rs index b0da7d9eb..c2c8817a5 100644 --- a/jason/tests/media/manager.rs +++ b/jason/tests/media/manager.rs @@ -8,9 +8,12 @@ use wasm_bindgen_test::*; use web_sys as sys; use medea_jason::{ - media::{MediaKind, MediaManager, MediaManagerError}, - AudioTrackConstraints, DeviceVideoTrackConstraints, - DisplayVideoTrackConstraints, MediaStreamSettings, + api, + media::{ + AudioTrackConstraints, DeviceVideoTrackConstraints, + DisplayVideoTrackConstraints, MediaKind, MediaManager, + MediaManagerError, MediaStreamSettings, + }, }; use crate::{get_jason_error, is_firefox, MockNavigator}; @@ -20,10 +23,12 @@ wasm_bindgen_test_configure!(run_in_browser); #[wasm_bindgen_test] async fn get_media_devices_info() { let media_manager = MediaManager::default(); - let devices = - JsFuture::from(media_manager.new_handle().enumerate_devices()) - .await - .unwrap(); + let devices = JsFuture::from( + api::MediaManagerHandle::from(media_manager.new_handle()) + .enumerate_devices(), + ) + .await + .unwrap(); let devices = JsArray::from(&devices); assert!(devices.length() >= 2); @@ -35,8 +40,11 @@ async fn failed_get_media_devices_info() { mock_navigator .error_enumerate_devices("failed_get_media_devices_info".into()); let media_manager = MediaManager::default(); - let result = - JsFuture::from(media_manager.new_handle().enumerate_devices()).await; + let result = JsFuture::from( + api::MediaManagerHandle::from(media_manager.new_handle()) + .enumerate_devices(), + ) + .await; mock_navigator.stop(); match result { Ok(_) => assert!(false), @@ -58,13 +66,14 @@ async fn failed_get_user_media() { mock_navigator.error_get_user_media("failed_get_user_media".into()); let media_manager = MediaManager::default(); let constraints = { - let mut constraints = MediaStreamSettings::new(); - constraints.audio(AudioTrackConstraints::new()); - constraints.device_video(DeviceVideoTrackConstraints::new()); + let mut constraints = api::MediaStreamSettings::new(); + constraints.audio(api::AudioTrackConstraints::new()); + constraints.device_video(api::DeviceVideoTrackConstraints::new()); constraints }; let result = JsFuture::from( - media_manager.new_handle().init_local_tracks(&constraints), + api::MediaManagerHandle::from(media_manager.new_handle()) + .init_local_tracks(&constraints), ) .await; mock_navigator.stop(); @@ -92,13 +101,14 @@ async fn failed_get_user_media2() { mock_navigator.error_get_user_media(error.into()); let media_manager = MediaManager::default(); let constraints = { - let mut constraints = MediaStreamSettings::new(); - constraints.audio(AudioTrackConstraints::new()); - constraints.device_video(DeviceVideoTrackConstraints::new()); + let mut constraints = api::MediaStreamSettings::new(); + constraints.audio(api::AudioTrackConstraints::new()); + constraints.device_video(api::DeviceVideoTrackConstraints::new()); constraints }; let result = JsFuture::from( - media_manager.new_handle().init_local_tracks(&constraints), + api::MediaManagerHandle::from(media_manager.new_handle()) + .init_local_tracks(&constraints), ) .await; mock_navigator.stop(); @@ -313,7 +323,7 @@ async fn new_tracks_should_be_live() { let mut constraints = MediaStreamSettings::new(); constraints.audio(AudioTrackConstraints::new()); - let track = Clone::clone( + let track: web_sys::MediaStreamTrack = Clone::clone( media_manager .get_tracks(constraints.clone()) .await @@ -321,7 +331,9 @@ async fn new_tracks_should_be_live() { .pop() .unwrap() .0 - .sys_track(), + .as_ref() + .as_ref() + .as_ref(), ); let ended_track = track.clone(); ended_track.stop(); diff --git a/jason/tests/media/mod.rs b/jason/tests/media/mod.rs index 84cb0b066..1e040a0a0 100644 --- a/jason/tests/media/mod.rs +++ b/jason/tests/media/mod.rs @@ -10,10 +10,8 @@ use medea_client_api_proto::{ }; use medea_jason::{ media::{MediaManager, RecvConstraints}, - peer::{ - LocalStreamUpdateCriteria, MediaConnections, RtcPeerConnection, - SimpleTracksRequest, TransceiverDirection, - }, + peer::{LocalStreamUpdateCriteria, MediaConnections, SimpleTracksRequest}, + platform::{RtcPeerConnection, TransceiverDirection}, }; use wasm_bindgen_test::*; diff --git a/jason/tests/media/track.rs b/jason/tests/media/track.rs index 7918cb1b6..0df7487d5 100644 --- a/jason/tests/media/track.rs +++ b/jason/tests/media/track.rs @@ -3,12 +3,12 @@ use std::rc::{Rc, Weak}; use futures::channel::oneshot; -use medea_jason::{ - media::MediaManager, DeviceVideoTrackConstraints, MediaStreamSettings, +use medea_jason::media::{ + track::remote, DeviceVideoTrackConstraints, MediaManager, + MediaStreamSettings, }; use wasm_bindgen::closure::Closure; use wasm_bindgen_test::*; -use web_sys::MediaStreamTrackState; use crate::{get_audio_track, timeout}; @@ -24,33 +24,34 @@ async fn track_autostop() { assert_eq!(1, tracks.len()); let (strong_track, strong_track_is_new) = tracks.pop().unwrap(); assert!(strong_track_is_new); - let sys_track = Clone::clone(strong_track.sys_track()); + let sys_track = Clone::clone(strong_track.as_ref().as_ref().as_ref()); let weak_track = Rc::downgrade(&strong_track); - assert!(sys_track.ready_state() == MediaStreamTrackState::Live); + assert!(sys_track.ready_state() == web_sys::MediaStreamTrackState::Live); drop(strong_track); - assert!(sys_track.ready_state() == MediaStreamTrackState::Ended); + assert!(sys_track.ready_state() == web_sys::MediaStreamTrackState::Ended); assert_eq!(Weak::strong_count(&weak_track), 0); } #[wasm_bindgen_test] async fn on_track_enabled_works() { - let track = get_audio_track().await; + let api_track = get_audio_track().await; + let core_track: remote::Track = api_track.clone().into(); - let track_clone = track.clone(); + let core_track_clone = core_track.clone(); let (test_tx, test_rx) = oneshot::channel(); - track.on_enabled( + api_track.on_enabled( Closure::once_into_js(move || { - assert!(track_clone.js_enabled()); + assert!(core_track_clone.enabled()); test_tx.send(()).unwrap(); }) .into(), ); - track.set_enabled(false); - assert!(!track.js_enabled()); - track.set_enabled(true); - assert!(track.js_enabled()); + core_track.set_enabled(false); + assert!(!api_track.enabled()); + core_track.set_enabled(true); + assert!(api_track.enabled()); timeout(100, test_rx).await.unwrap().unwrap(); } @@ -63,12 +64,13 @@ async fn on_track_disabled_works() { let (test_tx, test_rx) = oneshot::channel(); track.on_disabled( Closure::once_into_js(move || { - assert!(!track_clone.js_enabled()); + assert!(!track_clone.enabled()); test_tx.send(()).unwrap(); }) .into(), ); + let track = remote::Track::from(track); track.set_enabled(false); timeout(100, test_rx).await.unwrap().unwrap(); diff --git a/jason/tests/peer/media/mod.rs b/jason/tests/peer/media/mod.rs index 26ff8a7fc..1311f2495 100644 --- a/jason/tests/peer/media/mod.rs +++ b/jason/tests/peer/media/mod.rs @@ -10,8 +10,9 @@ use medea_jason::{ media::{LocalTracksConstraints, MediaManager, RecvConstraints}, peer::{ media_exchange_state, LocalStreamUpdateCriteria, MediaConnections, - MediaStateControllable, RtcPeerConnection, SimpleTracksRequest, + MediaStateControllable, SimpleTracksRequest, }, + platform::RtcPeerConnection, utils::Updatable as _, }; use wasm_bindgen_test::*; diff --git a/jason/tests/peer/mod.rs b/jason/tests/peer/mod.rs index 4b5c4ef68..b660f95d5 100644 --- a/jason/tests/peer/mod.rs +++ b/jason/tests/peer/mod.rs @@ -20,12 +20,13 @@ use medea_client_api_proto::{ VideoSettings, }; use medea_jason::{ - api::Connections, + connection::Connections, media::{LocalTracksConstraints, MediaKind, MediaManager, RecvConstraints}, peer::{ self, media_exchange_state, MediaStateControllable, PeerEvent, - RtcStats, TrackDirection, + TrackDirection, }, + platform::RtcStats, utils::Updatable, }; use wasm_bindgen_test::*; diff --git a/jason/tests/api/connection.rs b/jason/tests/room/connection.rs similarity index 64% rename from jason/tests/api/connection.rs rename to jason/tests/room/connection.rs index f759cd876..f00b41614 100644 --- a/jason/tests/api/connection.rs +++ b/jason/tests/room/connection.rs @@ -5,10 +5,7 @@ use futures::{ StreamExt, }; use medea_client_api_proto::PeerId; -use medea_jason::{ - api::{ConnectionHandle, Connections}, - media::{track::remote, MediaKind}, -}; +use medea_jason::{api, connection::Connections, platform}; use wasm_bindgen::{closure::Closure, JsValue}; use wasm_bindgen_test::*; @@ -22,13 +19,15 @@ wasm_bindgen_test_configure!(run_in_browser); async fn on_new_connection_fires() { let cons = Connections::default(); - let (cb, test_result) = js_callback!(|handle: ConnectionHandle| { + let (cb, test_result) = js_callback!(|handle: api::ConnectionHandle| { cb_assert_eq!( handle.get_remote_member_id().unwrap(), "bob".to_string() ); }); - cons.on_new_connection(cb.into()); + cons.on_new_connection(platform::Function::from(js_sys::Function::from( + cb, + ))); cons.create_connection(PeerId(1), &"bob".into()); @@ -43,13 +42,16 @@ async fn on_remote_track_added_fires() { let con = cons.get(&"bob".into()).unwrap(); let con_handle = con.new_handle(); - - let (cb, test_result) = js_callback!(|track: remote::Track| { - cb_assert_eq!(track.kind(), MediaKind::Video); + let (cb, test_result) = js_callback!(|track: api::RemoteMediaTrack| { + cb_assert_eq!(track.kind(), api::MediaKind::Video); }); - con_handle.on_remote_track_added(cb.into()).unwrap(); + con_handle + .on_remote_track_added(platform::Function::from( + js_sys::Function::from(cb), + )) + .unwrap(); - con.add_remote_track(get_video_track().await); + con.add_remote_track(get_video_track().await.into()); wait_and_check_test_result(test_result, || {}).await; } @@ -61,26 +63,26 @@ async fn tracks_are_added_to_connection() { cons.create_connection(PeerId(1), &"bob".into()); let con = cons.get(&"bob".into()).unwrap(); - let con_handle = con.new_handle(); + let con_handle = api::ConnectionHandle::from(con.new_handle()); let (tx, rx) = oneshot::channel(); - let closure = Closure::once_into_js(move |track: remote::Track| { + let closure = Closure::once_into_js(move |track: api::RemoteMediaTrack| { assert!(tx.send(track).is_ok()); }); con_handle.on_remote_track_added(closure.into()).unwrap(); - con.add_remote_track(get_video_track().await); + con.add_remote_track(get_video_track().await.into()); let video_track = timeout(100, rx).await.unwrap().unwrap(); - assert_eq!(video_track.kind(), MediaKind::Video); + assert_eq!(video_track.kind(), api::MediaKind::Video); let (tx, rx) = oneshot::channel(); - let closure = Closure::once_into_js(move |track: remote::Track| { + let closure = Closure::once_into_js(move |track: api::RemoteMediaTrack| { assert!(tx.send(track).is_ok()); }); con_handle.on_remote_track_added(closure.into()).unwrap(); - con.add_remote_track(get_audio_track().await); + con.add_remote_track(get_audio_track().await.into()); let audio_track = timeout(200, rx).await.unwrap().unwrap(); - assert_eq!(audio_track.kind(), MediaKind::Audio); + assert_eq!(audio_track.kind(), api::MediaKind::Audio); } #[wasm_bindgen_test] @@ -93,7 +95,9 @@ async fn on_closed_fires() { let (on_close, test_result) = js_callback!(|nothing: JsValue| { cb_assert_eq!(nothing.is_undefined(), true); }); - con_handle.on_close(on_close.into()).unwrap(); + con_handle + .on_close(platform::Function::from(js_sys::Function::from(on_close))) + .unwrap(); cons.close_connection(PeerId(1)); @@ -106,10 +110,13 @@ async fn two_peers_in_one_connection_works() { let (test_tx, mut test_rx) = mpsc::unbounded(); let on_new_connection = - Closure::wrap(Box::new(move |_: ConnectionHandle| { + Closure::wrap(Box::new(move |_: api::ConnectionHandle| { test_tx.unbounded_send(()).unwrap(); - }) as Box); - cons.on_new_connection(on_new_connection.as_ref().clone().into()); + }) as Box) + .into_js_value(); + cons.on_new_connection(platform::Function::from(js_sys::Function::from( + on_new_connection, + ))); cons.create_connection(PeerId(1), &"bob".into()); test_rx.next().await.unwrap(); @@ -124,10 +131,13 @@ async fn create_two_connections() { let (test_tx, mut test_rx) = mpsc::unbounded(); let on_new_connection = - Closure::wrap(Box::new(move |_: ConnectionHandle| { + Closure::wrap(Box::new(move |_: api::ConnectionHandle| { test_tx.unbounded_send(()).unwrap(); - }) as Box); - cons.on_new_connection(on_new_connection.as_ref().clone().into()); + }) as Box) + .into_js_value(); + cons.on_new_connection(platform::Function::from(js_sys::Function::from( + on_new_connection, + ))); cons.create_connection(PeerId(1), &"bob".into()); test_rx.next().await.unwrap(); diff --git a/jason/tests/api/mod.rs b/jason/tests/room/mod.rs similarity index 95% rename from jason/tests/api/mod.rs rename to jason/tests/room/mod.rs index 12942f799..002bb9fff 100644 --- a/jason/tests/api/mod.rs +++ b/jason/tests/room/mod.rs @@ -11,11 +11,10 @@ use medea_client_api_proto::{ ClientMsg, CloseReason, Command, Event, ServerMsg, }; use medea_jason::{ - rpc::{ - websocket::{MockRpcTransport, TransportState}, - CloseMsg, RpcTransport, WebSocketRpcClient, - }, - Jason, + api, + jason::Jason, + platform::{MockRpcTransport, RpcTransport, TransportState}, + rpc::{CloseMsg, WebSocketRpcClient}, }; use medea_reactive::ObservableCell; use wasm_bindgen::closure::Closure; @@ -54,14 +53,16 @@ async fn only_one_strong_rpc_rc_exists() { Ok(transport as Rc) }) }))); - let jason = Jason::with_rpc_client(ws.clone()); + let jason = api::Jason::from(Jason::with_rpc_client(ws.clone())); let room = jason.init_room(); room.on_failed_local_media(Closure::once_into_js(|| {}).into()) .unwrap(); room.on_connection_loss(Closure::once_into_js(|| {}).into()) .unwrap(); - room.inner_join(TEST_ROOM_URL.to_string()).await.unwrap(); + JsFuture::from(room.join(TEST_ROOM_URL.to_string())) + .await + .unwrap(); assert_eq!(Rc::strong_count(&ws), 3); jason.dispose(); @@ -102,7 +103,7 @@ async fn rpc_dropped_on_jason_dispose() { Ok(transport as Rc) }) }))); - let jason = Jason::with_rpc_client(ws); + let jason = api::Jason::from(Jason::with_rpc_client(ws)); let room = jason.init_room(); room.on_failed_local_media(Closure::once_into_js(|| {}).into()) @@ -157,7 +158,7 @@ async fn room_dispose_works() { }) }) })); - let jason = Jason::with_rpc_client(ws); + let jason = api::Jason::from(Jason::with_rpc_client(ws)); let room = jason.init_room(); room.on_failed_local_media(Closure::once_into_js(|| {}).into()) @@ -290,9 +291,9 @@ async fn room_closes_on_rpc_transport_close() { }) } }))); - let jason = Jason::with_rpc_client(ws); + let jason = api::Jason::from(Jason::with_rpc_client(ws)); - let mut room = jason.init_room(); + let room = jason.init_room(); room.on_failed_local_media(Closure::once_into_js(|| {}).into()) .unwrap(); room.on_connection_loss(Closure::once_into_js(|| {}).into()) diff --git a/jason/tests/api/room.rs b/jason/tests/room/room.rs similarity index 89% rename from jason/tests/api/room.rs rename to jason/tests/room/room.rs index 9a3481d85..4e1c1db78 100644 --- a/jason/tests/api/room.rs +++ b/jason/tests/room/room.rs @@ -1,9 +1,6 @@ #![cfg(target_arch = "wasm32")] -use std::{ - collections::{HashMap, HashSet}, - rc::Rc, -}; +use std::{collections::HashMap, rc::Rc}; use futures::{ channel::{ @@ -14,18 +11,14 @@ use futures::{ stream::{self, BoxStream, LocalBoxStream, StreamExt as _}, }; use medea_client_api_proto::{ - state, AudioSettings, Command, Direction, Event, IceConnectionState, + self as proto, Command, Direction, Event, IceConnectionState, MediaSourceKind, MediaType, MemberId, NegotiationRole, PeerId, PeerMetrics, Track, TrackId, TrackPatchCommand, TrackPatchEvent, TrackUpdate, VideoSettings, }; use medea_jason::{ - api::Room, - media::{AudioTrackConstraints, MediaKind, MediaStreamSettings}, - peer::PeerConnection, - rpc::MockRpcSession, - utils::{AsProtoState, JasonError, Updatable}, - DeviceVideoTrackConstraints, + api, media::MediaKind, peer::PeerConnection, room::Room, + rpc::MockRpcSession, utils::Updatable, }; use wasm_bindgen_futures::{spawn_local, JsFuture}; use wasm_bindgen_test::*; @@ -63,7 +56,7 @@ fn get_test_room( async fn get_test_room_and_exist_peer( tracks: Vec, - media_stream_settings: Option, + media_stream_settings: Option, ) -> ( Room, Rc, @@ -107,11 +100,13 @@ async fn get_test_room_and_exist_peer( let room = Room::new(Rc::new(rpc), Rc::default()); if let Some(media_stream_settings) = &media_stream_settings { - JsFuture::from(room.new_handle().set_local_media_settings( - &media_stream_settings, - false, - false, - )) + JsFuture::from( + api::RoomHandle::from(room.new_handle()).set_local_media_settings( + &media_stream_settings, + false, + false, + ), + ) .await .unwrap(); } @@ -135,7 +130,7 @@ async fn get_test_room_and_exist_peer( async fn error_get_local_stream_on_new_peer() { let (event_tx, event_rx) = mpsc::unbounded(); let (room, _) = get_test_room(Box::pin(event_rx)); - let room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); JsFuture::from(room_handle.set_local_media_settings( &media_stream_settings(true, true), false, @@ -144,7 +139,7 @@ async fn error_get_local_stream_on_new_peer() { .await .unwrap(); - let (cb, test_result) = js_callback!(|err: JasonError| { + let (cb, test_result) = js_callback!(|err: api::JasonError| { cb_assert_eq!(&err.name(), "MediaManager"); cb_assert_eq!( &err.message(), @@ -184,23 +179,24 @@ async fn error_get_local_stream_on_new_peer() { #[wasm_bindgen_test] async fn error_join_room_without_on_failed_stream_callback() { let (room, _) = get_test_room(stream::pending().boxed()); - let room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); room_handle .on_connection_loss(js_sys::Function::new_no_args("")) .unwrap(); - match room_handle.inner_join(String::from(TEST_ROOM_URL)).await { - Ok(_) => unreachable!(), - Err(e) => { - assert_eq!(e.name(), "CallbackNotSet"); - assert_eq!( - e.message(), - "`Room.on_failed_local_media()` callback isn't set.", - ); - assert!(!e.trace().is_empty()); - } - } + let err = get_jason_error( + JsFuture::from(room_handle.join(String::from(TEST_ROOM_URL))) + .await + .unwrap_err(), + ); + + assert_eq!(err.name(), "CallbackNotSet"); + assert_eq!( + err.message(), + "`Room.on_failed_local_media()` callback isn't set.", + ); + assert!(!err.trace().is_empty()); } /// Tests `Room::join` if `on_connection_loss` callback was not set. @@ -213,23 +209,24 @@ async fn error_join_room_without_on_failed_stream_callback() { #[wasm_bindgen_test] async fn error_join_room_without_on_connection_loss_callback() { let (room, _) = get_test_room(stream::pending().boxed()); - let room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); room_handle .on_failed_local_media(js_sys::Function::new_no_args("")) .unwrap(); - match room_handle.inner_join(String::from(TEST_ROOM_URL)).await { - Ok(_) => unreachable!(), - Err(e) => { - assert_eq!(e.name(), "CallbackNotSet"); - assert_eq!( - e.message(), - "`Room.on_connection_loss()` callback isn't set.", - ); - assert!(!e.trace().is_empty()); - } - } + let err = get_jason_error( + JsFuture::from(room_handle.join(String::from(TEST_ROOM_URL))) + .await + .unwrap_err(), + ); + + assert_eq!(err.name(), "CallbackNotSet"); + assert_eq!( + err.message(), + "`Room.on_connection_loss()` callback isn't set.", + ); + assert!(!err.trace().is_empty()); } mod disable_recv_tracks { @@ -244,7 +241,7 @@ mod disable_recv_tracks { async fn check_transceivers_statuses() { let (event_tx, event_rx) = mpsc::unbounded(); let (room, mut commands_rx) = get_test_room(Box::pin(event_rx)); - let room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); JsFuture::from(room_handle.disable_remote_audio()) .await @@ -324,11 +321,11 @@ mod disable_recv_tracks { /// Tests disabling tracks publishing. mod disable_send_tracks { use medea_client_api_proto::{ - AudioSettings, Direction, MediaSourceKind, MediaType, MemberId, - TrackPatchCommand, VideoSettings, + AudioSettings, Direction, MediaType, MemberId, TrackPatchCommand, + VideoSettings, }; use medea_jason::{ - media::{JsMediaSourceKind, MediaKind}, + media::MediaKind, peer::{media_exchange_state, TrackDirection}, }; @@ -345,11 +342,11 @@ mod disable_send_tracks { ) .await; - let handle = room.new_handle(); - assert!(JsFuture::from(handle.disable_audio()).await.is_ok()); + let room_handle = api::RoomHandle::from(room.new_handle()); + assert!(JsFuture::from(room_handle.disable_audio()).await.is_ok()); assert!(!peer.is_send_audio_enabled()); - assert!(JsFuture::from(handle.enable_audio()).await.is_ok()); + assert!(JsFuture::from(room_handle.enable_audio()).await.is_ok()); assert!(peer.is_send_audio_enabled()); } @@ -362,11 +359,13 @@ mod disable_send_tracks { ) .await; - let handle = room.new_handle(); - assert!(JsFuture::from(handle.disable_video(None)).await.is_ok()); + let room_handle = api::RoomHandle::from(room.new_handle()); + assert!(JsFuture::from(room_handle.disable_video(None)) + .await + .is_ok()); assert!(!peer.is_send_video_enabled(None)); - assert!(JsFuture::from(handle.enable_video(None)).await.is_ok()); + assert!(JsFuture::from(room_handle.enable_video(None)).await.is_ok()); assert!(peer.is_send_video_enabled(None)); } @@ -420,16 +419,20 @@ mod disable_send_tracks { return; } - let handle = room.new_handle(); - JsFuture::from(handle.disable_video(Some(JsMediaSourceKind::Device))) - .await - .unwrap(); + let room_handle = api::RoomHandle::from(room.new_handle()); + JsFuture::from( + room_handle.disable_video(Some(api::MediaSourceKind::Device)), + ) + .await + .unwrap(); assert!(!peer.is_send_video_enabled(Some(MediaSourceKind::Device))); assert!(peer.is_send_video_enabled(Some(MediaSourceKind::Display))); - JsFuture::from(handle.enable_video(Some(JsMediaSourceKind::Device))) - .await - .unwrap(); + JsFuture::from( + room_handle.enable_video(Some(api::MediaSourceKind::Device)), + ) + .await + .unwrap(); assert!(peer.is_send_video_enabled(Some(MediaSourceKind::Device))); assert!(peer.is_send_video_enabled(Some(MediaSourceKind::Display))); } @@ -455,18 +458,20 @@ mod disable_send_tracks { return; } - let handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); assert!(JsFuture::from( - handle.disable_video(Some(JsMediaSourceKind::Display)) + room_handle.disable_video(Some(api::MediaSourceKind::Display)) ) .await .is_ok()); assert!(!peer.is_send_video_enabled(Some(MediaSourceKind::Display))); assert!(peer.is_send_video_enabled(Some(MediaSourceKind::Device))); - JsFuture::from(handle.enable_video(Some(JsMediaSourceKind::Display))) - .await - .unwrap(); + JsFuture::from( + room_handle.enable_video(Some(api::MediaSourceKind::Display)), + ) + .await + .unwrap(); assert!(peer.is_send_video_enabled(Some(MediaSourceKind::Display))); assert!(peer.is_send_video_enabled(Some(MediaSourceKind::Device))); } @@ -491,10 +496,10 @@ mod disable_send_tracks { ) .await; - let handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); let (first, second) = futures::future::join( - JsFuture::from(handle.disable_audio()), - JsFuture::from(handle.disable_audio()), + JsFuture::from(room_handle.disable_audio()), + JsFuture::from(room_handle.disable_audio()), ) .await; first.unwrap(); @@ -528,10 +533,10 @@ mod disable_send_tracks { ) .await; - let handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); let (first, second) = futures::future::join( - JsFuture::from(handle.disable_video(None)), - JsFuture::from(handle.disable_video(None)), + JsFuture::from(room_handle.disable_video(None)), + JsFuture::from(room_handle.disable_video(None)), ) .await; first.unwrap(); @@ -574,11 +579,11 @@ mod disable_send_tracks { media_exchange_state::Stable::Enabled.into() )); - let handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); let (disable_audio_result, enable_audio_result) = futures::future::join( - JsFuture::from(handle.disable_audio()), - JsFuture::from(handle.enable_audio()), + JsFuture::from(room_handle.disable_audio()), + JsFuture::from(room_handle.enable_audio()), ) .await; disable_audio_result.unwrap_err(); @@ -621,11 +626,11 @@ mod disable_send_tracks { media_exchange_state::Stable::Enabled.into() )); - let handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); let (disable_video_result, enable_video_result) = futures::future::join( - JsFuture::from(handle.disable_video(None)), - JsFuture::from(handle.enable_video(None)), + JsFuture::from(room_handle.disable_video(None)), + JsFuture::from(room_handle.enable_video(None)), ) .await; disable_video_result.unwrap_err(); @@ -670,8 +675,8 @@ mod disable_send_tracks { media_exchange_state::Stable::Enabled.into() )); - let handle = room.new_handle(); - JsFuture::from(handle.disable_audio()).await.unwrap(); + let room_handle = api::RoomHandle::from(room.new_handle()); + JsFuture::from(room_handle.disable_audio()).await.unwrap(); assert!(peer.is_all_transceiver_sides_in_media_state( MediaKind::Audio, @@ -682,8 +687,8 @@ mod disable_send_tracks { let (disable_audio_result, enable_audio_result) = futures::future::join( - JsFuture::from(handle.disable_audio()), - JsFuture::from(handle.enable_audio()), + JsFuture::from(room_handle.disable_audio()), + JsFuture::from(room_handle.enable_audio()), ) .await; disable_audio_result.unwrap(); @@ -701,7 +706,9 @@ mod disable_send_tracks { async fn disable_audio_room_before_init_peer() { let (event_tx, event_rx) = mpsc::unbounded(); let (room, mut commands_rx) = get_test_room(Box::pin(event_rx)); - JsFuture::from(room.new_handle().set_local_media_settings( + let room_handle = api::RoomHandle::from(room.new_handle()); + + JsFuture::from(room_handle.set_local_media_settings( &media_stream_settings(true, true), false, false, @@ -709,9 +716,7 @@ mod disable_send_tracks { .await .unwrap(); - JsFuture::from(room.new_handle().disable_audio()) - .await - .unwrap(); + JsFuture::from(room_handle.disable_audio()).await.unwrap(); let (audio_track, video_track) = get_test_tracks(false, false); event_tx @@ -781,7 +786,9 @@ mod disable_send_tracks { async fn mute_audio_room_before_init_peer() { let (event_tx, event_rx) = mpsc::unbounded(); let (room, mut commands_rx) = get_test_room(Box::pin(event_rx)); - JsFuture::from(room.new_handle().set_local_media_settings( + let room_handle = api::RoomHandle::from(room.new_handle()); + + JsFuture::from(room_handle.set_local_media_settings( &media_stream_settings(true, true), false, false, @@ -789,9 +796,7 @@ mod disable_send_tracks { .await .unwrap(); - JsFuture::from(room.new_handle().mute_audio()) - .await - .unwrap(); + JsFuture::from(room_handle.mute_audio()).await.unwrap(); let (audio_track, video_track) = get_test_tracks(false, false); event_tx @@ -862,7 +867,9 @@ mod disable_send_tracks { async fn disable_video_room_before_init_peer() { let (event_tx, event_rx) = mpsc::unbounded(); let (room, mut commands_rx) = get_test_room(Box::pin(event_rx)); - JsFuture::from(room.new_handle().set_local_media_settings( + let room_handle = api::RoomHandle::from(room.new_handle()); + + JsFuture::from(room_handle.set_local_media_settings( &media_stream_settings(true, true), false, false, @@ -870,7 +877,7 @@ mod disable_send_tracks { .await .unwrap(); - JsFuture::from(room.new_handle().disable_video(None)) + JsFuture::from(room_handle.disable_video(None)) .await .unwrap(); @@ -977,7 +984,7 @@ mod on_close_callback { #[wasm_bindgen_test] async fn closed_by_server() { let (room, _) = get_test_room(stream::pending().boxed()); - let mut room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); let (cb, test_result) = js_callback!(|closed: JsValue| { cb_assert_eq!(get_reason(&closed), "Finished"); @@ -1005,7 +1012,7 @@ mod on_close_callback { #[wasm_bindgen_test] async fn unexpected_room_drop() { let (room, _) = get_test_room(stream::pending().boxed()); - let mut room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); let (cb, test_result) = js_callback!(|closed: JsValue| { cb_assert_eq!(get_reason(&closed), "RoomUnexpectedlyDropped"); @@ -1030,7 +1037,7 @@ mod on_close_callback { #[wasm_bindgen_test] async fn normal_close_by_client() { let (room, _) = get_test_room(stream::pending().boxed()); - let mut room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); let (cb, test_result) = js_callback!(|closed: JsValue| { cb_assert_eq!(get_reason(&closed), "RoomUnexpectedlyDropped"); @@ -1137,13 +1144,10 @@ mod rpc_close_reason_on_room_drop { /// Tests for [`TrackPatch`] generation in [`Room`]. mod patches_generation { use medea_client_api_proto::{ - AudioSettings, Direction, MediaSourceKind, MediaType, Track, TrackId, - TrackPatchCommand, VideoSettings, - }; - use medea_jason::{ - peer::{media_exchange_state, mute_state, MediaState}, - JsMediaSourceKind, + AudioSettings, Direction, MediaType, Track, TrackId, TrackPatchCommand, + VideoSettings, }; + use medea_jason::peer::{media_exchange_state, mute_state, MediaState}; use wasm_bindgen_futures::spawn_local; use crate::{is_firefox, timeout}; @@ -1162,7 +1166,7 @@ mod patches_generation { ( MediaType::Video(VideoSettings { required: false, - source_kind: MediaSourceKind::Device, + source_kind: proto::MediaSourceKind::Device, }), Direction::Send { receivers: Vec::new(), @@ -1286,7 +1290,7 @@ mod patches_generation { audio_and_device_video_tracks_content(), ) .await; - let room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); spawn_local(async move { JsFuture::from(room_handle.disable_audio()) @@ -1328,7 +1332,7 @@ mod patches_generation { audio_and_device_video_tracks_content(), ) .await; - let room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); spawn_local(async move { JsFuture::from(room_handle.disable_audio()) @@ -1389,7 +1393,7 @@ mod patches_generation { audio_and_device_video_tracks_content(), ) .await; - let room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); spawn_local(async move { JsFuture::from(room_handle.enable_audio()).await.unwrap(); @@ -1424,7 +1428,7 @@ mod patches_generation { audio_and_device_video_tracks_content(), ) .await; - let room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); spawn_local(async move { JsFuture::from(room_handle.disable_audio()) @@ -1458,7 +1462,7 @@ mod patches_generation { let mut tracks = audio_and_device_video_tracks_content(); tracks.push(( MediaType::Video(VideoSettings { - source_kind: MediaSourceKind::Display, + source_kind: proto::MediaSourceKind::Display, required: false, }), Direction::Send { @@ -1473,11 +1477,11 @@ mod patches_generation { ) .await; - let room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); spawn_local(async move { JsFuture::from( - room_handle.disable_video(Some(JsMediaSourceKind::Device)), + room_handle.disable_video(Some(api::MediaSourceKind::Device)), ) .await .unwrap_err(); @@ -1514,7 +1518,7 @@ mod patches_generation { let mut tracks = audio_and_device_video_tracks_content(); tracks.push(( MediaType::Video(VideoSettings { - source_kind: MediaSourceKind::Display, + source_kind: proto::MediaSourceKind::Display, required: false, }), Direction::Send { @@ -1529,11 +1533,11 @@ mod patches_generation { ) .await; - let room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); spawn_local(async move { JsFuture::from( - room_handle.disable_video(Some(JsMediaSourceKind::Display)), + room_handle.disable_video(Some(api::MediaSourceKind::Display)), ) .await .unwrap_err(); @@ -1566,7 +1570,7 @@ mod patches_generation { audio_and_device_video_tracks_content(), ) .await; - let room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); spawn_local(async move { JsFuture::from(room_handle.mute_audio()).await.unwrap_err(); @@ -1594,7 +1598,7 @@ mod patches_generation { audio_and_device_video_tracks_content(), ) .await; - let room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); spawn_local(async move { JsFuture::from(room_handle.unmute_audio()) @@ -1626,10 +1630,10 @@ async fn mute_unmute_audio() { ) .await; - let handle = room.new_handle(); - assert!(JsFuture::from(handle.mute_audio()).await.is_ok()); + let room_handle = api::RoomHandle::from(room.new_handle()); + assert!(JsFuture::from(room_handle.mute_audio()).await.is_ok()); assert!(!peer.is_send_audio_unmuted()); - assert!(JsFuture::from(handle.unmute_audio()).await.is_ok()); + assert!(JsFuture::from(room_handle.unmute_audio()).await.is_ok()); assert!(peer.is_send_audio_unmuted()); } @@ -1643,10 +1647,14 @@ async fn remote_disable_enable_audio() { ) .await; - let handle = room.new_handle(); - assert!(JsFuture::from(handle.disable_remote_audio()).await.is_ok()); + let room_handle = api::RoomHandle::from(room.new_handle()); + assert!(JsFuture::from(room_handle.disable_remote_audio()) + .await + .is_ok()); assert!(!peer.is_recv_audio_enabled()); - assert!(JsFuture::from(handle.enable_remote_audio()).await.is_ok()); + assert!(JsFuture::from(room_handle.enable_remote_audio()) + .await + .is_ok()); assert!(peer.is_recv_audio_enabled()); } @@ -1660,10 +1668,14 @@ async fn remote_disable_enable_video() { ) .await; - let handle = room.new_handle(); - assert!(JsFuture::from(handle.disable_remote_video()).await.is_ok()); + let room_handle = api::RoomHandle::from(room.new_handle()); + assert!(JsFuture::from(room_handle.disable_remote_video()) + .await + .is_ok()); assert!(!peer.is_recv_video_enabled()); - assert!(JsFuture::from(handle.enable_remote_video()).await.is_ok()); + assert!(JsFuture::from(room_handle.enable_remote_video()) + .await + .is_ok()); assert!(peer.is_recv_video_enabled()); } @@ -1761,7 +1773,7 @@ async fn only_one_gum_performed_on_enable() { Some(media_stream_settings(true, true)), ) .await; - let room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); assert_eq!(mock.get_user_media_requests_count(), 1); event_tx @@ -1812,7 +1824,7 @@ async fn no_updates_sent_if_gum_fails_on_enable() { Some(media_stream_settings(true, false)), ) .await; - let room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); assert_eq!(mock.get_user_media_requests_count(), 1); assert!(peer.is_send_audio_enabled()); @@ -1863,7 +1875,7 @@ async fn set_media_state_return_media_error() { Some(media_stream_settings(false, false)), ) .await; - let room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); JsFuture::from(room_handle.disable_audio()).await.unwrap(); mock.error_get_user_media(ERROR_MSG.into()); @@ -1959,7 +1971,8 @@ async fn send_enabling_holds_local_tracks() { }); let room = Room::new(Rc::new(rpc), Rc::default()); - JsFuture::from(room.new_handle().set_local_media_settings( + let room_handle = api::RoomHandle::from(room.new_handle()); + JsFuture::from(room_handle.set_local_media_settings( &media_stream_settings(true, true), false, false, @@ -1995,7 +2008,7 @@ async fn send_enabling_holds_local_tracks() { let mock = MockNavigator::new(); mock.error_get_user_media("foobar".into()); let err = get_jason_error( - JsFuture::from(room.new_handle().enable_video(None)) + JsFuture::from(room_handle.enable_video(None)) .await .unwrap_err(), ); @@ -2144,9 +2157,9 @@ mod set_local_media_settings { /// Returns [`MediaStreamSettings`] which requires that device ID should be /// `foobar`. - fn media_settings_with_device_id() -> MediaStreamSettings { - let mut settings = MediaStreamSettings::new(); - let mut device_video = DeviceVideoTrackConstraints::new(); + fn media_settings_with_device_id() -> api::MediaStreamSettings { + let mut settings = api::MediaStreamSettings::new(); + let mut device_video = api::DeviceVideoTrackConstraints::new(); device_video.device_id("foobar".to_string()); settings.device_video(device_video); @@ -2165,9 +2178,9 @@ mod set_local_media_settings { async fn error_inject_invalid_local_stream_into_new_peer() { let (event_tx, event_rx) = mpsc::unbounded(); let (room, _rx) = get_test_room(Box::pin(event_rx)); - let room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); - let (cb, test_result) = js_callback!(|err: JasonError| { + let (cb, test_result) = js_callback!(|err: api::JasonError| { cb_assert_eq!(&err.name(), "MediaConnections"); cb_assert_eq!( err.message(), @@ -2179,8 +2192,8 @@ mod set_local_media_settings { let (audio_track, video_track) = get_test_required_tracks(); - let mut constraints = MediaStreamSettings::new(); - constraints.audio(AudioTrackConstraints::new()); + let mut constraints = api::MediaStreamSettings::new(); + constraints.audio(api::AudioTrackConstraints::new()); JsFuture::from(room_handle.set_local_media_settings( &constraints, @@ -2213,7 +2226,7 @@ mod set_local_media_settings { /// 1. `on_failed_local_media` was invoked. #[wasm_bindgen_test] async fn error_inject_invalid_local_stream_into_room_on_exists_peer() { - let (cb, test_result) = js_callback!(|err: JasonError| { + let (cb, test_result) = js_callback!(|err: api::JasonError| { cb_assert_eq!(&err.name(), "TracksRequest"); cb_assert_eq!( &err.message(), @@ -2225,9 +2238,10 @@ mod set_local_media_settings { get_test_room_and_exist_peer(vec![audio_track, video_track], None) .await; - let mut constraints = MediaStreamSettings::new(); - constraints.audio(AudioTrackConstraints::new()); - let room_handle = room.new_handle(); + let mut constraints = api::MediaStreamSettings::new(); + constraints.audio(api::AudioTrackConstraints::new()); + + let room_handle = api::RoomHandle::from(room.new_handle()); room_handle.on_failed_local_media(cb.into()).unwrap(); let err = get_constraints_update_exception( JsFuture::from(room_handle.set_local_media_settings( @@ -2238,7 +2252,7 @@ mod set_local_media_settings { .await .unwrap_err(), ); - let err = get_jason_error(err.error()); + let err = err.error().unwrap(); assert_eq!(err.name(), "InvalidLocalTracks"); assert_eq!( err.message(), @@ -2270,15 +2284,16 @@ mod set_local_media_settings { ) .await; - let mut constraints = MediaStreamSettings::new(); + let mut constraints = api::MediaStreamSettings::new(); if add_audio { - constraints.audio(AudioTrackConstraints::new()); + constraints.audio(api::AudioTrackConstraints::new()); } if add_video { - constraints.device_video(DeviceVideoTrackConstraints::new()); + constraints + .device_video(api::DeviceVideoTrackConstraints::new()); } - let room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); room_handle.on_failed_local_media(closure.into()).unwrap(); let is_should_be_ok = @@ -2323,7 +2338,7 @@ mod set_local_media_settings { async fn set_local_media_stream_settings_updates_media_exchange_state() { let (event_tx, event_rx) = mpsc::unbounded(); let (room, commands_rx) = get_test_room(Box::pin(event_rx)); - let room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); room_handle .on_failed_local_media(js_sys::Function::new_no_args("")) .unwrap(); @@ -2378,11 +2393,11 @@ mod set_local_media_settings { #[wasm_bindgen_test] async fn disables_on_fail_if_no_rollback() { let (room, peer1, _peer2) = room_with_connected_peers().await; - + let room_handle = api::RoomHandle::from(room.new_handle()); let mock_navigator = MockNavigator::new(); mock_navigator.error_get_user_media("disables_on_fail".into()); let err = get_constraints_update_exception( - JsFuture::from(room.new_handle().set_local_media_settings( + JsFuture::from(room_handle.set_local_media_settings( &media_settings_with_device_id(), true, false, @@ -2393,7 +2408,7 @@ mod set_local_media_settings { mock_navigator.stop(); assert_eq!(&err.name(), "RecoveredException"); - let err = get_jason_error(err.recover_reason()); + let err = err.recover_reason().unwrap(); assert_eq!(err.name(), "CouldNotGetLocalMedia"); assert_eq!( @@ -2411,8 +2426,9 @@ mod set_local_media_settings { #[wasm_bindgen_test] async fn rollbacks_on_fail() { let (room, peer1, _peer2) = room_with_connected_peers().await; + let room_handle = api::RoomHandle::from(room.new_handle()); - JsFuture::from(room.new_handle().set_local_media_settings( + JsFuture::from(room_handle.set_local_media_settings( &media_stream_settings(true, true), false, false, @@ -2422,7 +2438,7 @@ mod set_local_media_settings { let mock_navigator = MockNavigator::new(); let err = get_constraints_update_exception( - JsFuture::from(room.new_handle().set_local_media_settings( + JsFuture::from(room_handle.set_local_media_settings( &media_settings_with_device_id(), true, true, @@ -2433,7 +2449,7 @@ mod set_local_media_settings { mock_navigator.stop(); assert_eq!(err.name(), "RecoveredException"); - let recover_reason = get_jason_error(err.recover_reason()); + let recover_reason = err.recover_reason().unwrap(); assert_eq!(recover_reason.name(), "CouldNotGetLocalMedia"); assert_eq!(mock_navigator.get_user_media_requests_count(), 2); @@ -2447,8 +2463,9 @@ mod set_local_media_settings { #[wasm_bindgen_test] async fn disables_on_rollback_fail() { let (room, peer1, _peer2) = room_with_connected_peers().await; + let room_handle = api::RoomHandle::from(room.new_handle()); - JsFuture::from(room.new_handle().set_local_media_settings( + JsFuture::from(room_handle.set_local_media_settings( &media_stream_settings(true, true), false, false, @@ -2459,7 +2476,7 @@ mod set_local_media_settings { let mock_navigator = MockNavigator::new(); mock_navigator.error_get_user_media("disables_on_rollback_fail".into()); let err = get_constraints_update_exception( - JsFuture::from(room.new_handle().set_local_media_settings( + JsFuture::from(room_handle.set_local_media_settings( &media_settings_with_device_id(), true, true, @@ -2470,7 +2487,7 @@ mod set_local_media_settings { mock_navigator.stop(); assert_eq!(err.name(), "RecoverFailedException"); - let recover_reason = get_jason_error(err.recover_reason()); + let recover_reason = err.recover_reason().unwrap(); assert_eq!(recover_reason.name(), "CouldNotGetLocalMedia"); assert_eq!( recover_reason.message(), @@ -2480,7 +2497,7 @@ mod set_local_media_settings { let recover_fail_reasons = js_sys::Array::from(&err.recover_fail_reasons()); assert_eq!(recover_fail_reasons.length(), 1); - let recover_fail_reason = get_jason_error(recover_fail_reasons.pop()); + let recover_fail_reason = err.recover_reason().unwrap(); assert_eq!( recover_fail_reason.message(), "Failed to get local tracks: MediaDevices.getUserMedia() failed: \ @@ -2497,8 +2514,9 @@ mod set_local_media_settings { #[wasm_bindgen_test] async fn doesnt_disables_if_not_stop_first() { let (room, peer1, _peer2) = room_with_connected_peers().await; + let room_handle = api::RoomHandle::from(room.new_handle()); - JsFuture::from(room.new_handle().set_local_media_settings( + JsFuture::from(room_handle.set_local_media_settings( &media_stream_settings(true, true), false, false, @@ -2511,7 +2529,7 @@ mod set_local_media_settings { .error_get_user_media("doesnt_disables_if_not_stop_first".into()); let err = get_constraints_update_exception( - JsFuture::from(room.new_handle().set_local_media_settings( + JsFuture::from(room_handle.set_local_media_settings( &media_settings_with_device_id(), false, true, @@ -2522,7 +2540,7 @@ mod set_local_media_settings { mock_navigator.stop(); assert_eq!(err.name(), "RecoveredException"); - let err = get_jason_error(err.recover_reason()); + let err = err.recover_reason().unwrap(); assert_eq!(err.name(), "CouldNotGetLocalMedia"); assert!(peer1.is_send_video_enabled(Some(MediaSourceKind::Device))); @@ -2531,7 +2549,23 @@ mod set_local_media_settings { } mod state_synchronization { - use super::*; + use std::{ + collections::{HashMap, HashSet}, + rc::Rc, + }; + + use futures::{channel::mpsc, stream, StreamExt as _}; + use medea_client_api_proto::{ + state, AudioSettings, Command, Event, MediaType, NegotiationRole, + PeerId, TrackId, + }; + use medea_jason::{ + media::MediaManager, room::Room, rpc::MockRpcSession, + utils::AsProtoState, + }; + use wasm_bindgen_test::*; + + use crate::timeout; /// Checks whether [`state::Room`] update can create a [`PeerConnection`] /// and its [`Sender`]s/[`Receiver`]s. @@ -2554,10 +2588,8 @@ mod state_synchronization { rpc_session.expect_send_command().returning(move |cmd| { let _ = command_tx.unbounded_send(cmd); }); - let room = Room::new( - Rc::new(rpc_session), - Rc::new(medea_jason::media::MediaManager::default()), - ); + let room = + Room::new(Rc::new(rpc_session), Rc::new(MediaManager::default())); let mut senders = HashMap::new(); senders.insert( @@ -2620,11 +2652,13 @@ mod state_synchronization { async fn intentions_are_sent_on_reconnect() { let (event_tx, event_rx) = mpsc::unbounded(); let (room, mut commands_rx) = get_test_room(Box::pin(event_rx)); - JsFuture::from(room.new_handle().set_local_media_settings( - &media_stream_settings(true, true), - false, - false, - )) + JsFuture::from( + api::RoomHandle::from(room.new_handle()).set_local_media_settings( + &media_stream_settings(true, true), + false, + false, + ), + ) .await .unwrap(); @@ -2646,7 +2680,7 @@ async fn intentions_are_sent_on_reconnect() { } } - let room_handle = room.new_handle(); + let room_handle = api::RoomHandle::from(room.new_handle()); let peer_state = room.get_peer_state_by_id(PeerId(1)).unwrap(); peer_state.connection_lost(); diff --git a/jason/tests/rpc/backoff_delayer.rs b/jason/tests/rpc/backoff_delayer.rs index 1cec9e3d7..f37d9d81d 100644 --- a/jason/tests/rpc/backoff_delayer.rs +++ b/jason/tests/rpc/backoff_delayer.rs @@ -10,8 +10,6 @@ use crate::timeout; wasm_bindgen_test_configure!(run_in_browser); /// Tests that `delay` multiplies by provided `multiplier`. -/// -/// Also this test checks that [`JsDuration`] correctly multiplies by [`f32`]. #[wasm_bindgen_test] async fn multiplier_works() { let mut delayer = BackoffDelayer::new( @@ -37,9 +35,8 @@ async fn max_delay_works() { timeout(103, delayer.delay()).await.unwrap(); } -/// Tests that multiplication of [`JsDuration`] by negative `multiplier` -/// will be calculated as `0` (this is default JS behavior which is recreated in -/// [`JsDuration`] implementation). +/// Tests that multiplication by negative `multiplier` will be calculated as +/// `0`. #[wasm_bindgen_test] async fn negative_multiplier() { let mut delayer = BackoffDelayer::new( diff --git a/jason/tests/rpc/heartbeat.rs b/jason/tests/rpc/heartbeat.rs index 02e1ee133..9e2519c02 100644 --- a/jason/tests/rpc/heartbeat.rs +++ b/jason/tests/rpc/heartbeat.rs @@ -7,9 +7,9 @@ use futures::{ stream, StreamExt, }; use medea_client_api_proto::{ClientMsg, ServerMsg}; -use medea_jason::rpc::{ - websocket::MockRpcTransport, Heartbeat, IdleTimeout, PingInterval, - RpcTransport, +use medea_jason::{ + platform::{MockRpcTransport, RpcTransport}, + rpc::{Heartbeat, IdleTimeout, PingInterval}, }; use wasm_bindgen_test::*; diff --git a/jason/tests/rpc/mod.rs b/jason/tests/rpc/mod.rs index e4ff247ca..f29262c18 100644 --- a/jason/tests/rpc/mod.rs +++ b/jason/tests/rpc/mod.rs @@ -17,9 +17,9 @@ use futures::{ use medea_client_api_proto::{ ClientMsg, CloseReason, Command, Event, PeerId, RpcSettings, ServerMsg, }; -use medea_jason::rpc::{ - websocket::{MockRpcTransport, RpcEvent, TransportState}, - ClientDisconnect, CloseMsg, RpcTransport, WebSocketRpcClient, +use medea_jason::{ + platform::{MockRpcTransport, RpcTransport, TransportState}, + rpc::{ClientDisconnect, CloseMsg, RpcEvent, WebSocketRpcClient}, }; use wasm_bindgen_futures::spawn_local; use wasm_bindgen_test::*; diff --git a/jason/tests/rpc/rpc_session.rs b/jason/tests/rpc/rpc_session.rs index ed6e67669..c8c03dcfd 100644 --- a/jason/tests/rpc/rpc_session.rs +++ b/jason/tests/rpc/rpc_session.rs @@ -11,10 +11,14 @@ use futures::{future, stream, FutureExt as _, StreamExt as _}; use medea_client_api_proto::{ ClientMsg, CloseReason, Command, Event, ServerMsg, }; -use medea_jason::rpc::{ - websocket::{MockRpcTransport, TransportState}, - CloseMsg, ConnectionInfo, RpcSession, RpcTransport, SessionError, - WebSocketRpcClient, WebSocketRpcSession, WebSocketRpcTransport, +use medea_jason::{ + platform::{ + MockRpcTransport, RpcTransport, TransportState, WebSocketRpcTransport, + }, + rpc::{ + CloseMsg, ConnectionInfo, RpcSession, SessionError, WebSocketRpcClient, + WebSocketRpcSession, + }, }; use wasm_bindgen_test::*; diff --git a/jason/tests/rpc/websocket.rs b/jason/tests/rpc/websocket.rs index 6e39d54e0..3fb1ef985 100644 --- a/jason/tests/rpc/websocket.rs +++ b/jason/tests/rpc/websocket.rs @@ -1,6 +1,6 @@ #![cfg(target_arch = "wasm32")] -use medea_jason::rpc::{TransportError, WebSocketRpcTransport}; +use medea_jason::platform::{TransportError, WebSocketRpcTransport}; use url::Url; use wasm_bindgen_test::*; diff --git a/jason/tests/web.rs b/jason/tests/web.rs index a83e0a4f2..cb038d2df 100644 --- a/jason/tests/web.rs +++ b/jason/tests/web.rs @@ -74,9 +74,9 @@ macro_rules! js_callback { }} } -mod api; mod media; mod peer; +mod room; mod rpc; mod utils; @@ -87,13 +87,13 @@ use medea_client_api_proto::{ TrackId, VideoSettings, }; use medea_jason::{ - api::ConstraintsUpdateException, - media::{track::remote, LocalTracksConstraints, MediaKind, MediaManager}, + api, + media::{ + track::remote, AudioTrackConstraints, DeviceVideoTrackConstraints, + LocalTracksConstraints, MediaKind, MediaManager, MediaStreamSettings, + }, peer::media_exchange_state, rpc::ApiUrl, - utils::{window, JasonError}, - AudioTrackConstraints, DeviceVideoTrackConstraints, - DisplayVideoTrackConstraints, MediaStreamSettings, }; use url::Url; use wasm_bindgen::prelude::*; @@ -139,7 +139,7 @@ extern "C" { #[wasm_bindgen(inline_js = "export const get_jason_error = (err) => err;")] extern "C" { - fn get_jason_error(err: JsValue) -> JasonError; + fn get_jason_error(err: JsValue) -> api::JasonError; } #[wasm_bindgen( @@ -148,7 +148,7 @@ extern "C" { extern "C" { fn get_constraints_update_exception( err: JsValue, - ) -> ConstraintsUpdateException; + ) -> api::ConstraintsUpdateException; } pub fn get_test_required_tracks() -> (Track, Track) { @@ -257,14 +257,14 @@ pub async fn delay_for(delay_ms: i32) { fn media_stream_settings( is_audio_enabled: bool, is_video_enabled: bool, -) -> MediaStreamSettings { - let mut settings = MediaStreamSettings::new(); +) -> api::MediaStreamSettings { + let mut settings = api::MediaStreamSettings::new(); if is_audio_enabled { - settings.audio(AudioTrackConstraints::default()); + settings.audio(api::AudioTrackConstraints::new()); } if is_video_enabled { - settings.device_video(DeviceVideoTrackConstraints::default()); - settings.display_video(DisplayVideoTrackConstraints::default()); + settings.device_video(api::DeviceVideoTrackConstraints::new()); + settings.display_video(api::DisplayVideoTrackConstraints::new()); } settings @@ -275,8 +275,9 @@ fn local_constraints( is_video_enabled: bool, ) -> LocalTracksConstraints { let constraints = LocalTracksConstraints::default(); - constraints - .constrain(media_stream_settings(is_audio_enabled, is_video_enabled)); + constraints.constrain( + media_stream_settings(is_audio_enabled, is_video_enabled).into(), + ); constraints } @@ -303,26 +304,26 @@ async fn wait_and_check_test_result( }; } -async fn get_video_track() -> remote::Track { +async fn get_video_track() -> api::RemoteMediaTrack { let manager = MediaManager::default(); let mut settings = MediaStreamSettings::new(); settings.device_video(DeviceVideoTrackConstraints::new()); - let stream = manager.get_tracks(settings).await.unwrap(); - let track = Clone::clone(stream.into_iter().next().unwrap().0.sys_track()); - remote::Track::new(track, MediaSourceKind::Device) + let mut tracks = manager.get_tracks(settings).await.unwrap(); + let track = Clone::clone(tracks.pop().unwrap().0.as_ref().as_ref()); + remote::Track::new(track, MediaSourceKind::Device).into() } -async fn get_audio_track() -> remote::Track { +async fn get_audio_track() -> api::RemoteMediaTrack { let manager = MediaManager::default(); let mut settings = MediaStreamSettings::new(); settings.audio(AudioTrackConstraints::new()); - let stream = manager.get_tracks(settings).await.unwrap(); - let track = Clone::clone(stream.into_iter().next().unwrap().0.sys_track()); - remote::Track::new(track, MediaSourceKind::Device) + let mut tracks = manager.get_tracks(settings).await.unwrap(); + let track = Clone::clone(tracks.pop().unwrap().0.as_ref().as_ref()); + remote::Track::new(track, MediaSourceKind::Device).into() } /// Awaits provided [`LocalBoxFuture`] for `timeout` milliseconds. If within -/// provided `timeout` time this [`LocalBoxFuture`] won'tbe resolved, then +/// provided `timeout` time this [`LocalBoxFuture`] won't be resolved, then /// `Err(String)` will be returned, otherwise a result of the provided /// [`LocalBoxFuture`] will be returned. async fn timeout(timeout_ms: i32, future: T) -> Result @@ -345,6 +346,17 @@ pub async fn yield_now() { delay_for(0).await; } +/// Returns [`Window`] object. +/// +/// # Panics +/// +/// When global [`Window`] object is inaccessible. +pub fn window() -> web_sys::Window { + // Cannot use `lazy_static` since `window` is `!Sync`. + // Safe to unwrap. + web_sys::window().unwrap() +} + // TODO: Might be extended to proc macro at some point. #[wasm_bindgen(inline_js = "export function is_firefox() { return \ navigator.userAgent.toLowerCase().indexOf('\