mirror of
https://github.com/rustdesk/hbb_common.git
synced 2026-04-23 08:36:56 +00:00
make webrtc-rs optional feature
This commit is contained in:
@@ -6,6 +6,10 @@ edition = "2018"
|
|||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["webrtc"]
|
||||||
|
webrtc = ["dep:webrtc"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# new flexi_logger failed on rustc 1.75
|
# new flexi_logger failed on rustc 1.75
|
||||||
flexi_logger = { version = "0.27", features = ["async"] }
|
flexi_logger = { version = "0.27", features = ["async"] }
|
||||||
@@ -61,7 +65,7 @@ rustls-pki-types = "1.11"
|
|||||||
rustls-native-certs = "0.8"
|
rustls-native-certs = "0.8"
|
||||||
webpki-roots = "1.0.4"
|
webpki-roots = "1.0.4"
|
||||||
async-recursion = "1.1"
|
async-recursion = "1.1"
|
||||||
webrtc = "0.14.0"
|
webrtc = { version = "0.14.0", optional = true }
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
mac_address = "1.1"
|
mac_address = "1.1"
|
||||||
@@ -73,7 +77,7 @@ protobuf-codegen = { version = "3.7" }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
clap = "4.5.51"
|
clap = "4.5.51"
|
||||||
webrtc-signal = "0.1.1"
|
webrtc = "0.14.0"
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
winapi = { version = "0.3", features = [
|
winapi = { version = "0.3", features = [
|
||||||
|
|||||||
@@ -1,51 +1,37 @@
|
|||||||
use std::io::Write;
|
extern crate hbb_common;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use std::io::Write;
|
||||||
|
use bytes::Bytes;
|
||||||
|
|
||||||
use clap::{Arg, Command};
|
use clap::{Arg, Command};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::time::Duration;
|
use tokio::time::Duration;
|
||||||
|
|
||||||
use webrtc::api::APIBuilder;
|
|
||||||
use webrtc::api::setting_engine::SettingEngine;
|
|
||||||
use webrtc::data_channel::RTCDataChannel;
|
|
||||||
use webrtc::ice_transport::ice_server::RTCIceServer;
|
|
||||||
use webrtc::peer_connection::configuration::RTCConfiguration;
|
|
||||||
use webrtc::peer_connection::math_rand_alpha;
|
use webrtc::peer_connection::math_rand_alpha;
|
||||||
use webrtc::peer_connection::peer_connection_state::RTCPeerConnectionState;
|
|
||||||
use webrtc::peer_connection::sdp::session_description::RTCSessionDescription;
|
|
||||||
|
|
||||||
use webrtc_signal::{self as signal};
|
|
||||||
|
|
||||||
// example from https://github.com/webrtc-rs/webrtc/tree/master/examples/examples/data-channels
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let mut app = Command::new("data-channels")
|
let app = Command::new("webrtc-stream")
|
||||||
.version("0.1.0")
|
.about("An example of webrtc stream using hbb_common and webrtc-rs")
|
||||||
.author("Rain Liu <yliu@webrtc.rs>")
|
|
||||||
.about("An example of Data-Channels.")
|
|
||||||
.arg(
|
|
||||||
Arg::new("FULLHELP")
|
|
||||||
.help("Prints more detailed help information")
|
|
||||||
.long("fullhelp"),
|
|
||||||
)
|
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("debug")
|
Arg::new("debug")
|
||||||
.long("debug")
|
.long("debug")
|
||||||
.short('d')
|
.short('d')
|
||||||
|
.action(clap::ArgAction::SetTrue)
|
||||||
.help("Prints debug log information"),
|
.help("Prints debug log information"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("offer")
|
||||||
|
.long("offer")
|
||||||
|
.short('o')
|
||||||
|
.help("set offer from other endpoint"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let matches = app.clone().get_matches();
|
let matches = app.clone().get_matches();
|
||||||
|
|
||||||
if matches.contains_id("FULLHELP") {
|
|
||||||
app.print_long_help().unwrap();
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let debug = matches.contains_id("debug");
|
let debug = matches.contains_id("debug");
|
||||||
if debug {
|
if debug {
|
||||||
|
println!("Debug log enabled");
|
||||||
env_logger::Builder::new()
|
env_logger::Builder::new()
|
||||||
.format(|buf, record| {
|
.format(|buf, record| {
|
||||||
writeln!(
|
writeln!(
|
||||||
@@ -58,173 +44,67 @@ async fn main() -> Result<()> {
|
|||||||
record.args()
|
record.args()
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.filter(None, log::LevelFilter::Trace)
|
.filter(None, log::LevelFilter::Debug)
|
||||||
.init();
|
.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Everything below is the WebRTC-rs API! Thanks for using it ❤️.
|
let remote_endpoint = if let Some(endpoint) = matches.get_one::<String>("offer") {
|
||||||
// Create a SettingEngine and enable Detach
|
endpoint.to_string()
|
||||||
let mut s = SettingEngine::default();
|
} else {
|
||||||
s.detach_data_channels();
|
"".to_string()
|
||||||
|
|
||||||
// Create the API object
|
|
||||||
let api = APIBuilder::new()
|
|
||||||
.with_setting_engine(s)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// Prepare the configuration
|
|
||||||
let config = RTCConfiguration {
|
|
||||||
ice_servers: vec![RTCIceServer {
|
|
||||||
urls: vec!["stun:stun.l.google.com:19302".to_owned()],
|
|
||||||
..Default::default()
|
|
||||||
}],
|
|
||||||
..Default::default()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a new RTCPeerConnection
|
let webrtc_stream = hbb_common::webrtc::WebRTCStream::new(&remote_endpoint, 30000).await?;
|
||||||
let peer_connection = Arc::new(api.new_peer_connection(config).await?);
|
// Print the offer to be sent to the other peer
|
||||||
|
webrtc_stream.get_local_endpoint().await;
|
||||||
|
|
||||||
let (done_tx, mut done_rx) = tokio::sync::mpsc::channel::<()>(1);
|
if remote_endpoint.is_empty() {
|
||||||
|
// Wait for the answer to be pasted
|
||||||
let bootstrap = peer_connection.create_data_channel("bootstrap", None).await?;
|
println!("Wait for the answer to be pasted");
|
||||||
let bootstrap_clone = Arc::clone(&bootstrap);
|
// readline blocking
|
||||||
bootstrap.on_open(Box::new(move || {
|
let line = std::io::stdin()
|
||||||
println!("Data channel bootstrap open.");
|
.lines()
|
||||||
Box::pin(async move {
|
.next()
|
||||||
let _raw = match bootstrap_clone.detach().await {
|
.ok_or_else(|| anyhow::anyhow!("No input received"))??;
|
||||||
Ok(raw) => raw,
|
webrtc_stream.set_remote_endpoint(&line).await?;
|
||||||
Err(err) => {
|
|
||||||
println!("data channel detach got err: {err}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Set the handler for Peer connection state
|
|
||||||
// This will notify you when the peer has connected/disconnected
|
|
||||||
peer_connection.on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| {
|
|
||||||
println!("Peer Connection State has changed: {s}");
|
|
||||||
|
|
||||||
if s == RTCPeerConnectionState::Failed {
|
|
||||||
// Wait until PeerConnection has had no network activity for 30 seconds or another failure.
|
|
||||||
// It may be reconnected using an ICE Restart.
|
|
||||||
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
|
|
||||||
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
|
|
||||||
println!("Peer Connection has gone to failed exiting");
|
|
||||||
let _ = done_tx.try_send(());
|
|
||||||
}
|
|
||||||
|
|
||||||
Box::pin(async {})
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
// Register data channel creation handling
|
|
||||||
peer_connection.on_data_channel(Box::new(move |d: Arc<RTCDataChannel>| {
|
|
||||||
let d_label = d.label().to_owned();
|
|
||||||
let d_id = d.id();
|
|
||||||
println!("New DataChannel {d_label} {d_id}");
|
|
||||||
|
|
||||||
// Register channel opening handling
|
|
||||||
Box::pin(async move {
|
|
||||||
let d2 = Arc::clone(&d);
|
|
||||||
let d3 = Arc::clone(&d);
|
|
||||||
let d_label2 = d_label.clone();
|
|
||||||
let d_id2 = d_id;
|
|
||||||
d.on_open(Box::new(move || {
|
|
||||||
println!("Data channel '{d_label2}'-'{d_id2}' open.");
|
|
||||||
|
|
||||||
Box::pin(async move {
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let _ = read_loop(d2).await;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle writing to the data channel
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let _ = write_loop(d3).await;
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}));
|
|
||||||
})
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Wait for the offer to be pasted
|
|
||||||
println!("Wait for the offer to be pasted");
|
|
||||||
let line = signal::must_read_stdin()?;
|
|
||||||
let desc_data = signal::decode(line.as_str())?;
|
|
||||||
let offer = serde_json::from_str::<RTCSessionDescription>(&desc_data)?;
|
|
||||||
|
|
||||||
// Set the remote SessionDescription
|
|
||||||
peer_connection.set_remote_description(offer).await?;
|
|
||||||
|
|
||||||
// Create an answer
|
|
||||||
let answer = peer_connection.create_answer(None).await?;
|
|
||||||
|
|
||||||
// Create channel that is blocked until ICE Gathering is complete
|
|
||||||
let mut gather_complete = peer_connection.gathering_complete_promise().await;
|
|
||||||
|
|
||||||
// Sets the LocalDescription, and starts our UDP listeners
|
|
||||||
peer_connection.set_local_description(answer).await?;
|
|
||||||
|
|
||||||
// Block until ICE Gathering is complete, disabling trickle ICE
|
|
||||||
// we do this because we only can exchange one signaling message
|
|
||||||
// in a production application you should exchange ICE Candidates via OnICECandidate
|
|
||||||
let _ = gather_complete.recv().await;
|
|
||||||
|
|
||||||
// Output the answer in base64 so we can paste it in browser
|
|
||||||
if let Some(local_desc) = peer_connection.local_description().await {
|
|
||||||
let json_str = serde_json::to_string(&local_desc)?;
|
|
||||||
println!("{json_str}");
|
|
||||||
let b64 = signal::encode(&json_str);
|
|
||||||
println!("--------------------- Copy the below base64 to browser --------------------");
|
|
||||||
println!("{b64}");
|
|
||||||
} else {
|
|
||||||
println!("generate local_description failed!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let s1 = hbb_common::Stream::WebRTC(webrtc_stream.clone());
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let _ = read_loop(s1).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
let s2 = hbb_common::Stream::WebRTC(webrtc_stream.clone());
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let _ = write_loop(s2).await;
|
||||||
|
});
|
||||||
|
|
||||||
println!("Press ctrl-c to stop");
|
println!("Press ctrl-c to stop");
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = done_rx.recv() => {
|
|
||||||
println!("received done signal!");
|
|
||||||
}
|
|
||||||
_ = tokio::signal::ctrl_c() => {
|
_ = tokio::signal::ctrl_c() => {
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
peer_connection.close().await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// read_loop shows how to read from the datachannel directly
|
// read_loop shows how to read from the datachannel directly
|
||||||
async fn read_loop(dc: Arc<RTCDataChannel>) -> Result<()> {
|
async fn read_loop(mut stream: hbb_common::Stream) -> Result<()> {
|
||||||
let mut buffer = BytesMut::zeroed(4096);
|
|
||||||
loop {
|
loop {
|
||||||
let d = dc.detach().await?;
|
let Some(res) = stream.next().await else {
|
||||||
println!("RTCDatachannel detach ok");
|
println!("Datachannel closed; Exit the read_loop");
|
||||||
let n = match d.read(&mut buffer).await {
|
|
||||||
Ok(n) => n,
|
|
||||||
Err(err) => {
|
|
||||||
println!("Datachannel closed; Exit the read_loop: {err}");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if n == 0 {
|
|
||||||
println!("Datachannel read 0 byte; Exit the read_loop");
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
};
|
||||||
println!(
|
println!("Message from DataChannel: {}",
|
||||||
"Message from DataChannel: {}",
|
String::from_utf8(res.unwrap().to_vec())?
|
||||||
String::from_utf8(buffer[..n].to_vec())?
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// write_loop shows how to write to the datachannel directly
|
// write_loop shows how to write to the datachannel directly
|
||||||
async fn write_loop(d: Arc<RTCDataChannel>) -> Result<()> {
|
async fn write_loop(mut stream: hbb_common::Stream) -> Result<()> {
|
||||||
let mut result = Result::<usize>::Ok(0);
|
let mut result = Result::<()>::Ok(());
|
||||||
while result.is_ok() {
|
while result.is_ok() {
|
||||||
let timeout = tokio::time::sleep(Duration::from_secs(5));
|
let timeout = tokio::time::sleep(Duration::from_secs(5));
|
||||||
tokio::pin!(timeout);
|
tokio::pin!(timeout);
|
||||||
@@ -233,7 +113,7 @@ async fn write_loop(d: Arc<RTCDataChannel>) -> Result<()> {
|
|||||||
_ = timeout.as_mut() =>{
|
_ = timeout.as_mut() =>{
|
||||||
let message = math_rand_alpha(15);
|
let message = math_rand_alpha(15);
|
||||||
println!("Sending '{message}'");
|
println!("Sending '{message}'");
|
||||||
result = d.send(&Bytes::from(message)).await.map_err(Into::into);
|
result = stream.send_bytes(Bytes::from(message)).await;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,12 @@ pub mod fingerprint;
|
|||||||
pub use flexi_logger;
|
pub use flexi_logger;
|
||||||
pub mod stream;
|
pub mod stream;
|
||||||
pub mod websocket;
|
pub mod websocket;
|
||||||
|
#[cfg(feature = "webrtc")]
|
||||||
pub mod webrtc;
|
pub mod webrtc;
|
||||||
|
#[cfg(not(feature = "webrtc"))]
|
||||||
|
pub mod webrtc_dummy;
|
||||||
|
#[cfg(not(feature = "webrtc"))]
|
||||||
|
pub use webrtc_dummy as webrtc;
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
pub use rustls_platform_verifier;
|
pub use rustls_platform_verifier;
|
||||||
pub use stream::Stream;
|
pub use stream::Stream;
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ use crate::{
|
|||||||
tcp::FramedStream,
|
tcp::FramedStream,
|
||||||
udp::FramedSocket,
|
udp::FramedSocket,
|
||||||
websocket::{self, check_ws, is_ws_endpoint},
|
websocket::{self, check_ws, is_ws_endpoint},
|
||||||
webrtc::{self, is_webrtc_endpoint},
|
|
||||||
ResultType, Stream,
|
ResultType, Stream,
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "webrtc")]
|
||||||
|
use crate::webrtc::{self, is_webrtc_endpoint};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use std::{net::SocketAddr, sync::Arc};
|
use std::{net::SocketAddr, sync::Arc};
|
||||||
use tokio::net::{ToSocketAddrs, UdpSocket};
|
use tokio::net::{ToSocketAddrs, UdpSocket};
|
||||||
@@ -130,6 +131,7 @@ pub async fn connect_tcp<
|
|||||||
target: T,
|
target: T,
|
||||||
ms_timeout: u64,
|
ms_timeout: u64,
|
||||||
) -> ResultType<crate::Stream> {
|
) -> ResultType<crate::Stream> {
|
||||||
|
#[cfg(feature = "webrtc")]
|
||||||
if is_webrtc_endpoint(&target.to_string()) {
|
if is_webrtc_endpoint(&target.to_string()) {
|
||||||
return Ok(Stream::WebRTC(
|
return Ok(Stream::WebRTC(
|
||||||
webrtc::WebRTCStream::new(&target.to_string(), ms_timeout).await?,
|
webrtc::WebRTCStream::new(&target.to_string(), ms_timeout).await?,
|
||||||
|
|||||||
153
src/webrtc.rs
153
src/webrtc.rs
@@ -1,4 +1,4 @@
|
|||||||
use std::sync::{Arc};
|
use std::sync::Arc;
|
||||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
use std::io::{Error, ErrorKind};
|
use std::io::{Error, ErrorKind};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -13,23 +13,25 @@ use webrtc::peer_connection::RTCPeerConnection;
|
|||||||
use webrtc::peer_connection::configuration::RTCConfiguration;
|
use webrtc::peer_connection::configuration::RTCConfiguration;
|
||||||
use webrtc::peer_connection::peer_connection_state::RTCPeerConnectionState;
|
use webrtc::peer_connection::peer_connection_state::RTCPeerConnectionState;
|
||||||
use webrtc::peer_connection::sdp::session_description::RTCSessionDescription;
|
use webrtc::peer_connection::sdp::session_description::RTCSessionDescription;
|
||||||
|
use webrtc::ice::mdns::MulticastDnsMode;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
protobuf::Message,
|
protobuf::Message,
|
||||||
sodiumoxide::crypto::secretbox::Key,
|
sodiumoxide::crypto::secretbox::Key,
|
||||||
ResultType,
|
ResultType,
|
||||||
};
|
};
|
||||||
use bytes::{Bytes, BytesMut};
|
|
||||||
use tokio::{time::timeout};
|
|
||||||
use tokio::sync::Notify;
|
|
||||||
use tokio::sync::Mutex;
|
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
||||||
|
use bytes::{Bytes, BytesMut};
|
||||||
|
use tokio::time::timeout;
|
||||||
|
use tokio::sync::watch;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
pub struct WebRTCStream {
|
pub struct WebRTCStream {
|
||||||
pc: Arc<RTCPeerConnection>,
|
pc: Arc<RTCPeerConnection>,
|
||||||
stream: Arc<RTCDataChannel>,
|
stream: Arc<RTCDataChannel>,
|
||||||
notify: Arc<Notify>,
|
state_notify: watch::Receiver<bool>,
|
||||||
send_timeout: u64,
|
send_timeout: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +47,7 @@ impl Clone for WebRTCStream {
|
|||||||
WebRTCStream {
|
WebRTCStream {
|
||||||
pc: self.pc.clone(),
|
pc: self.pc.clone(),
|
||||||
stream: self.stream.clone(),
|
stream: self.stream.clone(),
|
||||||
notify: self.notify.clone(),
|
state_notify: self.state_notify.clone(),
|
||||||
send_timeout: self.send_timeout,
|
send_timeout: self.send_timeout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,38 +55,40 @@ impl Clone for WebRTCStream {
|
|||||||
|
|
||||||
impl WebRTCStream {
|
impl WebRTCStream {
|
||||||
|
|
||||||
pub fn get_remote_offer(endpoint: &str) -> Option<String> {
|
pub fn get_remote_offer(endpoint: &str) -> ResultType<String> {
|
||||||
// Ensure the endpoint starts with the "webrtc://" prefix
|
// Ensure the endpoint starts with the "webrtc://" prefix
|
||||||
if !endpoint.starts_with("webrtc://") {
|
if !endpoint.starts_with("webrtc://") {
|
||||||
return None;
|
return Err(Error::new(ErrorKind::InvalidInput, "Invalid WebRTC endpoint format").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the Base64-encoded SDP part
|
// Extract the Base64-encoded SDP part
|
||||||
let encoded_sdp = &endpoint["webrtc://".len()..];
|
let encoded_sdp = &endpoint["webrtc://".len()..];
|
||||||
|
|
||||||
// Decode the Base64 string
|
// Decode the Base64 string
|
||||||
let decoded_bytes = BASE64_STANDARD.decode(encoded_sdp).ok()?;
|
let decoded_bytes = BASE64_STANDARD.decode(encoded_sdp).map_err(|_|
|
||||||
let decoded_sdp = String::from_utf8(decoded_bytes).ok()?;
|
Error::new(ErrorKind::InvalidInput, "Failed to decode Base64 SDP")
|
||||||
|
)?;
|
||||||
Some(decoded_sdp)
|
Ok(String::from_utf8(decoded_bytes).map_err(|_| {
|
||||||
|
Error::new(ErrorKind::InvalidInput, "Failed to convert decoded bytes to UTF-8")
|
||||||
|
})?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn new<T: AsRef<str>>(
|
pub fn sdp_to_endpoint(sdp: &str) -> String {
|
||||||
webrtc_endpoint: T,
|
let encoded_sdp = BASE64_STANDARD.encode(sdp);
|
||||||
|
format!("webrtc://{}", encoded_sdp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn new(
|
||||||
|
remote_endpoint: &str,
|
||||||
ms_timeout: u64,
|
ms_timeout: u64,
|
||||||
) -> ResultType<Self> {
|
) -> ResultType<Self> {
|
||||||
log::debug!("Start webrtc with endpoint: {}", webrtc_endpoint.as_ref());
|
log::debug!("New webrtc stream with endpoint: {}", remote_endpoint);
|
||||||
let remote_offer: String = match Self::get_remote_offer(webrtc_endpoint.as_ref()) {
|
let remote_offer = if remote_endpoint.is_empty() {
|
||||||
Some(offer) => offer,
|
"".into()
|
||||||
None => {
|
} else {
|
||||||
return Err(Error::new(
|
Self::get_remote_offer(remote_endpoint)?
|
||||||
ErrorKind::InvalidInput,
|
|
||||||
"Invalid WebRTC endpoint format",
|
|
||||||
).into());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let key = remote_offer.to_string();
|
let mut key = remote_offer.clone();
|
||||||
let mut lock = SESSIONS.lock().await;
|
let mut lock = SESSIONS.lock().await;
|
||||||
let contains = lock.contains_key(&key);
|
let contains = lock.contains_key(&key);
|
||||||
if contains {
|
if contains {
|
||||||
@@ -92,10 +96,10 @@ impl WebRTCStream {
|
|||||||
return Ok(lock.get(&key).unwrap().clone());
|
return Ok(lock.get(&key).unwrap().clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
log::debug!("Start webrtc with offer: {}", remote_offer);
|
|
||||||
// Create a SettingEngine and enable Detach
|
// Create a SettingEngine and enable Detach
|
||||||
let mut s = SettingEngine::default();
|
let mut s = SettingEngine::default();
|
||||||
s.detach_data_channels();
|
s.detach_data_channels();
|
||||||
|
s.set_ice_multicast_dns_mode(MulticastDnsMode::Disabled);
|
||||||
|
|
||||||
// Create the API object
|
// Create the API object
|
||||||
let api = APIBuilder::new()
|
let api = APIBuilder::new()
|
||||||
@@ -111,67 +115,96 @@ impl WebRTCStream {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let notify = Arc::new(Notify::new());
|
let (notify_tx, notify_rx) = watch::channel(false);
|
||||||
let notify_tx = notify.clone();
|
let on_open_notify = notify_tx.clone();
|
||||||
// Create a new RTCPeerConnection
|
// Create a new RTCPeerConnection
|
||||||
let peer_connection = Arc::new(api.new_peer_connection(config).await?);
|
let peer_connection = Arc::new(api.new_peer_connection(config).await?);
|
||||||
let bootstrap = peer_connection.create_data_channel("bootstrap", None).await?;
|
let data_channel = peer_connection.create_data_channel("bootstrap", None).await?;
|
||||||
bootstrap.on_open(Box::new(move || {
|
data_channel.on_open(Box::new(move || {
|
||||||
log::debug!("Data channel bootstrap open.");
|
log::debug!("Data channel bootstrap open.");
|
||||||
notify_tx.notify_waiters();
|
let _ = on_open_notify.send(true);
|
||||||
Box::pin(async {})
|
Box::pin(async {})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// This will notify you when the peer has connected/disconnected
|
// This will notify you when the peer has connected/disconnected
|
||||||
let notify_tx2 = notify.clone();
|
let on_connection_notify = notify_tx.clone();
|
||||||
peer_connection.on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| {
|
peer_connection.on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| {
|
||||||
log::debug!("Peer Connection State has changed: {}", s);
|
log::debug!("Peer Connection State has changed: {}", s);
|
||||||
if s == RTCPeerConnectionState::Disconnected {
|
if s == RTCPeerConnectionState::Disconnected {
|
||||||
notify_tx2.notify_waiters();
|
let _ = on_connection_notify.send(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO clear SESSIONS entry?
|
// TODO clear SESSIONS entry?
|
||||||
Box::pin(async {})
|
Box::pin(async {})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let offer = serde_json::from_str::<RTCSessionDescription>(&remote_offer)?;
|
// Register data channel creation handling
|
||||||
// Set the remote SessionDescription
|
let on_open_notify2 = notify_tx.clone();
|
||||||
peer_connection.set_remote_description(offer).await?;
|
peer_connection.on_data_channel(Box::new(move |dc: Arc<RTCDataChannel>| {
|
||||||
// Create an answer
|
let d_label = dc.label().to_owned();
|
||||||
let answer = peer_connection.create_answer(None).await?;
|
log::debug!("Remote data channel {}", d_label);
|
||||||
// Create channel that is blocked until ICE Gathering is complete
|
let notify = on_open_notify2.clone();
|
||||||
let mut gather_complete = peer_connection.gathering_complete_promise().await;
|
Box::pin(async move {
|
||||||
// Sets the LocalDescription, and starts our UDP listeners
|
dc.on_open(Box::new(move || {
|
||||||
peer_connection.set_local_description(answer).await?;
|
let _ = notify.send(true);
|
||||||
let _ = gather_complete.recv().await;
|
Box::pin(async {})
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
|
||||||
let ds = WebRTCStream {
|
if remote_offer.is_empty() {
|
||||||
|
let sdp = peer_connection.create_offer(None).await?;
|
||||||
|
let mut gather_complete = peer_connection.gathering_complete_promise().await;
|
||||||
|
peer_connection.set_local_description(sdp.clone()).await?;
|
||||||
|
let _ = gather_complete.recv().await;
|
||||||
|
|
||||||
|
let final_sdp = peer_connection.local_description().await.ok_or_else(|| {
|
||||||
|
Error::new(ErrorKind::Other, "Failed to get local description after gathering")
|
||||||
|
})?;
|
||||||
|
key = serde_json::to_string(&final_sdp).unwrap_or_default();
|
||||||
|
log::debug!("Start webrtc with local: {}", key);
|
||||||
|
} else {
|
||||||
|
let sdp = serde_json::from_str::<RTCSessionDescription>(&remote_offer)?;
|
||||||
|
peer_connection.set_remote_description(sdp).await?;
|
||||||
|
let answer = peer_connection.create_answer(None).await?;
|
||||||
|
let mut gather_complete = peer_connection.gathering_complete_promise().await;
|
||||||
|
peer_connection.set_local_description(answer).await?;
|
||||||
|
let _ = gather_complete.recv().await;
|
||||||
|
log::debug!("Start webrtc with remote: {}", remote_offer);
|
||||||
|
}
|
||||||
|
|
||||||
|
let webrtc_stream = WebRTCStream {
|
||||||
pc: peer_connection,
|
pc: peer_connection,
|
||||||
stream: bootstrap,
|
stream: data_channel,
|
||||||
notify: notify,
|
state_notify: notify_rx,
|
||||||
send_timeout: ms_timeout,
|
send_timeout: ms_timeout,
|
||||||
};
|
};
|
||||||
|
|
||||||
// log the answer
|
lock.insert(key, webrtc_stream.clone());
|
||||||
match ds.get_local_endpoint().await {
|
Ok(webrtc_stream)
|
||||||
Some(local_endpoint) => log::debug!("WebRTC local endpoint: {}", local_endpoint),
|
|
||||||
None => log::debug!("WebRTC local endpoint: <none>"),
|
|
||||||
}
|
|
||||||
|
|
||||||
lock.insert(key, ds.clone());
|
|
||||||
Ok(ds)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn get_local_endpoint(&self) -> Option<String> {
|
pub async fn get_local_endpoint(&self) -> Option<String> {
|
||||||
if let Some(local_desc) = self.pc.local_description().await {
|
if let Some(local_desc) = self.pc.local_description().await {
|
||||||
let sdp = serde_json::to_string(&local_desc).ok()?;
|
let sdp = serde_json::to_string(&local_desc).unwrap_or_default();
|
||||||
Some(format!("webrtc://{}", BASE64_STANDARD.encode(sdp)))
|
let endpoint = Self::sdp_to_endpoint(&sdp);
|
||||||
|
log::debug!("WebRTC get local endpoint: {}", endpoint);
|
||||||
|
Some(endpoint)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub async fn set_remote_endpoint(&self, endpoint: &str) -> ResultType<()> {
|
||||||
|
let offer = Self::get_remote_offer(endpoint)?;
|
||||||
|
log::debug!("WebRTC set remote sdp: {}", offer);
|
||||||
|
let sdp = serde_json::from_str::<RTCSessionDescription>(&offer)?;
|
||||||
|
self.pc.set_remote_description(sdp).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_raw(&mut self) {
|
pub fn set_raw(&mut self) {
|
||||||
// not-supported
|
// not-supported
|
||||||
@@ -208,8 +241,7 @@ impl WebRTCStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_bytes(&mut self, bytes: Bytes) -> ResultType<()> {
|
pub async fn send_bytes(&mut self, bytes: Bytes) -> ResultType<()> {
|
||||||
// wait for connected or disconnected
|
let _ = self.state_notify.changed().await;
|
||||||
self.notify.notified().await;
|
|
||||||
self.stream.send(&bytes).await?;
|
self.stream.send(&bytes).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -217,7 +249,7 @@ impl WebRTCStream {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub async fn next(&mut self) -> Option<Result<BytesMut, Error>> {
|
pub async fn next(&mut self) -> Option<Result<BytesMut, Error>> {
|
||||||
// wait for connected or disconnected
|
// wait for connected or disconnected
|
||||||
self.notify.notified().await;
|
let _ = self.state_notify.changed().await;
|
||||||
if self.stream.ready_state() != RTCDataChannelState::Open {
|
if self.stream.ready_state() != RTCDataChannelState::Open {
|
||||||
return Some(Err(Error::new(
|
return Some(Err(Error::new(
|
||||||
ErrorKind::Other,
|
ErrorKind::Other,
|
||||||
@@ -243,6 +275,7 @@ impl WebRTCStream {
|
|||||||
"data channel read exited with 0 bytes",
|
"data channel read exited with 0 bytes",
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
log::debug!("WebRTCStream read {} bytes", n);
|
||||||
buffer.truncate(n);
|
buffer.truncate(n);
|
||||||
Some(Ok(buffer))
|
Some(Ok(buffer))
|
||||||
}
|
}
|
||||||
|
|||||||
67
src/webrtc_dummy.rs
Normal file
67
src/webrtc_dummy.rs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
|
use std::io::Error;
|
||||||
|
|
||||||
|
use bytes::{Bytes, BytesMut};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
protobuf::Message,
|
||||||
|
sodiumoxide::crypto::secretbox::Key,
|
||||||
|
ResultType,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct WebRTCStream {
|
||||||
|
// mock struct
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WebRTCStream {
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_raw(&mut self) {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn local_addr(&self) -> SocketAddr {
|
||||||
|
SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_send_timeout(&mut self, _ms: u64) {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_key(&mut self, _key: Key) {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_secured(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub async fn send(&mut self, _msg: &impl Message) -> ResultType<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub async fn send_raw(&mut self, _msg: Vec<u8>) -> ResultType<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_bytes(&mut self, _bytes: Bytes) -> ResultType<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub async fn next(&mut self) -> Option<Result<BytesMut, Error>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub async fn next_timeout(&mut self, _ms: u64) -> Option<Result<BytesMut, Error>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_webrtc_endpoint(_endpoint: &str) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user