First step provider and user connections.

-> Missing shared streams connections
This commit is contained in:
euzu
2025-11-04 18:01:44 +01:00
parent be55b15872
commit 95dde2876d
19 changed files with 1412 additions and 1521 deletions
+54 -98
View File
@@ -1,17 +1,16 @@
use crate::api::endpoints::xtream_api::{get_xtream_player_api_stream_url, ApiStreamContext};
use crate::api::model::ProviderConfig;
use crate::api::model::{ProviderAllocation, ProviderConfig, ProviderStreamState, StreamDetails, StreamingStrategy};
use crate::api::model::UserSession;
use crate::api::model::{
create_channel_unavailable_stream, create_custom_video_stream_response,
create_provider_connections_exhausted_stream, create_provider_stream,
get_stream_response_with_headers, ActiveClientStream, AppState, BoxedProviderStream,
CustomVideoStreamType, PersistPipeStream, ProviderAllocation, ProviderConnectionGuard,
ProviderStreamFactoryOptions, ProviderStreamInfo, ProviderStreamResponse, SharedStreamManager,
get_stream_response_with_headers, ActiveClientStream, AppState,
CustomVideoStreamType, PersistPipeStream,
ProviderStreamFactoryOptions, SharedStreamManager,
StreamError, ThrottledStream, UserApiRequest,
};
use crate::model::ConfigInput;
use crate::model::{ConfigTarget, ProxyUserCredentials};
use crate::tools::atomic_once_flag::AtomicOnceFlag;
use crate::tools::lru_cache::LRUResourceCache;
use crate::utils::create_new_file_for_write;
use crate::utils::request;
@@ -318,51 +317,6 @@ async fn get_redirect_alternative_url<'a>(
Cow::Borrowed(redirect_url)
}
type StreamUrl = String;
type ProviderName = String;
enum ProviderStreamState {
Custom(ProviderStreamResponse),
Available(Option<ProviderName>, StreamUrl),
GracePeriod(Option<ProviderName>, StreamUrl),
}
pub struct StreamDetails {
pub stream: Option<BoxedProviderStream>,
stream_info: ProviderStreamInfo,
pub provider_name: Option<String>,
pub grace_period_millis: u64,
pub reconnect_flag: Option<Arc<AtomicOnceFlag>>,
pub provider_connection_guard: Option<Arc<ProviderConnectionGuard>>,
}
impl StreamDetails {
pub fn from_stream(stream: BoxedProviderStream) -> Self {
Self {
stream: Some(stream),
stream_info: None,
provider_name: None,
grace_period_millis: default_grace_period_millis(),
reconnect_flag: None,
provider_connection_guard: None,
}
}
#[inline]
pub fn has_stream(&self) -> bool {
self.stream.is_some()
}
#[inline]
pub fn has_grace_period(&self) -> bool {
self.grace_period_millis > 0
}
}
struct StreamingStrategy {
provider_connection_guard: Option<Arc<ProviderConnectionGuard>>,
provider_stream_state: ProviderStreamState,
input_headers: Option<HashMap<String, String>>,
}
/// Determines the appropriate streaming strategy for the given input and stream URL.
///
@@ -388,52 +342,54 @@ async fn resolve_streaming_strategy(
force_provider: Option<&str>,
) -> StreamingStrategy {
// allocate a provider connection
let provider_connection_guard = match force_provider {
Some(provider) => {
app_state
.active_provider
.force_exact_acquire_connection(provider, &fingerprint.addr, app_state.get_release_sender())
.await
}
None => {
app_state
.active_provider
.acquire_connection(&input.name, &fingerprint.addr, app_state.get_release_sender())
.await
}
let provider_connection_handle = match force_provider {
Some(provider) => app_state.active_provider.force_exact_acquire_connection(provider, &fingerprint.addr).await,
None => app_state.active_provider.acquire_connection(&input.name, &fingerprint.addr).await,
};
let stream_response_params = match &**provider_connection_guard {
ProviderAllocation::Exhausted => {
debug!("Input {} is exhausted. No connections allowed.", input.name);
let stream = create_provider_connections_exhausted_stream(&app_state.app_config, &[]);
ProviderStreamState::Custom(stream)
}
ProviderAllocation::Available(_, ref provider)
| ProviderAllocation::GracePeriod(_, ref provider) => {
// force_stream_provider means we keep the url and the provider.
// If force_stream_provider or the input is the same as the config we don't need to get new url
let (provider, url) = if force_provider.is_some() || provider.id == input.id {
(input.name.clone(), stream_url.to_string())
} else {
(
provider.name.clone(),
get_stream_alternative_url(stream_url, input, provider),
)
};
let stream_response_params =
if let Some(allocation) = provider_connection_handle.as_ref().map(|ph| &ph.allocation) {
match allocation {
ProviderAllocation::Exhausted => {
debug!("Input {} is exhausted. No connections allowed.", input.name);
let stream = create_provider_connections_exhausted_stream(&app_state.app_config, &[]);
ProviderStreamState::Custom(stream)
}
ProviderAllocation::Available(ref provider)
| ProviderAllocation::GracePeriod(ref provider) => {
// force_stream_provider means we keep the url and the provider.
// If force_stream_provider or the input is the same as the config we don't need to get new url
let (provider, url) = if force_provider.is_some() || provider.id == input.id {
(input.name.clone(), stream_url.to_string())
} else {
(
provider.name.clone(),
get_stream_alternative_url(stream_url, input, provider),
)
};
if matches!(
&**provider_connection_guard,
ProviderAllocation::Available(_, _)
) {
ProviderStreamState::Available(Some(provider), url)
} else {
ProviderStreamState::GracePeriod(Some(provider), url)
let available = match allocation {
ProviderAllocation::Exhausted
| ProviderAllocation::Available(_) => true,
ProviderAllocation::GracePeriod(_) => false,
};
if available {
ProviderStreamState::Available(Some(provider), url)
} else {
ProviderStreamState::GracePeriod(Some(provider), url)
}
}
}
}
} else {
debug!("Input {} is exhausted. No connections allowed.", input.name);
let stream = create_provider_connections_exhausted_stream(&app_state.app_config, &[]);
ProviderStreamState::Custom(stream)
};
StreamingStrategy {
provider_connection_guard: Some(provider_connection_guard),
provider_handle: provider_connection_handle,
provider_stream_state: stream_response_params,
input_headers: Some(input.headers.clone()),
}
@@ -485,9 +441,9 @@ async fn create_stream_response_details(
config_grace_period_millis,
);
let guard_provider_name = streaming_strategy
.provider_connection_guard
.provider_handle
.as_ref()
.and_then(|guard| guard.get_provider_name());
.and_then(|guard| guard.allocation.get_provider_name());
match streaming_strategy.provider_stream_state {
// custom stream means we display our own stream like connection exhausted, channel-unavailable...
@@ -499,7 +455,7 @@ async fn create_stream_response_details(
provider_name: guard_provider_name.clone(),
grace_period_millis,
reconnect_flag: None,
provider_connection_guard: streaming_strategy.provider_connection_guard.clone(),
provider_handle: streaming_strategy.provider_handle.clone(),
}
}
ProviderStreamState::Available(_provider_name, request_url)
@@ -553,7 +509,7 @@ async fn create_stream_response_details(
// if we have no stream, we should release the provider
if stream.is_none() {
if let Some(guard) = streaming_strategy.provider_connection_guard.take() {
if let Some(guard) = streaming_strategy.provider_handle.take() {
drop(guard);
}
error!("Cant open stream {}", sanitize_sensitive_info(&request_url));
@@ -565,7 +521,7 @@ async fn create_stream_response_details(
provider_name: guard_provider_name.clone(),
grace_period_millis,
reconnect_flag,
provider_connection_guard: streaming_strategy.provider_connection_guard.take(),
provider_handle: streaming_strategy.provider_handle.take(),
}
}
}
@@ -800,7 +756,7 @@ pub async fn force_provider_stream_response(
);
return try_unwrap_body!(response.body(body_stream));
}
drop(stream_details.provider_connection_guard.take());
drop(stream_details.provider_handle.take());
if let (Some(stream), _stream_info) = create_channel_unavailable_stream(
&app_state.app_config,
&[],
@@ -876,7 +832,7 @@ pub async fn stream_response(
let provider_name = stream_details.provider_name.clone();
let provider_guard = if share_stream {
stream_details.provider_connection_guard.take()
stream_details.provider_handle.take()
} else {
None
};
@@ -974,7 +930,7 @@ pub async fn stream_response(
return stream_resp.into_response();
}
drop(stream_details.provider_connection_guard.take());
drop(stream_details.provider_handle.take());
axum::http::StatusCode::BAD_REQUEST.into_response()
}