Fix VideoTile bug and improve screen sharing
This commit is contained in:
parent
f93f2efd28
commit
bb3fc6c891
7 changed files with 187 additions and 19 deletions
|
|
@ -15,4 +15,4 @@ tracing = "0.1.41"
|
|||
tracing-subscriber = "0.3.19"
|
||||
bincode = "1.3.3"
|
||||
futures = "0.3.31"
|
||||
shared = { path = "../shared" }
|
||||
shared = { path = "./shared" }
|
||||
|
|
|
|||
71
README.md
Normal file
71
README.md
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# Meet Server
|
||||
|
||||
The high-performance signaling and media relay server for the Meet application. Built with **Rust**, **Axum**, and **Tokio**.
|
||||
|
||||
## Features
|
||||
- **Signaling**: WebSocket-based room management and peer discovery.
|
||||
- **Media Relay**: Custom UDP protocol for low-latency video and screen sharing.
|
||||
- **Architecture**: Asynchronous, localized state management using `DashMap`.
|
||||
|
||||
## Prerequisites
|
||||
- **Rust**: Latest stable version. Install via [rustup.rs](https://rustup.rs).
|
||||
- **Build Tools**: `build-essential` (Ubuntu) or `Development Tools` group (Fedora/RHEL).
|
||||
|
||||
## Installation
|
||||
|
||||
1. **Clone the Repository**:
|
||||
```bash
|
||||
git clone <your-server-repo-url>
|
||||
cd server
|
||||
```
|
||||
|
||||
2. **Verify Shared Crate**:
|
||||
Ensure the `shared` directory exists in the root. This contains protocol definitions used by both client (logic port) and server.
|
||||
```bash
|
||||
ls -F shared/
|
||||
```
|
||||
|
||||
3. **Build**:
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
The binary will be located at `target/release/server`.
|
||||
|
||||
## Running the Server
|
||||
|
||||
### Ports
|
||||
- **TCP/WebSocket**: 6000 (Bind: `0.0.0.0:6000`)
|
||||
- **UDP (Media)**: 4000 (Bind: `0.0.0.0:4000`)
|
||||
|
||||
### Local Development
|
||||
```bash
|
||||
cargo run
|
||||
```
|
||||
Runs on `0.0.0.0:6000` (HTTP/WS) and `0.0.0.0:4000` (UDP).
|
||||
|
||||
### Production Deployment
|
||||
1. **Run the Binary**:
|
||||
```bash
|
||||
./target/release/server
|
||||
```
|
||||
2. **Firewall**:
|
||||
Ensure **TCP 6000** and **UDP 4000** are open.
|
||||
```bash
|
||||
# Fedora/CentOS
|
||||
sudo firewall-cmd --add-port=6000/tcp --permanent
|
||||
sudo firewall-cmd --add-port=4000/udp --permanent
|
||||
sudo firewall-cmd --reload
|
||||
|
||||
# Ubuntu/Debian (UFW)
|
||||
sudo ufw allow 6000/tcp
|
||||
sudo ufw allow 4000/udp
|
||||
```
|
||||
3. **Systemd (Optional)**:
|
||||
Create a service file `/etc/systemd/system/meet-server.service` to keep it running.
|
||||
|
||||
## Project Structure
|
||||
- `src/main.rs`: Entry point. Sets up Axum router and spawns the UDP listener.
|
||||
- `src/handlers.rs`: WebSocket handlers for joining rooms, signaling, and chat.
|
||||
- `src/udp.rs`: The core UDP packet handling loop. Manages media relaying.
|
||||
- `src/state.rs`: Shared application state (Rooms, Peers).
|
||||
- `shared/`: Local crate containing shared data structures (`Packet` headers, `MediaType` enums).
|
||||
1
shared/.gitignore
vendored
Normal file
1
shared/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
8
shared/Cargo.toml
Normal file
8
shared/Cargo.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "shared"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.3.3"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
80
shared/src/lib.rs
Normal file
80
shared/src/lib.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub type UserId = u32; // Unique identifier for a user within a room
|
||||
pub type StreamId = u16; // Typically mapped to User + Media Source
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[repr(u8)]
|
||||
pub enum MediaType {
|
||||
Audio = 0,
|
||||
Video = 1,
|
||||
Screen = 2,
|
||||
Command = 3, // For handshake/keepalive
|
||||
Unknown = 255,
|
||||
}
|
||||
|
||||
/// UDP Packet Header (fixed size binary struct)
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct PacketHeader {
|
||||
pub version: u8, // Protocol version (v1)
|
||||
pub media_type: MediaType,
|
||||
pub user_id: UserId, // Identifies the source user (was stream_id)
|
||||
pub sequence: u32, // User-defined sequence number (for reordering/loss)
|
||||
pub timestamp: u32, // RTP-like timestamp
|
||||
pub fragment_index: u16, // If packet is fragmented (0 if not)
|
||||
pub fragment_count: u16, // Total fragments (1 if not)
|
||||
pub flags: u8, // Bitmask: 0x01 = Keyframe, etc.
|
||||
}
|
||||
|
||||
pub const FLAG_KEYFRAME: u8 = 0x01;
|
||||
|
||||
/// Signaling Messages (WebSocket - JSON)
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "type", content = "data")]
|
||||
pub enum ControlMsg {
|
||||
/// Client -> Server: Request to join a room
|
||||
Join {
|
||||
room_code: String,
|
||||
display_name: String,
|
||||
},
|
||||
/// Server -> Client: Join success
|
||||
Joined {
|
||||
self_id: UserId,
|
||||
room_code: String,
|
||||
peers: Vec<PeerInfo>,
|
||||
},
|
||||
/// Server -> Client: New peer joined
|
||||
PeerJoined {
|
||||
user_id: UserId,
|
||||
display_name: String,
|
||||
},
|
||||
/// Server -> Client: Peer left
|
||||
PeerLeft {
|
||||
user_id: UserId,
|
||||
},
|
||||
/// Client -> Server: Update stream status (e.g. camera on/off)
|
||||
UpdateStream {
|
||||
user_id: UserId,
|
||||
stream_id: StreamId,
|
||||
active: bool,
|
||||
media_type: MediaType,
|
||||
},
|
||||
/// Client <-> Server: Chat message
|
||||
ChatMessage {
|
||||
user_id: UserId,
|
||||
display_name: String,
|
||||
message: String,
|
||||
timestamp: u64,
|
||||
},
|
||||
/// General Error
|
||||
Error {
|
||||
message: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct PeerInfo {
|
||||
pub user_id: UserId,
|
||||
pub display_name: String,
|
||||
// Could include active streams etc.
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
.route("/ws", get(ws_handler))
|
||||
.with_state(state);
|
||||
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], 5000));
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], 6000));
|
||||
info!("HTTP/WS Server listening on {}", addr);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(addr).await?;
|
||||
|
|
|
|||
10
src/udp.rs
10
src/udp.rs
|
|
@ -21,6 +21,7 @@ pub async fn run_udp_server(state: AppState) -> anyhow::Result<()> {
|
|||
match socket.recv_from(&mut buf).await {
|
||||
Ok((len, addr)) => {
|
||||
let data = &buf[..len];
|
||||
info!("UDP RECV from {}: {} bytes", addr, len);
|
||||
|
||||
// Manually parse header (22 bytes) to match client's raw byte layout:
|
||||
// Byte 0: version (u8)
|
||||
|
|
@ -60,7 +61,8 @@ pub async fn run_udp_server(state: AppState) -> anyhow::Result<()> {
|
|||
match media_type {
|
||||
MediaType::Command => {
|
||||
// Handshake
|
||||
if let Ok(handshake) = bincode::deserialize::<Handshake>(payload) {
|
||||
match bincode::deserialize::<Handshake>(payload) {
|
||||
Ok(handshake) => {
|
||||
// Validate User in Room
|
||||
if let Some(room) = state.rooms.get(&handshake.room_code) {
|
||||
if room.peers.contains_key(&handshake.user_id) {
|
||||
|
|
@ -81,6 +83,10 @@ pub async fn run_udp_server(state: AppState) -> anyhow::Result<()> {
|
|||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to deserialize Handshake from {}: {}", addr, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Media Packet: Relay
|
||||
|
|
@ -100,6 +106,8 @@ pub async fn run_udp_server(state: AppState) -> anyhow::Result<()> {
|
|||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("Dropping Relay Packet from unknown sender: {}", addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue