diff --git a/bin/api/streaming_handler.dart b/bin/api/streaming_handler.dart index eb545ba..59e9d4a 100644 --- a/bin/api/streaming_handler.dart +++ b/bin/api/streaming_handler.dart @@ -96,11 +96,42 @@ Handler createLiveStreamHandler(Future Function(Request) getPla Handler createVodStreamHandler(Future Function(Request) getPlaylist) { final router = Router(); - // Route: /api/vod/{streamId}/playlist.m3u8 - router.get('//playlist.m3u8', (Request request, String streamId) async { + // Route: /api/vod/{streamId_with_ext}/playlist.m3u8 + router.get('//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 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 Function(Request) getPlay ); }); - // Route: /api/vod/{streamId}/segment_{n}.ts (Serve segments) - router.get('//', (Request request, String streamId, String segment) async { + // Route: /api/vod/{filename}/segment_{n}.ts (Serve segments) + router.get('//', (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()) { diff --git a/lib/features/iptv/services/xtream_service.dart b/lib/features/iptv/services/xtream_service.dart index bbae33f..fa2562d 100644 --- a/lib/features/iptv/services/xtream_service.dart +++ b/lib/features/iptv/services/xtream_service.dart @@ -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 diff --git a/task.md b/task.md index 6e216a8..ce770ec 100644 --- a/task.md +++ b/task.md @@ -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)