live.umm.gay/wish-server-rs/src/wish/whip.rs

125 lines
3.7 KiB
Rust

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<WebRTC>,
) -> Result<Arc<RTCPeerConnection>> {
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<AppState>,
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() };
// TODO: Validate the stream key
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!"),
),
}
}