mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-23 15:08:07 +02:00
feat: Add IPTV player screen for stream playback with controls, EPG integration, and channel switching.
This commit is contained in:
1 parent
67e59b804c
commit
ddc0ea64e0
2 files changed
+134
-23
No files matched your search
@@ -70,6 +70,9 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
|||||||
_currentIndex = widget.channels!.indexWhere((c) => c.streamId == widget.streamId);
|
_currentIndex = widget.channels!.indexWhere((c) => c.streamId == widget.streamId);
|
||||||
if (_currentIndex == -1) _currentIndex = 0;
|
if (_currentIndex == -1) _currentIndex = 0;
|
||||||
}
|
}
|
||||||
|
// Show controls at start, then auto-hide after timeout
|
||||||
|
_showControls = true;
|
||||||
|
_onHover(); // Start the auto-hide timer
|
||||||
_initializePlayer(startTimeOverride: widget.startTime);
|
_initializePlayer(startTimeOverride: widget.startTime);
|
||||||
_setupMessageListener();
|
_setupMessageListener();
|
||||||
}
|
}
|
||||||
@@ -253,36 +256,127 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final settings = ref.watch(iptvSettingsProvider);
|
final settings = ref.watch(iptvSettingsProvider);
|
||||||
|
|
||||||
// LITE PLAYER MODE: Simple iframe with native HTML5 controls, no Flutter overlay
|
// LITE PLAYER MODE: Simple iframe with native HTML5 controls, minimal Flutter overlay
|
||||||
if (settings.playerType == PlayerType.lite) {
|
if (settings.playerType == PlayerType.lite) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
body: Stack(
|
body: MouseRegion(
|
||||||
fit: StackFit.expand,
|
onHover: (_) => _onHover(),
|
||||||
children: [
|
child: GestureDetector(
|
||||||
// Video Player (iframe with native controls) - player_lite.html has its own loading indicator
|
onTap: () {
|
||||||
if (_isInitialized)
|
if (_showControls) {
|
||||||
HtmlElementView(viewType: _viewId),
|
setState(() => _showControls = false);
|
||||||
|
} else {
|
||||||
|
_onHover();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
// Video Player (iframe with native controls)
|
||||||
|
if (_isInitialized)
|
||||||
|
HtmlElementView(viewType: _viewId),
|
||||||
|
|
||||||
// Simple Back Button - wrapped in PointerInterceptor to work above iframe
|
// Top Bar: Back Button + Title (with auto-hide)
|
||||||
Positioned(
|
AnimatedPositioned(
|
||||||
top: 24,
|
duration: const Duration(milliseconds: 200),
|
||||||
left: 24,
|
top: _showControls ? 24 : -80,
|
||||||
child: PointerInterceptor(
|
left: 24,
|
||||||
child: GestureDetector(
|
right: 24,
|
||||||
onTap: () => Navigator.pop(context),
|
child: PointerInterceptor(
|
||||||
child: Container(
|
child: Row(
|
||||||
padding: const EdgeInsets.all(12),
|
children: [
|
||||||
decoration: BoxDecoration(
|
Material(
|
||||||
color: Colors.black54,
|
color: Colors.black54,
|
||||||
shape: BoxShape.circle,
|
shape: const CircleBorder(),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => Navigator.pop(context),
|
||||||
|
customBorder: const CircleBorder(),
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.all(12),
|
||||||
|
child: Icon(Icons.arrow_back_rounded, color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
widget.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
shadows: [Shadow(blurRadius: 4, color: Colors.black)],
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
child: const Icon(Icons.arrow_back_rounded, color: Colors.white),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
// Channel Zapping Controls (with auto-hide)
|
||||||
|
if (widget.channels != null && widget.channels!.length > 1)
|
||||||
|
AnimatedPositioned(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
bottom: 100,
|
||||||
|
right: _showControls ? 24 : -80,
|
||||||
|
child: PointerInterceptor(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// Previous Channel
|
||||||
|
Material(
|
||||||
|
color: Colors.black54,
|
||||||
|
shape: const CircleBorder(),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: _previousChannel,
|
||||||
|
customBorder: const CircleBorder(),
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.all(14),
|
||||||
|
child: Icon(Icons.keyboard_arrow_up, color: Colors.white, size: 28),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
// Next Channel
|
||||||
|
Material(
|
||||||
|
color: Colors.black54,
|
||||||
|
shape: const CircleBorder(),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: _nextChannel,
|
||||||
|
customBorder: const CircleBorder(),
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.all(14),
|
||||||
|
child: Icon(Icons.keyboard_arrow_down, color: Colors.white, size: 28),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// EPG Overlay (for Live TV, with auto-hide, higher position)
|
||||||
|
if (widget.streamType == StreamType.live)
|
||||||
|
AnimatedPositioned(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
bottom: _showControls ? 60 : -120,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: PointerInterceptor(
|
||||||
|
child: EpgOverlay(
|
||||||
|
playlist: widget.playlist,
|
||||||
|
streamId: widget.channels != null
|
||||||
|
? widget.channels![_currentIndex].streamId
|
||||||
|
: widget.streamId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-1
@@ -169,6 +169,23 @@
|
|||||||
}, '*');
|
}, '*');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Report user activity (mouse movement or touch) to parent for auto-hide controls
|
||||||
|
let lastActivityTime = 0;
|
||||||
|
function reportActivity() {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - lastActivityTime > 100) { // Throttle to 100ms
|
||||||
|
lastActivityTime = now;
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: 'user_activity'
|
||||||
|
}, '*');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('mousemove', reportActivity);
|
||||||
|
document.addEventListener('touchstart', reportActivity);
|
||||||
|
document.addEventListener('touchmove', reportActivity);
|
||||||
|
document.addEventListener('click', reportActivity);
|
||||||
|
document.addEventListener('touchend', reportActivity);
|
||||||
|
|
||||||
async function playWithHLS(url) {
|
async function playWithHLS(url) {
|
||||||
log('Trying HLS.js with: ' + url);
|
log('Trying HLS.js with: ' + url);
|
||||||
showLoading('Loading HLS stream...');
|
showLoading('Loading HLS stream...');
|
||||||
@@ -331,4 +348,4 @@
|
|||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Reference in new issue
Block a user