livekit wip

This commit is contained in:
Timur Gordon
2025-08-29 15:29:24 +02:00
parent ba43a82db0
commit 7ca492346d
28 changed files with 8271 additions and 0 deletions

1245
examples/meet/server/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
[package]
name = "meet-server"
version = "0.1.0"
edition = "2021"
[workspace]
[dependencies]
tokio = { version = "1.0", features = ["full"] }
axum = "0.7"
tower = "0.4"
tower-http = { version = "0.5", features = ["cors", "fs"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
uuid = { version = "1.0", features = ["v4"] }
jsonwebtoken = "9.0"
chrono = { version = "0.4", features = ["serde"] }

View File

@@ -0,0 +1,63 @@
# LiveKit Meet Server
A simple backend server to provide connection details for the LiveKit Meet demo app.
## Features
- `/api/connection-details` endpoint for room connection details
- CORS support for frontend requests
- Static file serving for the built WASM app
- Health check endpoint
## Usage
1. Build the frontend first:
```bash
cd .. && trunk build
```
2. Run the server:
```bash
cd server && cargo run
```
3. Access the app at: http://localhost:8083
## API Endpoints
- `GET /api/connection-details?roomName=<room>&participantName=<name>` - Get connection details
- `GET /health` - Health check
## Production Setup
For production use, you'll need to:
1. Replace the mock token generation with proper LiveKit JWT tokens
2. Add your actual LiveKit server URL
3. Add proper authentication and validation
4. Use environment variables for configuration
Example with real LiveKit tokens:
```rust
use livekit_api::access_token::{AccessToken, VideoGrant};
fn generate_token(room_name: &str, participant_name: &str) -> String {
let api_key = std::env::var("LIVEKIT_API_KEY").unwrap();
let api_secret = std::env::var("LIVEKIT_API_SECRET").unwrap();
let grant = VideoGrant {
room_join: true,
room: room_name.to_string(),
..Default::default()
};
let token = AccessToken::new(&api_key, &api_secret)
.with_identity(participant_name)
.with_video_grant(grant)
.to_jwt()
.unwrap();
token
}
```

View File

@@ -0,0 +1,139 @@
use axum::{
extract::Query,
http::StatusCode,
response::Json,
routing::get,
Router,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tower_http::cors::CorsLayer;
use tower_http::services::ServeDir;
use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
use chrono::{Utc, Duration};
#[derive(Debug, Deserialize)]
struct ConnectionRequest {
#[serde(rename = "roomName")]
room_name: String,
#[serde(rename = "participantName")]
participant_name: String,
}
#[derive(Debug, Serialize)]
struct ConnectionDetails {
server_url: String,
participant_token: String,
}
async fn connection_details(
Query(params): Query<ConnectionRequest>,
) -> Result<Json<ConnectionDetails>, StatusCode> {
println!("Connection request: room={}, participant={}",
params.room_name, params.participant_name);
// For demo purposes, use a mock LiveKit server URL
// In production, you would:
// 1. Validate the room and participant
// 2. Generate a proper JWT token with LiveKit API key/secret
// 3. Return your actual LiveKit server URL
let token = match generate_livekit_token(&params.room_name, &params.participant_name) {
Ok(token) => token,
Err(e) => {
println!("Failed to generate token: {}", e);
return Err(StatusCode::INTERNAL_SERVER_ERROR);
}
};
let details = ConnectionDetails {
server_url: std::env::var("LIVEKIT_URL")
.unwrap_or_else(|_| "wss://meet-demo.livekit.cloud".to_string()),
participant_token: token,
};
Ok(Json(details))
}
#[derive(Debug, Serialize)]
struct LiveKitClaims {
iss: String,
sub: String,
iat: i64,
exp: i64,
video: VideoGrant,
}
#[derive(Debug, Serialize)]
struct VideoGrant {
#[serde(rename = "roomJoin")]
room_join: bool,
room: String,
}
fn generate_livekit_token(room_name: &str, participant_name: &str) -> Result<String, String> {
// For demo purposes - you should set these as environment variables
let api_key = std::env::var("LIVEKIT_API_KEY")
.unwrap_or_else(|_| "devkey".to_string());
let api_secret = std::env::var("LIVEKIT_API_SECRET")
.unwrap_or_else(|_| "secret".to_string());
println!("Generating token with:");
println!(" API Key: {}", api_key);
println!(" API Secret: {} (length: {})",
if api_secret.len() > 10 { format!("{}...", &api_secret[..10]) } else { api_secret.clone() },
api_secret.len());
println!(" Room: {}", room_name);
println!(" Participant: {}", participant_name);
let now = Utc::now();
let exp = now + Duration::hours(6); // Token valid for 6 hours
let claims = LiveKitClaims {
iss: api_key.clone(),
sub: participant_name.to_string(),
iat: now.timestamp(),
exp: exp.timestamp(),
video: VideoGrant {
room_join: true,
room: room_name.to_string(),
},
};
let header = Header::new(Algorithm::HS256);
let encoding_key = EncodingKey::from_secret(api_secret.as_ref());
encode(&header, &claims, &encoding_key)
.map_err(|e| format!("Failed to generate token: {}", e))
}
async fn health() -> &'static str {
"OK"
}
#[tokio::main]
async fn main() {
println!("Starting LiveKit Meet server on http://localhost:8083");
// Log environment variables for debugging
println!("Environment variables:");
println!(" LIVEKIT_URL: {:?}", std::env::var("LIVEKIT_URL"));
println!(" LIVEKIT_API_KEY: {:?}", std::env::var("LIVEKIT_API_KEY"));
println!(" LIVEKIT_API_SECRET: {:?}", std::env::var("LIVEKIT_API_SECRET"));
let app = Router::new()
.route("/api/connection-details", get(connection_details))
.route("/health", get(health))
// Serve static files from the dist directory
.nest_service("/", ServeDir::new("../dist"))
.layer(CorsLayer::permissive());
let listener = tokio::net::TcpListener::bind("0.0.0.0:8083")
.await
.unwrap();
println!("Server running on http://localhost:8083");
println!("API endpoint: http://localhost:8083/api/connection-details");
axum::serve(listener, app).await.unwrap();
}