mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-18 20:59:59 +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
@@ -101,6 +101,10 @@ class XtreamService {
|
||||
options.headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
}
|
||||
final playlistId = _currentPlaylist?.id;
|
||||
if (playlistId != null && playlistId.isNotEmpty) {
|
||||
options.headers['X-Playlist-Id'] = playlistId;
|
||||
}
|
||||
handler.next(options);
|
||||
},
|
||||
),
|
||||
@@ -115,7 +119,12 @@ class XtreamService {
|
||||
}) {
|
||||
return _dio.get(
|
||||
'$_backendBaseUrl/api/xtream-api',
|
||||
queryParameters: params,
|
||||
// `playlist` dans l'URL : le cache HTTP (clé = URL) ne mélange pas
|
||||
// les réponses de deux IPTV.
|
||||
queryParameters: {
|
||||
...params,
|
||||
if (_playlistId != null) 'playlist': _playlistId!,
|
||||
},
|
||||
options: options ?? Options(extra: _cacheOptions.toExtra()),
|
||||
);
|
||||
}
|
||||
@@ -165,11 +174,27 @@ class XtreamService {
|
||||
_currentPlaylist = playlist;
|
||||
}
|
||||
|
||||
String? get _playlistId {
|
||||
final id = _currentPlaylist?.id;
|
||||
return (id == null || id.isEmpty) ? null : id;
|
||||
}
|
||||
|
||||
/// Ajoute `playlist=<id>` : le serveur sert alors cette IPTV (lecteur,
|
||||
/// hls.js et mpegts.js ne peuvent pas envoyer d'en-tête).
|
||||
String _withPlaylist(String url) {
|
||||
final id = _playlistId;
|
||||
if (id == null) return url;
|
||||
final sep = url.contains('?') ? '&' : '?';
|
||||
return '$url${sep}playlist=${Uri.encodeQueryComponent(id)}';
|
||||
}
|
||||
|
||||
/// Generate stream URL for live TV (HLS)
|
||||
/// [quality]: source | high | medium | low (server-side FFmpeg preset)
|
||||
String getLiveStreamUrl(String streamId, {String quality = 'high'}) {
|
||||
if (_currentPlaylist == null) throw Exception('No playlist configured');
|
||||
return '$_backendBaseUrl/api/live/$streamId/$quality/playlist.m3u8';
|
||||
return _withPlaylist(
|
||||
'$_backendBaseUrl/api/live/$streamId/$quality/playlist.m3u8',
|
||||
);
|
||||
}
|
||||
|
||||
/// Generate stream URL for live TV (Direct MPEG-TS)
|
||||
@@ -180,7 +205,7 @@ class XtreamService {
|
||||
// Le proxy brut ($_backendBaseUrl/api/live/$streamId.ts) livrait l'audio
|
||||
// d'origine — AC-3/E-AC-3/MP2 que mpegts.js ne décode pas → chaînes
|
||||
// muettes. Il reste utilisé tel quel par le scheduler d'enregistrement.
|
||||
return '$_backendBaseUrl/api/live/$streamId/turbo.ts';
|
||||
return _withPlaylist('$_backendBaseUrl/api/live/$streamId/turbo.ts');
|
||||
}
|
||||
|
||||
/// Generate stream URL for VOD (movies)
|
||||
@@ -193,8 +218,10 @@ class XtreamService {
|
||||
if (_currentPlaylist == null) throw Exception('No playlist configured');
|
||||
// `ext` : le serveur demande le fichier au panneau sous son extension
|
||||
// réelle — un `.mp4` réclamé en `.mkv` est refusé (HTTP 551).
|
||||
return '$_backendBaseUrl/api/vod/$streamId/$quality/playlist.m3u8'
|
||||
'${_extQuery(containerExtension, first: true)}';
|
||||
return _withPlaylist(
|
||||
'$_backendBaseUrl/api/vod/$streamId/$quality/playlist.m3u8'
|
||||
'${_extQuery(containerExtension, first: true)}',
|
||||
);
|
||||
}
|
||||
|
||||
String _extQuery(String ext, {required bool first}) {
|
||||
@@ -210,8 +237,10 @@ class XtreamService {
|
||||
String quality = 'high',
|
||||
}) {
|
||||
if (_currentPlaylist == null) throw Exception('No playlist configured');
|
||||
return '$_backendBaseUrl/api/vod/$streamId/$quality/playlist.m3u8?type=series'
|
||||
'${_extQuery(containerExtension, first: false)}';
|
||||
return _withPlaylist(
|
||||
'$_backendBaseUrl/api/vod/$streamId/$quality/playlist.m3u8?type=series'
|
||||
'${_extQuery(containerExtension, first: false)}',
|
||||
);
|
||||
}
|
||||
|
||||
/// Authenticate and get server info
|
||||
@@ -749,14 +778,18 @@ class XtreamService {
|
||||
await Future.delayed(_throttleDelay);
|
||||
}
|
||||
|
||||
final cached = _epgCache[streamId];
|
||||
// Cache partagé entre instances : la clé inclut l'IPTV.
|
||||
final cacheKey = '${_playlistId ?? ''}|$streamId';
|
||||
final cached = _epgCache[cacheKey];
|
||||
if (cached != null && DateTime.now().isBefore(cached.expiresAt)) {
|
||||
return cached.programmes;
|
||||
}
|
||||
|
||||
await _epgGate.acquire();
|
||||
try {
|
||||
final response = await AuthedHttp.get(Uri.parse('/api/epg/$streamId'));
|
||||
final response = await AuthedHttp.get(
|
||||
Uri.parse(_withPlaylist('/api/epg/$streamId')),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
debugPrint(
|
||||
'[XtreamService] EPG $streamId : HTTP ${response.statusCode}',
|
||||
@@ -779,7 +812,7 @@ class XtreamService {
|
||||
if (result.length >= 8) break;
|
||||
}
|
||||
|
||||
_epgCache[streamId] = _EpgCacheEntry(
|
||||
_epgCache[cacheKey] = _EpgCacheEntry(
|
||||
programmes: result,
|
||||
expiresAt: DateTime.now().add(_epgCacheTtl),
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user