use std::sync::Arc; use axum::{ extract::State, http::{header, HeaderMap, Method, StatusCode}, response::{IntoResponse, Response}, }; use miette::{miette, Context, IntoDiagnostic, Result}; use tracing::instrument; use crate::{ streams::{get_ongoing_stream, OngoingStream}, util::log_http_error, AppState, }; use webrtc::{ api::API as WebRTC, peer_connection::{sdp::session_description::RTCSessionDescription, RTCPeerConnection}, track::track_local::TrackLocalWriter, }; use super::create_rtc_config; async fn setup_whip_connection( channel: &str, webrtc: Arc, ) -> Result> { let rtc_config = create_rtc_config(); let peer_connection = Arc::new( webrtc .new_peer_connection(rtc_config) .await .into_diagnostic()?, ); let OngoingStream { video_track, audio_track, .. } = get_ongoing_stream(channel).await?; peer_connection.on_track(Box::new(move |track, _recv, _tx| { let local_track = if track.codec().capability.mime_type.starts_with("audio/") { &audio_track } else { &video_track } .clone(); tokio::spawn(async move { let mut rtp_buf = vec![0u8; 1500]; while let Ok((rtp_read, _)) = track.read(&mut rtp_buf).await { let (Ok(_) | Err(webrtc::Error::ErrClosedPipe)) = local_track.write(&rtp_buf[..rtp_read]).await else { break }; } Result::<()>::Ok(()) }); Box::pin(async {}) })); Ok(peer_connection) } #[instrument(skip_all)] #[axum::debug_handler] pub async fn handle_whip( method: Method, headers: HeaderMap, State(app): State, offer: String, ) -> Response { if method != Method::POST { return (StatusCode::METHOD_NOT_ALLOWED, "Please use POST!").into_response(); } let Some(auth) = headers.get(header::AUTHORIZATION) else { return (StatusCode::UNAUTHORIZED, "Authorization was not set").into_response() }; let auth = auth .to_str() .into_diagnostic() .wrap_err("Failed to decode auth header") .unwrap(); let auth = auth.strip_prefix("Bearer ").unwrap_or(auth); let Some((channel, key)) = auth.split_once(':') else { return (StatusCode::UNAUTHORIZED, "Invalid Authorization header").into_response() }; let mut db = app .db .acquire() .await .expect("SQLite connections never fail"); match sqlx::query!( "SELECT stream FROM streams WHERE stream = ? AND password = ?", channel, key ) .fetch_one(&mut db) .await { Err(sqlx::Error::RowNotFound) => { return log_http_error(StatusCode::UNAUTHORIZED, "Invalid stream key.") } Err(e) => return log_http_error(StatusCode::INTERNAL_SERVER_ERROR, e), _ => {} }; let peer_connection = match setup_whip_connection(channel, app.webrtc) .await .wrap_err("Failed to initialize peer connection") { Ok(peer_connection) => peer_connection, Err(e) => { return log_http_error(StatusCode::INTERNAL_SERVER_ERROR, e); } }; let Ok(description) = RTCSessionDescription::offer(offer) else { return log_http_error(StatusCode::BAD_REQUEST, "Malformed SDP offer") }; if let Err(e) = peer_connection.set_remote_description(description).await { return log_http_error(StatusCode::INTERNAL_SERVER_ERROR, e); }; let mut gather_complete = peer_connection.gathering_complete_promise().await; let answer = match peer_connection.create_answer(None).await { Ok(answer) => answer, Err(e) => return log_http_error(StatusCode::INTERNAL_SERVER_ERROR, e), }; if let Err(e) = peer_connection.set_local_description(answer).await { return log_http_error(StatusCode::INTERNAL_SERVER_ERROR, e); } let _ = gather_complete.recv().await; match peer_connection.local_description().await { Some(desc) => (StatusCode::CREATED, desc.sdp).into_response(), None => log_http_error( StatusCode::INTERNAL_SERVER_ERROR, miette!("No local description exists!"), ), } }