feat: add XtreamService for managing IPTV playlists, fetching live channels and VODs, and generating stream URLs.

This commit is contained in:
Michael committed 2025-12-16 08:08:15 +01:00
1 parent 3452208cc3
commit 1c1198f1c1
3 files changed
+88 -18

No files matched your search

+43 -9
View File
@@ -96,11 +96,42 @@ Handler createLiveStreamHandler(Future<PlaylistConfig?> Function(Request) getPla
Handler createVodStreamHandler(Future<PlaylistConfig?> Function(Request) getPlaylist) {
final router = Router();
// Route: /api/vod/{streamId}/playlist.m3u8
router.get('/<streamId>/playlist.m3u8', (Request request, String streamId) async {
// Route: /api/vod/{streamId_with_ext}/playlist.m3u8
router.get('/<filename>/playlist.m3u8', (Request request, String filename) async {
final playlist = await getPlaylist(request);
if (playlist == null) return Response.internalServerError(body: 'No playlist');
// Parse streamId and extension from filename (e.g., "12345.mp4" -> id="12345", ext="mp4")
String streamId = filename;
String extension = ''; // No default yet
if (filename.contains('.')) {
final lastDotIndex = filename.lastIndexOf('.');
streamId = filename.substring(0, lastDotIndex);
extension = filename.substring(lastDotIndex + 1);
}
// If extension is missing, fetch it from API (Robustness fallback)
if (extension.isEmpty) {
print('[VOD] No extension provided for $streamId, fetching info...');
try {
final infoUrl = Uri.parse('${playlist.dns}/player_api.php?username=${playlist.username}&password=${playlist.password}&action=get_vod_info&vod_id=$streamId');
final response = await http.get(infoUrl);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data['movie_data'] != null && data['movie_data']['container_extension'] != null) {
extension = data['movie_data']['container_extension'].toString();
print('[VOD] Resolved extension: $extension');
}
}
} catch (e) {
print('[VOD] Failed to resolve extension: $e');
}
// Final fallback if API fails
if (extension.isEmpty) extension = 'mkv';
}
// Clean up old session if exists
if (_vodProcesses.containsKey(streamId)) {
print('[VOD] Killing previous session for $streamId');
@@ -115,11 +146,8 @@ Handler createVodStreamHandler(Future<PlaylistConfig?> Function(Request) getPlay
}
streamDir.createSync(recursive: true);
// Build Upstream URL (Try MKV first, then generic)
// Note: We don't know the extension here easily unless passed.
// Xtream usually allows access via streamId.mkv or streamId.mp4 regardless of actual file
// We'll trust the ID.
final targetUrl = '${playlist.dns}/movie/${playlist.username}/${playlist.password}/$streamId.mkv';
// Build Upstream URL with correct extension
final targetUrl = '${playlist.dns}/movie/${playlist.username}/${playlist.password}/$streamId.$extension';
print('[VOD] Starting Transcode: $targetUrl');
@@ -191,8 +219,14 @@ Handler createVodStreamHandler(Future<PlaylistConfig?> Function(Request) getPlay
);
});
// Route: /api/vod/{streamId}/segment_{n}.ts (Serve segments)
router.get('/<streamId>/<segment>', (Request request, String streamId, String segment) async {
// Route: /api/vod/{filename}/segment_{n}.ts (Serve segments)
router.get('/<filename>/<segment>', (Request request, String filename, String segment) async {
// Extract streamId from filename to find the directory
String streamId = filename;
if (filename.contains('.')) {
streamId = filename.substring(0, filename.lastIndexOf('.'));
}
final file = File('${_hlsTempDir.path}/$streamId/$segment');
if (!file.existsSync()) {
@@ -70,18 +70,18 @@ class XtreamService {
String getVodStreamUrl(String streamId, String containerExtension) {
if (_currentPlaylist == null) throw Exception('No playlist configured');
// Point to new dedicated VOD API (HLS playlist)
// Pass extension to backend for correct upstream URL construction
final baseUrl = html.window.location.origin;
return '$baseUrl/api/vod/$streamId/playlist.m3u8';
return '$baseUrl/api/vod/$streamId.$containerExtension/playlist.m3u8';
}
/// Generate stream URL for series episodes
String getSeriesStreamUrl(String streamId, String containerExtension) {
if (_currentPlaylist == null) throw Exception('No playlist configured');
// Point to new dedicated VOD API (HLS playlist)
// Pass extension to backend for correct upstream URL construction
final baseUrl = html.window.location.origin;
return '$baseUrl/api/vod/$streamId/playlist.m3u8';
return '$baseUrl/api/vod/$streamId.$containerExtension/playlist.m3u8';
}
/// Authenticate and get server info
+41 -5
View File
@@ -1,6 +1,42 @@
# Fix EPG Overlay Interaction
# XtremFlow Development
- [ ] Analyze `player_screen.dart` for overlap issues.
- [ ] Reproduce/Locate the code responsible for EPG overlay.
- [ ] Fix the touch event propagation issue.
- [x] Verify the fix.
Context: Developing a high-performance IPTV Web App with Flutter and Docker.
Current Focus: Resuming development after environment fix.
Master Plan:
## 🧹 Housekeeping
- [x] Commit pending changes in `xtream_service.dart` (Timeout increase).
## 🎥 Player Debugging (CRITICAL)
- [ ] **Fix VOD Playback**: User reports VOD is not playing.
- [x] Check VOD URL generation in `xtream_service.dart`. (Fixed upstream URL to include extension)
- [x] Check `web/player.html` compatibility with VOD formats. (HLS/Direct support verified)
- [x] Verify `PlayerScreen` logic for VOD StreamType. (Correctly passes extension)
- [x] Update `streaming_handler.dart` to handle extensions dynamically. (Done)
- [x] Rebuild Docker Image to apply backend changes.
- [x] Restart Docker containers.
- [x] Verify VOD playback. (Failed)
- [ ] **Phase 2 Debugging**:
- [x] Analyze Docker logs for FFmpeg/Proxy errors. (Found 404/4XX on MKV)
- [x] Implement backend auto-detection of extension via Xtream API.
- [x] Restart Docker containers.
- [x] Restart Docker containers.
- [x] Verify VOD playback. (Backend Fallback active)
- [ ] **Clean Reinstall** (User Request):
- [x] Stop and Remove Containers (`down`).
- [/] Force Rebuild and Start (`up --build`).
## 🎥 Player Improvements (Active Context)
- [ ] Review `player_screen.dart` controls logic.
- [ ] **Goal**: Enhance VOD/Series controls vs Live TV controls.
- *Context*: User wants skip/prev buttons to change channels only for Live TV? Need clarification on VOD behavior.
## 📦 Backlog
- [x] Resolve ModuleNotFoundError: No module named 'rich' (Done)
- [x] Update Docker Image (Timeout increase)