mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-21 22:18:04 +02:00
Multi-IPTV : chaque fonction utilise la playlist sélectionnée
Le serveur ignorait l'IPTV choisie : direct, VOD, guide, enregistrements, season passes et Auto NBA passaient tous par la playlist la plus récente (ou celle du premier utilisateur pour la capture FFmpeg), et les enregistrements s'affichaient sous toutes les playlists. - get_playlist respecte ?playlist=<id> (prioritaire) puis l'en-tête X-Playlist-Id, avec contrôle du propriétaire (admin : toutes). - Sessions FFmpeg HLS/VOD séparées par IPTV (même stream_id possible) ; le m3u8 propage ?playlist= aux segments, redirections et bascule .ts → HLS du lecteur web aussi. - tv_recordings, season_passes, auto_rules : colonne playlist_id (migrations v4-v6) ; lignes existantes rattachées à la playlist la plus récente de leur propriétaire ; URL de capture avec ?playlist= ; listes filtrées par IPTV ; une règle NBA par IPTV. - Proxy /api/xtream : cache d'allowlist par playlist, paramètre retiré avant l'appel au panneau ; CORS autorise X-Playlist-Id. - Client : AuthedHttp envoie X-Playlist-Id ; XtreamService ajoute ?playlist= aux flux, à l'EPG et aux appels xtream-api (cache distinct). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
29b4e11d13
commit
4f978755fd
15 files changed
+501
-70
No files matched your search
@@ -70,6 +70,8 @@ impl Session {
|
||||
/// Configuration Xtream utilisée par les proxys et le streaming.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PlaylistConfig {
|
||||
/// Identifiant de la playlist en base (vide dans certains tests).
|
||||
pub id: String,
|
||||
/// URL du serveur Xtream (sans `/` final).
|
||||
pub dns: String,
|
||||
pub username: String,
|
||||
@@ -91,6 +93,8 @@ pub struct Recording {
|
||||
pub error_reason: Option<String>,
|
||||
/// Règle automatique à l'origine de l'enregistrement (None = manuel/season pass).
|
||||
pub source_rule_id: Option<String>,
|
||||
/// IPTV de capture (None = lignes antérieures non rattachées).
|
||||
pub playlist_id: Option<String>,
|
||||
pub created_at: DartDateTime,
|
||||
pub updated_at: DartDateTime,
|
||||
}
|
||||
@@ -109,6 +113,7 @@ impl Recording {
|
||||
map.insert("file_path".into(), json!(self.file_path));
|
||||
map.insert("error_reason".into(), json!(self.error_reason));
|
||||
map.insert("source_rule_id".into(), json!(self.source_rule_id));
|
||||
map.insert("playlist_id".into(), json!(self.playlist_id));
|
||||
map.insert("created_at".into(), json!(self.created_at.to_iso()));
|
||||
map.insert("updated_at".into(), json!(self.updated_at.to_iso()));
|
||||
map
|
||||
@@ -141,6 +146,9 @@ pub struct AutoRule {
|
||||
pub team_filter: Vec<String>,
|
||||
/// Rétention en jours des fichiers créés par la règle ; None = illimitée.
|
||||
pub keep_days: Option<i64>,
|
||||
/// IPTV de la règle : une règle NBA par playlist.
|
||||
#[serde(default)]
|
||||
pub playlist_id: Option<String>,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
@@ -186,3 +194,69 @@ pub struct ScheduledGame {
|
||||
pub away_tri: String,
|
||||
pub tipoff_utc: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
impl From<Playlist> for PlaylistConfig {
|
||||
fn from(p: Playlist) -> Self {
|
||||
Self {
|
||||
id: p.id,
|
||||
dns: p.server_url,
|
||||
username: p.username,
|
||||
password: p.password,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlaylistConfig {
|
||||
/// Fragment court et sûr pour les identifiants de session et de
|
||||
/// dossier : deux IPTV peuvent avoir le même `stream_id`.
|
||||
pub fn tag(&self) -> String {
|
||||
let t: String = self
|
||||
.id
|
||||
.chars()
|
||||
.filter(char::is_ascii_alphanumeric)
|
||||
.take(12)
|
||||
.collect();
|
||||
if t.is_empty() { "default".into() } else { t }
|
||||
}
|
||||
}
|
||||
|
||||
/// URL de flux local liée à une IPTV : `/api/live/12.ts` →
|
||||
/// `/api/live/12.ts?playlist=<id>`. La capture FFmpeg (requête locale sans
|
||||
/// utilisateur) retrouve ainsi la bonne playlist.
|
||||
pub fn stream_url_for_playlist(url: &str, playlist_id: &str) -> String {
|
||||
if playlist_id.is_empty() || !url.starts_with("/api/") || url.contains("playlist=") {
|
||||
return url.to_string();
|
||||
}
|
||||
let enc: String = url::form_urlencoded::byte_serialize(playlist_id.as_bytes()).collect();
|
||||
let sep = if url.contains('?') { '&' } else { '?' };
|
||||
format!("{url}{sep}playlist={enc}")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod playlist_url_tests {
|
||||
use super::stream_url_for_playlist;
|
||||
|
||||
#[test]
|
||||
fn ajoute_la_playlist_aux_flux_locaux() {
|
||||
assert_eq!(
|
||||
stream_url_for_playlist("/api/live/12.ts", "ab"),
|
||||
"/api/live/12.ts?playlist=ab"
|
||||
);
|
||||
assert_eq!(
|
||||
stream_url_for_playlist("/api/live/12.ts?x=1", "ab"),
|
||||
"/api/live/12.ts?x=1&playlist=ab"
|
||||
);
|
||||
assert_eq!(
|
||||
stream_url_for_playlist("/api/live/12.ts?playlist=zz", "ab"),
|
||||
"/api/live/12.ts?playlist=zz"
|
||||
);
|
||||
assert_eq!(
|
||||
stream_url_for_playlist("http://x/12.ts", "ab"),
|
||||
"http://x/12.ts"
|
||||
);
|
||||
assert_eq!(
|
||||
stream_url_for_playlist("/api/live/12.ts", ""),
|
||||
"/api/live/12.ts"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user