diff --git a/bin/api/streaming_handler.dart b/bin/api/streaming_handler.dart index 3758ad2..296025e 100644 --- a/bin/api/streaming_handler.dart +++ b/bin/api/streaming_handler.dart @@ -19,8 +19,9 @@ String _getFFmpegPath() { if (Platform.isWindows) { if (File('ffmpeg.exe').existsSync()) return 'ffmpeg.exe'; if (File('bin/ffmpeg.exe').existsSync()) return 'bin/ffmpeg.exe'; - if (File('ffmpeg/bin/ffmpeg.exe').existsSync()) + if (File('ffmpeg/bin/ffmpeg.exe').existsSync()) { return 'ffmpeg/bin/ffmpeg.exe'; + } } return 'ffmpeg'; // Default to PATH } @@ -41,7 +42,8 @@ void _logGpuStatus() { print('[FFmpeg] NVIDIA GPU acceleration ENABLED (NVDEC/NVENC)'); } else { print( - '[FFmpeg] Using CPU processing (set NVIDIA_GPU=true to enable GPU)'); + '[FFmpeg] Using CPU processing (set NVIDIA_GPU=true to enable GPU)', + ); } _gpuStatusLogged = true; } @@ -116,8 +118,9 @@ PlaylistConfig? _getCurrentPlaylist(Request request) { // ========================================== Handler createLiveStreamHandler( - Future Function(Request) getPlaylist, - {bool Function()? isGpuEnabled}) { + Future Function(Request) getPlaylist, { + bool Function()? isGpuEnabled, +}) { final router = Router(); // Route: /api/live/{streamId} (Catch all pattern to debug) @@ -291,16 +294,18 @@ Handler createLiveStreamHandler( final _lastLogTime = {}; Handler createVodStreamHandler( - Future Function(Request) getPlaylist, - {bool Function()? isGpuEnabled}) { + Future Function(Request) getPlaylist, { + bool Function()? isGpuEnabled, +}) { final router = Router(); // Route: /api/vod/{streamId}/playlist.m3u8 router.get('//playlist.m3u8', (Request request, String streamId) async { final playlist = await getPlaylist(request); - if (playlist == null) + if (playlist == null) { return Response.internalServerError(body: 'No playlist'); + } final streamDir = Directory('${_hlsTempDir.path}/$streamId'); @@ -514,7 +519,8 @@ Handler createVodStreamHandler( if (!playlistFile.existsSync()) { return Response.internalServerError( - body: 'Timeout waiting for transcoder'); + body: 'Timeout waiting for transcoder', + ); } return Response.ok( diff --git a/bin/server.dart b/bin/server.dart index 938b0b0..061969a 100644 --- a/bin/server.dart +++ b/bin/server.dart @@ -91,7 +91,8 @@ void main(List args) async { final user = req.context['user'] as User?; if (user == null || !user.isAdmin) { return Response.forbidden( - jsonEncode({'error': 'Admin access required'})); + jsonEncode({'error': 'Admin access required'}), + ); } final result = await cleanupService.runCleanup(); @@ -173,13 +174,19 @@ void main(List args) async { // Create Streaming Router (Mount handlers on correct paths) final streamingRouter = Router() ..mount( - '/api/live', - createLiveStreamHandler(getPlaylist, - isGpuEnabled: db.isNvidiaGpuEnabled)) + '/api/live', + createLiveStreamHandler( + getPlaylist, + isGpuEnabled: db.isNvidiaGpuEnabled, + ), + ) ..mount( - '/api/vod', - createVodStreamHandler(getPlaylist, - isGpuEnabled: db.isNvidiaGpuEnabled)); + '/api/vod', + createVodStreamHandler( + getPlaylist, + isGpuEnabled: db.isNvidiaGpuEnabled, + ), + ); // Main handler with API proxy and NEW Streaming Handlers final handler = Cascade() diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart index 221057b..56646e8 100644 --- a/lib/core/theme/app_theme.dart +++ b/lib/core/theme/app_theme.dart @@ -41,43 +41,52 @@ class AppTheme { // Typography: "Cinematic Modern" (Outfit for Headings, Inter for UI) textTheme: TextTheme( displayLarge: GoogleFonts.outfit( - fontSize: 56, - fontWeight: FontWeight.w700, - letterSpacing: -1.0, - color: AppColors.textPrimary), + fontSize: 56, + fontWeight: FontWeight.w700, + letterSpacing: -1.0, + color: AppColors.textPrimary, + ), displayMedium: GoogleFonts.outfit( - fontSize: 48, - fontWeight: FontWeight.w700, - letterSpacing: -0.5, - color: AppColors.textPrimary), + fontSize: 48, + fontWeight: FontWeight.w700, + letterSpacing: -0.5, + color: AppColors.textPrimary, + ), displaySmall: GoogleFonts.outfit( - fontSize: 32, - fontWeight: FontWeight.w600, - color: AppColors.textPrimary), + fontSize: 32, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), headlineMedium: GoogleFonts.outfit( - fontSize: 24, - fontWeight: FontWeight.w600, - color: AppColors.textPrimary), + fontSize: 24, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), titleLarge: GoogleFonts.outfit( - fontSize: 20, - fontWeight: FontWeight.w600, - color: AppColors.textPrimary), + fontSize: 20, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), titleMedium: GoogleFonts.inter( - fontSize: 16, - fontWeight: FontWeight.w600, - color: AppColors.textPrimary), + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), bodyLarge: GoogleFonts.inter( - fontSize: 16, - fontWeight: FontWeight.w400, - color: AppColors.textSecondary), + fontSize: 16, + fontWeight: FontWeight.w400, + color: AppColors.textSecondary, + ), bodyMedium: GoogleFonts.inter( - fontSize: 14, - fontWeight: FontWeight.w400, - color: AppColors.textSecondary), + fontSize: 14, + fontWeight: FontWeight.w400, + color: AppColors.textSecondary, + ), labelLarge: GoogleFonts.inter( - fontSize: 14, - fontWeight: FontWeight.w600, - color: AppColors.textPrimary), + fontSize: 14, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), ), // AppBar: Transparent / Glass @@ -99,7 +108,9 @@ class AppTheme { filled: true, fillColor: AppColors.surface, contentPadding: const EdgeInsets.symmetric( - horizontal: spacing24, vertical: spacing16), + horizontal: spacing24, + vertical: spacing16, + ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(radiusMd), borderSide: BorderSide.none, @@ -123,15 +134,18 @@ class AppTheme { elevation: 0, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(radiusMd)), + borderRadius: BorderRadius.circular(radiusMd), + ), textStyle: GoogleFonts.inter(fontWeight: FontWeight.w600, fontSize: 16), ).copyWith( overlayColor: WidgetStateProperty.resolveWith((states) { - if (states.contains(WidgetState.hovered)) + if (states.contains(WidgetState.hovered)) { return Colors.black.withOpacity(0.1); - if (states.contains(WidgetState.pressed)) + } + if (states.contains(WidgetState.pressed)) { return Colors.black.withOpacity(0.2); + } return null; }), ), diff --git a/lib/core/widgets/hero_carousel.dart b/lib/core/widgets/hero_carousel.dart index fe64a84..cd480a5 100644 --- a/lib/core/widgets/hero_carousel.dart +++ b/lib/core/widgets/hero_carousel.dart @@ -254,8 +254,10 @@ class _HeroCarouselState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.play_arrow_rounded, - color: Colors.white), + const Icon( + Icons.play_arrow_rounded, + color: Colors.white, + ), const SizedBox(width: 8), Text( 'WATCH NOW', diff --git a/lib/features/iptv/screens/dashboard_screen.dart b/lib/features/iptv/screens/dashboard_screen.dart index 6f1237f..371cf81 100644 --- a/lib/features/iptv/screens/dashboard_screen.dart +++ b/lib/features/iptv/screens/dashboard_screen.dart @@ -121,8 +121,11 @@ class _DashboardScreenState extends ConsumerState { ), ], ), - child: const Icon(Icons.play_arrow_rounded, - color: Colors.white, size: 28), + child: const Icon( + Icons.play_arrow_rounded, + color: Colors.white, + size: 28, + ), ), ), @@ -149,7 +152,8 @@ class _DashboardScreenState extends ConsumerState { borderRadius: BorderRadius.circular(16), border: isSelected ? Border.all( - color: AppColors.primary.withOpacity(0.5)) + color: AppColors.primary.withOpacity(0.5), + ) : null, ), child: Icon( @@ -176,8 +180,11 @@ class _DashboardScreenState extends ConsumerState { child: CircleAvatar( radius: 20, backgroundColor: Colors.white.withOpacity(0.1), - child: const Icon(Icons.person, - color: Colors.white70, size: 20), + child: const Icon( + Icons.person, + color: Colors.white70, + size: 20, + ), ), ), ), diff --git a/lib/features/iptv/screens/player_screen.dart b/lib/features/iptv/screens/player_screen.dart index 406cc6b..4c944e8 100644 --- a/lib/features/iptv/screens/player_screen.dart +++ b/lib/features/iptv/screens/player_screen.dart @@ -243,7 +243,8 @@ class _PlayerScreenState extends ConsumerState { iframe.contentWindow?.postMessage(message, '*'); } else { print( - '[PlayerScreen] Error: Could not find iframe to send message: $message'); + '[PlayerScreen] Error: Could not find iframe to send message: $message', + ); } } @@ -359,7 +360,9 @@ class _PlayerScreenState extends ConsumerState { right: 0, child: Container( padding: const EdgeInsets.symmetric( - horizontal: 16, vertical: 12), + horizontal: 16, + vertical: 12, + ), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, @@ -409,12 +412,6 @@ class _PlayerScreenState extends ConsumerState { ], ), ), - _buildSimpleIconButton( - icon: _isMuted - ? Icons.volume_off_rounded - : Icons.volume_up_rounded, - onTap: _toggleMute, - ), ], ), ), @@ -429,12 +426,15 @@ class _PlayerScreenState extends ConsumerState { right: 24, child: Container( padding: const EdgeInsets.symmetric( - horizontal: 20, vertical: 16), + horizontal: 20, + vertical: 16, + ), decoration: BoxDecoration( color: const Color(0xFF1A1A2E).withOpacity(0.9), borderRadius: BorderRadius.circular(24), border: Border.all( - color: Colors.white.withOpacity(0.1)), + color: Colors.white.withOpacity(0.1), + ), ), child: Row( children: [ @@ -608,10 +608,14 @@ class _PlayerScreenState extends ConsumerState { Row( children: [ Text( - _formatDuration(Duration( - seconds: _currentPosition.toInt())), + _formatDuration( + Duration( + seconds: _currentPosition.toInt(), + ), + ), style: GoogleFonts.inter( - color: Colors.white70), + color: Colors.white70, + ), ), const SizedBox(width: 16), Expanded( @@ -623,7 +627,8 @@ class _PlayerScreenState extends ConsumerState { Colors.white.withOpacity(0.2), thumbColor: Colors.white, thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 8), + enabledThumbRadius: 8, + ), overlayColor: AppColors.primary.withOpacity(0.2), ), @@ -632,18 +637,22 @@ class _PlayerScreenState extends ConsumerState { min: 0, max: _totalDuration, onChanged: (val) => setState( - () => _currentPosition = val), + () => _currentPosition = val, + ), onChangeStart: (_) => setState(() => _isSeeking = true), onChangeEnd: (val) { _sendMessage( - {'type': 'seek', 'value': val}); + {'type': 'seek', 'value': val}, + ); Future.delayed( const Duration(milliseconds: 500), () { - if (mounted) + if (mounted) { setState( - () => _isSeeking = false); + () => _isSeeking = false, + ); + } }); }, ), @@ -651,10 +660,14 @@ class _PlayerScreenState extends ConsumerState { ), const SizedBox(width: 16), Text( - _formatDuration(Duration( - seconds: _totalDuration.toInt())), + _formatDuration( + Duration( + seconds: _totalDuration.toInt(), + ), + ), style: GoogleFonts.inter( - color: Colors.white70), + color: Colors.white70, + ), ), ], ), @@ -669,7 +682,7 @@ class _PlayerScreenState extends ConsumerState { onTap: () => _sendMessage({ 'type': 'seek', 'value': (_currentPosition - 10) - .clamp(0, _totalDuration) + .clamp(0, _totalDuration), }), transparent: true, ), @@ -688,7 +701,7 @@ class _PlayerScreenState extends ConsumerState { onTap: () => _sendMessage({ 'type': 'seek', 'value': (_currentPosition + 10) - .clamp(0, _totalDuration) + .clamp(0, _totalDuration), }), transparent: true, ), @@ -807,9 +820,11 @@ class _PlayerScreenState extends ConsumerState { return Consumer( builder: (context, ref, child) { - final epgAsync = ref.watch(epgByPlaylistProvider( - EpgRequestKey(playlist: widget.playlist, streamId: currentStreamId), - )); + final epgAsync = ref.watch( + epgByPlaylistProvider( + EpgRequestKey(playlist: widget.playlist, streamId: currentStreamId), + ), + ); return epgAsync.when( data: (epgEntries) { diff --git a/lib/features/iptv/screens/playlist_selection_screen.dart b/lib/features/iptv/screens/playlist_selection_screen.dart index 505d1a7..eea75b3 100644 --- a/lib/features/iptv/screens/playlist_selection_screen.dart +++ b/lib/features/iptv/screens/playlist_selection_screen.dart @@ -73,7 +73,8 @@ class PlaylistSelectionScreen extends ConsumerWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.error_outline, size: 64, color: AppColors.error), + const Icon(Icons.error_outline, + size: 64, color: AppColors.error), const SizedBox(height: 16), Text( 'Error loading playlists', @@ -235,7 +236,7 @@ class _PlaylistCardState extends State<_PlaylistCard> { color: AppColors.primary.withOpacity(0.4), blurRadius: 16, offset: const Offset(0, 4), - ) + ), ] : [], ), diff --git a/lib/features/iptv/widgets/series_tab.dart b/lib/features/iptv/widgets/series_tab.dart index 20fdf2b..71ef558 100644 --- a/lib/features/iptv/widgets/series_tab.dart +++ b/lib/features/iptv/widgets/series_tab.dart @@ -4,10 +4,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; import '../../../core/models/playlist_config.dart'; import '../../../core/theme/app_colors.dart'; -import '../../../core/theme/app_theme.dart'; import '../../../core/utils/responsive_layout.dart'; import '../../../core/widgets/hero_carousel.dart'; -import '../../../core/widgets/themed_loading_screen.dart'; import '../../../core/widgets/glass_container.dart'; import '../../../core/widgets/tv_focusable_card.dart'; diff --git a/lib/mobile/features/iptv/widgets/mobile_series_tab.dart b/lib/mobile/features/iptv/widgets/mobile_series_tab.dart index c00b015..71be95b 100644 --- a/lib/mobile/features/iptv/widgets/mobile_series_tab.dart +++ b/lib/mobile/features/iptv/widgets/mobile_series_tab.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; import '../../../../core/models/playlist_config.dart'; -import '../../../../core/theme/app_colors.dart'; import '../../../../core/widgets/components/hero_carousel.dart'; import '../../../../core/widgets/glass_container.dart'; import '../../../../features/iptv/models/xtream_models.dart';