Merge pull request #70 from R0m1k3/antigravity

feat: Add IPTV player screen with FFmpeg transcoding, playback contro…
This commit is contained in:
LogiFlow authored and GitHub committed 2025-12-07 10:27:38 +01:00
commit 07f1636dd2
1 file changed
+64 -43
+64 -43
View File
@@ -64,7 +64,6 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
void initState() { void initState() {
super.initState(); super.initState();
_currentIndex = widget.initialIndex; _currentIndex = widget.initialIndex;
_viewId = 'iptv-player-${widget.streamId}-${DateTime.now().millisecondsSinceEpoch}';
_initializePlayer(); _initializePlayer();
_setupMessageListener(); _setupMessageListener();
@@ -260,12 +259,16 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
debugPrint('PlayerScreen: Player URL: $playerUrl'); debugPrint('PlayerScreen: Player URL: $playerUrl');
// Generate a new view ID for every load to force iframe recreation
_viewId = 'iptv-player-${widget.streamId}-${DateTime.now().millisecondsSinceEpoch}';
// Register platform view factory // Register platform view factory
// ignore: undefined_prefixed_name // ignore: undefined_prefixed_name
ui_web.platformViewRegistry.registerViewFactory( ui_web.platformViewRegistry.registerViewFactory(
_viewId, _viewId,
(int viewId) { (int viewId) {
final iframe = html.IFrameElement() final iframe = html.IFrameElement()
..id = _viewId // CRITICAL: Set ID for getElementById lookup
..src = playerUrl ..src = playerUrl
..style.border = 'none' ..style.border = 'none'
..style.width = '100%' ..style.width = '100%'
@@ -293,54 +296,72 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
void _showSettingsDialog() { void _showSettingsDialog() {
showDialog( showDialog(
context: context, context: context,
useRootNavigator: true, // Ensure it's on top
builder: (context) => PointerInterceptor( builder: (context) => PointerInterceptor(
child: AlertDialog( child: Theme(
backgroundColor: const Color(0xFF1E1E1E), data: Theme.of(context).copyWith(dialogBackgroundColor: const Color(0xFF1C1C1E)),
title: const Text('Réglages du Lecteur', style: TextStyle(color: Colors.white)), child: AlertDialog(
content: Column( backgroundColor: const Color(0xFF1C1C1E).withOpacity(0.95),
mainAxisSize: MainAxisSize.min, shape: RoundedRectangleBorder(
crossAxisAlignment: CrossAxisAlignment.start, borderRadius: BorderRadius.circular(16),
children: [ side: BorderSide(color: Colors.white.withOpacity(0.1)),
const Text('Format d\'image', style: TextStyle(color: Colors.white70, fontSize: 13, fontWeight: FontWeight.bold)), ),
const SizedBox(height: 8), title: const Text('Réglages', style: TextStyle(color: Colors.white, fontWeight: FontWeight.w600)),
Wrap( content: SizedBox(
spacing: 8, width: 400,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildAspectRatioChip('Original', 'contain'), const Text('Format d\'image', style: TextStyle(color: Colors.white54, fontSize: 13, fontWeight: FontWeight.bold)),
_buildAspectRatioChip('Remplir', 'cover'), const SizedBox(height: 12),
_buildAspectRatioChip('Étirer', 'fill'), Wrap(
spacing: 8,
runSpacing: 8,
children: [
_buildAspectRatioChip('Original', 'contain'),
_buildAspectRatioChip('Remplir', 'cover'),
_buildAspectRatioChip('Étirer', 'fill'),
],
),
if (_audioTracks.isNotEmpty) ...[
const SizedBox(height: 24),
const Text('Pistes Audio', style: TextStyle(color: Colors.white54, fontSize: 13, fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
Container(
constraints: const BoxConstraints(maxHeight: 200),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.05),
borderRadius: BorderRadius.circular(8),
),
child: SingleChildScrollView(
child: Column(
children: _audioTracks.map((track) {
return ListTile(
title: Text(track['label'] ?? 'Piste ${track['id']}', style: const TextStyle(color: Colors.white, fontSize: 14)),
subtitle: Text(track['lang'] ?? '', style: const TextStyle(color: Colors.white54, fontSize: 12)),
onTap: () => _setAudioTrack(track['id']),
dense: true,
leading: const Icon(Icons.audiotrack, size: 20, color: Colors.white54),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
hoverColor: Colors.white.withOpacity(0.1),
);
}).toList(),
),
),
),
],
], ],
), ),
if (_audioTracks.isNotEmpty) ...[ ),
const SizedBox(height: 24), actions: [
const Text('Pistes Audio', style: TextStyle(color: Colors.white70, fontSize: 13, fontWeight: FontWeight.bold)), TextButton(
const SizedBox(height: 8), onPressed: () => Navigator.pop(context),
Container( style: TextButton.styleFrom(foregroundColor: Colors.white),
constraints: const BoxConstraints(maxHeight: 200), child: const Text('Fermer'),
child: SingleChildScrollView( ),
child: Column(
children: _audioTracks.map((track) {
return ListTile(
title: Text(track['label'] ?? 'Piste ${track['id']}', style: const TextStyle(color: Colors.white)),
subtitle: Text(track['lang'] ?? '', style: const TextStyle(color: Colors.white54)),
onTap: () => _setAudioTrack(track['id']),
dense: true,
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.audiotrack, color: Colors.white54),
);
}).toList(),
),
),
),
],
], ],
), ),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Fermer'),
),
],
), ),
), ),
); );