Merge pull request #3 from livekit/theo/prototype
Initial client-sdk-native
@@ -1,8 +1,3 @@
|
||||
target
|
||||
cmake-build-debug
|
||||
.idea/
|
||||
.cargo/
|
||||
test/
|
||||
libwebrtc/
|
||||
libwebrtc_android/
|
||||
CMakeLists.txt
|
||||
/target
|
||||
/.idea
|
||||
/libwebrtc
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "crates/livekit-core/protocol"]
|
||||
path = crates/livekit-core/protocol
|
||||
url = https://github.com/livekit/protocol
|
||||
@@ -1,20 +1,13 @@
|
||||
[package]
|
||||
name = "livekit-native"
|
||||
name = "livekit"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
homepage = "https://livekit.io"
|
||||
crate-type = ["lib"]
|
||||
repository = "https://github.com/livekit/client-sdk-native"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cxx = "1.0"
|
||||
|
||||
[build-dependencies]
|
||||
cxx-build = "1.0"
|
||||
glob = "0.3.0"
|
||||
regex = "1.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib", "cdylib"]
|
||||
bench = false
|
||||
[workspace]
|
||||
exclude = ["libwebrtc"]
|
||||
members = [
|
||||
"crates/livekit-core",
|
||||
"crates/livekit-webrtc",
|
||||
"crates/livekit-webrtc/libwebrtc-sys"
|
||||
]
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
@@ -0,0 +1,935 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"humantime",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2acedae88d38235936c3922476b10fced7b2b68136f5e3c03c2d5be348a1115"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0db9cce532b0eae2ccf2766ab246f114b56b9cf6d445e00c2549fbc100ca045d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca0bae1fe9752cf7fd9b0064c674ae63f97b37bc714d745cbde0afb7ec4e6765"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "842fc63b931f4056a24d59de13fb1272134ce261816e063e634ad0c15cdc5306"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0828a5471e340229c11c77ca80017937ce3c58cb788a17e5f1c2d5c485a9577"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
|
||||
|
||||
[[package]]
|
||||
name = "livekit"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"env_logger",
|
||||
"futures-util",
|
||||
"log",
|
||||
"prost",
|
||||
"prost-build",
|
||||
"prost-types",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multimap"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-build"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae5a4388762d5815a9fc0dea33c56b021cdc8dde0c55e0c9ca57197254b0cab"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cfg-if",
|
||||
"cmake",
|
||||
"heck",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"multimap",
|
||||
"petgraph",
|
||||
"prost",
|
||||
"prost-types",
|
||||
"regex",
|
||||
"tempfile",
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-derive"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-types"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.17.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"tokio",
|
||||
"tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.17.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand",
|
||||
"sha-1",
|
||||
"thiserror",
|
||||
"url",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae"
|
||||
dependencies = [
|
||||
"either",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
|
||||
dependencies = [
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
||||
@@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "livekit-core"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
log = "0.4"
|
||||
tokio-tungstenite = { version = "0.17.2", features = ["native-tls"] }
|
||||
tokio = { version = "1.20.1", features = ["full"] }
|
||||
url = "2.2.2"
|
||||
futures-util = "0.3.23"
|
||||
thiserror = "1.0"
|
||||
prost = "0.11.0"
|
||||
prost-types = "0.11.1"
|
||||
anyhow = "1.0.63"
|
||||
livekit-webrtc = { path = "../livekit-webrtc" }
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
[build-dependencies]
|
||||
prost-build = { version = "0.10" }
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.9"
|
||||
@@ -0,0 +1,12 @@
|
||||
use std::io::Result;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
prost_build::compile_protos(
|
||||
&[
|
||||
"protocol/livekit_rtc.proto",
|
||||
"protocol/livekit_models.proto",
|
||||
],
|
||||
&["protocol/"],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
pub mod proto {
|
||||
include!(concat!(env!("OUT_DIR"), "/livekit.rs"));
|
||||
}
|
||||
|
||||
mod lk_runtime;
|
||||
mod pc_transport;
|
||||
mod rtc_engine;
|
||||
mod signal_client;
|
||||
|
||||
pub mod room;
|
||||
@@ -0,0 +1,25 @@
|
||||
use log::trace;
|
||||
|
||||
use livekit_webrtc::peer_connection_factory::PeerConnectionFactory;
|
||||
use livekit_webrtc::webrtc::RTCRuntime;
|
||||
|
||||
pub struct LKRuntime {
|
||||
pub rtc_runtime: RTCRuntime,
|
||||
pub pc_factory: PeerConnectionFactory,
|
||||
}
|
||||
|
||||
impl LKRuntime {
|
||||
pub fn new() -> Self {
|
||||
trace!("LKRuntime::new()");
|
||||
Self {
|
||||
rtc_runtime: RTCRuntime::new(),
|
||||
pc_factory: PeerConnectionFactory::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LKRuntime {
|
||||
fn drop(&mut self) {
|
||||
trace!("LKRuntime::drop()");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use log::{error, trace};
|
||||
|
||||
use livekit_webrtc::jsep::{IceCandidate, SessionDescription};
|
||||
use livekit_webrtc::peer_connection::{
|
||||
PeerConnection, RTCOfferAnswerOptions, SignalingState,
|
||||
};
|
||||
use livekit_webrtc::peer_connection_factory::RTCConfiguration;
|
||||
use livekit_webrtc::rtc_error::RTCError;
|
||||
|
||||
use crate::lk_runtime::LKRuntime;
|
||||
|
||||
const NEGOTIATION_FREQUENCY: Duration = Duration::from_millis(150); // TODO(theomonnom)
|
||||
|
||||
pub type OnOfferHandler = Box<dyn FnMut(SessionDescription) + Send>;
|
||||
|
||||
pub struct PCTransport {
|
||||
peer_connection: PeerConnection,
|
||||
pending_candidates: Vec<IceCandidate>,
|
||||
on_offer_handler: Option<OnOfferHandler>,
|
||||
restarting_ice: bool,
|
||||
renegotiate: bool,
|
||||
}
|
||||
|
||||
impl PCTransport {
|
||||
pub fn new(lk_runtime: Arc<LKRuntime>, cfg: RTCConfiguration) -> Result<Self, RTCError> {
|
||||
let peer_connection = lk_runtime.pc_factory.create_peer_connection(cfg)?;
|
||||
|
||||
Ok(Self {
|
||||
peer_connection,
|
||||
pending_candidates: Vec::default(),
|
||||
on_offer_handler: None,
|
||||
restarting_ice: false,
|
||||
renegotiate: false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn peer_connection(&mut self) -> &mut PeerConnection {
|
||||
&mut self.peer_connection
|
||||
}
|
||||
|
||||
pub fn on_offer(&mut self, handler: OnOfferHandler) {
|
||||
self.on_offer_handler = Some(handler);
|
||||
}
|
||||
|
||||
pub async fn add_ice_candidate(&mut self, ice_candidate: IceCandidate) -> Result<(), RTCError> {
|
||||
if self.peer_connection.remote_description().is_none() {
|
||||
self.pending_candidates.push(ice_candidate);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.peer_connection.add_ice_candidate(ice_candidate).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_remote_description(
|
||||
&mut self,
|
||||
remote_description: SessionDescription,
|
||||
) -> Result<(), RTCError> {
|
||||
self.peer_connection
|
||||
.set_remote_description(remote_description)
|
||||
.await?;
|
||||
|
||||
for ic in self.pending_candidates.drain(..) {
|
||||
self.peer_connection.add_ice_candidate(ic).await?;
|
||||
}
|
||||
self.restarting_ice = false;
|
||||
|
||||
if self.renegotiate {
|
||||
self.renegotiate = false;
|
||||
self.create_and_send_offer(RTCOfferAnswerOptions::default())
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn negotiate(&mut self) -> Result<(), RTCError> {
|
||||
// TODO(theomonnom) Debounce here with NEGOTIATION_FREQUENCY
|
||||
self.create_and_send_offer(RTCOfferAnswerOptions::default())
|
||||
.await
|
||||
}
|
||||
|
||||
async fn create_and_send_offer(
|
||||
&mut self,
|
||||
options: RTCOfferAnswerOptions,
|
||||
) -> Result<(), RTCError> {
|
||||
if self.on_offer_handler.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if options.ice_restart {
|
||||
trace!("restarting ICE");
|
||||
self.restarting_ice = true;
|
||||
}
|
||||
|
||||
if self.peer_connection.signaling_state() == SignalingState::HaveLocalOffer {
|
||||
if options.ice_restart {
|
||||
if let Some(remote_description) = self.peer_connection.remote_description() {
|
||||
self.peer_connection
|
||||
.set_remote_description(remote_description)
|
||||
.await?;
|
||||
} else {
|
||||
error!("trying to ice restart when the pc doesn't have remote description");
|
||||
}
|
||||
} else {
|
||||
self.renegotiate = true;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let offer = self.peer_connection.create_offer(options).await?;
|
||||
trace!("created offer {:?}", offer);
|
||||
self.peer_connection
|
||||
.set_local_description(offer.clone())
|
||||
.await?;
|
||||
self.on_offer_handler.as_mut().unwrap()(offer);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub struct Room {}
|
||||
@@ -0,0 +1,419 @@
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
use std::time::Duration;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use log::{error, trace};
|
||||
use prost::Message as ProstMessage;
|
||||
use thiserror::Error;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::sleep;
|
||||
use tokio_tungstenite::tungstenite::protocol::frame::coding::Data;
|
||||
|
||||
use livekit_webrtc::data_channel::{DataChannel, DataChannelInit};
|
||||
use livekit_webrtc::jsep::{IceCandidate, SdpParseError, SessionDescription};
|
||||
use livekit_webrtc::peer_connection::{PeerConnectionState, RTCOfferAnswerOptions};
|
||||
use livekit_webrtc::peer_connection_factory::{
|
||||
ContinualGatheringPolicy, ICEServer, IceTransportsType, RTCConfiguration,
|
||||
};
|
||||
use livekit_webrtc::rtc_error::RTCError;
|
||||
|
||||
use crate::{proto, signal_client};
|
||||
use crate::lk_runtime::LKRuntime;
|
||||
use crate::pc_transport::PCTransport;
|
||||
use crate::proto::{
|
||||
DataPacket, JoinResponse, signal_request, signal_response, SignalTarget, TrickleRequest,
|
||||
};
|
||||
use crate::signal_client::{SignalClient, SignalError};
|
||||
|
||||
const LOSSY_DC_LABEL: &str = "_lossy";
|
||||
const RELIABLE_DC_LABEL: &str = "_reliable";
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum EngineError {
|
||||
#[error("signal failure")]
|
||||
Signal(#[from] SignalError),
|
||||
#[error("internal webrtc failure")]
|
||||
Rtc(#[from] RTCError),
|
||||
#[error("failed to parse sdp")]
|
||||
Parse(#[from] SdpParseError),
|
||||
#[error("serde error")]
|
||||
Serde(#[from] serde_json::Error),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
enum PCState {
|
||||
New,
|
||||
Connected,
|
||||
Disconnected,
|
||||
Reconnecting,
|
||||
Closed,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
// Share one LKRuntime across all RTCEngine instances
|
||||
static ref LK_RUNTIME: Mutex<Weak<LKRuntime>> = Mutex::new(Weak::new());
|
||||
}
|
||||
|
||||
enum EngineMessage {}
|
||||
|
||||
struct PeerInternal {
|
||||
publisher_pc: PCTransport,
|
||||
subscriber_pc: PCTransport,
|
||||
|
||||
lossy_dc: DataChannel,
|
||||
reliable_dc: DataChannel,
|
||||
|
||||
pub_ice_rx: mpsc::Receiver<IceCandidate>,
|
||||
sub_ice_rx: mpsc::Receiver<IceCandidate>,
|
||||
|
||||
pub_offer_rx: mpsc::Receiver<SessionDescription>,
|
||||
|
||||
primary_connection_state_rx: mpsc::Receiver<PeerConnectionState>,
|
||||
secondary_connection_state_rx: mpsc::Receiver<PeerConnectionState>,
|
||||
|
||||
lossy_data_rx: mpsc::Receiver<DataPacket>,
|
||||
reliable_data_rx: mpsc::Receiver<DataPacket>,
|
||||
|
||||
sub_dc_rx: mpsc::Receiver<DataChannel>,
|
||||
|
||||
pc_state: PCState,
|
||||
}
|
||||
|
||||
struct RTCInternal {
|
||||
#[allow(unused)]
|
||||
lk_runtime: Arc<LKRuntime>,
|
||||
signal_client: Arc<SignalClient>,
|
||||
pc_internal: PeerInternal,
|
||||
}
|
||||
|
||||
impl RTCInternal {
|
||||
async fn connect(url: &str, token: &str) -> Result<Self, EngineError> {
|
||||
let mut lk_runtime = None;
|
||||
{
|
||||
// Acquire an existing/a new LKRuntime
|
||||
let mut lk_runtime_ref = LK_RUNTIME.lock().unwrap();
|
||||
lk_runtime = lk_runtime_ref.upgrade();
|
||||
|
||||
if lk_runtime.is_none() {
|
||||
let new_runtime = Arc::new(LKRuntime::new());
|
||||
*lk_runtime_ref = Arc::downgrade(&new_runtime);
|
||||
lk_runtime = Some(new_runtime);
|
||||
}
|
||||
}
|
||||
let lk_runtime = lk_runtime.unwrap();
|
||||
let signal_client = Arc::new(signal_client::connect(url, token).await?);
|
||||
|
||||
trace!("waiting join_response..");
|
||||
if let signal_response::Message::Join(join) = signal_client.recv().await? {
|
||||
trace!("configuring peer_connections: {:?}", join);
|
||||
let mut pc_internal = Self::configure(lk_runtime.clone(), join.clone())?;
|
||||
|
||||
if !join.subscriber_primary {
|
||||
pc_internal.publisher_pc.negotiate().await?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
lk_runtime,
|
||||
signal_client,
|
||||
pc_internal,
|
||||
})
|
||||
} else {
|
||||
panic!("the first received message isn't a JoinResponse");
|
||||
}
|
||||
}
|
||||
|
||||
fn request_signal(&mut self, msg: signal_request::Message) {
|
||||
tokio::spawn({
|
||||
let sc = self.signal_client.clone();
|
||||
|
||||
async move {
|
||||
if let Err(err) = sc.send(msg).await {
|
||||
error!("failed to send signal: {:?}", err);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn handle_signal(&mut self, signal: signal_response::Message) -> Result<(), EngineError> {
|
||||
match signal {
|
||||
signal_response::Message::Answer(answer) => {
|
||||
let sdp = SessionDescription::from(answer.r#type.parse().unwrap(), &answer.sdp)?;
|
||||
self.pc_internal.publisher_pc.set_remote_description(sdp).await?;
|
||||
},
|
||||
signal_response::Message::Offer(offer) => {
|
||||
let sdp = SessionDescription::from(offer.r#type.parse().unwrap(), &offer.sdp)?;
|
||||
self.pc_internal.subscriber_pc.set_remote_description(sdp).await?;
|
||||
let answer = self.pc_internal.subscriber_pc.peer_connection().create_answer(RTCOfferAnswerOptions::default()).await?;
|
||||
self.pc_internal.subscriber_pc.peer_connection().set_local_description(answer.clone()).await?;
|
||||
|
||||
self.request_signal(signal_request::Message::Answer(proto::SessionDescription {
|
||||
r#type: "answer".to_string(),
|
||||
sdp: answer.to_string(),
|
||||
}));
|
||||
},
|
||||
signal_response::Message::Trickle(trickle) => {
|
||||
let json: serde_json::Value = serde_json::from_str(&trickle.candidate_init)?;
|
||||
let ice = IceCandidate::from(
|
||||
json["sdpMid"].as_str().unwrap(),
|
||||
json["sdpMLineIndex"].as_i64().unwrap().try_into().unwrap(),
|
||||
json["candidate"].as_str().unwrap()
|
||||
)?;
|
||||
|
||||
if trickle.target == SignalTarget::Publisher as i32 {
|
||||
self.pc_internal.publisher_pc.add_ice_candidate(ice).await?;
|
||||
} else {
|
||||
self.pc_internal.subscriber_pc.add_ice_candidate(ice).await?;
|
||||
}
|
||||
}
|
||||
_ => {},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run(&mut self) {
|
||||
loop {
|
||||
tokio::select! {
|
||||
Ok(signal) = self.signal_client.recv() => {
|
||||
if let Err(err) = self.handle_signal(signal).await {
|
||||
error!("failed to handle signal: {:?}", err);
|
||||
}
|
||||
},
|
||||
Some(ice_candidate) = self.pc_internal.pub_ice_rx.recv() => {
|
||||
self.request_signal(signal_request::Message::Trickle(TrickleRequest {
|
||||
candidate_init: ice_candidate.to_string(),
|
||||
target: SignalTarget::Publisher as i32
|
||||
}));
|
||||
},
|
||||
Some(ice_candidate) = self.pc_internal.sub_ice_rx.recv() => {
|
||||
self.request_signal(signal_request::Message::Trickle(TrickleRequest {
|
||||
candidate_init: ice_candidate.to_string(),
|
||||
target: SignalTarget::Subscriber as i32
|
||||
}));
|
||||
},
|
||||
Some(sdp) = self.pc_internal.pub_offer_rx.recv() => {
|
||||
trace!("received publisher offer: {:?}", sdp);
|
||||
self.request_signal(signal_request::Message::Offer(proto::SessionDescription {
|
||||
r#type: "offer".to_string(),
|
||||
sdp: sdp.to_string(),
|
||||
}));
|
||||
},
|
||||
Some(state) = self.pc_internal.primary_connection_state_rx.recv() => {
|
||||
if state == PeerConnectionState::Connected {
|
||||
let old_state = self.pc_internal.pc_state;
|
||||
self.pc_internal.pc_state = PCState::Connected;
|
||||
|
||||
if old_state == PCState::New {
|
||||
// TODO(theomonnom) OnConnected
|
||||
}
|
||||
} else if state == PeerConnectionState::Failed {
|
||||
self.pc_internal.pc_state = PCState::Disconnected;
|
||||
// TODO(theomonnom) Handle Disconnect
|
||||
}
|
||||
},
|
||||
Some(state) = self.pc_internal.secondary_connection_state_rx.recv() => {
|
||||
if state == PeerConnectionState::Failed {
|
||||
self.pc_internal.pc_state = PCState::Disconnected;
|
||||
// TODO(theomonnom) Handle Disconnect
|
||||
}
|
||||
},
|
||||
Some(data) = self.pc_internal.lossy_data_rx.recv() => {
|
||||
|
||||
},
|
||||
Some(data) = self.pc_internal.reliable_data_rx.recv() => {
|
||||
|
||||
},
|
||||
Some(mut dc) = self.pc_internal.sub_dc_rx.recv() => {
|
||||
// Subscriber DataChannels
|
||||
// Only received when the subscriber_primary is enabled
|
||||
trace!("using subscriber data channels");
|
||||
|
||||
let (data_tx, data_rx) = mpsc::channel(8);
|
||||
Self::configure_dc(&mut dc, data_tx);
|
||||
|
||||
if dc.label() == RELIABLE_DC_LABEL {
|
||||
self.pc_internal.reliable_dc = dc;
|
||||
self.pc_internal.reliable_data_rx = data_rx;
|
||||
} else {
|
||||
self.pc_internal.lossy_dc = dc;
|
||||
self.pc_internal.lossy_data_rx = data_rx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_dc(data_channel: &mut DataChannel, data_tx: mpsc::Sender<DataPacket>) {
|
||||
let label = data_channel.label();
|
||||
data_channel.on_message(Box::new(move |data, _| {
|
||||
if let Ok(data) = DataPacket::decode(data) {
|
||||
let _ = data_tx.blocking_send(data);
|
||||
} else {
|
||||
trace!("{} - failed to decode DataPacket", label);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
fn configure(
|
||||
lk_runtime: Arc<LKRuntime>,
|
||||
join: JoinResponse,
|
||||
) -> Result<PeerInternal, EngineError> {
|
||||
let cfg = RTCConfiguration {
|
||||
ice_servers: {
|
||||
let mut servers = vec![];
|
||||
for is in join.ice_servers {
|
||||
servers.push(ICEServer {
|
||||
urls: is.urls,
|
||||
username: is.username,
|
||||
password: is.credential,
|
||||
})
|
||||
}
|
||||
servers
|
||||
},
|
||||
continual_gathering_policy: ContinualGatheringPolicy::GatherContinually,
|
||||
ice_transport_type: IceTransportsType::All,
|
||||
};
|
||||
|
||||
// Create the PeerConnections
|
||||
let mut publisher_pc = PCTransport::new(lk_runtime.clone(), cfg.clone())?;
|
||||
let mut subscriber_pc = PCTransport::new(lk_runtime, cfg)?;
|
||||
|
||||
let (pub_ice_tx, pub_ice_rx) = mpsc::channel(8);
|
||||
let (sub_ice_tx, sub_ice_rx) = mpsc::channel(8);
|
||||
let (pub_offer_tx, pub_offer_rx) = mpsc::channel(8);
|
||||
let (primary_connection_state_tx, primary_connection_state_rx) = mpsc::channel(8);
|
||||
let (secondary_connection_state_tx, secondary_connection_state_rx) = mpsc::channel(8);
|
||||
let (lossy_data_tx, lossy_data_rx) = mpsc::channel(8);
|
||||
let (reliable_data_tx, reliable_data_rx) = mpsc::channel(8);
|
||||
let (sub_dc_tx, sub_dc_rx) = mpsc::channel(8);
|
||||
|
||||
publisher_pc
|
||||
.peer_connection()
|
||||
.on_ice_candidate(Box::new(move |ice_candidate| {
|
||||
trace!("publisher - on_ice_candidate: {:?}", ice_candidate);
|
||||
let _ = pub_ice_tx.blocking_send(ice_candidate);
|
||||
}));
|
||||
|
||||
subscriber_pc
|
||||
.peer_connection()
|
||||
.on_ice_candidate(Box::new(move |ice_candidate| {
|
||||
trace!("subscriber - on_ice_candidate: {:?}", ice_candidate);
|
||||
let _ = sub_ice_tx.blocking_send(ice_candidate);
|
||||
}));
|
||||
|
||||
publisher_pc.on_offer(Box::new(move |offer| {
|
||||
trace!("publisher - on_offer: {:?}", offer);
|
||||
let _ = pub_offer_tx.blocking_send(offer); // TODO(theomonnom) Don't use blocking_send here
|
||||
}));
|
||||
|
||||
let mut primary_pc = &mut publisher_pc;
|
||||
let mut secondary_pc = &mut subscriber_pc;
|
||||
if join.subscriber_primary {
|
||||
primary_pc = &mut subscriber_pc;
|
||||
secondary_pc = &mut publisher_pc;
|
||||
|
||||
primary_pc.peer_connection().on_data_channel(Box::new(move |dc| {
|
||||
let _ = sub_dc_tx.blocking_send(dc);
|
||||
}));
|
||||
}
|
||||
|
||||
primary_pc
|
||||
.peer_connection()
|
||||
.on_connection_change(Box::new(move |state| {
|
||||
let _ = primary_connection_state_tx.blocking_send(state);
|
||||
}));
|
||||
|
||||
secondary_pc
|
||||
.peer_connection()
|
||||
.on_connection_change(Box::new(move |state| {
|
||||
let _ = secondary_connection_state_tx.blocking_send(state);
|
||||
}));
|
||||
|
||||
// Note that when subscriber_primary feature is enabled,
|
||||
// the subscriber uses his own data channels created by the server.
|
||||
let mut lossy_dc = publisher_pc.peer_connection().create_data_channel(
|
||||
LOSSY_DC_LABEL,
|
||||
DataChannelInit {
|
||||
ordered: true,
|
||||
max_retransmits: Some(0),
|
||||
..DataChannelInit::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let mut reliable_dc = publisher_pc.peer_connection().create_data_channel(
|
||||
RELIABLE_DC_LABEL,
|
||||
DataChannelInit {
|
||||
ordered: true,
|
||||
..DataChannelInit::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
Self::configure_dc(&mut lossy_dc, lossy_data_tx);
|
||||
Self::configure_dc(&mut reliable_dc, reliable_data_tx);
|
||||
|
||||
Ok(PeerInternal {
|
||||
publisher_pc,
|
||||
subscriber_pc,
|
||||
lossy_dc,
|
||||
reliable_dc,
|
||||
pub_ice_rx,
|
||||
sub_ice_rx,
|
||||
pub_offer_rx,
|
||||
primary_connection_state_rx,
|
||||
secondary_connection_state_rx,
|
||||
lossy_data_rx,
|
||||
reliable_data_rx,
|
||||
sub_dc_rx,
|
||||
pc_state: PCState::New,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RTCEngine {}
|
||||
|
||||
/// Initialize the SignalClient & the PeerConnections
|
||||
pub async fn connect(url: &str, token: &str) -> Result<RTCEngine, EngineError> {
|
||||
let mut rtc_internal = RTCInternal::connect(url, token).await?;
|
||||
tokio::spawn(async move {
|
||||
rtc_internal.run().await
|
||||
});
|
||||
|
||||
Ok(RTCEngine{})
|
||||
}
|
||||
|
||||
impl RTCEngine {
|
||||
async fn rtc_handle() {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_test() {
|
||||
env_logger::init();
|
||||
|
||||
let engine = connect("ws://localhost:7880", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NzEyMzk4NjAsImlzcyI6IkFQSXpLYkFTaUNWYWtnSiIsIm5hbWUiOiJ0ZXN0IiwibmJmIjoxNjY0MDM5ODYwLCJzdWIiOiJ0ZXN0IiwidmlkZW8iOnsicm9vbUFkbWluIjp0cnVlLCJyb29tQ3JlYXRlIjp0cnVlLCJyb29tSm9pbiI6dHJ1ZX19.0Bee2jI2cSZveAbZ8MLc-ADoMYQ4l8IRxcAxpXAS6a8").await.unwrap();
|
||||
|
||||
|
||||
sleep(Duration::from_secs(60)).await;
|
||||
|
||||
}
|
||||
|
||||
/*sync fn handle_rtc(mut signal_receiver: broadcast::Receiver<Message>) {
|
||||
loop {
|
||||
let msg = match signal_receiver.recv().await {
|
||||
Ok(msg) => msg,
|
||||
Err(error) => {
|
||||
error!("Failed to receive SignalResponse: {:?}", error);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
match msg {
|
||||
Message::Join(join) => {}
|
||||
Message::Trickle(trickle) => {}
|
||||
Message::Answer(answer) => {}
|
||||
Message::Offer(offer) => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
@@ -0,0 +1,186 @@
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use futures_util::stream::{SplitSink, SplitStream};
|
||||
use log::{error, info};
|
||||
use prost::Message as ProstMessage;
|
||||
use thiserror::Error;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream};
|
||||
use tokio_tungstenite::tungstenite::{
|
||||
Error as WsError,
|
||||
Message, protocol::frame::{CloseFrame, coding::CloseCode},
|
||||
};
|
||||
|
||||
use crate::proto::{signal_request, signal_response, SignalRequest, SignalResponse};
|
||||
|
||||
pub const PROTOCOL_VERSION: u32 = 8;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SignalError {
|
||||
#[error("websocket failure")]
|
||||
WsError(#[from] WsError),
|
||||
#[error("failed to parse the url")]
|
||||
UrlParse(#[from] url::ParseError),
|
||||
#[error("failed to decode messages from server")]
|
||||
ProtoParse(#[from] prost::DecodeError),
|
||||
}
|
||||
|
||||
type SignalResult<T> = Result<T, SignalError>;
|
||||
type WebSocket = WebSocketStream<MaybeTlsStream<TcpStream>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RecvMessage {
|
||||
response_chn: oneshot::Sender<SignalResult<signal_response::Message>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SendMessage {
|
||||
signal: signal_request::Message,
|
||||
response_chn: oneshot::Sender<SignalResult<()>>,
|
||||
}
|
||||
|
||||
pub struct SignalClient {
|
||||
read_sender: mpsc::Sender<RecvMessage>,
|
||||
write_sender: mpsc::Sender<SendMessage>,
|
||||
write_shutdown_sender: oneshot::Sender<()>,
|
||||
read_shutdown_sender: oneshot::Sender<()>,
|
||||
read_handle: JoinHandle<()>,
|
||||
write_handle: JoinHandle<()>,
|
||||
}
|
||||
|
||||
pub async fn connect(url: &str, token: &str) -> SignalResult<SignalClient> {
|
||||
let mut lk_url = url::Url::parse(url)?;
|
||||
lk_url.set_path("/rtc");
|
||||
lk_url
|
||||
.query_pairs_mut()
|
||||
.append_pair("access_token", token)
|
||||
.append_pair("protocol", PROTOCOL_VERSION.to_string().as_str());
|
||||
|
||||
let (ws_stream, _) = connect_async(lk_url).await?;
|
||||
let (ws_writer, ws_reader) = ws_stream.split();
|
||||
|
||||
let (read_tx, read_rx) = mpsc::channel::<RecvMessage>(1);
|
||||
let (write_tx, write_rx) = mpsc::channel::<SendMessage>(1);
|
||||
let (read_shutdown_tx, read_shutdown_rx) = oneshot::channel();
|
||||
let (write_shutdown_tx, write_shutdown_rx) = oneshot::channel();
|
||||
|
||||
let read_handle = tokio::spawn(SignalClient::ws_read(read_rx, ws_reader, read_shutdown_rx));
|
||||
let write_handle = tokio::spawn(SignalClient::ws_write(
|
||||
write_rx,
|
||||
ws_writer,
|
||||
write_shutdown_rx,
|
||||
));
|
||||
|
||||
Ok(SignalClient {
|
||||
read_sender: read_tx,
|
||||
write_sender: write_tx,
|
||||
write_shutdown_sender: write_shutdown_tx,
|
||||
read_shutdown_sender: read_shutdown_tx,
|
||||
read_handle,
|
||||
write_handle,
|
||||
})
|
||||
}
|
||||
|
||||
impl SignalClient {
|
||||
pub async fn close(self) {
|
||||
let _ = self.write_shutdown_sender.send(());
|
||||
let _ = self.write_handle.await;
|
||||
let _ = self.read_shutdown_sender.send(());
|
||||
let _ = self.read_handle.await;
|
||||
}
|
||||
|
||||
pub async fn recv(&self) -> SignalResult<signal_response::Message> {
|
||||
let (send, recv) = oneshot::channel();
|
||||
let msg = RecvMessage { response_chn: send };
|
||||
let _ = self.read_sender.send(msg).await;
|
||||
recv.await.expect("channel closed")
|
||||
}
|
||||
|
||||
pub async fn send(&self, signal: signal_request::Message) -> SignalResult<()> {
|
||||
let (send, recv) = oneshot::channel();
|
||||
let msg = SendMessage {
|
||||
signal,
|
||||
response_chn: send,
|
||||
};
|
||||
let _ = self.write_sender.send(msg).await;
|
||||
recv.await.expect("channel closed")
|
||||
}
|
||||
|
||||
async fn ws_write(
|
||||
mut write_receiver: mpsc::Receiver<SendMessage>,
|
||||
mut ws_writer: SplitSink<WebSocket, Message>,
|
||||
mut shutdown_receiver: oneshot::Receiver<()>,
|
||||
) {
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(msg) = write_receiver.recv() => {
|
||||
let req = SignalRequest {
|
||||
message: Some(msg.signal),
|
||||
};
|
||||
|
||||
let write_res = ws_writer.send(Message::Binary(req.encode_to_vec())).await;
|
||||
if let Err(err) = write_res {
|
||||
error!("failed to send message to ws: {:?}", err);
|
||||
let _ = msg.response_chn.send(Err(err.into()));
|
||||
break;
|
||||
}
|
||||
|
||||
let _ = msg.response_chn.send(Ok(()));
|
||||
},
|
||||
_ = (&mut shutdown_receiver) => {
|
||||
let _ = ws_writer.send(Message::Close(Some(CloseFrame {
|
||||
code: CloseCode::Normal,
|
||||
reason: "disconnected by client".into()
|
||||
}))).await;
|
||||
let _ = ws_writer.flush().await;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn ws_read(
|
||||
mut write_receiver: mpsc::Receiver<RecvMessage>,
|
||||
mut ws_reader: SplitStream<WebSocket>,
|
||||
mut shutdown_receiver: oneshot::Receiver<()>,
|
||||
) {
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(msg) = write_receiver.recv() => {
|
||||
let read = ws_reader.next().await;
|
||||
if read.is_none() {
|
||||
let _ = msg.response_chn.send(Err(SignalError::WsError(WsError::ConnectionClosed)));
|
||||
break;
|
||||
}
|
||||
let read = read.unwrap();
|
||||
match read {
|
||||
Ok(Message::Binary(data)) => {
|
||||
let res = SignalResponse::decode(data.as_slice()).expect("failed to decode incoming SignalResponse");
|
||||
|
||||
// TODO(theomonnon) Handle Message::Pong
|
||||
let res = res.message.unwrap();
|
||||
let _ = msg.response_chn.send(Ok(res));
|
||||
}
|
||||
_ => {
|
||||
error!("unhandled websocket message: {:?}", read);
|
||||
let _ = msg.response_chn.send(Err(SignalError::WsError(WsError::ConnectionClosed)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
_ = (&mut shutdown_receiver) => break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_test() {
|
||||
env_logger::init();
|
||||
let client = connect("ws://localhost:7880", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NzEyMzk4NjAsImlzcyI6IkFQSXpLYkFTaUNWYWtnSiIsIm5hbWUiOiJ0ZXN0IiwibmJmIjoxNjY0MDM5ODYwLCJzdWIiOiJ0ZXN0IiwidmlkZW8iOnsicm9vbUFkbWluIjp0cnVlLCJyb29tQ3JlYXRlIjp0cnVlLCJyb29tSm9pbiI6dHJ1ZX19.0Bee2jI2cSZveAbZ8MLc-ADoMYQ4l8IRxcAxpXAS6a8").await.unwrap();
|
||||
let msg = client.recv().await.unwrap();
|
||||
|
||||
client.close().await;
|
||||
info!("Received message {:?}", msg);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "livekit-webrtc"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
homepage = "https://livekit.io"
|
||||
|
||||
[dependencies]
|
||||
libwebrtc-sys = { path = "./libwebrtc-sys" }
|
||||
tokio = { version = "1.20.1", features = ["full"] }
|
||||
cxx = "1.0"
|
||||
log = "0.4"
|
||||
thiserror = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.9"
|
||||
@@ -0,0 +1,24 @@
|
||||
# Using the same .clang-format as libwebrtc
|
||||
# https://github.com/webrtc-sdk/webrtc/blob/m104_release/.clang-format
|
||||
BasedOnStyle: Chromium
|
||||
---
|
||||
Language: Java
|
||||
BasedOnStyle: Google
|
||||
---
|
||||
Language: ObjC
|
||||
BasedOnStyle: Google
|
||||
BinPackParameters: false
|
||||
BinPackArguments: false
|
||||
ColumnLimit: 100
|
||||
ObjCBlockIndentWidth: 2
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AlignOperands: false
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
BreakBeforeTernaryOperators: false
|
||||
IndentWrappedFunctionNames: true
|
||||
ContinuationIndentWidth: 4
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
---
|
||||
Language: Cpp
|
||||
IncludeBlocks: Regroup
|
||||
@@ -0,0 +1,2 @@
|
||||
/libwebrtc
|
||||
/cmake-build-debug
|
||||
@@ -0,0 +1,23 @@
|
||||
# IMPORTANT NOTE
|
||||
# This file is just used because some IDEs need to understand how to do autocompletion.
|
||||
# This file is completely ignored by the library ( See build.rs for the build system )
|
||||
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
project(livekit-webrtc)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
add_definitions(-DWEBRTC_WIN)
|
||||
|
||||
include_directories(libwebrtc/include)
|
||||
include_directories(libwebrtc/include/third_party/abseil-cpp/)
|
||||
include_directories(libwebrtc/include/third_party/libc++/)
|
||||
include_directories(include/)
|
||||
include_directories(../../../target/cxxbridge) # Can be different
|
||||
|
||||
file(GLOB_RECURSE SRC src/*.cpp)
|
||||
add_library(livekit-webrtc ${SRC})
|
||||
|
||||
#include_directories(/Users/theomonnom/Library/Android/sdk/ndk/25.0.8775105/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/include)
|
||||
#find_library(ANDROID_LIB_ANDROID android)
|
||||
#target_link_libraries(client_sdk_native PRIVATE android)
|
||||
@@ -0,0 +1,213 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
|
||||
dependencies = [
|
||||
"termcolor",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "873c2e83af70859af2aaecd1f5d862f3790b747b1f4f50fb45a931d000ac0422"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxxbridge-flags",
|
||||
"cxxbridge-macro",
|
||||
"link-cplusplus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx-build"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b49edea7163bbc7a39e3d829b4b0b66a9d30486973152842b7413f2c7b5632bf"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"codespan-reporting",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"scratch",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46b787c15af80277db5c88c6ac6c502ae545e622f010e06f95e540d34931acf"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ba3f3a7efa46626878fb5d324fabca4d19d2956b6ae97ce43044ef4515f5abc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
|
||||
[[package]]
|
||||
name = "link-cplusplus"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8cae2cd7ba2f3f63938b9c724475dfb7b9861b545a90324476324ed21dbc8c8"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "livekit-native"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cxx",
|
||||
"cxx-build",
|
||||
"glob",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c278e965f1d8cf32d6e0e96de3d3e79712178ae67986d9cf9151f51e95aac89b"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||
|
||||
[[package]]
|
||||
name = "scratch"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96311ef4a16462c757bb6a39152c40f58f31cd2602a40fceb937e2bc34e6cbab"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "libwebrtc-sys"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
homepage = "https://livekit.io"
|
||||
|
||||
[dependencies]
|
||||
cxx = "1.0"
|
||||
log = "0.4"
|
||||
|
||||
[build-dependencies]
|
||||
cxx-build = "1.0"
|
||||
glob = "0.3.0"
|
||||
regex = "1.0"
|
||||
cc = { version = "1.0", features = ["parallel"] }
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.9"
|
||||
|
||||
#[lib]
|
||||
#crate-type = ["staticlib", "cdylib"]
|
||||
@@ -3,11 +3,56 @@ use std::env;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path;
|
||||
use std::process::Command;
|
||||
|
||||
const MAC_SDKS: &str =
|
||||
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs";
|
||||
|
||||
fn get_mac_sysroot() -> String {
|
||||
let mut sdks: Vec<String> = vec![];
|
||||
let files = fs::read_dir(MAC_SDKS).unwrap();
|
||||
for entry in files {
|
||||
let entry = entry.unwrap();
|
||||
let filename = entry.file_name().to_str().unwrap().to_owned();
|
||||
sdks.push(filename);
|
||||
}
|
||||
|
||||
sdks = sdks
|
||||
.iter()
|
||||
.filter(|value| value.contains("MacOSX1"))
|
||||
.map(|original| original.to_owned())
|
||||
.collect();
|
||||
|
||||
let last = sdks.last().unwrap();
|
||||
|
||||
format!("{}/{}", MAC_SDKS, &last)
|
||||
}
|
||||
|
||||
fn macos_link_search_path() -> Option<String> {
|
||||
let output = Command::new("clang")
|
||||
.arg("--print-search-dirs")
|
||||
.output()
|
||||
.ok()?;
|
||||
if !output.status.success() {
|
||||
// Failed to run 'clang --print-search-dirs'.
|
||||
return None;
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
for line in stdout.lines() {
|
||||
if line.contains("libraries: =") {
|
||||
let path = line.split('=').nth(1)?;
|
||||
return Some(format!("{}/lib/darwin", path));
|
||||
}
|
||||
}
|
||||
|
||||
// Failed to determine link search path.
|
||||
None
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// TODO Download precompiled binaries of WebRTC for the target_os
|
||||
|
||||
let target_os = "ios";
|
||||
let target_os = "windows";
|
||||
//let target_arch = "arm64";
|
||||
|
||||
let libwebrtc_dir = path::PathBuf::from("libwebrtc");
|
||||
@@ -18,15 +63,34 @@ fn main() {
|
||||
libwebrtc_dir.join("include/"),
|
||||
libwebrtc_dir.join("include/third_party/abseil-cpp/"),
|
||||
libwebrtc_dir.join("include/third_party/libc++/"),
|
||||
|
||||
// For mac & ios
|
||||
libwebrtc_dir.join("include/sdk/objc"),
|
||||
libwebrtc_dir.join("include/sdk/objc/base"),
|
||||
];
|
||||
|
||||
let mut builder = cxx_build::bridges(&["src/lib.rs"]);
|
||||
builder.flag("-std=c++17");
|
||||
let mut builder = cxx_build::bridges(&[
|
||||
"src/peer_connection.rs",
|
||||
"src/peer_connection_factory.rs",
|
||||
"src/media_stream_interface.rs",
|
||||
"src/data_channel.rs",
|
||||
"src/jsep.rs",
|
||||
"src/candidate.rs",
|
||||
"src/rtp_receiver.rs",
|
||||
"src/rtp_transceiver.rs",
|
||||
"src/rtc_error.rs",
|
||||
"src/webrtc.rs",
|
||||
]);
|
||||
|
||||
builder.file("src/peer_connection.cpp");
|
||||
builder.file("src/peer_connection_factory.cpp");
|
||||
builder.file("src/media_stream_interface.cpp");
|
||||
builder.file("src/data_channel.cpp");
|
||||
builder.file("src/jsep.cpp");
|
||||
builder.file("src/candidate.cpp");
|
||||
builder.file("src/rtp_receiver.cpp");
|
||||
builder.file("src/rtp_transceiver.cpp");
|
||||
builder.file("src/rtc_error.cpp");
|
||||
builder.file("src/webrtc.cpp");
|
||||
|
||||
for include in includes {
|
||||
builder.include(include);
|
||||
@@ -38,6 +102,28 @@ fn main() {
|
||||
);
|
||||
|
||||
match target_os {
|
||||
"windows" => {
|
||||
println!("cargo:rustc-link-lib=dylib=msdmo");
|
||||
println!("cargo:rustc-link-lib=dylib=wmcodecdspuuid");
|
||||
println!("cargo:rustc-link-lib=dylib=dmoguids");
|
||||
println!("cargo:rustc-link-lib=dylib=crypt32");
|
||||
println!("cargo:rustc-link-lib=dylib=iphlpapi");
|
||||
println!("cargo:rustc-link-lib=dylib=ole32");
|
||||
println!("cargo:rustc-link-lib=dylib=secur32");
|
||||
println!("cargo:rustc-link-lib=dylib=winmm");
|
||||
println!("cargo:rustc-link-lib=dylib=ws2_32");
|
||||
println!("cargo:rustc-link-lib=dylib=strmiids");
|
||||
println!("cargo:rustc-link-lib=dylib=d3d11");
|
||||
println!("cargo:rustc-link-lib=dylib=dxgi");
|
||||
println!("cargo:rustc-link-lib=dylib=dwmapi");
|
||||
println!("cargo:rustc-link-lib=dylib=webrtc");
|
||||
|
||||
builder
|
||||
.flag("/std:c++17")
|
||||
.flag("/EHsc")
|
||||
.define("WEBRTC_WIN", None)
|
||||
.define("NOMINMAX", None);
|
||||
}
|
||||
"macos" => {
|
||||
println!("cargo:rustc-link-lib=dylib=c++");
|
||||
println!("cargo:rustc-link-lib=framework=Foundation");
|
||||
@@ -47,16 +133,31 @@ fn main() {
|
||||
println!("cargo:rustc-link-lib=framework=Appkit");
|
||||
println!("cargo:rustc-link-lib=framework=CoreMedia");
|
||||
println!("cargo:rustc-link-lib=framework=CoreGraphics");
|
||||
println!("cargo:rustc-link-lib=framework=VideoToolbox");
|
||||
println!("cargo:rustc-link-lib=framework=CoreVideo");
|
||||
println!("cargo:rustc-link-lib=framework=OpenGL");
|
||||
println!("cargo:rustc-link-lib=framework=Metal");
|
||||
println!("cargo:rustc-link-lib=framework=QuartzCore");
|
||||
println!("cargo:rustc-link-lib=framework=IOKit");
|
||||
println!("cargo:rustc-link-lib=framework=IOSurface");
|
||||
println!("cargo:rustc-link-lib=static=webrtc");
|
||||
|
||||
builder
|
||||
.flag("-stdlib=libc++")
|
||||
.flag("-std=c++17")
|
||||
.flag(format!("-isysroot{}", get_mac_sysroot()).as_str())
|
||||
.define("WEBRTC_ENABLE_OBJC_SYMBOL_EXPORT", None)
|
||||
.define("WEBRTC_POSIX", None)
|
||||
.define("WEBRTC_MAC", None);
|
||||
|
||||
if let Some(path) = macos_link_search_path() {
|
||||
println!("cargo:rustc-link-lib=clang_rt.osx");
|
||||
println!("cargo:rustc-link-search={}", path);
|
||||
}
|
||||
}
|
||||
"ios" => {
|
||||
|
||||
builder
|
||||
.flag("-std=c++17")
|
||||
.file("src/objc_test.mm")
|
||||
.define("WEBRTC_ENABLE_OBJC_SYMBOL_EXPORT", None)
|
||||
.define("WEBRTC_MAC", None)
|
||||
@@ -84,10 +185,7 @@ fn main() {
|
||||
// libgcc ( redirects to libunwind )
|
||||
println!(
|
||||
"cargo:rustc-link-search={}",
|
||||
toolchain
|
||||
.join("lib")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
toolchain.join("lib").to_str().unwrap()
|
||||
);
|
||||
|
||||
// Needed when loading the library in the JVM ( System.loadLibrary )
|
||||
@@ -129,30 +227,26 @@ fn main() {
|
||||
);
|
||||
|
||||
builder
|
||||
.flag("-std=c++17")
|
||||
.define("WEBRTC_LINUX", None)
|
||||
.define("WEBRTC_POSIX", None)
|
||||
.define("WEBRTC_ANDROID", None);
|
||||
}
|
||||
_ => {
|
||||
panic!("Unsupported platform, {}", target_os);
|
||||
panic!("Unsupported target, {}", target_os);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(theomonnom) Only add this define when building tests
|
||||
builder.define("LIVEKIT_TEST", None);
|
||||
|
||||
builder.warnings(false).compile("lkwebrtc");
|
||||
|
||||
for entry in glob::glob("./src/**/*.cpp").unwrap() {
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
entry.unwrap().display().to_string()
|
||||
);
|
||||
println!("cargo:rerun-if-changed={}", entry.unwrap().display());
|
||||
}
|
||||
|
||||
for entry in glob::glob("./include/**/*.h").unwrap() {
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
entry.unwrap().display().to_string()
|
||||
);
|
||||
println!("cargo:rerun-if-changed={}", entry.unwrap().display());
|
||||
}
|
||||
|
||||
println!("cargo:rerun-if-changed=src/main.rs");
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// Created by Théo Monnom on 01/09/2022.
|
||||
//
|
||||
|
||||
#ifndef CLIENT_SDK_NATIVE_CANDIDATE_H
|
||||
#define CLIENT_SDK_NATIVE_CANDIDATE_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/candidate.h"
|
||||
|
||||
// cricket::Candidate
|
||||
namespace livekit {
|
||||
|
||||
class Candidate {
|
||||
public:
|
||||
explicit Candidate(const cricket::Candidate& candidate);
|
||||
|
||||
private:
|
||||
cricket::Candidate candidate_;
|
||||
};
|
||||
|
||||
static std::unique_ptr<Candidate> _unique_candidate() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace livekit
|
||||
|
||||
#endif // CLIENT_SDK_NATIVE_CANDIDATE_H
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// Created by Théo Monnom on 01/09/2022.
|
||||
//
|
||||
|
||||
#ifndef CLIENT_SDK_NATIVE_DATA_CHANNEL_H
|
||||
#define CLIENT_SDK_NATIVE_DATA_CHANNEL_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/data_channel_interface.h"
|
||||
#include "rust/cxx.h"
|
||||
#include "rust_types.h"
|
||||
|
||||
namespace livekit {
|
||||
using NativeDataChannelInit = webrtc::DataChannelInit;
|
||||
class NativeDataChannelObserver;
|
||||
|
||||
class DataChannel {
|
||||
public:
|
||||
explicit DataChannel(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel);
|
||||
|
||||
void register_observer(NativeDataChannelObserver& observer);
|
||||
void unregister_observer();
|
||||
bool send(const DataBuffer& buffer);
|
||||
rust::String label() const;
|
||||
void close();
|
||||
|
||||
private:
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel_;
|
||||
};
|
||||
|
||||
std::unique_ptr<NativeDataChannelInit> create_data_channel_init(
|
||||
DataChannelInit init);
|
||||
|
||||
static std::unique_ptr<DataChannel> _unique_data_channel() {
|
||||
return nullptr; // Ignore
|
||||
}
|
||||
|
||||
class NativeDataChannelObserver : public webrtc::DataChannelObserver {
|
||||
public:
|
||||
explicit NativeDataChannelObserver(
|
||||
rust::Box<DataChannelObserverWrapper> observer);
|
||||
|
||||
void OnStateChange() override;
|
||||
void OnMessage(const webrtc::DataBuffer& buffer) override;
|
||||
void OnBufferedAmountChange(uint64_t sent_data_size) override;
|
||||
|
||||
private:
|
||||
rust::Box<DataChannelObserverWrapper> observer_;
|
||||
};
|
||||
|
||||
std::unique_ptr<NativeDataChannelObserver> create_native_data_channel_observer(
|
||||
rust::Box<DataChannelObserverWrapper> observer);
|
||||
} // namespace livekit
|
||||
|
||||
#endif // CLIENT_SDK_NATIVE_DATA_CHANNEL_H
|
||||
@@ -0,0 +1,121 @@
|
||||
//
|
||||
// Created by Théo Monnom on 01/09/2022.
|
||||
//
|
||||
|
||||
#ifndef CLIENT_SDK_NATIVE_JSEP_H
|
||||
#define CLIENT_SDK_NATIVE_JSEP_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/jsep.h"
|
||||
#include "api/ref_counted_base.h"
|
||||
#include "rust/cxx.h"
|
||||
#include "rust_types.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
class IceCandidate {
|
||||
public:
|
||||
explicit IceCandidate(
|
||||
std::unique_ptr<webrtc::IceCandidateInterface> ice_candidate);
|
||||
|
||||
rust::String stringify() const;
|
||||
std::unique_ptr<webrtc::IceCandidateInterface> release();
|
||||
|
||||
private:
|
||||
std::unique_ptr<webrtc::IceCandidateInterface> ice_candidate_;
|
||||
};
|
||||
|
||||
std::unique_ptr<IceCandidate> create_ice_candidate(rust::String sdp_mid, int sdp_mline_index, rust::String sdp);
|
||||
|
||||
static std::unique_ptr<IceCandidate> _unique_ice_candidate() {
|
||||
return nullptr; // Ignore
|
||||
}
|
||||
|
||||
class SessionDescription {
|
||||
public:
|
||||
explicit SessionDescription(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> session_description);
|
||||
|
||||
rust::String stringify() const;
|
||||
std::unique_ptr<SessionDescription> clone() const;
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> release();
|
||||
|
||||
private:
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> session_description_;
|
||||
};
|
||||
|
||||
std::unique_ptr<SessionDescription> create_session_description(SdpType type, rust::String sdp);
|
||||
|
||||
static std::unique_ptr<SessionDescription> _unique_session_description() {
|
||||
return nullptr; // Ignore
|
||||
}
|
||||
|
||||
// SetCreateSdpObserver
|
||||
|
||||
class NativeCreateSdpObserver
|
||||
: public webrtc::CreateSessionDescriptionObserver {
|
||||
public:
|
||||
explicit NativeCreateSdpObserver(
|
||||
rust::Box<CreateSdpObserverWrapper> observer);
|
||||
|
||||
void OnSuccess(webrtc::SessionDescriptionInterface* desc) override;
|
||||
void OnFailure(webrtc::RTCError error) override;
|
||||
|
||||
private:
|
||||
rust::Box<CreateSdpObserverWrapper> observer_;
|
||||
};
|
||||
|
||||
struct NativeCreateSdpObserverHandle {
|
||||
rtc::scoped_refptr<NativeCreateSdpObserver> observer;
|
||||
};
|
||||
|
||||
std::unique_ptr<NativeCreateSdpObserverHandle>
|
||||
create_native_create_sdp_observer(rust::Box<CreateSdpObserverWrapper> observer);
|
||||
|
||||
// SetLocalSdpObserver
|
||||
|
||||
class NativeSetLocalSdpObserver
|
||||
: public webrtc::SetLocalDescriptionObserverInterface {
|
||||
public:
|
||||
explicit NativeSetLocalSdpObserver(
|
||||
rust::Box<SetLocalSdpObserverWrapper> observer);
|
||||
|
||||
void OnSetLocalDescriptionComplete(webrtc::RTCError error) override;
|
||||
|
||||
private:
|
||||
rust::Box<SetLocalSdpObserverWrapper> observer_;
|
||||
};
|
||||
|
||||
struct NativeSetLocalSdpObserverHandle {
|
||||
rtc::scoped_refptr<NativeSetLocalSdpObserver> observer;
|
||||
};
|
||||
|
||||
std::unique_ptr<NativeSetLocalSdpObserverHandle>
|
||||
create_native_set_local_sdp_observer(
|
||||
rust::Box<SetLocalSdpObserverWrapper> observer);
|
||||
|
||||
// SetRemoteSdpObserver
|
||||
|
||||
class NativeSetRemoteSdpObserver
|
||||
: public webrtc::SetRemoteDescriptionObserverInterface {
|
||||
public:
|
||||
explicit NativeSetRemoteSdpObserver(
|
||||
rust::Box<SetRemoteSdpObserverWrapper> observer);
|
||||
|
||||
void OnSetRemoteDescriptionComplete(webrtc::RTCError error) override;
|
||||
|
||||
private:
|
||||
rust::Box<SetRemoteSdpObserverWrapper> observer_;
|
||||
};
|
||||
|
||||
struct NativeSetRemoteSdpObserverHandle {
|
||||
rtc::scoped_refptr<NativeSetRemoteSdpObserver> observer;
|
||||
};
|
||||
|
||||
std::unique_ptr<NativeSetRemoteSdpObserverHandle>
|
||||
create_native_set_remote_sdp_observer(
|
||||
rust::Box<SetRemoteSdpObserverWrapper> observer);
|
||||
} // namespace livekit
|
||||
|
||||
#endif // CLIENT_SDK_NATIVE_JSEP_H
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Created by Théo Monnom on 31/08/2022.
|
||||
//
|
||||
|
||||
#ifndef CLIENT_SDK_NATIVE_MEDIA_STREAM_INTERFACE_H
|
||||
#define CLIENT_SDK_NATIVE_MEDIA_STREAM_INTERFACE_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/media_stream_interface.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
class MediaStreamInterface {
|
||||
public:
|
||||
explicit MediaStreamInterface(
|
||||
rtc::scoped_refptr<webrtc::MediaStreamInterface> stream);
|
||||
|
||||
private:
|
||||
rtc::scoped_refptr<webrtc::MediaStreamInterface> media_stream_;
|
||||
};
|
||||
|
||||
static std::unique_ptr<MediaStreamInterface> _unique_media_stream() {
|
||||
return nullptr; // Ignore
|
||||
}
|
||||
} // namespace livekit
|
||||
|
||||
#endif // CLIENT_SDK_NATIVE_MEDIA_STREAM_INTERFACE_H
|
||||
@@ -0,0 +1,137 @@
|
||||
//
|
||||
// Created by Théo Monnom on 30/08/2022.
|
||||
//
|
||||
|
||||
#ifndef CLIENT_SDK_NATIVE_PEER_CONNECTION_H
|
||||
#define CLIENT_SDK_NATIVE_PEER_CONNECTION_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/peer_connection_interface.h"
|
||||
#include "data_channel.h"
|
||||
#include "jsep.h"
|
||||
#include "rust/cxx.h"
|
||||
#include "rust_types.h"
|
||||
|
||||
namespace livekit {
|
||||
class NativeAddIceCandidateObserver;
|
||||
|
||||
class PeerConnection {
|
||||
public:
|
||||
explicit PeerConnection(
|
||||
rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection);
|
||||
|
||||
void create_offer(NativeCreateSdpObserverHandle& observer,
|
||||
RTCOfferAnswerOptions options);
|
||||
void create_answer(NativeCreateSdpObserverHandle& observer,
|
||||
RTCOfferAnswerOptions options);
|
||||
void set_local_description(std::unique_ptr<SessionDescription> desc,
|
||||
NativeSetLocalSdpObserverHandle& observer);
|
||||
void set_remote_description(std::unique_ptr<SessionDescription> desc,
|
||||
NativeSetRemoteSdpObserverHandle& observer);
|
||||
std::unique_ptr<DataChannel> create_data_channel(
|
||||
rust::String label,
|
||||
std::unique_ptr<NativeDataChannelInit> init);
|
||||
void add_ice_candidate(std::unique_ptr<IceCandidate> candidate,
|
||||
NativeAddIceCandidateObserver& observer);
|
||||
std::unique_ptr<SessionDescription> local_description() const;
|
||||
std::unique_ptr<SessionDescription> remote_description() const;
|
||||
SignalingState signaling_state() const;
|
||||
IceGatheringState ice_gathering_state() const;
|
||||
void close();
|
||||
|
||||
private:
|
||||
rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
|
||||
};
|
||||
|
||||
static std::unique_ptr<PeerConnection> _unique_peer_connection() {
|
||||
return nullptr; // Ignore
|
||||
}
|
||||
|
||||
class NativeAddIceCandidateObserver {
|
||||
public:
|
||||
explicit NativeAddIceCandidateObserver(
|
||||
rust::Box<AddIceCandidateObserverWrapper> observer);
|
||||
|
||||
void OnComplete(const RTCError& error);
|
||||
|
||||
private:
|
||||
rust::Box<AddIceCandidateObserverWrapper> observer_;
|
||||
};
|
||||
|
||||
std::unique_ptr<NativeAddIceCandidateObserver>
|
||||
create_native_add_ice_candidate_observer(
|
||||
rust::Box<AddIceCandidateObserverWrapper> observer);
|
||||
|
||||
class NativePeerConnectionObserver : public webrtc::PeerConnectionObserver {
|
||||
public:
|
||||
explicit NativePeerConnectionObserver(
|
||||
rust::Box<PeerConnectionObserverWrapper> observer);
|
||||
|
||||
void OnSignalingChange(
|
||||
webrtc::PeerConnectionInterface::SignalingState new_state) override;
|
||||
|
||||
void OnAddStream(
|
||||
rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) override;
|
||||
|
||||
void OnRemoveStream(
|
||||
rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) override;
|
||||
|
||||
void OnDataChannel(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) override;
|
||||
|
||||
void OnRenegotiationNeeded() override;
|
||||
|
||||
void OnNegotiationNeededEvent(uint32_t event_id) override;
|
||||
|
||||
void OnIceConnectionChange(
|
||||
webrtc::PeerConnectionInterface::IceConnectionState new_state) override;
|
||||
|
||||
void OnStandardizedIceConnectionChange(
|
||||
webrtc::PeerConnectionInterface::IceConnectionState new_state) override;
|
||||
|
||||
void OnConnectionChange(
|
||||
webrtc::PeerConnectionInterface::PeerConnectionState new_state) override;
|
||||
|
||||
void OnIceGatheringChange(
|
||||
webrtc::PeerConnectionInterface::IceGatheringState new_state) override;
|
||||
|
||||
void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override;
|
||||
|
||||
void OnIceCandidateError(const std::string& address,
|
||||
int port,
|
||||
const std::string& url,
|
||||
int error_code,
|
||||
const std::string& error_text) override;
|
||||
|
||||
void OnIceCandidatesRemoved(
|
||||
const std::vector<cricket::Candidate>& candidates) override;
|
||||
|
||||
void OnIceConnectionReceivingChange(bool receiving) override;
|
||||
|
||||
void OnIceSelectedCandidatePairChanged(
|
||||
const cricket::CandidatePairChangeEvent& event) override;
|
||||
|
||||
void OnAddTrack(
|
||||
rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver,
|
||||
const std::vector<rtc::scoped_refptr<webrtc::MediaStreamInterface>>&
|
||||
streams) override;
|
||||
|
||||
void OnTrack(
|
||||
rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver) override;
|
||||
|
||||
void OnRemoveTrack(
|
||||
rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) override;
|
||||
|
||||
void OnInterestingUsage(int usage_pattern) override;
|
||||
|
||||
private:
|
||||
rust::Box<PeerConnectionObserverWrapper> observer_;
|
||||
};
|
||||
|
||||
std::unique_ptr<NativePeerConnectionObserver>
|
||||
create_native_peer_connection_observer(
|
||||
rust::Box<PeerConnectionObserverWrapper> observer);
|
||||
} // namespace livekit
|
||||
|
||||
#endif // CLIENT_SDK_NATIVE_PEER_CONNECTION_H
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Created by Théo Monnom on 03/08/2022.
|
||||
//
|
||||
|
||||
#ifndef PEER_CONNECTION_FACTORY_H
|
||||
#define PEER_CONNECTION_FACTORY_H
|
||||
|
||||
#include "api/peer_connection_interface.h"
|
||||
#include "peer_connection.h"
|
||||
#include "rust_types.h"
|
||||
|
||||
namespace livekit {
|
||||
using NativeRTCConfiguration =
|
||||
webrtc::PeerConnectionInterface::RTCConfiguration;
|
||||
|
||||
class PeerConnectionFactory {
|
||||
public:
|
||||
PeerConnectionFactory();
|
||||
|
||||
std::unique_ptr<PeerConnection> create_peer_connection(
|
||||
std::unique_ptr<NativeRTCConfiguration> config,
|
||||
NativePeerConnectionObserver& observer) const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<rtc::Thread> network_thread_;
|
||||
std::unique_ptr<rtc::Thread> worker_thread_;
|
||||
std::unique_ptr<rtc::Thread> signaling_thread_;
|
||||
|
||||
rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> peer_factory_;
|
||||
};
|
||||
|
||||
std::unique_ptr<PeerConnectionFactory> create_peer_connection_factory();
|
||||
std::unique_ptr<NativeRTCConfiguration> create_rtc_configuration(
|
||||
RTCConfiguration conf);
|
||||
} // namespace livekit
|
||||
|
||||
#endif // PEER_CONNECTION_FACTORY_H
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Created by theom on 04/09/2022.
|
||||
//
|
||||
|
||||
#ifndef CLIENT_SDK_NATIVE_RTC_ERROR_H
|
||||
#define CLIENT_SDK_NATIVE_RTC_ERROR_H
|
||||
|
||||
#include "api/rtc_error.h"
|
||||
#include "libwebrtc-sys/src/rtc_error.rs.h"
|
||||
#include "rust/cxx.h"
|
||||
#include "rust_types.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
RTCError to_error(const webrtc::RTCError& error);
|
||||
std::string serialize_error(
|
||||
const RTCError& error); // to be used inside cxx::Exception msg
|
||||
|
||||
#ifdef LIVEKIT_TEST
|
||||
rust::String serialize_deserialize();
|
||||
void throw_error();
|
||||
#endif
|
||||
|
||||
} // namespace livekit
|
||||
|
||||
#endif // CLIENT_SDK_NATIVE_RTC_ERROR_H
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Created by Théo Monnom on 01/09/2022.
|
||||
//
|
||||
|
||||
#ifndef CLIENT_SDK_NATIVE_RTP_RECEIVER_H
|
||||
#define CLIENT_SDK_NATIVE_RTP_RECEIVER_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/rtp_receiver_interface.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
class RtpReceiver {
|
||||
public:
|
||||
explicit RtpReceiver(
|
||||
rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver);
|
||||
|
||||
private:
|
||||
rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver_;
|
||||
};
|
||||
|
||||
static std::unique_ptr<RtpReceiver> _unique_rtp_receiver() {
|
||||
return nullptr; // Ignore
|
||||
}
|
||||
} // namespace livekit
|
||||
|
||||
#endif // CLIENT_SDK_NATIVE_RTP_RECEIVER_H
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Created by Théo Monnom on 02/09/2022.
|
||||
//
|
||||
|
||||
#ifndef CLIENT_SDK_NATIVE_RTP_TRANSCEIVER_H
|
||||
#define CLIENT_SDK_NATIVE_RTP_TRANSCEIVER_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/rtp_transceiver_interface.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
class RtpTransceiver {
|
||||
public:
|
||||
explicit RtpTransceiver(
|
||||
rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver);
|
||||
|
||||
private:
|
||||
rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver_;
|
||||
};
|
||||
|
||||
static std::unique_ptr<RtpTransceiver> _unique_rtp_transceiver() {
|
||||
return nullptr; // Ignore
|
||||
}
|
||||
} // namespace livekit
|
||||
|
||||
#endif // CLIENT_SDK_NATIVE_RTP_TRANSCEIVER_H
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// Created by Théo Monnom on 30/08/2022.
|
||||
//
|
||||
|
||||
#ifndef RUST_TYPES_H
|
||||
#define RUST_TYPES_H
|
||||
|
||||
#include "api/peer_connection_interface.h"
|
||||
|
||||
namespace livekit {
|
||||
struct RTCConfiguration;
|
||||
struct PeerConnectionObserverWrapper;
|
||||
struct CreateSdpObserverWrapper;
|
||||
struct SetLocalSdpObserverWrapper;
|
||||
struct SetRemoteSdpObserverWrapper;
|
||||
struct DataChannelObserverWrapper;
|
||||
struct AddIceCandidateObserverWrapper;
|
||||
|
||||
// Shared types
|
||||
enum class PeerConnectionState;
|
||||
enum class SignalingState;
|
||||
enum class IceConnectionState;
|
||||
enum class IceGatheringState;
|
||||
enum class SdpType;
|
||||
struct SdpParseError;
|
||||
struct RTCOfferAnswerOptions;
|
||||
struct RTCError;
|
||||
struct DataChannelInit;
|
||||
struct DataBuffer;
|
||||
} // namespace livekit
|
||||
|
||||
#endif // RUST_TYPES_H
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Created by theom on 18/09/2022.
|
||||
//
|
||||
|
||||
#ifndef LIVEKIT_WEBRTC_WEBRTC_H
|
||||
#define LIVEKIT_WEBRTC_WEBRTC_H
|
||||
|
||||
#include "rtc_base/physical_socket_server.h"
|
||||
#include "rtc_base/ssl_adapter.h"
|
||||
|
||||
#ifdef WEBRTC_WIN
|
||||
#include "rtc_base/win32_socket_init.h"
|
||||
#endif
|
||||
|
||||
namespace livekit {
|
||||
|
||||
class RTCRuntime {
|
||||
public:
|
||||
RTCRuntime();
|
||||
~RTCRuntime();
|
||||
|
||||
RTCRuntime(const RTCRuntime&) = delete;
|
||||
RTCRuntime& operator=(const RTCRuntime&) = delete;
|
||||
|
||||
private:
|
||||
#ifdef WEBRTC_WIN
|
||||
rtc::WinsockInitializer winsock_;
|
||||
#endif
|
||||
};
|
||||
|
||||
std::unique_ptr<RTCRuntime> create_rtc_runtime();
|
||||
|
||||
} // namespace livekit
|
||||
|
||||
#endif // LIVEKIT_WEBRTC_WEBRTC_H
|
||||
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Created by Théo Monnom on 01/09/2022.
|
||||
//
|
||||
|
||||
#include "livekit/candidate.h"
|
||||
|
||||
namespace livekit {
|
||||
Candidate::Candidate(const cricket::Candidate& candidate)
|
||||
: candidate_(candidate) {}
|
||||
} // namespace livekit
|
||||
@@ -0,0 +1,10 @@
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/candidate.h");
|
||||
|
||||
type Candidate; // cricket::Candidate
|
||||
|
||||
fn _unique_candidate() -> UniquePtr<Candidate>; // Ignore
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// Created by Théo Monnom on 01/09/2022.
|
||||
//
|
||||
|
||||
#include "livekit/data_channel.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "libwebrtc-sys/src/data_channel.rs.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
DataChannel::DataChannel(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel)
|
||||
: data_channel_(std::move(data_channel)) {}
|
||||
|
||||
void DataChannel::register_observer(NativeDataChannelObserver& observer) {
|
||||
data_channel_->RegisterObserver(&observer);
|
||||
}
|
||||
|
||||
void DataChannel::unregister_observer() {
|
||||
data_channel_->UnregisterObserver();
|
||||
}
|
||||
|
||||
bool DataChannel::send(const DataBuffer& buffer) {
|
||||
return data_channel_->Send(webrtc::DataBuffer{
|
||||
rtc::CopyOnWriteBuffer(buffer.ptr, buffer.len), buffer.binary});
|
||||
}
|
||||
|
||||
rust::String DataChannel::label() const {
|
||||
return data_channel_->label();
|
||||
}
|
||||
|
||||
void DataChannel::close() {
|
||||
return data_channel_->Close();
|
||||
}
|
||||
|
||||
std::unique_ptr<NativeDataChannelInit> create_data_channel_init(
|
||||
DataChannelInit init) {
|
||||
auto rtc_init = std::make_unique<webrtc::DataChannelInit>();
|
||||
rtc_init->id = init.id;
|
||||
rtc_init->negotiated = init.negotiated;
|
||||
rtc_init->ordered = init.ordered;
|
||||
rtc_init->protocol = init.protocol.c_str();
|
||||
rtc_init->reliable = init.reliable;
|
||||
|
||||
if (init.has_max_retransmit_time)
|
||||
rtc_init->maxRetransmitTime = init.max_retransmit_time;
|
||||
|
||||
if (init.has_max_retransmits)
|
||||
rtc_init->maxRetransmits = init.max_retransmits;
|
||||
|
||||
if (init.has_priority)
|
||||
rtc_init->priority = static_cast<webrtc::Priority>(init.priority);
|
||||
|
||||
return rtc_init;
|
||||
}
|
||||
|
||||
NativeDataChannelObserver::NativeDataChannelObserver(
|
||||
rust::Box<DataChannelObserverWrapper> observer)
|
||||
: observer_(std::move(observer)) {}
|
||||
|
||||
void NativeDataChannelObserver::OnStateChange() {
|
||||
observer_->on_state_change();
|
||||
}
|
||||
|
||||
void NativeDataChannelObserver::OnMessage(const webrtc::DataBuffer& buffer) {
|
||||
DataBuffer data{};
|
||||
data.ptr = buffer.data.data();
|
||||
data.len = buffer.data.size();
|
||||
data.binary = buffer.binary;
|
||||
observer_->on_message(data);
|
||||
}
|
||||
|
||||
void NativeDataChannelObserver::OnBufferedAmountChange(
|
||||
uint64_t sent_data_size) {
|
||||
observer_->on_buffered_amount_change(sent_data_size);
|
||||
}
|
||||
|
||||
std::unique_ptr<NativeDataChannelObserver> create_native_data_channel_observer(
|
||||
rust::Box<DataChannelObserverWrapper> observer) {
|
||||
return std::make_unique<NativeDataChannelObserver>(std::move(observer));
|
||||
}
|
||||
} // namespace livekit
|
||||
@@ -0,0 +1,122 @@
|
||||
use std::slice;
|
||||
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum Priority {
|
||||
VeryLow,
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DataChannelInit {
|
||||
#[allow(deprecated)]
|
||||
#[deprecated]
|
||||
pub reliable: bool,
|
||||
pub ordered: bool,
|
||||
pub has_max_retransmit_time: bool,
|
||||
pub max_retransmit_time: i32,
|
||||
pub has_max_retransmits: bool,
|
||||
pub max_retransmits: i32,
|
||||
pub protocol: String,
|
||||
pub negotiated: bool,
|
||||
pub id: i32,
|
||||
pub has_priority: bool,
|
||||
pub priority: Priority,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DataBuffer {
|
||||
pub ptr: *const u8,
|
||||
pub len: usize,
|
||||
pub binary: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum DataState {
|
||||
Connecting,
|
||||
Open,
|
||||
Closing,
|
||||
Closed,
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
type DataChannelObserverWrapper;
|
||||
|
||||
fn on_state_change(self: &DataChannelObserverWrapper);
|
||||
fn on_message(self: &DataChannelObserverWrapper, buffer: DataBuffer);
|
||||
fn on_buffered_amount_change(self: &DataChannelObserverWrapper, sent_data_size: u64);
|
||||
}
|
||||
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/data_channel.h");
|
||||
|
||||
type DataChannel;
|
||||
type NativeDataChannelInit;
|
||||
type NativeDataChannelObserver;
|
||||
|
||||
/// SAFETY
|
||||
/// The observer must live as the datachannel uses it
|
||||
unsafe fn register_observer(
|
||||
self: Pin<&mut DataChannel>,
|
||||
observer: Pin<&mut NativeDataChannelObserver>,
|
||||
);
|
||||
|
||||
fn unregister_observer(self: Pin<&mut DataChannel>);
|
||||
fn send(self: Pin<&mut DataChannel>, data: &DataBuffer) -> bool;
|
||||
fn label(self: &DataChannel) -> String;
|
||||
fn close(self: Pin<&mut DataChannel>);
|
||||
|
||||
fn create_data_channel_init(init: DataChannelInit) -> UniquePtr<NativeDataChannelInit>;
|
||||
fn create_native_data_channel_observer(
|
||||
observer: Box<DataChannelObserverWrapper>,
|
||||
) -> UniquePtr<NativeDataChannelObserver>;
|
||||
|
||||
fn _unique_data_channel() -> UniquePtr<DataChannel>; // Ignore
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for ffi::DataChannel {}
|
||||
|
||||
unsafe impl Send for ffi::NativeDataChannelObserver {}
|
||||
|
||||
// DataChannelObserver
|
||||
|
||||
pub trait DataChannelObserver: Send {
|
||||
fn on_state_change(&self);
|
||||
fn on_message(&self, data: &[u8], is_binary: bool);
|
||||
fn on_buffered_amount_change(&self, sent_data_size: u64);
|
||||
}
|
||||
|
||||
pub struct DataChannelObserverWrapper {
|
||||
observer: *mut dyn DataChannelObserver,
|
||||
}
|
||||
|
||||
impl DataChannelObserverWrapper {
|
||||
/// SAFETY
|
||||
/// DataChannelObserver must lives as long as DataChannelObserverWrapper does
|
||||
pub unsafe fn new(observer: *mut dyn DataChannelObserver) -> Self {
|
||||
Self { observer }
|
||||
}
|
||||
|
||||
fn on_state_change(&self) {
|
||||
unsafe {
|
||||
(*self.observer).on_state_change();
|
||||
}
|
||||
}
|
||||
|
||||
fn on_message(&self, buffer: ffi::DataBuffer) {
|
||||
unsafe {
|
||||
let data = slice::from_raw_parts(buffer.ptr, buffer.len);
|
||||
(*self.observer).on_message(data, buffer.binary);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_buffered_amount_change(&self, sent_data_size: u64) {
|
||||
unsafe { (*self.observer).on_buffered_amount_change(sent_data_size) };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
//
|
||||
// Created by Théo Monnom on 01/09/2022.
|
||||
//
|
||||
|
||||
#include "livekit/jsep.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
|
||||
#include "libwebrtc-sys/src/jsep.rs.h"
|
||||
#include "livekit/rtc_error.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
const std::string& serialize_sdp_error(webrtc::SdpParseError error) {
|
||||
std::stringstream ss;
|
||||
ss << std::hex << std::setfill('0');
|
||||
ss << std::setw(8) << (uint32_t)error.line.length();
|
||||
ss << std::dec << std::setw(1) << error.line;
|
||||
ss << std::dec << std::setw(1) << error.description;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
IceCandidate::IceCandidate(
|
||||
std::unique_ptr<webrtc::IceCandidateInterface> ice_candidate)
|
||||
: ice_candidate_(std::move(ice_candidate)) {}
|
||||
|
||||
rust::String IceCandidate::stringify() const {
|
||||
std::string str;
|
||||
ice_candidate_->ToString(&str);
|
||||
return rust::String{str};
|
||||
}
|
||||
|
||||
std::unique_ptr<webrtc::IceCandidateInterface> IceCandidate::release() {
|
||||
return std::move(ice_candidate_);
|
||||
}
|
||||
|
||||
std::unique_ptr<IceCandidate> create_ice_candidate(rust::String sdp_mid,
|
||||
int sdp_mline_index,
|
||||
rust::String sdp) {
|
||||
webrtc::SdpParseError error;
|
||||
auto ice_rtc = webrtc::CreateIceCandidate(sdp_mid.c_str(), sdp_mline_index,
|
||||
sdp.c_str(), &error);
|
||||
if (!ice_rtc) {
|
||||
throw std::runtime_error(serialize_sdp_error(error));
|
||||
}
|
||||
|
||||
return std::make_unique<IceCandidate>(
|
||||
std::unique_ptr<webrtc::IceCandidateInterface>(ice_rtc));
|
||||
}
|
||||
|
||||
SessionDescription::SessionDescription(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> session_description)
|
||||
: session_description_(std::move(session_description)) {}
|
||||
|
||||
rust::String SessionDescription::stringify() const {
|
||||
std::string str;
|
||||
session_description_->ToString(&str);
|
||||
return rust::String{str};
|
||||
}
|
||||
|
||||
std::unique_ptr<SessionDescription> SessionDescription::clone() const {
|
||||
return std::make_unique<SessionDescription>(session_description_->Clone());
|
||||
}
|
||||
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface>
|
||||
SessionDescription::release() {
|
||||
return std::move(session_description_);
|
||||
}
|
||||
|
||||
std::unique_ptr<SessionDescription> create_session_description(
|
||||
SdpType type,
|
||||
rust::String sdp) {
|
||||
webrtc::SdpParseError error;
|
||||
auto rtc_sdp = webrtc::CreateSessionDescription(
|
||||
static_cast<webrtc::SdpType>(type), sdp.c_str(), &error);
|
||||
if (!rtc_sdp) {
|
||||
throw std::runtime_error(serialize_sdp_error(error));
|
||||
}
|
||||
|
||||
return std::make_unique<SessionDescription>(std::move(rtc_sdp));
|
||||
}
|
||||
|
||||
// CreateSdpObserver
|
||||
|
||||
NativeCreateSdpObserver::NativeCreateSdpObserver(
|
||||
rust::Box<CreateSdpObserverWrapper> observer)
|
||||
: observer_(std::move(observer)) {}
|
||||
|
||||
void NativeCreateSdpObserver::OnSuccess(
|
||||
webrtc::SessionDescriptionInterface* desc) {
|
||||
// We have ownership of desc
|
||||
observer_->on_success(std::make_unique<SessionDescription>(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface>(desc)));
|
||||
}
|
||||
|
||||
void NativeCreateSdpObserver::OnFailure(webrtc::RTCError error) {
|
||||
observer_->on_failure(to_error(error));
|
||||
}
|
||||
|
||||
std::unique_ptr<NativeCreateSdpObserverHandle>
|
||||
create_native_create_sdp_observer(
|
||||
rust::Box<CreateSdpObserverWrapper> observer) {
|
||||
return std::make_unique<NativeCreateSdpObserverHandle>(
|
||||
NativeCreateSdpObserverHandle{
|
||||
rtc::make_ref_counted<NativeCreateSdpObserver>(std::move(observer))});
|
||||
}
|
||||
|
||||
// SetLocalSdpObserver
|
||||
|
||||
NativeSetLocalSdpObserver::NativeSetLocalSdpObserver(
|
||||
rust::Box<SetLocalSdpObserverWrapper> observer)
|
||||
: observer_(std::move(observer)) {}
|
||||
|
||||
void NativeSetLocalSdpObserver::OnSetLocalDescriptionComplete(
|
||||
webrtc::RTCError error) {
|
||||
observer_->on_set_local_description_complete(to_error(error));
|
||||
}
|
||||
|
||||
std::unique_ptr<NativeSetLocalSdpObserverHandle>
|
||||
create_native_set_local_sdp_observer(
|
||||
rust::Box<SetLocalSdpObserverWrapper> observer) {
|
||||
return std::make_unique<NativeSetLocalSdpObserverHandle>(
|
||||
NativeSetLocalSdpObserverHandle{
|
||||
rtc::make_ref_counted<NativeSetLocalSdpObserver>(
|
||||
std::move(observer))});
|
||||
}
|
||||
|
||||
// SetRemoteSdpObserver
|
||||
|
||||
NativeSetRemoteSdpObserver::NativeSetRemoteSdpObserver(
|
||||
rust::Box<SetRemoteSdpObserverWrapper> observer)
|
||||
: observer_(std::move(observer)) {}
|
||||
|
||||
void NativeSetRemoteSdpObserver::OnSetRemoteDescriptionComplete(
|
||||
webrtc::RTCError error) {
|
||||
observer_->on_set_remote_description_complete(to_error(error));
|
||||
}
|
||||
|
||||
std::unique_ptr<NativeSetRemoteSdpObserverHandle>
|
||||
create_native_set_remote_sdp_observer(
|
||||
rust::Box<SetRemoteSdpObserverWrapper> observer) {
|
||||
return std::make_unique<NativeSetRemoteSdpObserverHandle>(
|
||||
NativeSetRemoteSdpObserverHandle{
|
||||
rtc::make_ref_counted<NativeSetRemoteSdpObserver>(
|
||||
std::move(observer))});
|
||||
}
|
||||
|
||||
} // namespace livekit
|
||||
@@ -0,0 +1,228 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
use cxx::UniquePtr;
|
||||
|
||||
use crate::rtc_error::ffi::RTCError;
|
||||
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum SdpType {
|
||||
Offer,
|
||||
PrAnswer,
|
||||
Answer,
|
||||
Rollback,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SdpParseError {
|
||||
pub line: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
type CreateSdpObserverWrapper;
|
||||
fn on_success(
|
||||
self: &CreateSdpObserverWrapper,
|
||||
session_description: UniquePtr<SessionDescription>,
|
||||
);
|
||||
fn on_failure(self: &CreateSdpObserverWrapper, error: RTCError);
|
||||
|
||||
type SetLocalSdpObserverWrapper;
|
||||
fn on_set_local_description_complete(self: &SetLocalSdpObserverWrapper, error: RTCError);
|
||||
|
||||
type SetRemoteSdpObserverWrapper;
|
||||
fn on_set_remote_description_complete(self: &SetRemoteSdpObserverWrapper, error: RTCError);
|
||||
}
|
||||
|
||||
unsafe extern "C++" {
|
||||
include!("libwebrtc-sys/src/rtc_error.rs.h");
|
||||
include!("livekit/jsep.h");
|
||||
|
||||
type RTCError = crate::rtc_error::ffi::RTCError;
|
||||
type IceCandidate;
|
||||
type SessionDescription;
|
||||
type NativeCreateSdpObserverHandle;
|
||||
type NativeSetLocalSdpObserverHandle;
|
||||
type NativeSetRemoteSdpObserverHandle;
|
||||
|
||||
fn stringify(self: &IceCandidate) -> String;
|
||||
|
||||
fn stringify(self: &SessionDescription) -> String;
|
||||
fn clone(self: &SessionDescription) -> UniquePtr<SessionDescription>;
|
||||
|
||||
fn create_native_create_sdp_observer(
|
||||
observer: Box<CreateSdpObserverWrapper>,
|
||||
) -> UniquePtr<NativeCreateSdpObserverHandle>;
|
||||
fn create_native_set_local_sdp_observer(
|
||||
observer: Box<SetLocalSdpObserverWrapper>,
|
||||
) -> UniquePtr<NativeSetLocalSdpObserverHandle>;
|
||||
fn create_native_set_remote_sdp_observer(
|
||||
observer: Box<SetRemoteSdpObserverWrapper>,
|
||||
) -> UniquePtr<NativeSetRemoteSdpObserverHandle>;
|
||||
|
||||
fn create_ice_candidate(sdp_mid: String, sdp_mline_index: i32, sdp: String) -> Result<UniquePtr<IceCandidate>>;
|
||||
fn create_session_description(sdp_type: SdpType, sdp: String) -> Result<UniquePtr<SessionDescription>>;
|
||||
|
||||
fn _unique_ice_candidate() -> UniquePtr<IceCandidate>; // Ignore
|
||||
fn _unique_session_description() -> UniquePtr<SessionDescription>; // Ignore
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ffi::SdpParseError {}
|
||||
|
||||
impl Display for ffi::SdpParseError {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
write!(f, "SdpParseError occurred {}: {}", self.line, self.description)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ffi::SessionDescription {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}", self.stringify())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for ffi::SessionDescription {}
|
||||
unsafe impl Sync for ffi::SessionDescription {}
|
||||
|
||||
impl Debug for ffi::IceCandidate {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}", self.stringify())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for ffi::IceCandidate {}
|
||||
unsafe impl Sync for ffi::IceCandidate {}
|
||||
|
||||
impl ffi::SdpParseError {
|
||||
/// # Safety
|
||||
/// The value must be correctly encoded
|
||||
pub unsafe fn from(value: &str) -> Self {
|
||||
// Parse the hex encoded error from c++
|
||||
let line_length = u32::from_str_radix(&value[0..8], 16).unwrap() as usize + 8;
|
||||
let line = String::from(&value[8..line_length]);
|
||||
let description = String::from(&value[line_length..]);
|
||||
|
||||
Self {
|
||||
line,
|
||||
description,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ffi::SdpType {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"offer" => Ok(ffi::SdpType::Offer),
|
||||
"pranswer" => Ok(ffi::SdpType::PrAnswer),
|
||||
"answer" => Ok(ffi::SdpType::Answer),
|
||||
"rollback" => Ok(ffi::SdpType::Rollback),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CreateSdpObserver
|
||||
|
||||
pub trait CreateSdpObserver: Send {
|
||||
fn on_success(&self, session_description: UniquePtr<ffi::SessionDescription>);
|
||||
fn on_failure(&self, error: RTCError);
|
||||
}
|
||||
|
||||
pub struct CreateSdpObserverWrapper {
|
||||
observer: Box<dyn CreateSdpObserver>,
|
||||
}
|
||||
|
||||
impl CreateSdpObserverWrapper {
|
||||
pub fn new(observer: Box<dyn CreateSdpObserver>) -> Self {
|
||||
Self { observer }
|
||||
}
|
||||
|
||||
fn on_success(&self, session_description: UniquePtr<ffi::SessionDescription>) {
|
||||
self.observer.on_success(session_description);
|
||||
}
|
||||
|
||||
fn on_failure(&self, error: RTCError) {
|
||||
self.observer.on_failure(error);
|
||||
}
|
||||
}
|
||||
|
||||
// SetLocalSdpObserver
|
||||
|
||||
pub trait SetLocalSdpObserver: Send {
|
||||
fn on_set_local_description_complete(&self, error: RTCError);
|
||||
}
|
||||
|
||||
pub struct SetLocalSdpObserverWrapper {
|
||||
observer: Box<dyn SetLocalSdpObserver>,
|
||||
}
|
||||
|
||||
impl SetLocalSdpObserverWrapper {
|
||||
pub fn new(observer: Box<dyn SetLocalSdpObserver>) -> Self {
|
||||
Self { observer }
|
||||
}
|
||||
|
||||
fn on_set_local_description_complete(&self, error: RTCError) {
|
||||
self.observer.on_set_local_description_complete(error);
|
||||
}
|
||||
}
|
||||
|
||||
// SetRemoteSdpObserver
|
||||
|
||||
pub trait SetRemoteSdpObserver: Send {
|
||||
fn on_set_remote_description_complete(&self, error: RTCError);
|
||||
}
|
||||
|
||||
pub struct SetRemoteSdpObserverWrapper {
|
||||
observer: Box<dyn SetRemoteSdpObserver>,
|
||||
}
|
||||
|
||||
impl SetRemoteSdpObserverWrapper {
|
||||
pub fn new(observer: Box<dyn SetRemoteSdpObserver>) -> Self {
|
||||
Self { observer }
|
||||
}
|
||||
|
||||
fn on_set_remote_description_complete(&self, error: RTCError) {
|
||||
self.observer.on_set_remote_description_complete(error);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use log::info;
|
||||
|
||||
use crate::jsep::ffi;
|
||||
|
||||
#[test]
|
||||
fn throw_error() {
|
||||
let sdp_string = "v=0
|
||||
o=- 6549709950142776241 2 IN IP4 127.0.0.1
|
||||
s=-
|
||||
t=0 0
|
||||
======================== ERROR HERE
|
||||
a=group:BUNDLE 0
|
||||
a=extmap-allow-mixed
|
||||
a=msid-semantic: WMS
|
||||
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
|
||||
c=IN IP4 0.0.0.0
|
||||
a=ice-ufrag:Tw7h
|
||||
a=ice-pwd:6XOVUD6HpcB4c1M8EB8jXJE9
|
||||
a=ice-options:trickle
|
||||
a=fingerprint:sha-256 4F:EC:23:59:5D:A5:E6:3E:3E:5D:8A:09:B6:FA:04:AA:19:99:49:67:BD:65:93:06:BB:EE:AC:D5:21:0F:57:D6
|
||||
a=setup:actpass
|
||||
a=mid:0
|
||||
a=sctp-port:5000
|
||||
a=max-message-size:262144
|
||||
";
|
||||
|
||||
let sdp = ffi::create_session_description(ffi::SdpType::Offer, sdp_string.to_string());
|
||||
let err = unsafe { ffi::SdpParseError::from(sdp.err().unwrap().what()) };
|
||||
info!("parse err: {:?}", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
pub mod candidate;
|
||||
pub mod data_channel;
|
||||
pub mod jsep;
|
||||
pub mod media_stream_interface;
|
||||
pub mod peer_connection;
|
||||
pub mod peer_connection_factory;
|
||||
pub mod rtc_error;
|
||||
pub mod rtp_receiver;
|
||||
pub mod rtp_transceiver;
|
||||
pub mod webrtc;
|
||||
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// Created by Théo Monnom on 31/08/2022.
|
||||
//
|
||||
|
||||
#include "livekit/media_stream_interface.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
MediaStreamInterface::MediaStreamInterface(
|
||||
rtc::scoped_refptr<webrtc::MediaStreamInterface> stream)
|
||||
: media_stream_(std::move(stream)) {}
|
||||
} // namespace livekit
|
||||
@@ -0,0 +1,10 @@
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/media_stream_interface.h");
|
||||
|
||||
type MediaStreamInterface;
|
||||
|
||||
fn _unique_media_stream() -> UniquePtr<MediaStreamInterface>; // Ignore
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
//
|
||||
// Created by Théo Monnom on 30/08/2022.
|
||||
//
|
||||
|
||||
#include "livekit/peer_connection.h"
|
||||
|
||||
#include "libwebrtc-sys/src/peer_connection.rs.h"
|
||||
#include "livekit/rtc_error.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
inline webrtc::PeerConnectionInterface::RTCOfferAnswerOptions
|
||||
toNativeOfferAnswerOptions(const RTCOfferAnswerOptions& options) {
|
||||
webrtc::PeerConnectionInterface::RTCOfferAnswerOptions rtc_options;
|
||||
rtc_options.offer_to_receive_video = options.offer_to_receive_video;
|
||||
rtc_options.offer_to_receive_audio = options.offer_to_receive_audio;
|
||||
rtc_options.voice_activity_detection = options.voice_activity_detection;
|
||||
rtc_options.ice_restart = options.ice_restart;
|
||||
rtc_options.use_rtp_mux = options.use_rtp_mux;
|
||||
rtc_options.raw_packetization_for_video = options.raw_packetization_for_video;
|
||||
rtc_options.num_simulcast_layers = options.num_simulcast_layers;
|
||||
rtc_options.use_obsolete_sctp_sdp = options.use_obsolete_sctp_sdp;
|
||||
return rtc_options;
|
||||
}
|
||||
|
||||
PeerConnection::PeerConnection(
|
||||
rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection)
|
||||
: peer_connection_(std::move(peer_connection)) {}
|
||||
|
||||
void PeerConnection::create_offer(
|
||||
NativeCreateSdpObserverHandle& observer_handle,
|
||||
RTCOfferAnswerOptions options) {
|
||||
peer_connection_->CreateOffer(observer_handle.observer.get(),
|
||||
toNativeOfferAnswerOptions(options));
|
||||
}
|
||||
|
||||
void PeerConnection::create_answer(
|
||||
NativeCreateSdpObserverHandle& observer_handle,
|
||||
RTCOfferAnswerOptions options) {
|
||||
peer_connection_->CreateAnswer(observer_handle.observer.get(),
|
||||
toNativeOfferAnswerOptions(options));
|
||||
}
|
||||
|
||||
void PeerConnection::set_local_description(
|
||||
std::unique_ptr<SessionDescription> desc,
|
||||
NativeSetLocalSdpObserverHandle& observer) {
|
||||
peer_connection_->SetLocalDescription(desc->clone()->release(),
|
||||
observer.observer);
|
||||
}
|
||||
|
||||
void PeerConnection::set_remote_description(
|
||||
std::unique_ptr<SessionDescription> desc,
|
||||
NativeSetRemoteSdpObserverHandle& observer) {
|
||||
peer_connection_->SetRemoteDescription(desc->clone()->release(),
|
||||
observer.observer);
|
||||
}
|
||||
|
||||
std::unique_ptr<DataChannel> PeerConnection::create_data_channel(
|
||||
rust::String label,
|
||||
std::unique_ptr<NativeDataChannelInit> init) {
|
||||
auto result =
|
||||
peer_connection_->CreateDataChannelOrError(label.c_str(), init.get());
|
||||
|
||||
if (!result.ok()) {
|
||||
throw std::runtime_error(serialize_error(to_error(result.error())));
|
||||
}
|
||||
|
||||
return std::make_unique<DataChannel>(result.value());
|
||||
}
|
||||
|
||||
void PeerConnection::add_ice_candidate(
|
||||
std::unique_ptr<IceCandidate> candidate,
|
||||
NativeAddIceCandidateObserver& observer) {
|
||||
peer_connection_->AddIceCandidate(
|
||||
candidate->release(),
|
||||
[&](const webrtc::RTCError& err) { observer.OnComplete(to_error(err)); });
|
||||
}
|
||||
|
||||
std::unique_ptr<SessionDescription> PeerConnection::local_description() const {
|
||||
auto local_description = peer_connection_->local_description();
|
||||
if (local_description)
|
||||
return std::make_unique<SessionDescription>(local_description->Clone());
|
||||
|
||||
return std::unique_ptr<SessionDescription>();
|
||||
}
|
||||
|
||||
std::unique_ptr<SessionDescription> PeerConnection::remote_description() const {
|
||||
auto remote_description = peer_connection_->remote_description();
|
||||
if (remote_description)
|
||||
return std::make_unique<SessionDescription>(remote_description->Clone());
|
||||
|
||||
return std::unique_ptr<SessionDescription>();
|
||||
}
|
||||
|
||||
SignalingState PeerConnection::signaling_state() const {
|
||||
return static_cast<SignalingState>(peer_connection_->signaling_state());
|
||||
}
|
||||
|
||||
IceGatheringState PeerConnection::ice_gathering_state() const {
|
||||
return static_cast<IceGatheringState>(peer_connection_->ice_gathering_state());
|
||||
}
|
||||
|
||||
void PeerConnection::close() {
|
||||
peer_connection_->Close();
|
||||
}
|
||||
|
||||
|
||||
// AddIceCandidateObserver
|
||||
|
||||
NativeAddIceCandidateObserver::NativeAddIceCandidateObserver(
|
||||
rust::Box<AddIceCandidateObserverWrapper> observer)
|
||||
: observer_(std::move(observer)) {}
|
||||
|
||||
void NativeAddIceCandidateObserver::OnComplete(const RTCError& error) {
|
||||
observer_->on_complete(error);
|
||||
}
|
||||
|
||||
std::unique_ptr<NativeAddIceCandidateObserver>
|
||||
create_native_add_ice_candidate_observer(
|
||||
rust::Box<AddIceCandidateObserverWrapper> observer) {
|
||||
return std::make_unique<NativeAddIceCandidateObserver>(std::move(observer));
|
||||
}
|
||||
|
||||
// PeerConnectionObserver
|
||||
|
||||
NativePeerConnectionObserver::NativePeerConnectionObserver(
|
||||
rust::Box<PeerConnectionObserverWrapper> observer)
|
||||
: observer_(std::move(observer)) {}
|
||||
|
||||
void NativePeerConnectionObserver::OnSignalingChange(
|
||||
webrtc::PeerConnectionInterface::SignalingState new_state) {
|
||||
observer_->on_signaling_change(static_cast<SignalingState>(new_state));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnAddStream(
|
||||
rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) {
|
||||
observer_->on_add_stream(std::make_unique<MediaStreamInterface>(stream));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnRemoveStream(
|
||||
rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) {
|
||||
observer_->on_remove_stream(std::make_unique<MediaStreamInterface>(stream));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnDataChannel(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) {
|
||||
observer_->on_data_channel(std::make_unique<DataChannel>(data_channel));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnRenegotiationNeeded() {
|
||||
observer_->on_renegotiation_needed();
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnNegotiationNeededEvent(uint32_t event_id) {
|
||||
observer_->on_negotiation_needed_event(event_id);
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnIceConnectionChange(
|
||||
webrtc::PeerConnectionInterface::IceConnectionState new_state) {
|
||||
observer_->on_ice_connection_change(
|
||||
static_cast<IceConnectionState>(new_state));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnStandardizedIceConnectionChange(
|
||||
webrtc::PeerConnectionInterface::IceConnectionState new_state) {
|
||||
observer_->on_standardized_ice_connection_change(
|
||||
static_cast<IceConnectionState>(new_state));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnConnectionChange(
|
||||
webrtc::PeerConnectionInterface::PeerConnectionState new_state) {
|
||||
observer_->on_connection_change(static_cast<PeerConnectionState>(new_state));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnIceGatheringChange(
|
||||
webrtc::PeerConnectionInterface::IceGatheringState new_state) {
|
||||
observer_->on_ice_gathering_change(static_cast<IceGatheringState>(new_state));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnIceCandidate(
|
||||
const webrtc::IceCandidateInterface* candidate) {
|
||||
auto new_candidate = webrtc::CreateIceCandidate(candidate->sdp_mid(),
|
||||
candidate->sdp_mline_index(),
|
||||
candidate->candidate());
|
||||
observer_->on_ice_candidate(
|
||||
std::make_unique<IceCandidate>(std::move(new_candidate)));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnIceCandidateError(
|
||||
const std::string& address,
|
||||
int port,
|
||||
const std::string& url,
|
||||
int error_code,
|
||||
const std::string& error_text) {
|
||||
observer_->on_ice_candidate_error(address, port, url, error_code, error_text);
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnIceCandidatesRemoved(
|
||||
const std::vector<cricket::Candidate>& candidates) {
|
||||
rust::Vec<CandidatePtr> vec;
|
||||
|
||||
for (const auto& item : candidates) {
|
||||
vec.push_back(CandidatePtr{std::make_unique<Candidate>(item)});
|
||||
}
|
||||
|
||||
observer_->on_ice_candidates_removed(std::move(vec));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnIceConnectionReceivingChange(
|
||||
bool receiving) {
|
||||
observer_->on_ice_connection_receiving_change(receiving);
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnIceSelectedCandidatePairChanged(
|
||||
const cricket::CandidatePairChangeEvent& event) {
|
||||
CandidatePairChangeEvent e;
|
||||
e.selected_candidate_pair.local =
|
||||
std::make_unique<Candidate>(event.selected_candidate_pair.local);
|
||||
e.selected_candidate_pair.remote =
|
||||
std::make_unique<Candidate>(event.selected_candidate_pair.remote);
|
||||
e.last_data_received_ms = event.last_data_received_ms;
|
||||
e.reason = event.reason;
|
||||
e.estimated_disconnected_time_ms = event.estimated_disconnected_time_ms;
|
||||
|
||||
observer_->on_ice_selected_candidate_pair_changed(std::move(e));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnAddTrack(
|
||||
rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver,
|
||||
const std::vector<rtc::scoped_refptr<webrtc::MediaStreamInterface>>&
|
||||
streams) {
|
||||
rust::Vec<MediaStreamPtr> vec;
|
||||
|
||||
for (const auto& item : streams) {
|
||||
vec.push_back(MediaStreamPtr{std::make_unique<MediaStreamInterface>(item)});
|
||||
}
|
||||
|
||||
observer_->on_add_track(std::make_unique<RtpReceiver>(receiver),
|
||||
std::move(vec));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnTrack(
|
||||
rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver) {
|
||||
observer_->on_track(std::make_unique<RtpTransceiver>(transceiver));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnRemoveTrack(
|
||||
rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) {
|
||||
observer_->on_remove_track(std::make_unique<RtpReceiver>(receiver));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnInterestingUsage(int usage_pattern) {
|
||||
observer_->on_interesting_usage(usage_pattern);
|
||||
}
|
||||
|
||||
std::unique_ptr<NativePeerConnectionObserver>
|
||||
create_native_peer_connection_observer(
|
||||
rust::Box<PeerConnectionObserverWrapper> observer) {
|
||||
return std::make_unique<NativePeerConnectionObserver>(std::move(observer));
|
||||
}
|
||||
} // namespace livekit
|
||||
@@ -0,0 +1,490 @@
|
||||
use cxx::UniquePtr;
|
||||
|
||||
use crate::candidate::ffi::Candidate;
|
||||
use crate::data_channel::ffi::DataChannel;
|
||||
use crate::jsep::ffi::IceCandidate;
|
||||
use crate::media_stream_interface::ffi::MediaStreamInterface;
|
||||
use crate::rtc_error::ffi::RTCError;
|
||||
use crate::rtp_receiver::ffi::RtpReceiver;
|
||||
use crate::rtp_transceiver::ffi::RtpTransceiver;
|
||||
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
struct CandidatePair {
|
||||
local: UniquePtr<Candidate>,
|
||||
remote: UniquePtr<Candidate>,
|
||||
}
|
||||
|
||||
struct CandidatePairChangeEvent {
|
||||
selected_candidate_pair: CandidatePair,
|
||||
last_data_received_ms: i64,
|
||||
reason: String,
|
||||
estimated_disconnected_time_ms: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum PeerConnectionState {
|
||||
New,
|
||||
Connecting,
|
||||
Connected,
|
||||
Disconnected,
|
||||
Failed,
|
||||
Closed,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum SignalingState {
|
||||
Stable,
|
||||
HaveLocalOffer,
|
||||
HaveLocalPrAnswer,
|
||||
HaveRemoteOffer,
|
||||
HaveRemotePrAnswer,
|
||||
Closed,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum IceConnectionState {
|
||||
IceConnectionNew,
|
||||
IceConnectionChecking,
|
||||
IceConnectionConnected,
|
||||
IceConnectionCompleted,
|
||||
IceConnectionFailed,
|
||||
IceConnectionDisconnected,
|
||||
IceConnectionClosed,
|
||||
IceConnectionMax,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum IceGatheringState {
|
||||
IceGatheringNew,
|
||||
IceGatheringGathering,
|
||||
IceGatheringComplete,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RTCOfferAnswerOptions {
|
||||
offer_to_receive_video: i32,
|
||||
offer_to_receive_audio: i32,
|
||||
voice_activity_detection: bool,
|
||||
ice_restart: bool,
|
||||
use_rtp_mux: bool,
|
||||
raw_packetization_for_video: bool,
|
||||
num_simulcast_layers: i32,
|
||||
use_obsolete_sctp_sdp: bool,
|
||||
}
|
||||
|
||||
// Wrapper to opaque C++ objects
|
||||
// https://github.com/dtolnay/cxx/issues/741
|
||||
struct MediaStreamPtr {
|
||||
pub ptr: UniquePtr<MediaStreamInterface>,
|
||||
}
|
||||
|
||||
struct CandidatePtr {
|
||||
pub ptr: UniquePtr<Candidate>,
|
||||
}
|
||||
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/peer_connection.h");
|
||||
include!("livekit/jsep.h");
|
||||
include!("livekit/data_channel.h");
|
||||
include!("livekit/rtp_receiver.h");
|
||||
include!("livekit/rtp_transceiver.h");
|
||||
include!("livekit/media_stream_interface.h");
|
||||
include!("livekit/candidate.h");
|
||||
include!("libwebrtc-sys/src/rtc_error.rs.h");
|
||||
|
||||
type RTCError = crate::rtc_error::ffi::RTCError;
|
||||
type Candidate = crate::candidate::ffi::Candidate;
|
||||
type IceCandidate = crate::jsep::ffi::IceCandidate;
|
||||
type DataChannel = crate::data_channel::ffi::DataChannel;
|
||||
type RtpReceiver = crate::rtp_receiver::ffi::RtpReceiver;
|
||||
type RtpTransceiver = crate::rtp_transceiver::ffi::RtpTransceiver;
|
||||
type MediaStreamInterface = crate::media_stream_interface::ffi::MediaStreamInterface;
|
||||
type NativeCreateSdpObserverHandle = crate::jsep::ffi::NativeCreateSdpObserverHandle;
|
||||
type NativeSetLocalSdpObserverHandle = crate::jsep::ffi::NativeSetLocalSdpObserverHandle;
|
||||
type NativeSetRemoteSdpObserverHandle = crate::jsep::ffi::NativeSetRemoteSdpObserverHandle;
|
||||
type NativeDataChannelInit = crate::data_channel::ffi::NativeDataChannelInit;
|
||||
type SessionDescription = crate::jsep::ffi::SessionDescription;
|
||||
|
||||
type NativeAddIceCandidateObserver;
|
||||
type NativePeerConnectionObserver;
|
||||
type PeerConnection;
|
||||
|
||||
/// SAFETY
|
||||
/// The observer must live as long as the operation ends
|
||||
unsafe fn create_offer(
|
||||
self: Pin<&mut PeerConnection>,
|
||||
observer: Pin<&mut NativeCreateSdpObserverHandle>,
|
||||
options: RTCOfferAnswerOptions,
|
||||
);
|
||||
|
||||
/// SAFETY
|
||||
/// The observer must live as long as the operation ends
|
||||
unsafe fn create_answer(
|
||||
self: Pin<&mut PeerConnection>,
|
||||
observer: Pin<&mut NativeCreateSdpObserverHandle>,
|
||||
options: RTCOfferAnswerOptions,
|
||||
);
|
||||
|
||||
/// SAFETY
|
||||
/// The observer must live as long as the operation ends
|
||||
unsafe fn set_local_description(
|
||||
self: Pin<&mut PeerConnection>,
|
||||
desc: UniquePtr<SessionDescription>,
|
||||
observer: Pin<&mut NativeSetLocalSdpObserverHandle>,
|
||||
);
|
||||
|
||||
/// SAFETY
|
||||
/// The observer must live as long as the operation ends
|
||||
unsafe fn set_remote_description(
|
||||
self: Pin<&mut PeerConnection>,
|
||||
desc: UniquePtr<SessionDescription>,
|
||||
observer: Pin<&mut NativeSetRemoteSdpObserverHandle>,
|
||||
);
|
||||
|
||||
fn create_data_channel(
|
||||
self: Pin<&mut PeerConnection>,
|
||||
label: String,
|
||||
init: UniquePtr<NativeDataChannelInit>,
|
||||
) -> Result<UniquePtr<DataChannel>>;
|
||||
|
||||
fn add_ice_candidate(
|
||||
self: Pin<&mut PeerConnection>,
|
||||
candidate: UniquePtr<IceCandidate>,
|
||||
observer: Pin<&mut NativeAddIceCandidateObserver>,
|
||||
);
|
||||
|
||||
fn local_description(self: &PeerConnection) -> UniquePtr<SessionDescription>;
|
||||
|
||||
fn remote_description(self: &PeerConnection) -> UniquePtr<SessionDescription>;
|
||||
|
||||
fn signaling_state(self: &PeerConnection) -> SignalingState;
|
||||
|
||||
fn ice_gathering_state(self: &PeerConnection) -> IceGatheringState;
|
||||
|
||||
fn close(self: Pin<&mut PeerConnection>);
|
||||
|
||||
fn create_native_peer_connection_observer(
|
||||
observer: Box<PeerConnectionObserverWrapper>,
|
||||
) -> UniquePtr<NativePeerConnectionObserver>;
|
||||
|
||||
fn create_native_add_ice_candidate_observer(
|
||||
observer: Box<AddIceCandidateObserverWrapper>,
|
||||
) -> UniquePtr<NativeAddIceCandidateObserver>;
|
||||
|
||||
fn _unique_peer_connection() -> UniquePtr<PeerConnection>; // Ignore
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
type AddIceCandidateObserverWrapper;
|
||||
|
||||
fn on_complete(self: &AddIceCandidateObserverWrapper, error: RTCError);
|
||||
|
||||
type PeerConnectionObserverWrapper;
|
||||
|
||||
fn on_signaling_change(self: &PeerConnectionObserverWrapper, new_state: SignalingState);
|
||||
fn on_add_stream(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
stream: UniquePtr<MediaStreamInterface>,
|
||||
);
|
||||
fn on_remove_stream(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
stream: UniquePtr<MediaStreamInterface>,
|
||||
);
|
||||
fn on_data_channel(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
data_channel: UniquePtr<DataChannel>,
|
||||
);
|
||||
fn on_renegotiation_needed(self: &PeerConnectionObserverWrapper);
|
||||
fn on_negotiation_needed_event(self: &PeerConnectionObserverWrapper, event: u32);
|
||||
fn on_ice_connection_change(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
new_state: IceConnectionState,
|
||||
);
|
||||
fn on_standardized_ice_connection_change(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
new_state: IceConnectionState,
|
||||
);
|
||||
fn on_connection_change(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
new_state: PeerConnectionState,
|
||||
);
|
||||
fn on_ice_gathering_change(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
new_state: IceGatheringState,
|
||||
);
|
||||
fn on_ice_candidate(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
candidate: UniquePtr<IceCandidate>,
|
||||
);
|
||||
fn on_ice_candidate_error(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
address: String,
|
||||
port: i32,
|
||||
url: String,
|
||||
error_code: i32,
|
||||
error_text: String,
|
||||
);
|
||||
fn on_ice_candidates_removed(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
removed: Vec<CandidatePtr>,
|
||||
);
|
||||
fn on_ice_connection_receiving_change(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
receiving: bool,
|
||||
);
|
||||
fn on_ice_selected_candidate_pair_changed(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
event: CandidatePairChangeEvent,
|
||||
);
|
||||
fn on_add_track(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
receiver: UniquePtr<RtpReceiver>,
|
||||
streams: Vec<MediaStreamPtr>,
|
||||
);
|
||||
fn on_track(self: &PeerConnectionObserverWrapper, transceiver: UniquePtr<RtpTransceiver>);
|
||||
fn on_remove_track(self: &PeerConnectionObserverWrapper, receiver: UniquePtr<RtpReceiver>);
|
||||
fn on_interesting_usage(self: &PeerConnectionObserverWrapper, usage_pattern: i32);
|
||||
}
|
||||
}
|
||||
|
||||
// https://webrtc.github.io/webrtc-org/native-code/native-apis/
|
||||
unsafe impl Send for ffi::PeerConnection {}
|
||||
unsafe impl Sync for ffi::PeerConnection {}
|
||||
|
||||
unsafe impl Send for ffi::NativePeerConnectionObserver {}
|
||||
unsafe impl Sync for ffi::NativePeerConnectionObserver {}
|
||||
|
||||
unsafe impl Sync for ffi::NativeAddIceCandidateObserver {}
|
||||
unsafe impl Send for ffi::NativeAddIceCandidateObserver {}
|
||||
|
||||
unsafe impl Sync for ffi::NativeSetRemoteSdpObserverHandle {}
|
||||
unsafe impl Send for ffi::NativeSetRemoteSdpObserverHandle {}
|
||||
|
||||
unsafe impl Sync for ffi::NativeSetLocalSdpObserverHandle {}
|
||||
unsafe impl Send for ffi::NativeSetLocalSdpObserverHandle {}
|
||||
|
||||
unsafe impl Sync for ffi::NativeCreateSdpObserverHandle {}
|
||||
unsafe impl Send for ffi::NativeCreateSdpObserverHandle {}
|
||||
|
||||
impl Default for ffi::RTCOfferAnswerOptions {
|
||||
/*
|
||||
static const int kUndefined = -1;
|
||||
static const int kMaxOfferToReceiveMedia = 1;
|
||||
static const int kOfferToReceiveMediaTrue = 1;
|
||||
*/
|
||||
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
offer_to_receive_video: -1,
|
||||
offer_to_receive_audio: -1,
|
||||
voice_activity_detection: true,
|
||||
ice_restart: false,
|
||||
use_rtp_mux: true,
|
||||
raw_packetization_for_video: false,
|
||||
num_simulcast_layers: 1,
|
||||
use_obsolete_sctp_sdp: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AddIceCandidateObserver: Send {
|
||||
fn on_complete(&self, error: RTCError);
|
||||
}
|
||||
|
||||
pub struct AddIceCandidateObserverWrapper {
|
||||
observer: Box<dyn AddIceCandidateObserver>,
|
||||
}
|
||||
|
||||
impl AddIceCandidateObserverWrapper {
|
||||
pub fn new(observer: Box<dyn AddIceCandidateObserver>) -> Self {
|
||||
Self { observer }
|
||||
}
|
||||
|
||||
fn on_complete(&self, error: RTCError) {
|
||||
self.observer.on_complete(error);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PeerConnectionObserver: Send + Sync {
|
||||
fn on_signaling_change(&self, new_state: ffi::SignalingState);
|
||||
fn on_add_stream(&self, stream: UniquePtr<MediaStreamInterface>);
|
||||
fn on_remove_stream(&self, stream: UniquePtr<MediaStreamInterface>);
|
||||
fn on_data_channel(&self, data_channel: UniquePtr<DataChannel>);
|
||||
fn on_renegotiation_needed(&self);
|
||||
fn on_negotiation_needed_event(&self, event: u32);
|
||||
fn on_ice_connection_change(&self, new_state: ffi::IceConnectionState);
|
||||
fn on_standardized_ice_connection_change(&self, new_state: ffi::IceConnectionState);
|
||||
fn on_connection_change(&self, new_state: ffi::PeerConnectionState);
|
||||
fn on_ice_gathering_change(&self, new_state: ffi::IceGatheringState);
|
||||
fn on_ice_candidate(&self, candidate: UniquePtr<IceCandidate>);
|
||||
fn on_ice_candidate_error(
|
||||
&self,
|
||||
address: String,
|
||||
port: i32,
|
||||
url: String,
|
||||
error_code: i32,
|
||||
error_text: String,
|
||||
);
|
||||
fn on_ice_candidates_removed(&self, removed: Vec<UniquePtr<Candidate>>);
|
||||
fn on_ice_connection_receiving_change(&self, receiving: bool);
|
||||
fn on_ice_selected_candidate_pair_changed(&self, event: ffi::CandidatePairChangeEvent);
|
||||
fn on_add_track(
|
||||
&self,
|
||||
receiver: UniquePtr<RtpReceiver>,
|
||||
streams: Vec<UniquePtr<MediaStreamInterface>>,
|
||||
);
|
||||
fn on_track(&self, transceiver: UniquePtr<RtpTransceiver>);
|
||||
fn on_remove_track(&self, receiver: UniquePtr<RtpReceiver>);
|
||||
fn on_interesting_usage(&self, usage_pattern: i32);
|
||||
}
|
||||
|
||||
// Thread safety is handled inside PeerConnectionObserver
|
||||
pub struct PeerConnectionObserverWrapper {
|
||||
observer: *mut dyn PeerConnectionObserver,
|
||||
}
|
||||
|
||||
impl PeerConnectionObserverWrapper {
|
||||
/// SAFETY
|
||||
/// PeerConnectionObserver must lives as long as PeerConnectionObserverWrapper does
|
||||
pub unsafe fn new(observer: *mut dyn PeerConnectionObserver) -> Self {
|
||||
Self { observer }
|
||||
}
|
||||
|
||||
fn on_signaling_change(&self, new_state: ffi::SignalingState) {
|
||||
unsafe {
|
||||
(*self.observer).on_signaling_change(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_add_stream(&self, stream: UniquePtr<MediaStreamInterface>) {
|
||||
unsafe {
|
||||
(*self.observer).on_add_stream(stream);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_remove_stream(&self, stream: UniquePtr<MediaStreamInterface>) {
|
||||
unsafe {
|
||||
(*self.observer).on_remove_stream(stream);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_data_channel(&self, data_channel: UniquePtr<DataChannel>) {
|
||||
unsafe {
|
||||
(*self.observer).on_data_channel(data_channel);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_renegotiation_needed(&self) {
|
||||
unsafe {
|
||||
(*self.observer).on_renegotiation_needed();
|
||||
}
|
||||
}
|
||||
|
||||
fn on_negotiation_needed_event(&self, event: u32) {
|
||||
unsafe {
|
||||
(*self.observer).on_negotiation_needed_event(event);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_connection_change(&self, new_state: ffi::IceConnectionState) {
|
||||
unsafe {
|
||||
(*self.observer).on_ice_connection_change(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_standardized_ice_connection_change(&self, new_state: ffi::IceConnectionState) {
|
||||
unsafe {
|
||||
(*self.observer).on_standardized_ice_connection_change(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_connection_change(&self, new_state: ffi::PeerConnectionState) {
|
||||
unsafe {
|
||||
(*self.observer).on_connection_change(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_gathering_change(&self, new_state: ffi::IceGatheringState) {
|
||||
unsafe {
|
||||
(*self.observer).on_ice_gathering_change(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_candidate(&self, candidate: UniquePtr<IceCandidate>) {
|
||||
unsafe {
|
||||
(*self.observer).on_ice_candidate(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_candidate_error(
|
||||
&self,
|
||||
address: String,
|
||||
port: i32,
|
||||
url: String,
|
||||
error_code: i32,
|
||||
error_text: String,
|
||||
) {
|
||||
unsafe {
|
||||
(*self.observer).on_ice_candidate_error(address, port, url, error_code, error_text);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_candidates_removed(&self, removed: Vec<ffi::CandidatePtr>) {
|
||||
let mut vec = Vec::new();
|
||||
|
||||
for v in removed {
|
||||
vec.push(v.ptr);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
(*self.observer).on_ice_candidates_removed(vec);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_connection_receiving_change(&self, receiving: bool) {
|
||||
unsafe {
|
||||
(*self.observer).on_ice_connection_receiving_change(receiving);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_selected_candidate_pair_changed(&self, event: ffi::CandidatePairChangeEvent) {
|
||||
unsafe {
|
||||
(*self.observer).on_ice_selected_candidate_pair_changed(event);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_add_track(&self, receiver: UniquePtr<RtpReceiver>, streams: Vec<ffi::MediaStreamPtr>) {
|
||||
let mut vec = Vec::new();
|
||||
|
||||
for v in streams {
|
||||
vec.push(v.ptr);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
(*self.observer).on_add_track(receiver, vec);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_track(&self, transceiver: UniquePtr<RtpTransceiver>) {
|
||||
unsafe {
|
||||
(*self.observer).on_track(transceiver);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_remove_track(&self, receiver: UniquePtr<RtpReceiver>) {
|
||||
unsafe {
|
||||
(*self.observer).on_remove_track(receiver);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_interesting_usage(&self, usage_pattern: i32) {
|
||||
unsafe {
|
||||
(*self.observer).on_interesting_usage(usage_pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// Created by Théo Monnom on 03/08/2022.
|
||||
//
|
||||
|
||||
#include "livekit/peer_connection_factory.h"
|
||||
|
||||
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
|
||||
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
|
||||
#include "api/rtc_event_log/rtc_event_log_factory.h"
|
||||
#include "api/task_queue/default_task_queue_factory.h"
|
||||
#include "api/video_codecs/builtin_video_decoder_factory.h"
|
||||
#include "api/video_codecs/builtin_video_encoder_factory.h"
|
||||
#include "libwebrtc-sys/src/peer_connection_factory.rs.h"
|
||||
#include "livekit/rtc_error.h"
|
||||
#include "media/engine/webrtc_media_engine.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
PeerConnectionFactory::PeerConnectionFactory() {
|
||||
rtc::LogMessage::LogToDebug(rtc::LS_INFO);
|
||||
RTC_LOG(LS_INFO) << "PeerConnectionFactory::PeerConnectionFactory()";
|
||||
|
||||
network_thread_ = rtc::Thread::CreateWithSocketServer();
|
||||
network_thread_->SetName("network_thread", &network_thread_);
|
||||
network_thread_->Start();
|
||||
worker_thread_ = rtc::Thread::Create();
|
||||
worker_thread_->SetName("worker_thread", &worker_thread_);
|
||||
worker_thread_->Start();
|
||||
signaling_thread_ = rtc::Thread::Create();
|
||||
signaling_thread_->SetName("signaling_thread", &signaling_thread_);
|
||||
signaling_thread_->Start();
|
||||
|
||||
webrtc::PeerConnectionFactoryDependencies dependencies;
|
||||
dependencies.network_thread = network_thread_.get();
|
||||
dependencies.worker_thread = worker_thread_.get();
|
||||
dependencies.signaling_thread = signaling_thread_.get();
|
||||
dependencies.task_queue_factory = webrtc::CreateDefaultTaskQueueFactory();
|
||||
dependencies.event_log_factory = std::make_unique<webrtc::RtcEventLogFactory>(
|
||||
dependencies.task_queue_factory.get());
|
||||
|
||||
cricket::MediaEngineDependencies media_deps;
|
||||
media_deps.task_queue_factory = dependencies.task_queue_factory.get();
|
||||
media_deps.video_encoder_factory = webrtc::CreateBuiltinVideoEncoderFactory();
|
||||
media_deps.video_decoder_factory = webrtc::CreateBuiltinVideoDecoderFactory();
|
||||
media_deps.audio_encoder_factory = webrtc::CreateBuiltinAudioEncoderFactory();
|
||||
media_deps.audio_decoder_factory = webrtc::CreateBuiltinAudioDecoderFactory();
|
||||
|
||||
dependencies.media_engine = cricket::CreateMediaEngine(std::move(media_deps));
|
||||
|
||||
peer_factory_ =
|
||||
webrtc::CreateModularPeerConnectionFactory(std::move(dependencies));
|
||||
|
||||
if (peer_factory_.get() == nullptr) {
|
||||
RTC_LOG_ERR(LS_ERROR) << "Failed to create PeerConnectionFactory";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerConnection> PeerConnectionFactory::create_peer_connection(
|
||||
std::unique_ptr<webrtc::PeerConnectionInterface::RTCConfiguration> config,
|
||||
NativePeerConnectionObserver& observer) const {
|
||||
webrtc::PeerConnectionDependencies deps{&observer};
|
||||
auto result =
|
||||
peer_factory_->CreatePeerConnectionOrError(*config, std::move(deps));
|
||||
|
||||
if (!result.ok()) {
|
||||
throw std::runtime_error(serialize_error(to_error(result.error())));
|
||||
}
|
||||
|
||||
return std::make_unique<PeerConnection>(result.value());
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerConnectionFactory> create_peer_connection_factory() {
|
||||
return std::make_unique<PeerConnectionFactory>();
|
||||
}
|
||||
|
||||
std::unique_ptr<NativeRTCConfiguration> create_rtc_configuration(
|
||||
RTCConfiguration conf) {
|
||||
auto rtc =
|
||||
std::make_unique<webrtc::PeerConnectionInterface::RTCConfiguration>();
|
||||
for (auto& item : conf.ice_servers) {
|
||||
webrtc::PeerConnectionInterface::IceServer ice_server;
|
||||
ice_server.username = item.username.c_str();
|
||||
ice_server.password = item.password.c_str();
|
||||
|
||||
for (auto& url : item.urls) {
|
||||
ice_server.urls.emplace_back(url.c_str());
|
||||
}
|
||||
rtc->servers.push_back(ice_server);
|
||||
rtc->continual_gathering_policy =
|
||||
static_cast<webrtc::PeerConnectionInterface::ContinualGatheringPolicy>(
|
||||
conf.continual_gathering_policy);
|
||||
|
||||
rtc->type = static_cast<webrtc::PeerConnectionInterface::IceTransportsType>(
|
||||
conf.ice_transport_type);
|
||||
}
|
||||
|
||||
return rtc;
|
||||
}
|
||||
} // namespace livekit
|
||||
@@ -0,0 +1,61 @@
|
||||
use std::any::Any;
|
||||
|
||||
use crate::jsep::CreateSdpObserver;
|
||||
use crate::peer_connection::PeerConnectionObserver;
|
||||
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ICEServer {
|
||||
pub urls: Vec<String>,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum ContinualGatheringPolicy {
|
||||
GatherOnce,
|
||||
GatherContinually,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum IceTransportsType {
|
||||
None,
|
||||
Relay,
|
||||
NoHost,
|
||||
All,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RTCConfiguration {
|
||||
pub ice_servers: Vec<ICEServer>,
|
||||
pub continual_gathering_policy: ContinualGatheringPolicy,
|
||||
pub ice_transport_type: IceTransportsType,
|
||||
}
|
||||
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/peer_connection_factory.h");
|
||||
|
||||
type PeerConnection = crate::peer_connection::ffi::PeerConnection;
|
||||
type NativePeerConnectionObserver =
|
||||
crate::peer_connection::ffi::NativePeerConnectionObserver;
|
||||
type PeerConnectionFactory;
|
||||
type NativeRTCConfiguration;
|
||||
|
||||
fn create_peer_connection_factory() -> UniquePtr<PeerConnectionFactory>;
|
||||
fn create_rtc_configuration(conf: RTCConfiguration) -> UniquePtr<NativeRTCConfiguration>;
|
||||
|
||||
/// SAFETY
|
||||
/// The observer must live as long as the PeerConnection
|
||||
unsafe fn create_peer_connection(
|
||||
self: &PeerConnectionFactory,
|
||||
config: UniquePtr<NativeRTCConfiguration>,
|
||||
observer: Pin<&mut NativePeerConnectionObserver>,
|
||||
) -> Result<UniquePtr<PeerConnection>>;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for ffi::PeerConnectionFactory {}
|
||||
unsafe impl Sync for ffi::PeerConnectionFactory {}
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// Created by theom on 04/09/2022.
|
||||
//
|
||||
|
||||
#include "livekit/rtc_error.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace livekit {
|
||||
|
||||
RTCError to_error(const webrtc::RTCError& error) {
|
||||
RTCError lk_error;
|
||||
lk_error.error_detail = static_cast<RTCErrorDetailType>(error.error_detail());
|
||||
lk_error.error_type = static_cast<RTCErrorType>(error.type());
|
||||
lk_error.has_sctp_cause_code = error.sctp_cause_code().has_value();
|
||||
lk_error.sctp_cause_code = error.sctp_cause_code().value_or(0);
|
||||
lk_error.message = error.message();
|
||||
return lk_error;
|
||||
}
|
||||
|
||||
std::string serialize_error(const RTCError& error) {
|
||||
std::stringstream ss;
|
||||
ss << std::hex << std::setfill('0');
|
||||
ss << std::setw(8) << (uint32_t)error.error_type;
|
||||
ss << std::setw(8) << (uint32_t)error.error_detail;
|
||||
ss << std::setw(2) << (uint16_t)error.has_sctp_cause_code;
|
||||
ss << std::setw(4) << (uint16_t)error.sctp_cause_code;
|
||||
ss << std::dec << std::setw(1) << std::string(error.message);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
#ifdef LIVEKIT_TEST
|
||||
rust::String serialize_deserialize() {
|
||||
RTCError lk_error;
|
||||
lk_error.error_type = RTCErrorType::InternalError;
|
||||
lk_error.error_detail = RTCErrorDetailType::DataChannelFailure;
|
||||
lk_error.has_sctp_cause_code = true;
|
||||
lk_error.sctp_cause_code = 24;
|
||||
lk_error.message = "this is not a test, I repeat, this is not a test";
|
||||
return serialize_error(lk_error);
|
||||
}
|
||||
|
||||
void throw_error() {
|
||||
RTCError lk_error;
|
||||
lk_error.error_type = RTCErrorType::InvalidModification;
|
||||
lk_error.error_detail = RTCErrorDetailType::None;
|
||||
lk_error.has_sctp_cause_code = false;
|
||||
lk_error.sctp_cause_code = 0;
|
||||
lk_error.message = "exception is thrown!";
|
||||
throw std::runtime_error(serialize_error(lk_error));
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace livekit
|
||||
@@ -0,0 +1,127 @@
|
||||
use crate::rtc_error::ffi::RTCErrorType;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
// cxx doesn't support custom Exception type, so we serialize RTCError inside the cxx::Exception "what" string
|
||||
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum RTCErrorType {
|
||||
None,
|
||||
UnsupportedOperation,
|
||||
UnsupportedParameter,
|
||||
InvalidParameter,
|
||||
InvalidRange,
|
||||
SyntaxError,
|
||||
InvalidState,
|
||||
InvalidModification,
|
||||
NetworkError,
|
||||
ResourceExhausted,
|
||||
InternalError,
|
||||
OperationErrorWithData,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum RTCErrorDetailType {
|
||||
None,
|
||||
DataChannelFailure,
|
||||
DtlsFailure,
|
||||
FingerprintFailure,
|
||||
SctpFailure,
|
||||
SdpSyntaxError,
|
||||
HardwareEncoderNotAvailable,
|
||||
HardwareEncoderError,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RTCError {
|
||||
pub error_type: RTCErrorType,
|
||||
pub message: String,
|
||||
pub error_detail: RTCErrorDetailType,
|
||||
pub has_sctp_cause_code: bool,
|
||||
// cxx doesn't support the Option trait
|
||||
pub sctp_cause_code: u16,
|
||||
}
|
||||
}
|
||||
|
||||
impl ffi::RTCError {
|
||||
/// # Safety
|
||||
/// The value must be correctly encoded
|
||||
pub unsafe fn from(value: &str) -> Self {
|
||||
// Parse the hex encoded error from c++
|
||||
let error_type = u32::from_str_radix(&value[0..8], 16).unwrap();
|
||||
let error_detail = u32::from_str_radix(&value[8..16], 16).unwrap();
|
||||
let has_scp_cause_code = u8::from_str_radix(&value[16..18], 16).unwrap();
|
||||
let sctp_cause_code = u16::from_str_radix(&value[18..22], 16).unwrap();
|
||||
let message = String::from(&value[22..]); // msg isn't encoded
|
||||
|
||||
Self {
|
||||
error_type: unsafe { std::mem::transmute(error_type) },
|
||||
error_detail: unsafe { std::mem::transmute(error_detail) },
|
||||
sctp_cause_code,
|
||||
has_sctp_cause_code: has_scp_cause_code == 1,
|
||||
message,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ok(&self) -> bool {
|
||||
return self.error_type == RTCErrorType::None;
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ffi::RTCError {}
|
||||
|
||||
impl Display for ffi::RTCError {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"RtcError occurred {:?}: {}",
|
||||
self.error_type, self.message
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::rtc_error::ffi::{RTCError, RTCErrorDetailType, RTCErrorType};
|
||||
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/rtc_error.h");
|
||||
|
||||
fn serialize_deserialize() -> String;
|
||||
fn throw_error() -> Result<()>;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_deserialize() {
|
||||
let str = ffi::serialize_deserialize();
|
||||
let error = unsafe { RTCError::from(&str) };
|
||||
|
||||
assert_eq!(error.error_type, RTCErrorType::InternalError);
|
||||
assert_eq!(error.error_detail, RTCErrorDetailType::DataChannelFailure);
|
||||
assert_eq!(error.has_sctp_cause_code, true);
|
||||
assert_eq!(error.sctp_cause_code, 24);
|
||||
assert_eq!(
|
||||
error.message,
|
||||
"this is not a test, I repeat, this is not a test"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn throw_error() {
|
||||
let exc: cxx::Exception = ffi::throw_error().err().unwrap();
|
||||
let error = unsafe { RTCError::from(exc.what()) };
|
||||
|
||||
assert_eq!(error.error_type, RTCErrorType::InvalidModification);
|
||||
assert_eq!(error.error_detail, RTCErrorDetailType::None);
|
||||
assert_eq!(error.has_sctp_cause_code, false);
|
||||
assert_eq!(error.sctp_cause_code, 0);
|
||||
assert_eq!(error.message, "exception is thrown!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
//
|
||||
// Created by Théo Monnom on 01/09/2022.
|
||||
//
|
||||
|
||||
#include "livekit/rtp_receiver.h"
|
||||
|
||||
namespace livekit {
|
||||
RtpReceiver::RtpReceiver(
|
||||
rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver)
|
||||
: receiver_(std::move(receiver)) {}
|
||||
} // namespace livekit
|
||||
@@ -0,0 +1,12 @@
|
||||
use cxx::UniquePtr;
|
||||
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/rtp_receiver.h");
|
||||
|
||||
type RtpReceiver;
|
||||
|
||||
fn _unique_rtp_receiver() -> UniquePtr<RtpReceiver>; // Ignore
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
//
|
||||
// Created by Théo Monnom on 02/09/2022.
|
||||
//
|
||||
|
||||
#include "livekit/rtp_transceiver.h"
|
||||
|
||||
namespace livekit {
|
||||
RtpTransceiver::RtpTransceiver(
|
||||
rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver)
|
||||
: transceiver_(std::move(transceiver)) {}
|
||||
} // namespace livekit
|
||||
@@ -0,0 +1,12 @@
|
||||
use cxx::UniquePtr;
|
||||
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/rtp_transceiver.h");
|
||||
|
||||
type RtpTransceiver;
|
||||
|
||||
fn _unique_rtp_transceiver() -> UniquePtr<RtpTransceiver>; // Ignore
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Created by theom on 18/09/2022.
|
||||
//
|
||||
|
||||
#include "livekit/webrtc.h"
|
||||
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace livekit {
|
||||
RTCRuntime::RTCRuntime() {
|
||||
RTC_LOG(LS_INFO) << "RTCRuntime()";
|
||||
RTC_CHECK(rtc::InitializeSSL()) << "Failed to InitializeSSL()";
|
||||
}
|
||||
|
||||
RTCRuntime::~RTCRuntime() {
|
||||
RTC_LOG(LS_INFO) << "~RTCRuntime()";
|
||||
RTC_CHECK(rtc::CleanupSSL()) << "Failed to CleanupSSL()";
|
||||
}
|
||||
|
||||
std::unique_ptr<RTCRuntime> create_rtc_runtime() {
|
||||
return std::make_unique<RTCRuntime>();
|
||||
}
|
||||
} // namespace livekit
|
||||
@@ -0,0 +1,15 @@
|
||||
use cxx::UniquePtr;
|
||||
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/webrtc.h");
|
||||
|
||||
type RTCRuntime;
|
||||
|
||||
fn create_rtc_runtime() -> UniquePtr<RTCRuntime>;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for ffi::RTCRuntime {}
|
||||
unsafe impl Sync for ffi::RTCRuntime {}
|
||||
@@ -0,0 +1,172 @@
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use cxx::UniquePtr;
|
||||
use log::trace;
|
||||
|
||||
use libwebrtc_sys::data_channel as sys_dc;
|
||||
pub use sys_dc::ffi::Priority;
|
||||
|
||||
pub struct DataChannel {
|
||||
cxx_handle: UniquePtr<sys_dc::ffi::DataChannel>,
|
||||
observer: Box<InternalDataChannelObserver>,
|
||||
|
||||
// Keep alive for C++
|
||||
native_observer: UniquePtr<sys_dc::ffi::NativeDataChannelObserver>,
|
||||
}
|
||||
|
||||
impl Debug for DataChannel {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
write!(f, "DataChannel [{:?}]", self.label())
|
||||
}
|
||||
}
|
||||
|
||||
impl DataChannel {
|
||||
pub(crate) fn new(cxx_handle: UniquePtr<sys_dc::ffi::DataChannel>) -> Self {
|
||||
let mut observer = Box::new(InternalDataChannelObserver::default());
|
||||
|
||||
let mut dc = unsafe {
|
||||
Self {
|
||||
cxx_handle,
|
||||
native_observer: sys_dc::ffi::create_native_data_channel_observer(Box::new(
|
||||
sys_dc::DataChannelObserverWrapper::new(&mut *observer),
|
||||
)),
|
||||
observer,
|
||||
}
|
||||
};
|
||||
|
||||
unsafe {
|
||||
dc.cxx_handle
|
||||
.pin_mut()
|
||||
.register_observer(dc.native_observer.pin_mut());
|
||||
}
|
||||
|
||||
dc
|
||||
}
|
||||
|
||||
pub fn send(&mut self, data: &[u8], binary: bool) -> bool {
|
||||
let buffer = sys_dc::ffi::DataBuffer {
|
||||
ptr: data.as_ptr(),
|
||||
len: data.len(),
|
||||
binary,
|
||||
};
|
||||
self.cxx_handle.pin_mut().send(&buffer)
|
||||
}
|
||||
|
||||
pub fn label(&self) -> String {
|
||||
self.cxx_handle.label()
|
||||
}
|
||||
|
||||
pub fn close(&mut self) {
|
||||
self.cxx_handle.pin_mut().close();
|
||||
}
|
||||
|
||||
pub fn on_state_change(&mut self, handler: OnStateChangeHandler) {
|
||||
*self.observer.on_state_change_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_message(&mut self, handler: OnMessageHandler) {
|
||||
*self.observer.on_message_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_buffer(&mut self, handler: OnBufferedAmountChangeHandler) {
|
||||
*self
|
||||
.observer
|
||||
.on_buffered_amount_change_handler
|
||||
.lock()
|
||||
.unwrap() = Some(handler);
|
||||
}
|
||||
}
|
||||
|
||||
pub type OnStateChangeHandler = Box<dyn FnMut() + Send + Sync>;
|
||||
pub type OnMessageHandler = Box<dyn FnMut(&[u8], bool) + Send + Sync>;
|
||||
// data, is_binary
|
||||
pub type OnBufferedAmountChangeHandler = Box<dyn FnMut(u64) + Send + Sync>;
|
||||
|
||||
struct InternalDataChannelObserver {
|
||||
on_state_change_handler: Arc<Mutex<Option<OnStateChangeHandler>>>,
|
||||
on_message_handler: Arc<Mutex<Option<OnMessageHandler>>>,
|
||||
on_buffered_amount_change_handler: Arc<Mutex<Option<OnBufferedAmountChangeHandler>>>,
|
||||
}
|
||||
|
||||
impl sys_dc::DataChannelObserver for InternalDataChannelObserver {
|
||||
fn on_state_change(&self) {
|
||||
trace!("DataChannel: on_state_change");
|
||||
let mut handler = self.on_state_change_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f();
|
||||
}
|
||||
}
|
||||
|
||||
fn on_message(&self, data: &[u8], is_binary: bool) {
|
||||
trace!("DataChannel: on_message");
|
||||
let mut handler = self.on_message_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(data, is_binary);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_buffered_amount_change(&self, sent_data_size: u64) {
|
||||
trace!("DataChannel: on_buffered_amount_change");
|
||||
let mut handler = self.on_buffered_amount_change_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(sent_data_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for InternalDataChannelObserver {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
on_state_change_handler: Arc::new(Default::default()),
|
||||
on_message_handler: Arc::new(Default::default()),
|
||||
on_buffered_amount_change_handler: Arc::new(Default::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DataChannelInit {
|
||||
#[deprecated]
|
||||
pub reliable: bool,
|
||||
pub ordered: bool,
|
||||
pub max_retransmit_time: Option<i32>,
|
||||
pub max_retransmits: Option<i32>,
|
||||
pub protocol: String,
|
||||
pub negotiated: bool,
|
||||
pub id: i32,
|
||||
pub priority: Option<Priority>,
|
||||
}
|
||||
|
||||
impl Default for DataChannelInit {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
reliable: false,
|
||||
ordered: true,
|
||||
max_retransmit_time: None,
|
||||
max_retransmits: None,
|
||||
protocol: "".to_string(),
|
||||
negotiated: false,
|
||||
id: -1,
|
||||
priority: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DataChannelInit> for sys_dc::ffi::DataChannelInit {
|
||||
fn from(init: DataChannelInit) -> Self {
|
||||
Self {
|
||||
reliable: init.reliable,
|
||||
ordered: init.ordered,
|
||||
has_max_retransmit_time: init.max_retransmit_time.is_some(),
|
||||
max_retransmit_time: init.max_retransmit_time.unwrap_or_default(),
|
||||
has_max_retransmits: init.max_retransmits.is_some(),
|
||||
max_retransmits: init.max_retransmits.unwrap_or_default(),
|
||||
protocol: init.protocol,
|
||||
negotiated: init.negotiated,
|
||||
id: init.id,
|
||||
has_priority: init.priority.is_some(),
|
||||
priority: init.priority.unwrap_or(Priority::Low),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
use cxx::UniquePtr;
|
||||
use libwebrtc_sys::jsep as sys_jsep;
|
||||
|
||||
pub use sys_jsep::ffi::{SdpType, SdpParseError};
|
||||
|
||||
// TODO Maybe we can replace that by a serialized IceCandidateInit
|
||||
#[derive(Debug)]
|
||||
pub struct IceCandidate {
|
||||
cxx_handle: UniquePtr<sys_jsep::ffi::IceCandidate>,
|
||||
}
|
||||
|
||||
impl IceCandidate {
|
||||
pub fn from(sdp_mid: &str, sdp_mline_index: i32, sdp: &str) -> Result<IceCandidate, SdpParseError> {
|
||||
let res = sys_jsep::ffi::create_ice_candidate(sdp_mid.to_string(), sdp_mline_index, sdp.to_string());
|
||||
|
||||
match res {
|
||||
Ok(cxx_handle) => Ok(IceCandidate::new(cxx_handle)),
|
||||
Err(e) => Err(unsafe { SdpParseError::from(e.what()) }),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new(cxx_handle: UniquePtr<sys_jsep::ffi::IceCandidate>) -> Self {
|
||||
Self { cxx_handle }
|
||||
}
|
||||
|
||||
pub(crate) fn release(self) -> UniquePtr<sys_jsep::ffi::IceCandidate> {
|
||||
self.cxx_handle
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for IceCandidate {
|
||||
fn to_string(&self) -> String {
|
||||
self.cxx_handle.stringify()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SessionDescription {
|
||||
cxx_handle: UniquePtr<sys_jsep::ffi::SessionDescription>,
|
||||
}
|
||||
|
||||
impl SessionDescription {
|
||||
pub fn from(sdp_type: SdpType, description: &str) -> Result<SessionDescription, SdpParseError> {
|
||||
let res = sys_jsep::ffi::create_session_description(sdp_type, description.to_string());
|
||||
|
||||
match res {
|
||||
Ok(cxx_handle) => Ok(SessionDescription::new(cxx_handle)),
|
||||
Err(e) => Err(unsafe { SdpParseError::from(e.what()) }),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new(cxx_handle: UniquePtr<sys_jsep::ffi::SessionDescription>) -> Self {
|
||||
Self { cxx_handle }
|
||||
}
|
||||
|
||||
pub(crate) fn release(self) -> UniquePtr<sys_jsep::ffi::SessionDescription> {
|
||||
self.cxx_handle
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for SessionDescription {
|
||||
fn to_string(&self) -> String {
|
||||
self.cxx_handle.stringify()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for SessionDescription {
|
||||
fn clone(&self) -> Self {
|
||||
SessionDescription::new(self.cxx_handle.clone())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
pub mod data_channel;
|
||||
pub mod jsep;
|
||||
pub mod media_stream;
|
||||
pub mod peer_connection;
|
||||
pub mod peer_connection_factory;
|
||||
pub mod rtc_error;
|
||||
pub mod rtp_receiver;
|
||||
pub mod rtp_transceiver;
|
||||
pub mod webrtc;
|
||||
@@ -0,0 +1,2 @@
|
||||
#[derive(Debug)]
|
||||
pub struct MediaStream {}
|
||||
@@ -0,0 +1,706 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use cxx::UniquePtr;
|
||||
use log::trace;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use libwebrtc_sys::data_channel as sys_dc;
|
||||
use libwebrtc_sys::jsep as sys_jsep;
|
||||
use libwebrtc_sys::peer_connection as sys_pc;
|
||||
pub use libwebrtc_sys::peer_connection::ffi::IceConnectionState;
|
||||
pub use libwebrtc_sys::peer_connection::ffi::IceGatheringState;
|
||||
pub use libwebrtc_sys::peer_connection::ffi::PeerConnectionState;
|
||||
pub use libwebrtc_sys::peer_connection::ffi::RTCOfferAnswerOptions;
|
||||
pub use libwebrtc_sys::peer_connection::ffi::SignalingState;
|
||||
|
||||
use crate::data_channel::{DataChannel, DataChannelInit};
|
||||
use crate::jsep::{IceCandidate, SessionDescription};
|
||||
use crate::media_stream::MediaStream;
|
||||
use crate::rtc_error::RTCError;
|
||||
use crate::rtp_receiver::RtpReceiver;
|
||||
use crate::rtp_transceiver::RtpTransceiver;
|
||||
|
||||
pub struct PeerConnection {
|
||||
cxx_handle: UniquePtr<sys_pc::ffi::PeerConnection>,
|
||||
observer: Box<InternalObserver>,
|
||||
|
||||
// Keep alive for C++
|
||||
native_observer: UniquePtr<sys_pc::ffi::NativePeerConnectionObserver>,
|
||||
}
|
||||
|
||||
impl PeerConnection {
|
||||
pub(crate) fn new(
|
||||
cxx_handle: UniquePtr<sys_pc::ffi::PeerConnection>,
|
||||
observer: Box<InternalObserver>,
|
||||
native_observer: UniquePtr<sys_pc::ffi::NativePeerConnectionObserver>,
|
||||
) -> Self {
|
||||
Self {
|
||||
cxx_handle,
|
||||
observer,
|
||||
native_observer,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_offer(&mut self, options: RTCOfferAnswerOptions) -> Result<SessionDescription, RTCError> {
|
||||
let (tx, mut rx) = mpsc::channel(1);
|
||||
|
||||
let wrapper =
|
||||
sys_jsep::CreateSdpObserverWrapper::new(Box::new(InternalCreateSdpObserver { tx }));
|
||||
let mut native_wrapper =
|
||||
sys_jsep::ffi::create_native_create_sdp_observer(Box::new(wrapper));
|
||||
|
||||
unsafe {
|
||||
self.cxx_handle
|
||||
.pin_mut()
|
||||
.create_offer(native_wrapper.pin_mut(), options);
|
||||
}
|
||||
|
||||
rx.recv().await.unwrap()
|
||||
}
|
||||
|
||||
pub async fn create_answer(&mut self, options: RTCOfferAnswerOptions) -> Result<SessionDescription, RTCError> {
|
||||
let (tx, mut rx) = mpsc::channel(1);
|
||||
|
||||
let wrapper =
|
||||
sys_jsep::CreateSdpObserverWrapper::new(Box::new(InternalCreateSdpObserver { tx }));
|
||||
let mut native_wrapper =
|
||||
sys_jsep::ffi::create_native_create_sdp_observer(Box::new(wrapper));
|
||||
|
||||
unsafe {
|
||||
self.cxx_handle
|
||||
.pin_mut()
|
||||
.create_answer(native_wrapper.pin_mut(), options);
|
||||
}
|
||||
|
||||
rx.recv().await.unwrap()
|
||||
}
|
||||
|
||||
pub async fn set_local_description(
|
||||
&mut self,
|
||||
desc: SessionDescription,
|
||||
) -> Result<(), RTCError> {
|
||||
let (tx, mut rx) = mpsc::channel(1);
|
||||
let wrapper =
|
||||
sys_jsep::SetLocalSdpObserverWrapper::new(Box::new(InternalSetLocalSdpObserver { tx }));
|
||||
let mut native_wrapper =
|
||||
sys_jsep::ffi::create_native_set_local_sdp_observer(Box::new(wrapper));
|
||||
|
||||
unsafe {
|
||||
self.cxx_handle
|
||||
.pin_mut()
|
||||
.set_local_description(desc.release(), native_wrapper.pin_mut());
|
||||
}
|
||||
|
||||
rx.recv().await.unwrap()
|
||||
}
|
||||
|
||||
pub async fn set_remote_description(
|
||||
&mut self,
|
||||
desc: SessionDescription,
|
||||
) -> Result<(), RTCError> {
|
||||
let (tx, mut rx) = mpsc::channel(1);
|
||||
let wrapper =
|
||||
sys_jsep::SetRemoteSdpObserverWrapper::new(Box::new(InternalSetRemoteSdpObserver {
|
||||
tx,
|
||||
}));
|
||||
let mut native_wrapper =
|
||||
sys_jsep::ffi::create_native_set_remote_sdp_observer(Box::new(wrapper));
|
||||
|
||||
unsafe {
|
||||
self.cxx_handle
|
||||
.pin_mut()
|
||||
.set_remote_description(desc.release(), native_wrapper.pin_mut());
|
||||
}
|
||||
|
||||
rx.recv().await.unwrap()
|
||||
}
|
||||
|
||||
pub fn create_data_channel(
|
||||
&mut self,
|
||||
label: &str,
|
||||
init: DataChannelInit,
|
||||
) -> Result<DataChannel, RTCError> {
|
||||
let native_init = sys_dc::ffi::create_data_channel_init(init.into());
|
||||
let res = self
|
||||
.cxx_handle
|
||||
.pin_mut()
|
||||
.create_data_channel(label.to_string(), native_init);
|
||||
|
||||
match res {
|
||||
Ok(cxx_handle) => Ok(DataChannel::new(cxx_handle)),
|
||||
Err(e) => Err(unsafe { RTCError::from(e.what()) }),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(theomonnom) Use IceCandidateInit instead of IceCandidate
|
||||
pub async fn add_ice_candidate(&mut self, candidate: IceCandidate) -> Result<(), RTCError> {
|
||||
let (tx, mut rx) = mpsc::channel(1);
|
||||
let observer = sys_pc::AddIceCandidateObserverWrapper::new(Box::new(InternalAddIceCandidateObserver {
|
||||
tx,
|
||||
}));
|
||||
|
||||
let mut native_observer =
|
||||
sys_pc::ffi::create_native_add_ice_candidate_observer(Box::new(observer));
|
||||
self.cxx_handle
|
||||
.pin_mut()
|
||||
.add_ice_candidate(candidate.release(), native_observer.pin_mut());
|
||||
|
||||
rx.recv().await.unwrap()
|
||||
}
|
||||
|
||||
pub fn local_description(&self) -> Option<SessionDescription> {
|
||||
let local_description = self.cxx_handle.local_description();
|
||||
if local_description.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(SessionDescription::new(local_description))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remote_description(&self) -> Option<SessionDescription> {
|
||||
let remote_description = self.cxx_handle.remote_description();
|
||||
if remote_description.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(SessionDescription::new(remote_description))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn signaling_state(&self) -> SignalingState {
|
||||
self.cxx_handle.signaling_state()
|
||||
}
|
||||
|
||||
pub fn ice_gathering_state(&self) -> IceGatheringState {
|
||||
self.cxx_handle.ice_gathering_state()
|
||||
}
|
||||
|
||||
pub fn close(&mut self) {
|
||||
self.cxx_handle.pin_mut().close();
|
||||
}
|
||||
|
||||
pub fn on_signaling_change(&mut self, handler: OnSignalingChangeHandler) {
|
||||
*self.observer.on_signaling_change_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_add_stream(&mut self, handler: OnAddStreamHandler) {
|
||||
*self.observer.on_add_stream_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_remove_stream(&mut self, handler: OnRemoveStreamHandler) {
|
||||
*self.observer.on_remove_stream_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_data_channel(&mut self, handler: OnDataChannelHandler) {
|
||||
*self.observer.on_data_channel_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_renegotiation_needed(&mut self, handler: OnRenegotiationNeededHandler) {
|
||||
*self
|
||||
.observer
|
||||
.on_renegotiation_needed_handler
|
||||
.lock()
|
||||
.unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_ice_connection_change(&mut self, handler: OnIceConnectionChangeHandler) {
|
||||
*self
|
||||
.observer
|
||||
.on_ice_connection_change_handler
|
||||
.lock()
|
||||
.unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_standardized_ice_connection_change(
|
||||
&mut self,
|
||||
handler: OnStandardizedIceConnectionChangeHandler,
|
||||
) {
|
||||
*self
|
||||
.observer
|
||||
.on_standardized_ice_connection_change_handler
|
||||
.lock()
|
||||
.unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_connection_change(&mut self, handler: OnConnectionChangeHandler) {
|
||||
*self.observer.on_connection_change_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_ice_gathering_change(&mut self, handler: OnIceGatheringChangeHandler) {
|
||||
*self
|
||||
.observer
|
||||
.on_ice_gathering_change_handler
|
||||
.lock()
|
||||
.unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_ice_candidate(&mut self, handler: OnIceCandidateHandler) {
|
||||
*self.observer.on_ice_candidate_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_ice_candidate_error(&mut self, handler: OnIceCandidateErrorHandler) {
|
||||
*self.observer.on_ice_candidate_error_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_ice_candidates_removed(&mut self, handler: OnIceCandidatesRemovedHandler) {
|
||||
*self
|
||||
.observer
|
||||
.on_ice_candidates_removed_handler
|
||||
.lock()
|
||||
.unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_ice_connection_receiving_change(
|
||||
&mut self,
|
||||
handler: OnIceConnectionReceivingChangeHandler,
|
||||
) {
|
||||
*self
|
||||
.observer
|
||||
.on_ice_connection_receiving_change_handler
|
||||
.lock()
|
||||
.unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_ice_selected_candidate_pair_changed(
|
||||
&mut self,
|
||||
handler: OnIceSelectedCandidatePairChangedHandler,
|
||||
) {
|
||||
*self
|
||||
.observer
|
||||
.on_ice_selected_candidate_pair_changed_handler
|
||||
.lock()
|
||||
.unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_add_track(&mut self, handler: OnAddTrackHandler) {
|
||||
*self.observer.on_add_track_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_track(&mut self, handler: OnTrackHandler) {
|
||||
*self.observer.on_track_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_remove_track(&mut self, handler: OnRemoveTrackHandler) {
|
||||
*self.observer.on_remove_track_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
|
||||
pub fn on_interesting_usage(&mut self, handler: OnInterestingUsageHandler) {
|
||||
*self.observer.on_interesting_usage_handler.lock().unwrap() = Some(handler);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// SetLocalSdpObserver
|
||||
|
||||
struct InternalAddIceCandidateObserver {
|
||||
tx: mpsc::Sender<Result<(), RTCError>>,
|
||||
}
|
||||
|
||||
impl sys_pc::AddIceCandidateObserver for InternalAddIceCandidateObserver {
|
||||
fn on_complete(&self, error: RTCError) {
|
||||
let res = if error.ok() { Ok(()) } else { Err(error) };
|
||||
let _ = self.tx.blocking_send(res);
|
||||
}
|
||||
}
|
||||
|
||||
// CreateSdpObserver
|
||||
|
||||
struct InternalCreateSdpObserver {
|
||||
tx: mpsc::Sender<Result<SessionDescription, RTCError>>,
|
||||
}
|
||||
|
||||
impl sys_jsep::CreateSdpObserver for InternalCreateSdpObserver {
|
||||
fn on_success(
|
||||
&self,
|
||||
session_description: UniquePtr<libwebrtc_sys::jsep::ffi::SessionDescription>,
|
||||
) {
|
||||
let _ = self.tx.blocking_send(Ok(SessionDescription::new(session_description)));
|
||||
}
|
||||
|
||||
fn on_failure(&self, error: RTCError) {
|
||||
let _ = self.tx.blocking_send(Err(error));
|
||||
}
|
||||
}
|
||||
|
||||
// SetLocalSdpObserver
|
||||
|
||||
struct InternalSetLocalSdpObserver {
|
||||
tx: mpsc::Sender<Result<(), RTCError>>,
|
||||
}
|
||||
|
||||
impl sys_jsep::SetLocalSdpObserver for InternalSetLocalSdpObserver {
|
||||
fn on_set_local_description_complete(&self, error: RTCError) {
|
||||
let res = if error.ok() { Ok(()) } else { Err(error) };
|
||||
let _ = self.tx.blocking_send(res);
|
||||
}
|
||||
}
|
||||
|
||||
// SetRemoteSdpObserver
|
||||
|
||||
struct InternalSetRemoteSdpObserver {
|
||||
tx: mpsc::Sender<Result<(), RTCError>>,
|
||||
}
|
||||
|
||||
impl sys_jsep::SetRemoteSdpObserver for InternalSetRemoteSdpObserver {
|
||||
fn on_set_remote_description_complete(&self, error: RTCError) {
|
||||
let res = if error.ok() { Ok(()) } else { Err(error) };
|
||||
let _ = self.tx.blocking_send(res);
|
||||
}
|
||||
}
|
||||
|
||||
// PeerConnectionObserver
|
||||
|
||||
// TODO(theomonnom) Should we return futures?
|
||||
pub type OnSignalingChangeHandler = Box<dyn FnMut(SignalingState) + Send + Sync>;
|
||||
pub type OnAddStreamHandler = Box<dyn FnMut(MediaStream) + Send + Sync>;
|
||||
pub type OnRemoveStreamHandler = Box<dyn FnMut(MediaStream) + Send + Sync>;
|
||||
pub type OnDataChannelHandler = Box<dyn FnMut(DataChannel) + Send + Sync>;
|
||||
pub type OnRenegotiationNeededHandler = Box<dyn FnMut() + Send + Sync>;
|
||||
pub type OnNegotiationNeededEventHandler = Box<dyn FnMut(u32) + Send + Sync>;
|
||||
pub type OnIceConnectionChangeHandler = Box<dyn FnMut(IceConnectionState) + Send + Sync>;
|
||||
pub type OnStandardizedIceConnectionChangeHandler =
|
||||
Box<dyn FnMut(IceConnectionState) + Send + Sync>;
|
||||
pub type OnConnectionChangeHandler = Box<dyn FnMut(PeerConnectionState) + Send + Sync>;
|
||||
pub type OnIceGatheringChangeHandler = Box<dyn FnMut(IceGatheringState) + Send + Sync>;
|
||||
pub type OnIceCandidateHandler = Box<dyn FnMut(IceCandidate) + Send + Sync>;
|
||||
pub type OnIceCandidateErrorHandler =
|
||||
Box<dyn FnMut(String, i32, String, i32, String) + Send + Sync>;
|
||||
pub type OnIceCandidatesRemovedHandler = Box<dyn FnMut(Vec<IceCandidate>) + Send + Sync>;
|
||||
pub type OnIceConnectionReceivingChangeHandler = Box<dyn FnMut(bool) + Send + Sync>;
|
||||
pub type OnIceSelectedCandidatePairChangedHandler =
|
||||
Box<dyn FnMut(libwebrtc_sys::peer_connection::ffi::CandidatePairChangeEvent) + Send + Sync>;
|
||||
pub type OnAddTrackHandler = Box<dyn FnMut(RtpReceiver, Vec<MediaStream>) + Send + Sync>;
|
||||
pub type OnTrackHandler = Box<dyn FnMut(RtpTransceiver) + Send + Sync>;
|
||||
pub type OnRemoveTrackHandler = Box<dyn FnMut(RtpReceiver) + Send + Sync>;
|
||||
pub type OnInterestingUsageHandler = Box<dyn FnMut(i32) + Send + Sync>;
|
||||
|
||||
pub(crate) struct InternalObserver {
|
||||
on_signaling_change_handler: Arc<Mutex<Option<OnSignalingChangeHandler>>>,
|
||||
on_add_stream_handler: Arc<Mutex<Option<OnAddStreamHandler>>>,
|
||||
on_remove_stream_handler: Arc<Mutex<Option<OnRemoveStreamHandler>>>,
|
||||
on_data_channel_handler: Arc<Mutex<Option<OnDataChannelHandler>>>,
|
||||
on_renegotiation_needed_handler: Arc<Mutex<Option<OnRenegotiationNeededHandler>>>,
|
||||
on_negotiation_needed_event_handler: Arc<Mutex<Option<OnNegotiationNeededEventHandler>>>,
|
||||
on_ice_connection_change_handler: Arc<Mutex<Option<OnIceConnectionChangeHandler>>>,
|
||||
on_standardized_ice_connection_change_handler:
|
||||
Arc<Mutex<Option<OnStandardizedIceConnectionChangeHandler>>>,
|
||||
on_connection_change_handler: Arc<Mutex<Option<OnConnectionChangeHandler>>>,
|
||||
on_ice_gathering_change_handler: Arc<Mutex<Option<OnIceGatheringChangeHandler>>>,
|
||||
on_ice_candidate_handler: Arc<Mutex<Option<OnIceCandidateHandler>>>,
|
||||
on_ice_candidate_error_handler: Arc<Mutex<Option<OnIceCandidateErrorHandler>>>,
|
||||
on_ice_candidates_removed_handler: Arc<Mutex<Option<OnIceCandidatesRemovedHandler>>>,
|
||||
on_ice_connection_receiving_change_handler:
|
||||
Arc<Mutex<Option<OnIceConnectionReceivingChangeHandler>>>,
|
||||
on_ice_selected_candidate_pair_changed_handler:
|
||||
Arc<Mutex<Option<OnIceSelectedCandidatePairChangedHandler>>>,
|
||||
on_add_track_handler: Arc<Mutex<Option<OnAddTrackHandler>>>,
|
||||
on_track_handler: Arc<Mutex<Option<OnTrackHandler>>>,
|
||||
on_remove_track_handler: Arc<Mutex<Option<OnRemoveTrackHandler>>>,
|
||||
on_interesting_usage_handler: Arc<Mutex<Option<OnInterestingUsageHandler>>>,
|
||||
}
|
||||
|
||||
impl Default for InternalObserver {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
on_signaling_change_handler: Arc::new(Default::default()),
|
||||
on_add_stream_handler: Arc::new(Default::default()),
|
||||
on_remove_stream_handler: Arc::new(Default::default()),
|
||||
on_data_channel_handler: Arc::new(Default::default()),
|
||||
on_renegotiation_needed_handler: Arc::new(Default::default()),
|
||||
on_negotiation_needed_event_handler: Arc::new(Default::default()),
|
||||
on_ice_connection_change_handler: Arc::new(Default::default()),
|
||||
on_standardized_ice_connection_change_handler: Arc::new(Default::default()),
|
||||
on_connection_change_handler: Arc::new(Default::default()),
|
||||
on_ice_gathering_change_handler: Arc::new(Default::default()),
|
||||
on_ice_candidate_handler: Arc::new(Default::default()),
|
||||
on_ice_candidate_error_handler: Arc::new(Default::default()),
|
||||
on_ice_candidates_removed_handler: Arc::new(Default::default()),
|
||||
on_ice_connection_receiving_change_handler: Arc::new(Default::default()),
|
||||
on_ice_selected_candidate_pair_changed_handler: Arc::new(Default::default()),
|
||||
on_add_track_handler: Arc::new(Default::default()),
|
||||
on_track_handler: Arc::new(Default::default()),
|
||||
on_remove_track_handler: Arc::new(Default::default()),
|
||||
on_interesting_usage_handler: Arc::new(Default::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Observers are being called on the Signaling Thread
|
||||
impl sys_pc::PeerConnectionObserver for InternalObserver {
|
||||
fn on_signaling_change(&self, new_state: SignalingState) {
|
||||
trace!("on_signaling_change, {:?}", new_state);
|
||||
let mut handler = self.on_signaling_change_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_add_stream(
|
||||
&self,
|
||||
stream: UniquePtr<libwebrtc_sys::media_stream_interface::ffi::MediaStreamInterface>,
|
||||
) {
|
||||
trace!("on_add_stream");
|
||||
let mut handler = self.on_add_stream_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
// TODO(theomonnom)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_remove_stream(
|
||||
&self,
|
||||
stream: UniquePtr<libwebrtc_sys::media_stream_interface::ffi::MediaStreamInterface>,
|
||||
) {
|
||||
trace!("on_remove_stream");
|
||||
let mut handler = self.on_remove_stream_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
// TODO(theomonnom)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_data_channel(
|
||||
&self,
|
||||
data_channel: UniquePtr<libwebrtc_sys::data_channel::ffi::DataChannel>,
|
||||
) {
|
||||
trace!("on_data_channel");
|
||||
let mut handler = self.on_data_channel_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(DataChannel::new(data_channel));
|
||||
}
|
||||
}
|
||||
|
||||
fn on_renegotiation_needed(&self) {
|
||||
trace!("on_renegotiation_needed");
|
||||
let mut handler = self.on_renegotiation_needed_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f();
|
||||
}
|
||||
}
|
||||
|
||||
fn on_negotiation_needed_event(&self, event: u32) {
|
||||
trace!("on_negotiation_needed_event");
|
||||
let mut handler = self.on_negotiation_needed_event_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(event);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_connection_change(&self, new_state: IceConnectionState) {
|
||||
trace!("on_ice_connection_change");
|
||||
let mut handler = self.on_ice_connection_change_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_standardized_ice_connection_change(&self, new_state: IceConnectionState) {
|
||||
trace!("on_standardized_ice_connection_change");
|
||||
let mut handler = self
|
||||
.on_standardized_ice_connection_change_handler
|
||||
.lock()
|
||||
.unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_connection_change(&self, new_state: PeerConnectionState) {
|
||||
trace!("on_connection_change");
|
||||
let mut handler = self.on_connection_change_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_gathering_change(&self, new_state: IceGatheringState) {
|
||||
trace!("on_ice_gathering_change");
|
||||
let mut handler = self.on_ice_gathering_change_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_candidate(&self, candidate: UniquePtr<libwebrtc_sys::jsep::ffi::IceCandidate>) {
|
||||
trace!("on_ice_candidate");
|
||||
let mut handler = self.on_ice_candidate_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(IceCandidate::new(candidate));
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_candidate_error(
|
||||
&self,
|
||||
address: String,
|
||||
port: i32,
|
||||
url: String,
|
||||
error_code: i32,
|
||||
error_text: String,
|
||||
) {
|
||||
trace!("on_ice_candidate_error");
|
||||
let mut handler = self.on_ice_candidate_error_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(address, port, url, error_code, error_text);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_candidates_removed(
|
||||
&self,
|
||||
removed: Vec<UniquePtr<libwebrtc_sys::candidate::ffi::Candidate>>,
|
||||
) {
|
||||
trace!("on_ice_candidates_removed");
|
||||
let mut handler = self.on_ice_candidates_removed_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
// TODO(theomonnom)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_connection_receiving_change(&self, receiving: bool) {
|
||||
trace!("on_ice_connection_receiving_change");
|
||||
let mut handler = self
|
||||
.on_ice_connection_receiving_change_handler
|
||||
.lock()
|
||||
.unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(receiving);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_selected_candidate_pair_changed(
|
||||
&self,
|
||||
event: libwebrtc_sys::peer_connection::ffi::CandidatePairChangeEvent,
|
||||
) {
|
||||
trace!("on_ice_selected_candidate_pair_changed");
|
||||
let mut handler = self
|
||||
.on_ice_selected_candidate_pair_changed_handler
|
||||
.lock()
|
||||
.unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(event);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_add_track(
|
||||
&self,
|
||||
receiver: UniquePtr<libwebrtc_sys::rtp_receiver::ffi::RtpReceiver>,
|
||||
streams: Vec<UniquePtr<libwebrtc_sys::media_stream_interface::ffi::MediaStreamInterface>>,
|
||||
) {
|
||||
trace!("on_add_track");
|
||||
let mut handler = self.on_add_track_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
// TODO(theomonnom)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_track(
|
||||
&self,
|
||||
transceiver: UniquePtr<libwebrtc_sys::rtp_transceiver::ffi::RtpTransceiver>,
|
||||
) {
|
||||
trace!("on_track");
|
||||
let mut handler = self.on_track_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
// TODO(theomonnom)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_remove_track(&self, receiver: UniquePtr<libwebrtc_sys::rtp_receiver::ffi::RtpReceiver>) {
|
||||
trace!("on_remove_track");
|
||||
let mut handler = self.on_remove_track_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
// TODO(theomonnom)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_interesting_usage(&self, usage_pattern: i32) {
|
||||
trace!("on_interesting_usage");
|
||||
let mut handler = self.on_interesting_usage_handler.lock().unwrap();
|
||||
if let Some(f) = handler.as_mut() {
|
||||
f(usage_pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use log::trace;
|
||||
use tokio::sync::mpsc;
|
||||
use libwebrtc_sys::peer_connection::ffi::RTCOfferAnswerOptions;
|
||||
use libwebrtc_sys::peer_connection_factory::ffi::{ContinualGatheringPolicy, IceTransportsType};
|
||||
|
||||
use crate::data_channel::{DataChannel, DataChannelInit};
|
||||
use crate::jsep::IceCandidate;
|
||||
use crate::peer_connection_factory::{ICEServer, PeerConnectionFactory, RTCConfiguration};
|
||||
use crate::webrtc::RTCRuntime;
|
||||
|
||||
fn init_log() {
|
||||
let _ = env_logger::builder().is_test(true).try_init();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_pc() {
|
||||
init_log();
|
||||
|
||||
let test = RTCRuntime::new();
|
||||
|
||||
let factory = PeerConnectionFactory::new();
|
||||
let config = RTCConfiguration {
|
||||
ice_servers: vec![ICEServer {
|
||||
urls: vec!["stun:stun1.l.google.com:19302".to_string()],
|
||||
username: "".into(),
|
||||
password: "".into(),
|
||||
}],
|
||||
continual_gathering_policy: ContinualGatheringPolicy::GatherOnce,
|
||||
ice_transport_type: IceTransportsType::All
|
||||
};
|
||||
|
||||
let mut bob = factory.create_peer_connection(config.clone()).unwrap();
|
||||
let mut alice = factory.create_peer_connection(config.clone()).unwrap();
|
||||
|
||||
let (bob_ice_tx, mut bob_ice_rx) = mpsc::channel::<IceCandidate>(16);
|
||||
let (alice_ice_tx, mut alice_ice_rx) = mpsc::channel::<IceCandidate>(16);
|
||||
let (alice_dc_tx, mut alice_dc_rx) = mpsc::channel::<DataChannel>(16);
|
||||
|
||||
bob.on_ice_candidate(Box::new(move |candidate| {
|
||||
bob_ice_tx.blocking_send(candidate).unwrap();
|
||||
}));
|
||||
|
||||
alice.on_ice_candidate(Box::new(move |candidate| {
|
||||
alice_ice_tx.blocking_send(candidate).unwrap();
|
||||
}));
|
||||
|
||||
alice.on_data_channel(Box::new(move |dc| {
|
||||
alice_dc_tx.blocking_send(dc).unwrap();
|
||||
}));
|
||||
|
||||
let mut bob_dc = bob
|
||||
.create_data_channel("test_dc", DataChannelInit::default())
|
||||
.unwrap();
|
||||
|
||||
let offer = bob.create_offer(RTCOfferAnswerOptions::default()).await.unwrap();
|
||||
trace!("Bob offer: {:?}", offer);
|
||||
bob.set_local_description(offer.clone()).await.unwrap();
|
||||
alice.set_remote_description(offer).await.unwrap();
|
||||
|
||||
let answer = alice.create_answer(RTCOfferAnswerOptions::default()).await.unwrap();
|
||||
trace!("Alice answer: {:?}", answer);
|
||||
alice.set_local_description(answer.clone()).await.unwrap();
|
||||
bob.set_remote_description(answer).await.unwrap();
|
||||
|
||||
let bob_ice = bob_ice_rx.recv().await.unwrap();
|
||||
let alice_ice = alice_ice_rx.recv().await.unwrap();
|
||||
|
||||
bob.add_ice_candidate(alice_ice).await.unwrap();
|
||||
alice.add_ice_candidate(bob_ice).await.unwrap();
|
||||
|
||||
let (data_tx, mut data_rx) = mpsc::channel::<String>(1);
|
||||
let mut alice_dc = alice_dc_rx.recv().await.unwrap();
|
||||
alice_dc.on_message(Box::new(move |data, is_binary| {
|
||||
data_tx
|
||||
.blocking_send(String::from_utf8_lossy(data).to_string())
|
||||
.unwrap();
|
||||
}));
|
||||
|
||||
assert!(bob_dc.send(b"This is a test", true));
|
||||
assert_eq!(data_rx.recv().await.unwrap(), "This is a test");
|
||||
|
||||
alice.close();
|
||||
bob.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use cxx::UniquePtr;
|
||||
|
||||
use libwebrtc_sys::peer_connection as sys_pc;
|
||||
use libwebrtc_sys::peer_connection_factory as sys_factory;
|
||||
pub use sys_factory::ffi::{
|
||||
ContinualGatheringPolicy, ICEServer, IceTransportsType, RTCConfiguration,
|
||||
};
|
||||
|
||||
use crate::peer_connection::{InternalObserver, PeerConnection};
|
||||
use crate::rtc_error::RTCError;
|
||||
|
||||
pub struct PeerConnectionFactory {
|
||||
cxx_handle: UniquePtr<sys_factory::ffi::PeerConnectionFactory>,
|
||||
}
|
||||
|
||||
impl PeerConnectionFactory {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
cxx_handle: sys_factory::ffi::create_peer_connection_factory(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_peer_connection(
|
||||
&self,
|
||||
config: RTCConfiguration,
|
||||
) -> Result<PeerConnection, RTCError> {
|
||||
let native_config = sys_factory::ffi::create_rtc_configuration(config);
|
||||
|
||||
unsafe {
|
||||
let mut observer = Box::new(InternalObserver::default());
|
||||
let mut native_observer = sys_pc::ffi::create_native_peer_connection_observer(
|
||||
Box::new(sys_pc::PeerConnectionObserverWrapper::new(&mut *observer)),
|
||||
);
|
||||
|
||||
let res = self
|
||||
.cxx_handle
|
||||
.create_peer_connection(native_config, native_observer.pin_mut());
|
||||
|
||||
match res {
|
||||
Ok(cxx_handle) => Ok(PeerConnection::new(cxx_handle, observer, native_observer)),
|
||||
Err(e) => Err(RTCError::from(e.what())),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// TODO(theomonnom) Wrap the RTCError ffi so we can use Option(u16)
|
||||
pub use libwebrtc_sys::rtc_error::ffi::RTCError;
|
||||
@@ -0,0 +1,2 @@
|
||||
#[derive(Debug)]
|
||||
pub struct RtpReceiver {}
|
||||
@@ -0,0 +1,2 @@
|
||||
#[derive(Debug)]
|
||||
pub struct RtpTransceiver {}
|
||||
@@ -0,0 +1,15 @@
|
||||
use cxx::UniquePtr;
|
||||
|
||||
use libwebrtc_sys::webrtc as sys_rtc;
|
||||
|
||||
pub struct RTCRuntime {
|
||||
cxx_handle: UniquePtr<sys_rtc::ffi::RTCRuntime>,
|
||||
}
|
||||
|
||||
impl RTCRuntime {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
cxx_handle: sys_rtc::ffi::create_rtc_runtime(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
|
||||
|
||||
# For now ( I don't want the libraries to be on GH )
|
||||
app/libs
|
||||
app/src/main/jniLibs
|
||||
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,60 +0,0 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 32
|
||||
|
||||
defaultConfig {
|
||||
applicationId "io.livekit.android"
|
||||
minSdk 26
|
||||
targetSdk 32
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
jniLibs.srcDirs = ['src/main/jniLibs']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
//implementation(files('libs/webrtc.jar'))
|
||||
|
||||
implementation 'net.java.dev.jna:jna:5.12.1@aar'
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.7.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.2'
|
||||
implementation 'com.google.android.material:material:1.6.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -1,24 +0,0 @@
|
||||
package io.livekit.android
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("io.livekit.android", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="io.livekit.android">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.LKAndroid"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.LKAndroid.NoActionBar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1,35 +0,0 @@
|
||||
package io.livekit.android
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.sun.jna.Library
|
||||
import com.sun.jna.Native
|
||||
import org.webrtc.PeerConnectionFactory
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
/*interface LKLib : Library {
|
||||
companion object {
|
||||
internal val Instance: LKLib by lazy {
|
||||
Native.load("livekit_native", LKLib::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
fun test_rust()
|
||||
}*/
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
println("Started activity")
|
||||
|
||||
val opt = PeerConnectionFactory.InitializationOptions.builder(applicationContext).setNativeLibraryName("livekit_native").createInitializationOptions()
|
||||
|
||||
PeerConnectionFactory.initialize(opt)
|
||||
val factory = PeerConnectionFactory.builder().createPeerConnectionFactory()
|
||||
|
||||
//LKLib.Instance.test_rust()
|
||||
|
||||
println("Called Rust fnc")
|
||||
println("Now, start investigating the JNI platform specific stuff !")
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="49.59793"
|
||||
android:startX="42.9492"
|
||||
android:endY="92.4963"
|
||||
android:endX="85.84757"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
@@ -1,170 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="108dp"
|
||||
android:width="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
</vector>
|
||||
@@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:theme="@style/Theme.LKAndroid.AppBarOverlay">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:popupTheme="@style/Theme.LKAndroid.PopupOverlay"
|
||||
android:layout_height="?attr/actionBarSize" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<include layout="@layout/content_main" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="@dimen/fab_margin"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:srcCompat="@android:drawable/ic_dialog_email" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_host_fragment_content_main"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:defaultNavHost="true"
|
||||
app:navGraph="@navigation/nav_graph" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,10 +0,0 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="io.livekit.android.MainActivity">
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:title="@string/action_settings"
|
||||
android:orderInCategory="100"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 982 B |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/nav_graph"
|
||||
app:startDestination="@id/FirstFragment">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/FirstFragment"
|
||||
android:name="io.livekit.android.FirstFragment"
|
||||
android:label="@string/first_fragment_label"
|
||||
tools:layout="@layout/fragment_first">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_FirstFragment_to_SecondFragment"
|
||||
app:destination="@id/SecondFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/SecondFragment"
|
||||
android:name="io.livekit.android.SecondFragment"
|
||||
android:label="@string/second_fragment_label"
|
||||
tools:layout="@layout/fragment_second">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_SecondFragment_to_FirstFragment"
|
||||
app:destination="@id/FirstFragment" />
|
||||
</fragment>
|
||||
</navigation>
|
||||
@@ -1,3 +0,0 @@
|
||||
<resources>
|
||||
<dimen name="fab_margin">48dp</dimen>
|
||||
</resources>
|
||||
@@ -1,16 +0,0 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.LKAndroid" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_200</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/black</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
@@ -1,3 +0,0 @@
|
||||
<resources>
|
||||
<dimen name="fab_margin">200dp</dimen>
|
||||
</resources>
|
||||
@@ -1,3 +0,0 @@
|
||||
<resources>
|
||||
<dimen name="fab_margin">48dp</dimen>
|
||||
</resources>
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
@@ -1,3 +0,0 @@
|
||||
<resources>
|
||||
<dimen name="fab_margin">16dp</dimen>
|
||||
</resources>
|
||||
@@ -1,12 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">LKAndroid</string>
|
||||
<string name="action_settings">Settings</string>
|
||||
<!-- Strings used for fragments for navigation -->
|
||||
<string name="first_fragment_label">First Fragment</string>
|
||||
<string name="second_fragment_label">Second Fragment</string>
|
||||
<string name="next">Next</string>
|
||||
<string name="previous">Previous</string>
|
||||
|
||||
<string name="hello_first_fragment">Hello first fragment</string>
|
||||
<string name="hello_second_fragment">Hello second fragment. Arg: %1$s</string>
|
||||
</resources>
|
||||
@@ -1,25 +0,0 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.LKAndroid" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_500</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
|
||||
<style name="Theme.LKAndroid.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.LKAndroid.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||
|
||||
<style name="Theme.LKAndroid.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
|
||||
</resources>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample backup rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/guide/topics/data/autobackup
|
||||
for details.
|
||||
Note: This file is ignored for devices older that API 31
|
||||
See https://developer.android.com/about/versions/12/backup-restore
|
||||
-->
|
||||
<full-backup-content>
|
||||
<!--
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="sharedpref" path="device.xml"/>
|
||||
-->
|
||||
</full-backup-content>
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample data extraction rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||
for details.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
-->
|
||||
</cloud-backup>
|
||||
<!--
|
||||
<device-transfer>
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
</device-transfer>
|
||||
-->
|
||||
</data-extraction-rules>
|
||||
@@ -1,11 +0,0 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id 'com.android.application' version '7.2.1' apply false
|
||||
id 'com.android.library' version '7.2.1' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.7.0' apply false
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||