按照需求,本来实现 WebSocket 的目的是收发 Banshee 的消息(主要是收)

后面突然灵光一闪:上个 WebRTC 怎么样?于是开始一段「为了醋包饺子」的心路历程:

  1. 虽说原本是为了本地调试,但这套技术很明显内网都能用
  2. 如果有一个「授权」对话,那么就可以在内部进行「远程问诊」
  3. 测试或者验收遇到什么问题,望闻问切的第一步是
  4. 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 转发出去;监听 WebSocketWebRTC 相关的信息并从 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 侧),所以这里的「信令」服务相当于只转发了 SDPICE,其他啥都没做

因为 tauri 这边已经将前端和后端用 Tauri Event 打通,接入前端跟 Tauri AppWebSocket 打通,所以只需要在接入前端侧实现 Offer SDP 并将 mediaDevices.getDisplayMedia 的数据 addTrack,拿到 ICE 发送出去

而 tauri 这边只需要等收到 Offer SDP 之后换一个 Answer SDP 回去即可在内网环境内传输音视频

因为这部分哪里都是一样的就不贴实现,全靠 RTCPeerConnection

References