reconnect stream wip

This commit is contained in:
euzu
2025-01-07 14:00:13 +01:00
parent cd286de664
commit d486f04905
5 changed files with 174 additions and 106 deletions
+87
View File
@@ -0,0 +1,87 @@
use crate::model::config::ConfigInput;
use crate::utils::request_utils;
use actix_web::{HttpRequest, HttpResponse, HttpResponseBuilder};
use async_std::stream::StreamExt;
use bytes::Bytes;
use core::time::Duration;
use reqwest::{Error, RequestBuilder};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::mpsc;
use tokio_stream::Stream;
use tokio_stream::wrappers::ReceiverStream;
use url::Url;
const BUFFER_SIZE: usize = 8092;
pub struct BufferedStreamHandler {
client: Arc<RequestBuilder>,
}
impl BufferedStreamHandler {
pub fn new(url: &Url, req: &HttpRequest, input: Option<&ConfigInput>) -> Self {
let req_headers: HashMap<&str, &[u8]> = req.headers().iter().map(|(k, v)| (k.as_str(), v.as_bytes())).collect();
let client = Arc::new(request_utils::get_client_request(input, url, Some(&req_headers)));
BufferedStreamHandler {
client,
}
}
pub fn get_stream(&mut self) -> impl Stream<Item = Result<Bytes, Error>> + Unpin + 'static {
let (tx, rx) = mpsc::channel::<Result<Bytes, Error>>(BUFFER_SIZE);
let client = Arc::clone(&self.client);
actix_web::rt::spawn({
async move {
loop {
let Some(client) = client.try_clone() else { break };
println!("Connection to stream");
match client.send().await {
Ok(response) => {
if !response.status().is_success() {
continue;
}
let mut byte_stream = response.bytes_stream();
while let Some(chunk) = byte_stream.next().await {
match chunk {
Ok(chunk) => {
if tx.send(Ok(chunk)).await.is_err() {
println!("Stream finished, client disconnect ?");
return;
}
}
Err(err) => {
println!("Stream disconnected, cant read from server {err}");
break;
}
}
}
}
Err(err) => {
if err.is_timeout() {
actix_web::rt::time::sleep(Duration::from_secs(1)).await;
continue;
}
println!("Stream finished {err}");
break;
}
}
actix_web::rt::time::sleep(Duration::from_secs(1)).await;
}
}
});
ReceiverStream::new(rx)
}
}
pub fn get_stream_response_with_headers() -> HttpResponseBuilder {
let mut response_builder = HttpResponse::Ok();
response_builder.insert_header((actix_web::http::header::CONTENT_TYPE, "application/octet-stream"));
response_builder.insert_header((actix_web::http::header::CONTENT_LENGTH, 0));
response_builder.insert_header((actix_web::http::header::CONNECTION, "keep-alive"));
response_builder.insert_header((actix_web::http::header::CACHE_CONTROL, "no-cache"));
response_builder
}
+2 -1
View File
@@ -3,4 +3,5 @@ pub mod config;
pub mod download;
pub mod xtream;
pub mod app_state;
pub mod shared_stream;
pub mod shared_stream;
pub mod buffered_stream;
+59 -2
View File
@@ -1,9 +1,66 @@
use bytes::Bytes;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use async_std::prelude::{Stream, StreamExt};
use tokio::sync::broadcast;
use crate::api::model::app_state::AppState;
use crate::debug_if_enabled;
use crate::utils::request_utils::mask_sensitive_info;
pub struct SharedStream {
pub data_stream: Arc<tokio::sync::broadcast::Sender<Bytes>>,
pub header: HashMap<String, Vec<u8>>,
}
impl SharedStream {
/// Creates a shared stream and stores it in the shared state.
pub(crate) async fn register<S, E>(
app_state: &AppState,
stream_url: &str,
bytes_stream: S,
) where
S: Stream<Item=Result<Bytes, E>> + Unpin + 'static,
{
// Create a broadcast channel for the shared stream
let (tx, _) = broadcast::channel(100);
let sender = Arc::new(tx);
// Insert the shared stream into the shared state
app_state
.shared_streams
.lock()
.await
.insert(
stream_url.to_string(),
SharedStream {
data_stream: sender.clone(),
},
);
let shared_streams_map = Arc::clone(&app_state.shared_streams);
let mut source_stream = Box::pin(bytes_stream);
let streaming_url = stream_url.to_string();
// Spawn a task to forward items from the source stream to the broadcast channel
actix_rt::spawn(async move {
while let Some(item) = source_stream.next().await {
if let Ok(data) = item {
if sender.receiver_count() > 0 {
// if let Err(err) = sender.send(data) {
// debug!("{err}")
// }
if sender.send(data).is_err() {
// ignore
}
actix_web::rt::time::sleep(Duration::from_millis(20)).await;
} else {
debug_if_enabled!("No active subscribers. Closing stream {}", mask_sensitive_info(&streaming_url));
// Cleanup for removing unused shared streams
let mut shared_streams = shared_streams_map.lock().await;
shared_streams.remove(&streaming_url);
return;
}
}
}
});
}
}