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"
|
tracing-subscriber = "0.3.19"
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
futures = "0.3.31"
|
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))
|
.route("/ws", get(ws_handler))
|
||||||
.with_state(state);
|
.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);
|
info!("HTTP/WS Server listening on {}", addr);
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind(addr).await?;
|
let listener = tokio::net::TcpListener::bind(addr).await?;
|
||||||
|
|
|
||||||
42
src/udp.rs
42
src/udp.rs
|
|
@ -21,6 +21,7 @@ pub async fn run_udp_server(state: AppState) -> anyhow::Result<()> {
|
||||||
match socket.recv_from(&mut buf).await {
|
match socket.recv_from(&mut buf).await {
|
||||||
Ok((len, addr)) => {
|
Ok((len, addr)) => {
|
||||||
let data = &buf[..len];
|
let data = &buf[..len];
|
||||||
|
info!("UDP RECV from {}: {} bytes", addr, len);
|
||||||
|
|
||||||
// Manually parse header (22 bytes) to match client's raw byte layout:
|
// Manually parse header (22 bytes) to match client's raw byte layout:
|
||||||
// Byte 0: version (u8)
|
// Byte 0: version (u8)
|
||||||
|
|
@ -60,26 +61,31 @@ pub async fn run_udp_server(state: AppState) -> anyhow::Result<()> {
|
||||||
match media_type {
|
match media_type {
|
||||||
MediaType::Command => {
|
MediaType::Command => {
|
||||||
// Handshake
|
// Handshake
|
||||||
if let Ok(handshake) = bincode::deserialize::<Handshake>(payload) {
|
match bincode::deserialize::<Handshake>(payload) {
|
||||||
// Validate User in Room
|
Ok(handshake) => {
|
||||||
if let Some(room) = state.rooms.get(&handshake.room_code) {
|
// Validate User in Room
|
||||||
if room.peers.contains_key(&handshake.user_id) {
|
if let Some(room) = state.rooms.get(&handshake.room_code) {
|
||||||
// Update Address
|
if room.peers.contains_key(&handshake.user_id) {
|
||||||
state.peers_by_addr.insert(addr, PeerLocation {
|
// Update Address
|
||||||
room_id: handshake.room_code.clone(),
|
state.peers_by_addr.insert(addr, PeerLocation {
|
||||||
user_id: handshake.user_id,
|
room_id: handshake.room_code.clone(),
|
||||||
});
|
user_id: handshake.user_id,
|
||||||
|
});
|
||||||
// Update Peer in Room
|
|
||||||
if let Some(mut peer) = room.peers.get_mut(&handshake.user_id) {
|
// Update Peer in Room
|
||||||
peer.addr = Some(addr);
|
if let Some(mut peer) = room.peers.get_mut(&handshake.user_id) {
|
||||||
info!(
|
peer.addr = Some(addr);
|
||||||
"UDP Handshake: User {} at {}, Room {}",
|
info!(
|
||||||
handshake.user_id, addr, handshake.room_code
|
"UDP Handshake: User {} at {}, Room {}",
|
||||||
);
|
handshake.user_id, addr, handshake.room_code
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to deserialize Handshake from {}: {}", addr, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|
@ -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