mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-21 05:58:05 +02:00
feat: Introduce backend server with live TV proxy, VOD HLS transcoding, and IPTV streaming settings UI.
This commit is contained in:
1 parent
f290f5a7fc
commit
3dc9a8def2
8 files changed
+32
-43
No files matched your search
+9
-3
@@ -54,7 +54,13 @@ RUN apt-get update && apt-get install -y sqlite3 libsqlite3-dev ffmpeg && rm -rf
|
|||||||
# Create directory for HLS stream output
|
# Create directory for HLS stream output
|
||||||
RUN mkdir -p /tmp/streams && chmod 777 /tmp/streams
|
RUN mkdir -p /tmp/streams && chmod 777 /tmp/streams
|
||||||
|
|
||||||
# Copy entire bin directory (API, database, etc.)
|
# Copy pubspec.yaml (required for dependencies)
|
||||||
|
COPY pubspec.yaml ./
|
||||||
|
|
||||||
|
# Copy lib directory (required for shared models)
|
||||||
|
COPY lib/ ./lib/
|
||||||
|
|
||||||
|
# Copy bin directory
|
||||||
COPY bin/ ./bin/
|
COPY bin/ ./bin/
|
||||||
|
|
||||||
# Get dependencies
|
# Get dependencies
|
||||||
@@ -67,5 +73,5 @@ COPY --from=builder /app/build/web /app/web
|
|||||||
EXPOSE 8089
|
EXPOSE 8089
|
||||||
|
|
||||||
# Serve web application with API proxy
|
# Serve web application with API proxy
|
||||||
WORKDIR /app/bin
|
WORKDIR /app
|
||||||
CMD ["dart", "run", "server.dart", "--port", "8089", "--path", "/app/web"]
|
CMD ["dart", "run", "bin/server.dart", "--port", "8089", "--path", "/app/web"]
|
||||||
@@ -34,13 +34,19 @@ PlaylistConfig? _getCurrentPlaylist(Request request) {
|
|||||||
Handler createLiveStreamHandler(Future<PlaylistConfig?> Function(Request) getPlaylist) {
|
Handler createLiveStreamHandler(Future<PlaylistConfig?> Function(Request) getPlaylist) {
|
||||||
final router = Router();
|
final router = Router();
|
||||||
|
|
||||||
// Route: /api/live/{streamId}.ts
|
// Route: /api/live/{streamId} (Catch all pattern to debug)
|
||||||
router.get('/<streamId>.ts', (Request request, String streamId) async {
|
router.get('/<streamId|.*>', (Request request, String streamId) async {
|
||||||
final playlist = await getPlaylist(request);
|
print('[Live] Incoming request for streamId: $streamId');
|
||||||
if (playlist == null) {
|
|
||||||
return Response.internalServerError(body: 'No playlist configured');
|
// Remove .ts extension if present
|
||||||
|
if (streamId.endsWith('.ts')) {
|
||||||
|
streamId = streamId.substring(0, streamId.length - 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final playlist = await getPlaylist(request);
|
||||||
|
|
||||||
|
// ... existing logic ...
|
||||||
|
|
||||||
final targetUrl = Uri.parse('${playlist.dns}/live/${playlist.username}/${playlist.password}/$streamId.ts');
|
final targetUrl = Uri.parse('${playlist.dns}/live/${playlist.username}/${playlist.password}/$streamId.ts');
|
||||||
print('[Live] Proxying: $targetUrl');
|
print('[Live] Proxying: $targetUrl');
|
||||||
|
|
||||||
|
|||||||
@@ -15,3 +15,5 @@ dependencies:
|
|||||||
sqlite3: ^2.4.0
|
sqlite3: ^2.4.0
|
||||||
crypto: ^3.0.3
|
crypto: ^3.0.3
|
||||||
uuid: ^4.3.3
|
uuid: ^4.3.3
|
||||||
|
hive: ^2.2.3
|
||||||
|
equatable: ^2.0.5
|
||||||
+9
-20
@@ -10,7 +10,7 @@ import 'package:args/args.dart';
|
|||||||
import 'database/database.dart';
|
import 'database/database.dart';
|
||||||
import 'models/user.dart';
|
import 'models/user.dart';
|
||||||
import 'models/playlist.dart';
|
import 'models/playlist.dart';
|
||||||
import 'package:xtremflow/core/models/playlist_config.dart';
|
import '../lib/core/models/playlist_config.dart';
|
||||||
import 'api/auth_handler.dart';
|
import 'api/auth_handler.dart';
|
||||||
import 'api/users_handler.dart';
|
import 'api/users_handler.dart';
|
||||||
import 'api/playlists_handler.dart';
|
import 'api/playlists_handler.dart';
|
||||||
@@ -153,12 +153,16 @@ void main(List<String> args) async {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create Streaming Router (Mount handlers on correct paths)
|
||||||
|
final streamingRouter = Router()
|
||||||
|
..mount('/api/live', createLiveStreamHandler(_getPlaylist))
|
||||||
|
..mount('/api/vod', createVodStreamHandler(_getPlaylist));
|
||||||
|
|
||||||
// Main handler with API proxy and NEW Streaming Handlers
|
// Main handler with API proxy and NEW Streaming Handlers
|
||||||
final handler = Cascade()
|
final handler = Cascade()
|
||||||
.add(_createApiHandler(apiRouter))
|
.add(apiRouter.call) /* Standard API endpoints */
|
||||||
.add(_createXtreamProxyHandler()) /* Keep for general API calls */
|
.add(_createXtreamProxyHandler()) /* Keep for general API calls */
|
||||||
.add(createLiveStreamHandler(_getPlaylist))
|
.add(streamingRouter.call) /* Streaming endpoints */
|
||||||
.add(createVodStreamHandler(_getPlaylist))
|
|
||||||
.add(staticHandler)
|
.add(staticHandler)
|
||||||
.handler;
|
.handler;
|
||||||
|
|
||||||
@@ -190,22 +194,7 @@ void main(List<String> args) async {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create API handler
|
|
||||||
Handler _createApiHandler(Router apiRouter) {
|
|
||||||
return (Request request) async {
|
|
||||||
final path = request.url.path;
|
|
||||||
|
|
||||||
// Only handle /api/* requests (excluding special handlers)
|
|
||||||
if (path.startsWith('api/') &&
|
|
||||||
!path.startsWith('api/xtream/') &&
|
|
||||||
!path.startsWith('api/live/') &&
|
|
||||||
!path.startsWith('api/vod/')) {
|
|
||||||
return apiRouter(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Response.notFound('Not found');
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// CORS middleware to allow cross-origin requests
|
/// CORS middleware to allow cross-origin requests
|
||||||
Middleware _corsMiddleware() {
|
Middleware _corsMiddleware() {
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -299,7 +299,6 @@ class IptvSettingsNotifier extends StateNotifier<IptvSettings> {
|
|||||||
preferredAspectRatio: getValue<String>(_SettingsKeys.preferredAspectRatio),
|
preferredAspectRatio: getValue<String>(_SettingsKeys.preferredAspectRatio),
|
||||||
playerType: remoteSettings[_SettingsKeys.playerType] != null
|
playerType: remoteSettings[_SettingsKeys.playerType] != null
|
||||||
? PlayerType.values[remoteSettings[_SettingsKeys.playerType] as int] : null,
|
? PlayerType.values[remoteSettings[_SettingsKeys.playerType] as int] : null,
|
||||||
forceVodTranscoding: getValue<bool>(_SettingsKeys.forceVodTranscoding),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
|
|||||||
@@ -150,20 +150,7 @@ class StreamingSettingsTab extends ConsumerWidget {
|
|||||||
activeColor: Colors.blueAccent,
|
activeColor: Colors.blueAccent,
|
||||||
onChanged: (v) => notifier.setPreferDirectPlay(v),
|
onChanged: (v) => notifier.setPreferDirectPlay(v),
|
||||||
),
|
),
|
||||||
Divider(height: 1, color: Colors.white.withOpacity(0.1)),
|
|
||||||
SwitchListTile(
|
|
||||||
title: Text(
|
|
||||||
'Forcer audio VOD (Films/Séries)',
|
|
||||||
style: GoogleFonts.roboto(fontSize: 14, color: Colors.white),
|
|
||||||
),
|
|
||||||
subtitle: Text(
|
|
||||||
'Active FFmpeg pour garantir le son\n⚠️ Désactive le seeking',
|
|
||||||
style: GoogleFonts.roboto(fontSize: 12, color: Colors.white54),
|
|
||||||
),
|
|
||||||
value: settings.forceVodTranscoding,
|
|
||||||
activeColor: Colors.orangeAccent,
|
|
||||||
onChanged: (v) => notifier.setForceVodTranscoding(v),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|||||||
Reference in new issue
Block a user