use std::sync::{atomic::Ordering, Arc}; use axum::{ extract::State, http::{header, HeaderMap, Method, StatusCode}, response::{IntoResponse, Response}, }; use miette::{miette, Context, IntoDiagnostic, Result}; use crate::{ streams::{get_ongoing_stream, OngoingStream}, util::log_http_error, AppState, }; use tracing::instrument; use webrtc::{ api::API as WebRTC, peer_connection::{ configuration::RTCConfiguration, peer_connection_state::RTCPeerConnectionState, sdp::session_description::RTCSessionDescription, RTCPeerConnection, }, }; async fn setup_whep_connection( channel: &str, webrtc: Arc, ) -> Result> { let rtc_config = RTCConfiguration::default(); let peer_connection = Arc::new( webrtc .new_peer_connection(rtc_config) .await .into_diagnostic()?, ); let OngoingStream { video_track, audio_track, viewer_count, } = get_ongoing_stream(channel).await?; peer_connection.on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| { if s == RTCPeerConnectionState::Connected { viewer_count.fetch_add(1, Ordering::Relaxed); } if s == RTCPeerConnectionState::Disconnected { viewer_count.fetch_sub(1, Ordering::Relaxed); } Box::pin(async {}) })); peer_connection .add_track(video_track) .await .into_diagnostic() .wrap_err("Failed to add video track")?; peer_connection .add_track(audio_track) .await .into_diagnostic() .wrap_err("Failed to add audio track")?; Ok(peer_connection) } #[instrument(skip_all)] pub async fn handle_whep( 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 channel = match headers .get(header::AUTHORIZATION) .ok_or(miette!("Authorization header was not set")) .and_then(|h| { h.to_str() .into_diagnostic() .wrap_err("Authorization header was malformed") }) { Ok(a) => a, Err(e) => return log_http_error(StatusCode::BAD_REQUEST, e), }; let channel = channel.strip_prefix("Bearer ").unwrap_or(channel); let peer_connection = match setup_whep_connection(channel, app.webrtc).await { Ok(p) => p, 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!"), ), } }