按照需求,本来实现 WebSocket
的目的是收发 Banshee
的消息(主要是收)
后面突然灵光一闪:上个 WebRTC
怎么样?于是开始一段「为了醋包饺子」的心路历程:
- 虽说原本是为了本地调试,但这套技术很明显内网都能用
- 如果有一个「授权」对话,那么就可以在内部进行「远程问诊」
- 测试或者验收遇到什么问题,望闻问切的第一步是望
- Remote Display / Remote Desktop
TL;DR
- 调整
WebSocket
支持的实现 - 实现信令服务器部分
- 因为流量都在内网,所以不需要转发(STUN)
什么是 WebRTC
WebRTC(Web Real-Time Communications)是一项实时通讯技术,它允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流和(或)音频流或者其他任意数据的传输。对于原生客户端(例如 Android 和 iOS 应用),可以使用具备相同功能的库
一言以蔽之:跟打电话一样
调整实现
虽然 rust 有 WebRTC
实现,数据处理可以直接在应用后端搞定,但为了方便(浏览器支持才是原生支持),我希望将数据转发到应用前端,使用 TypeScript
完成
所以上一篇提到的 WebSocket
实现需要调整:监听 tauri 的 Event
并从 WebSocket
转发出去;监听 WebSocket
跟 WebRTC
相关的信息并从 tauri 的 Event
转发出去
麻就麻在这,有两套观察者互相套起来了
TODO: 已经忘记具体是什么问题了,总之我是这么解决的
只要阻塞就拆线程
tauri 的 Event
同样会因为监听的关系出现上一篇文章关于 socket.recv
的问题,一旦阻塞就拆线程
spawn(async move {
// FTN mean Frontend to Native
t.listen("FTN", move |e| {
let payload = e.payload().unwrap();
sender
.send(Message::Binary(Vec::from(payload.as_bytes())))
.unwrap();
});
});
但是如果这么写的话,就会被编译器叼柒:sender
的生命周期可能没那么长(闭包还异步闭包)
彳亍。先借一下广播解决一下这个问题:广播跟 sender
同一层作用域,通过广播来转发这层数据,回收时间一致
let (tx, _) = broadcast::channel(100);
spawn(async move {
t.get_win().listen("FTN", move |e| {
let payload = e.payload().unwrap();
tx
.send(Message::Binary(Vec::from(payload.as_bytes())))
.unwrap();
});
});
接着,广播收到包之后,就要转发给 WebSocket
让它发出去
let mut rx = tx.subscribe();
spawn(async move {
let mut sender = sender.lock_owned().await;
while let Ok(msg) = rx.recv().await {
if sender.send(msg).await.is_err() {
break;
}
}
});
跟住就又碌柒:如果这么写,tx
的所有权早在刚刚 send
那层就转移了(move)
彳亍。先解决问题:clone
一份
let (tx, _) = broadcast::channel(100);
let tx_for_tauri_send = tx.clone();
spawn(async move {
t.get_win().listen("FTN", move |e| {
let payload = e.payload().unwrap();
tx
.send(Message::Binary(Vec::from(payload.as_bytes())))
.unwrap();
});
});
let mut rx = tx.subscribe();
spawn(async move {
while let Ok(msg) = rx.recv().await {
if sender.send(msg).await.is_err() {
break;
}
}
});
这个解决方式只能说是为了快速实现而写的,我认为它有很大重构空间(但重构也不更新文章)
实现信令服务
这个需求的场景相当于永远只有固定一方拨(banshee
侧),固定一方接(cockpit
侧),所以这里的「信令」服务相当于只转发了 SDP
和 ICE
,其他啥都没做
因为 tauri 这边已经将前端和后端用 Tauri Event
打通,接入前端跟 Tauri App
用 WebSocket
打通,所以只需要在接入前端侧实现 Offer SDP
并将 mediaDevices.getDisplayMedia
的数据 addTrack
,拿到 ICE
发送出去
而 tauri 这边只需要等收到 Offer SDP
之后换一个 Answer SDP
回去即可在内网环境内传输音视频
因为这部分哪里都是一样的就不贴实现,全靠 RTCPeerConnection