Merge pull request #3 from livekit/theo/prototype

Initial client-sdk-native
This commit is contained in:
Théo Monnom
2022-09-26 20:06:13 +02:00
committed by GitHub
134 changed files with 6651 additions and 2315 deletions
+3 -8
View File
@@ -1,8 +1,3 @@
target
cmake-build-debug
.idea/
.cargo/
test/
libwebrtc/
libwebrtc_android/
CMakeLists.txt
/target
/.idea
/libwebrtc
+3
View File
@@ -0,0 +1,3 @@
[submodule "crates/livekit-core/protocol"]
path = crates/livekit-core/protocol
url = https://github.com/livekit/protocol
Generated
+1088 -32
View File
File diff suppressed because it is too large Load Diff
+9 -16
View File
@@ -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"
]
+2
View File
@@ -0,0 +1,2 @@
[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]
+935
View File
@@ -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"
+24
View File
@@ -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"
+12
View File
@@ -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(())
}
+10
View File
@@ -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;
+25
View File
@@ -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()");
}
}
+122
View File
@@ -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(())
}
}
+1
View File
@@ -0,0 +1 @@
pub struct Room {}
+419
View File
@@ -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) => {}
_ => {}
}
}
}*/
+186
View File
@@ -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);
}
+15
View File
@@ -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)
+213
View File
@@ -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 {}
+172
View File
@@ -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),
}
}
}
+71
View File
@@ -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())
}
}
+9
View File
@@ -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())),
}
}
}
}
+2
View File
@@ -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 {}
+15
View File
@@ -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(),
}
}
}
-20
View File
@@ -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
View File
@@ -1 +0,0 @@
/build
-60
View File
@@ -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'
}
-21
View File
@@ -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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

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>
-11
View File
@@ -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
}

Some files were not shown because too many files have changed in this diff Show More