mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-16 21:02:28 +02:00
Fixed iptv player using live tv stream without /live/ context.
This commit is contained in:
+2
-1
@@ -3,9 +3,10 @@
|
||||
- !BREAKING CHANGE! unique `input` `name` is now mandatory, because rearranging the `source.yml` could lead to wrong results without a playlist update.
|
||||
- !BREAKING_CHANGE! `log_sanitize_sensitive_info` is now under `log` section as `sanitize_sensitive_info`
|
||||
- !BREAKING_CHANGE! uuid generation for entries changed to `input.name` + `stream_id`. Virtual id mapping changed. The new Virtual id is not a sequence anymore.
|
||||
- Added `log_level`to `log` config. Priority: CLI-Argument, Env-Var, Config, Default(`info`)
|
||||
- Added Active clients count (for reverse proxy mode users) which is now displayed in `/status` and can be logged with setting
|
||||
`active_clients: true` under `log`section in `config.yml`
|
||||
- Fixed iptv player using live tv stream without `/live/` context.
|
||||
- Added `log_level`to `log` config. Priority: CLI-Argument, Env-Var, Config, Default(`info`)
|
||||
```yaml
|
||||
log:
|
||||
sanitize_sensitive_info: false
|
||||
|
||||
@@ -211,7 +211,7 @@ If you set it `true` `cache` is disabled! Because the cache cant work without re
|
||||
|
||||
```yaml
|
||||
reverse_proxy:
|
||||
resource_rewrite_disabled: false,
|
||||
resource_rewrite_disabled: false
|
||||
stream:
|
||||
retry: true
|
||||
buffer:
|
||||
|
||||
@@ -5,6 +5,8 @@ source "${HOME}/.ghcr.io"
|
||||
|
||||
TARGET=x86_64-unknown-linux-musl
|
||||
|
||||
(bin/build_fe.sh && bin/build_lin_static.sh) || exit 1;
|
||||
|
||||
# Check if the binary exists
|
||||
if [ ! -f "./target/${TARGET}/release/m3u-filter" ]; then
|
||||
echo "Error: Static binary '../target/${TARGET}/release/m3u-filter' does not exist."
|
||||
|
||||
@@ -10,8 +10,8 @@ server:
|
||||
- name: extern
|
||||
protocol: https
|
||||
host: m3u-filter.mydns.com
|
||||
http_port: 8900
|
||||
https_port:
|
||||
http_port:
|
||||
https_port: 8443
|
||||
rtmp_port:
|
||||
timezone: Europe/Paris
|
||||
message: Welcome to m3u-filter
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::api::model::provider_stream_factory::BufferStreamOptions;
|
||||
use crate::api::model::request::UserApiRequest;
|
||||
use crate::api::model::shared_stream::SharedStream;
|
||||
use crate::api::model::stream_error::StreamError;
|
||||
use crate::debug_if_enabled;
|
||||
use crate::{debug_if_enabled, trace_if_enabled};
|
||||
use crate::model::api_proxy::ProxyUserCredentials;
|
||||
use crate::model::config::{ConfigInput, ConfigTarget};
|
||||
use crate::model::playlist::PlaylistItemType;
|
||||
@@ -212,7 +212,7 @@ pub async fn resource_response(app_state: &AppState, resource_url: &str, req: &H
|
||||
}
|
||||
}
|
||||
}
|
||||
debug_if_enabled!("Try to fetch resource {}", sanitize_sensitive_info(resource_url));
|
||||
trace_if_enabled!("Try to fetch resource {}", sanitize_sensitive_info(resource_url));
|
||||
if let Ok(url) = Url::parse(resource_url) {
|
||||
let client = request_utils::get_client_request(&app_state.http_client, input.map(|i| &i.headers), &url, Some(&req_headers));
|
||||
match client.send().await {
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::debug_if_enabled;
|
||||
use crate::model::config::ConfigInput;
|
||||
use crate::model::playlist::PlaylistItemType;
|
||||
use crate::utils::atomic_once_flag::AtomicOnceFlag;
|
||||
use crate::utils::request_utils::{get_request_headers, sanitize_sensitive_info};
|
||||
use crate::utils::request_utils::{classify_content_type, get_request_headers, sanitize_sensitive_info, MimeCategory};
|
||||
use actix_web::HttpRequest;
|
||||
use bytes::Bytes;
|
||||
use futures::stream::{self, BoxStream};
|
||||
@@ -307,7 +307,6 @@ fn create_provider_stream_options(stream_url: &Url,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn create_provider_stream(client: Arc<reqwest::Client>,
|
||||
stream_url: &Url,
|
||||
req: &HttpRequest,
|
||||
@@ -326,8 +325,14 @@ pub async fn create_provider_stream(client: Arc<reqwest::Client>,
|
||||
|
||||
match get_initial_stream(Arc::clone(&client), &stream_options).await {
|
||||
Some((init_stream, info)) => {
|
||||
let is_video_stream = if let Some((headers, _)) = &info {
|
||||
classify_content_type(headers) == MimeCategory::Video
|
||||
} else {
|
||||
true // don't know what it is but lets assume it is
|
||||
};
|
||||
|
||||
let continue_signal = stream_options.get_continue_flag_clone();
|
||||
if stream_options.should_reconnect() {
|
||||
if is_video_stream && stream_options.should_reconnect() {
|
||||
let client_signal = Arc::clone(&continue_signal);
|
||||
let stream_options_provider = stream_options.clone();
|
||||
let unfold: ResponseStream = stream::unfold((), move |()| {
|
||||
|
||||
@@ -47,7 +47,7 @@ impl XtreamAuthorizationResponse {
|
||||
Self {
|
||||
user_info: XtreamUserInfo {
|
||||
active_cons: "0".to_string(),
|
||||
allowed_output_formats: Vec::from(["ts".to_string(), "m3u8".to_string(), "rtmp".to_string()]),
|
||||
allowed_output_formats: Vec::from(["ts".to_string()]),
|
||||
auth: 1,
|
||||
created_at: (now - Duration::days(365)).timestamp(), // fake
|
||||
exp_date: (now + Duration::days(365)).timestamp(), // fake
|
||||
|
||||
@@ -82,6 +82,7 @@ macro_rules! try_result_bad_request {
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum XtreamApiStreamContext {
|
||||
LiveAlt,
|
||||
Live,
|
||||
@@ -91,7 +92,6 @@ enum XtreamApiStreamContext {
|
||||
}
|
||||
|
||||
impl XtreamApiStreamContext {
|
||||
const LIVE_ALT: &'static str = "";
|
||||
const LIVE: &'static str = "live";
|
||||
const MOVIE: &'static str = "movie";
|
||||
const SERIES: &'static str = "series";
|
||||
@@ -101,7 +101,7 @@ impl XtreamApiStreamContext {
|
||||
impl Display for XtreamApiStreamContext {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", match self {
|
||||
Self::LiveAlt => Self::LIVE_ALT,
|
||||
Self::LiveAlt => Self::LIVE,
|
||||
Self::Live => Self::LIVE,
|
||||
Self::Movie => Self::MOVIE,
|
||||
Self::Series => Self::SERIES,
|
||||
@@ -110,6 +110,7 @@ impl Display for XtreamApiStreamContext {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct XtreamApiStreamRequest<'a> {
|
||||
context: XtreamApiStreamContext,
|
||||
username: &'a str,
|
||||
@@ -217,6 +218,7 @@ async fn xtream_player_api_stream(
|
||||
stream_req.context.to_string().as_str(), &query_path, pli.url.as_str()),
|
||||
true, format!("Cant find stream url for target {target_name}, context {}, stream_id {virtual_id}",
|
||||
stream_req.context));
|
||||
|
||||
debug_if_enabled!("Streaming stream request from {}", sanitize_sensitive_info(&stream_url));
|
||||
stream_response(app_state, &stream_url, req, Some(input), pli.item_type, target).await
|
||||
}
|
||||
|
||||
@@ -33,3 +33,18 @@ macro_rules! debug_if_enabled {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! trace_if_enabled {
|
||||
($fmt:expr, $( $args:expr ),*) => {
|
||||
if log::log_enabled!(log::Level::Trace) {
|
||||
log::log!(log::Level::Trace, $fmt, $($args),*);
|
||||
}
|
||||
};
|
||||
|
||||
($txt:expr) => {
|
||||
if log::log_enabled!(log::Level::Trace) {
|
||||
log::log!(Level::Trace, $txt);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -397,6 +397,35 @@ pub fn extract_extension_from_url(url: &str) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum MimeCategory {
|
||||
Unknown,
|
||||
Video,
|
||||
M3U8,
|
||||
Image,
|
||||
Json,
|
||||
Xml,
|
||||
Text,
|
||||
Unclassified,
|
||||
}
|
||||
|
||||
pub fn classify_content_type(headers: &[(String, String)]) -> MimeCategory {
|
||||
headers.iter()
|
||||
.find_map(|(k, v)| {
|
||||
(k == actix_web::http::header::CONTENT_TYPE.as_str()).then_some(v)
|
||||
})
|
||||
.map(|v| match v.as_str() {
|
||||
v if v.starts_with("video/") || v == "application/octet-stream" => MimeCategory::Video,
|
||||
"application/vnd.apple.mpegurl" | "application/x-mpegURL" => MimeCategory::M3U8,
|
||||
v if v.starts_with("image/") => MimeCategory::Image,
|
||||
v if v.starts_with("application/json") || v.ends_with("+json") => MimeCategory::Json,
|
||||
v if v.starts_with("application/xml") || v.ends_with("+xml") || v == "text/xml" => MimeCategory::Xml,
|
||||
v if v.starts_with("text/") => MimeCategory::Text,
|
||||
_ => MimeCategory::Unclassified,
|
||||
})
|
||||
.unwrap_or(MimeCategory::Unknown)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::utils::request_utils::{sanitize_sensitive_info};
|
||||
@@ -409,4 +438,4 @@ mod tests {
|
||||
println!("{masked}")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user