125 lines
3.6 KiB
Rust
125 lines
3.6 KiB
Rust
|
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<WebRTC>,
|
||
|
) -> Result<Arc<RTCPeerConnection>> {
|
||
|
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<AppState>,
|
||
|
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!"),
|
||
|
),
|
||
|
}
|
||
|
}
|