feat: Install FFmpeg with NVIDIA NVENC/NVDEC support via apt-get instead of a static build.

This commit is contained in:
Michael committed 2026-01-03 10:26:17 +01:00
1 parent 33a331dce9
commit 71630d75e8
9 files changed
+135 -86

No files matched your search

+47 -33
View File
@@ -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;
}),
),
+4 -2
View File
@@ -254,8 +254,10 @@ class _HeroCarouselState extends State<HeroCarousel> {
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',
@@ -121,8 +121,11 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
),
],
),
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<DashboardScreen> {
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<DashboardScreen> {
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,
),
),
),
),
+41 -26
View File
@@ -243,7 +243,8 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
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<PlayerScreen> {
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<PlayerScreen> {
],
),
),
_buildSimpleIconButton(
icon: _isMuted
? Icons.volume_off_rounded
: Icons.volume_up_rounded,
onTap: _toggleMute,
),
],
),
),
@@ -429,12 +426,15 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
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<PlayerScreen> {
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<PlayerScreen> {
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<PlayerScreen> {
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<PlayerScreen> {
),
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<PlayerScreen> {
onTap: () => _sendMessage({
'type': 'seek',
'value': (_currentPosition - 10)
.clamp(0, _totalDuration)
.clamp(0, _totalDuration),
}),
transparent: true,
),
@@ -688,7 +701,7 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
onTap: () => _sendMessage({
'type': 'seek',
'value': (_currentPosition + 10)
.clamp(0, _totalDuration)
.clamp(0, _totalDuration),
}),
transparent: true,
),
@@ -807,9 +820,11 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
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) {
@@ -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),
)
),
]
: [],
),
@@ -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';
@@ -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';