feat(whip): close publishes idle for 30s
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
use std::{net::SocketAddr, sync::Arc, time::Instant};
|
use std::{net::SocketAddr, sync::Arc, time::{Duration, Instant}};
|
||||||
|
|
||||||
use async_broadcast::broadcast;
|
use async_broadcast::broadcast;
|
||||||
use axum::{
|
use axum::{
|
||||||
@@ -18,13 +18,16 @@ use str0m::{
|
|||||||
net::{Protocol, Receive},
|
net::{Protocol, Receive},
|
||||||
};
|
};
|
||||||
use tokio::{net::UdpSocket, sync::mpsc::Receiver};
|
use tokio::{net::UdpSocket, sync::mpsc::Receiver};
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
StreamSession, audio::OpusAudioFrame, codec::VideoFrame, http::HttpServer,
|
StreamSession, audio::OpusAudioFrame, codec::VideoFrame, http::HttpServer,
|
||||||
http_error::HttpError,
|
http_error::HttpError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Kill a WHIP publish if it delivers no media (video or audio) this long.
|
||||||
|
const NO_MEDIA_TIMEOUT: Duration = Duration::from_secs(30);
|
||||||
|
|
||||||
pub async fn handle_whip_injest_delete(
|
pub async fn handle_whip_injest_delete(
|
||||||
State(state): State<Arc<HttpServer>>,
|
State(state): State<Arc<HttpServer>>,
|
||||||
ConnectInfo(remote): ConnectInfo<SocketAddr>,
|
ConnectInfo(remote): ConnectInfo<SocketAddr>,
|
||||||
@@ -343,12 +346,12 @@ pub async fn handle_whip_injest(
|
|||||||
.status(StatusCode::CREATED)
|
.status(StatusCode::CREATED)
|
||||||
.header(header::CONTENT_TYPE, "application/sdp")
|
.header(header::CONTENT_TYPE, "application/sdp")
|
||||||
.header(header::LOCATION, &location)
|
.header(header::LOCATION, &location)
|
||||||
// Provide a STUN server via Link header so OBS can gather
|
// // Provide a STUN server via Link header so OBS can gather
|
||||||
// ICE candidates even without explicit STUN configuration.
|
// // ICE candidates even without explicit STUN configuration.
|
||||||
.header(
|
// .header(
|
||||||
header::LINK,
|
// header::LINK,
|
||||||
"<stun:stun.l.google.com:19302>; rel=\"ice-server\"",
|
// "<stun:stun.l.google.com:19302>; rel=\"ice-server\"",
|
||||||
)
|
// )
|
||||||
.body(axum::body::Body::from(answer_sdp))
|
.body(axum::body::Body::from(answer_sdp))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
@@ -395,7 +398,7 @@ async fn detach_inject_rtc(
|
|||||||
match rtc.poll_output() {
|
match rtc.poll_output() {
|
||||||
Ok(Output::Timeout(t)) => break t,
|
Ok(Output::Timeout(t)) => break t,
|
||||||
Ok(Output::Transmit(t)) => {
|
Ok(Output::Transmit(t)) => {
|
||||||
debug!(
|
trace!(
|
||||||
"Whip TX: {} bytes → {}:{}",
|
"Whip TX: {} bytes → {}:{}",
|
||||||
t.contents.len(),
|
t.contents.len(),
|
||||||
t.destination.ip(),
|
t.destination.ip(),
|
||||||
@@ -498,13 +501,21 @@ async fn detach_inject_rtc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = rx.recv() => {
|
// No UDP input at all for 30s: closing connection.
|
||||||
let Some((data, from)) = result else {
|
// (ICE keepalives still arrive when only the encoder is stalled,
|
||||||
|
// so this fires on a dead peer, not a paused one.)
|
||||||
|
result = tokio::time::timeout(NO_MEDIA_TIMEOUT, rx.recv()) => {
|
||||||
|
let Ok(Some((data, from))) = result else {
|
||||||
|
if result.is_err() {
|
||||||
|
warn!(stream_key_id, "Whip: no input for {NO_MEDIA_TIMEOUT:?}, closing connection");
|
||||||
|
rtc.disconnect();
|
||||||
|
} else {
|
||||||
info!(stream_key_id, "Whip proxy channel closed, cleaning up");
|
info!(stream_key_id, "Whip proxy channel closed, cleaning up");
|
||||||
|
}
|
||||||
cleanup().await;
|
cleanup().await;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
debug!(
|
trace!(
|
||||||
stream_key_id,
|
stream_key_id,
|
||||||
len = data.len(),
|
len = data.len(),
|
||||||
%from,
|
%from,
|
||||||
|
|||||||
Reference in New Issue
Block a user