feat(design): Warm Cinema theme + event-driven adaptive players

Replaces the Projector Noir palette with "Warm Cinema" and rebuilds the
web players around a shared, event-driven engine.

Design
- app_colors: warm charcoal (#0B0908), cream (#F3E9DF), ember (#D9541F);
  adds posterScrim/heroScrim gradients, lift()/emberFocus() shadows,
  const cream-alpha tokens replacing Colors.whiteNN, and primaryFill,
  a deeper ember reserved for text-bearing fills so white labels clear
  the 4.5:1 AA threshold that #D9541F alone does not
- app_theme / mobile_theme: Fraunces (display serif) + Karla (body),
  tighter radii, 20+ component themes
- glass_container: opaque layered surfaces replace the blurred glass
- themed_loading_screen restyled; players and boot splash follow

Performance
- drop every BackdropFilter: backdrop blur forced a full framebuffer
  read per frame per card, the main source of scroll jank
- bundle Fraunces/Karla as assets so google_fonts stops fetching from
  fonts.gstatic.com at startup
- bound image decode size on 12 sites: a 40-poster grid drops from
  ~240 MB to a few tens of MB of GPU memory

Players (new shared engine: web/xf-player-core.js)
- start on media events instead of polling the buffer every 100-200 ms
- hls.js/mpegts.js loaded on demand, so ~213 KB of mpegts is never
  fetched for HLS streams
- liveSyncDurationCount 10 -> 2, startFragPrefetch on, testBandwidth
  off, MPEG-TS initial stash 512 KB -> 64 KB
- adaptive escalation fast -> balanced -> safe after repeated stalls in
  a 60 s window, applied live for HLS so there is no visible cut

Preserved from the recent streaming work on main: server-side quality
selection, stable player viewId, the turbo direct MPEG-TS path, vendored
library cache headers and the CI/Docker changes. Only the theme layer and
the player front-end were replaced; GlassCard stays in ui_components.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 5 committed 2026-08-10 21:42:56 +02:00
1 parent e332895d7d
commit 1feeaae78a
49 files changed
+2341 -1751

No files matched your search

@@ -19,7 +19,7 @@ class StreamingSettingsTab extends ConsumerWidget {
// Header
Text(
'Paramètres Streaming Live TV',
style: GoogleFonts.instrumentSans(
style: GoogleFonts.karla(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColors.onSurface,
@@ -28,7 +28,7 @@ class StreamingSettingsTab extends ConsumerWidget {
const SizedBox(height: 8),
Text(
'Ces paramètres s\'appliquent uniquement aux flux TV en direct.',
style: GoogleFonts.instrumentSans(
style: GoogleFonts.karla(
fontSize: 12,
color: AppColors.onSurfaceVariant,
),
@@ -101,15 +101,15 @@ class StreamingSettingsTab extends ConsumerWidget {
ConnectionTimeout.long => 'Long (60s)',
},
),
Divider(height: 1, color: Colors.white.withOpacity(0.1)),
Divider(height: 1, color: AppColors.onSurface.withOpacity(0.1)),
SwitchListTile(
title: Text(
'Reconnexion automatique',
style: GoogleFonts.instrumentSans(fontSize: 14, color: AppColors.onSurface),
style: GoogleFonts.karla(fontSize: 14, color: AppColors.onSurface),
),
subtitle: Text(
'Se reconnecter en cas de coupure',
style: GoogleFonts.instrumentSans(fontSize: 12, color: AppColors.onSurfaceVariant),
style: GoogleFonts.karla(fontSize: 12, color: AppColors.onSurfaceVariant),
),
value: settings.autoReconnect,
activeThumbColor: AppColors.primaryContainer,
@@ -137,15 +137,15 @@ class StreamingSettingsTab extends ConsumerWidget {
TranscodingMode.disabled => 'Désactivé (direct)',
},
),
Divider(height: 1, color: Colors.white.withOpacity(0.1)),
Divider(height: 1, color: AppColors.onSurface.withOpacity(0.1)),
SwitchListTile(
title: Text(
'Préférer lecture directe',
style: GoogleFonts.instrumentSans(fontSize: 14, color: AppColors.onSurface),
style: GoogleFonts.karla(fontSize: 14, color: AppColors.onSurface),
),
subtitle: Text(
'Utiliser le flux original si compatible',
style: GoogleFonts.instrumentSans(fontSize: 12, color: AppColors.onSurfaceVariant),
style: GoogleFonts.karla(fontSize: 12, color: AppColors.onSurfaceVariant),
),
value: settings.preferDirectPlay,
activeThumbColor: AppColors.primaryContainer,
@@ -186,11 +186,11 @@ class StreamingSettingsTab extends ConsumerWidget {
SwitchListTile(
title: Text(
'Accélération NVIDIA GPU',
style: GoogleFonts.instrumentSans(fontSize: 14, color: AppColors.onSurface),
style: GoogleFonts.karla(fontSize: 14, color: AppColors.onSurface),
),
subtitle: Text(
'Utiliser NVENC/NVDEC pour le transcodage (requiert GPU NVIDIA)',
style: GoogleFonts.instrumentSans(fontSize: 12, color: AppColors.onSurfaceVariant),
style: GoogleFonts.karla(fontSize: 12, color: AppColors.onSurfaceVariant),
),
value: settings.enableNvidiaGpu,
activeThumbColor: Colors.green,
@@ -215,7 +215,7 @@ class StreamingSettingsTab extends ConsumerWidget {
Expanded(
child: Text(
'Les changements prennent effet immédiatement pour les nouveaux flux.',
style: GoogleFonts.instrumentSans(
style: GoogleFonts.karla(
fontSize: 12,
color: AppColors.primary,
),
@@ -236,9 +236,9 @@ class StreamingSettingsTab extends ConsumerWidget {
}) {
return Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.05),
color: AppColors.onSurface.withOpacity(0.05),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.white.withOpacity(0.1)),
border: Border.all(color: AppColors.onSurface.withOpacity(0.1)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -251,7 +251,7 @@ class StreamingSettingsTab extends ConsumerWidget {
const SizedBox(width: 8),
Text(
title,
style: GoogleFonts.instrumentSans(
style: GoogleFonts.karla(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.onSurface,
@@ -260,7 +260,7 @@ class StreamingSettingsTab extends ConsumerWidget {
],
),
),
Divider(height: 1, color: Colors.white.withOpacity(0.1)),
Divider(height: 1, color: AppColors.onSurface.withOpacity(0.1)),
...children,
],
),
@@ -278,11 +278,11 @@ class StreamingSettingsTab extends ConsumerWidget {
return ListTile(
title: Text(
title,
style: GoogleFonts.instrumentSans(fontSize: 14, color: AppColors.onSurface),
style: GoogleFonts.karla(fontSize: 14, color: AppColors.onSurface),
),
subtitle: Text(
subtitle,
style: GoogleFonts.instrumentSans(fontSize: 12, color: AppColors.onSurfaceVariant),
style: GoogleFonts.karla(fontSize: 12, color: AppColors.onSurfaceVariant),
),
trailing: DropdownButton<T>(
value: value,
@@ -293,12 +293,12 @@ class StreamingSettingsTab extends ConsumerWidget {
value: item,
child: Text(
labelBuilder(item),
style: GoogleFonts.instrumentSans(fontSize: 13, color: AppColors.onSurface),
style: GoogleFonts.karla(fontSize: 13, color: AppColors.onSurface),
),
);
}).toList(),
onChanged: onChanged,
style: GoogleFonts.instrumentSans(color: AppColors.onSurface),
style: GoogleFonts.karla(color: AppColors.onSurface),
iconEnabledColor: AppColors.onSurfaceVariant,
),
);