feat: implement IPTV PlayerScreen with embedded web player for live, VOD, and series streams.

This commit is contained in:
Michael committed 2026-01-01 19:19:52 +01:00
1 parent e74aa822ad
commit b6a0016c00
1 file changed
+89 -92
+89 -92
View File
@@ -59,6 +59,7 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
bool _isSeeking = false; bool _isSeeking = false;
String _viewId = 'iptv-player'; String _viewId = 'iptv-player';
String? _currentStreamUrl; String? _currentStreamUrl;
String _statusMessage = 'Loading...';
String? _errorMessage; String? _errorMessage;
bool _isMuted = false; bool _isMuted = false;
bool _ignoreStatusUpdates = false; bool _ignoreStatusUpdates = false;
@@ -347,59 +348,57 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
top: _showControls ? 24 : -100, top: _showControls ? 24 : -100,
left: 24, left: 24,
right: 24, right: 24,
child: PointerInterceptor( child: GlassContainer(
child: GlassContainer( height: 72,
height: 72, borderRadius: 24,
borderRadius: 24, opacity: 0.15,
opacity: 0.15, border: true,
border: true, borderColor: Colors.white.withOpacity(0.1),
borderColor: Colors.white.withOpacity(0.1), padding: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.symmetric(horizontal: 16), child: Row(
child: Row( children: [
children: [ _buildGlassIconButton(
_buildGlassIconButton( icon: Icons.arrow_back_rounded,
icon: Icons.arrow_back_rounded, onTap: () => Navigator.pop(context),
onTap: () => Navigator.pop(context), ),
), const SizedBox(width: 16),
const SizedBox(width: 16), Expanded(
Expanded( child: Column(
child: Column( mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ Text(
Text( widget.channels != null
widget.channels != null ? widget.channels![_currentIndex].name
? widget.channels![_currentIndex].name : widget.title,
: widget.title, style: GoogleFonts.outfit(
style: GoogleFonts.outfit( color: Colors.white,
color: Colors.white, fontSize: 18,
fontSize: 18, fontWeight: FontWeight.w600,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
), ),
if (widget.channels != null) overflow: TextOverflow.ellipsis,
Text( ),
'Live TV', if (widget.channels != null)
style: GoogleFonts.inter( Text(
color: AppColors.primary, 'Live TV',
fontSize: 12, style: GoogleFonts.inter(
fontWeight: FontWeight.bold, color: AppColors.primary,
letterSpacing: 1.0, fontSize: 12,
), fontWeight: FontWeight.bold,
letterSpacing: 1.0,
), ),
], ),
), ],
), ),
const SizedBox(width: 16), ),
_buildGlassIconButton( const SizedBox(width: 16),
icon: _isMuted _buildGlassIconButton(
? Icons.volume_off_rounded icon: _isMuted
: Icons.volume_up_rounded, ? Icons.volume_off_rounded
onTap: _toggleMute, : Icons.volume_up_rounded,
), onTap: _toggleMute,
], ),
), ],
), ),
), ),
), ),
@@ -411,51 +410,49 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
bottom: 40, bottom: 40,
left: 0, left: 0,
right: 0, right: 0,
child: PointerInterceptor( child: Center(
child: Center( child: GlassContainer(
child: GlassContainer( borderRadius: 30,
borderRadius: 30, opacity: 0.15,
opacity: 0.15, border: true,
border: true, borderColor: Colors.white.withOpacity(0.1),
borderColor: Colors.white.withOpacity(0.1), padding: const EdgeInsets.symmetric(
padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 12),
horizontal: 24, vertical: 12), child: Row(
child: Row( mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min, children: [
children: [ // Previous Channel
// Previous Channel if (widget.channels != null &&
if (widget.channels != null && widget.channels!.length > 1) ...[
widget.channels!.length > 1) ...[
_buildGlassIconButton(
icon: Icons.skip_previous_rounded,
onTap: _previousChannel,
transparent: true,
),
const SizedBox(width: 24),
],
// Play/Pause
_buildGlassIconButton( _buildGlassIconButton(
icon: _isPlaying icon: Icons.skip_previous_rounded,
? Icons.pause_rounded onTap: _previousChannel,
: Icons.play_arrow_rounded, transparent: true,
onTap: _togglePlayPause,
size: 56,
iconSize: 32,
), ),
const SizedBox(width: 24),
// Next Channel
if (widget.channels != null &&
widget.channels!.length > 1) ...[
const SizedBox(width: 24),
_buildGlassIconButton(
icon: Icons.skip_next_rounded,
onTap: _nextChannel,
transparent: true,
),
],
], ],
),
// Play/Pause
_buildGlassIconButton(
icon: _isPlaying
? Icons.pause_rounded
: Icons.play_arrow_rounded,
onTap: _togglePlayPause,
size: 56,
iconSize: 32,
),
// Next Channel
if (widget.channels != null &&
widget.channels!.length > 1) ...[
const SizedBox(width: 24),
_buildGlassIconButton(
icon: Icons.skip_next_rounded,
onTap: _nextChannel,
transparent: true,
),
],
],
), ),
), ),
), ),