feat: implement mobile live TV tab with channel browsing, search, and a dedicated player screen supporting channel zapping.

This commit is contained in:
Michael committed 2026-03-09 14:32:41 +01:00
1 parent 12b799a7fd
commit 4d1e08148b
4 files changed
+136 -32

No files matched your search

@@ -12,6 +12,7 @@ class MobilePlayerScreen extends ConsumerWidget {
final StreamType streamType;
final String containerExtension;
final PlaylistConfig playlist;
final List<dynamic>? channels; // New: List of channels for zapping
const MobilePlayerScreen({
super.key,
@@ -20,6 +21,7 @@ class MobilePlayerScreen extends ConsumerWidget {
required this.playlist,
this.streamType = StreamType.live,
this.containerExtension = 'mp4',
this.channels,
});
@override
@@ -35,12 +37,72 @@ class MobilePlayerScreen extends ConsumerWidget {
streamUrl = service.getSeriesStreamUrl(streamId, containerExtension);
}
// Zapping logic
VoidCallback? onNext;
VoidCallback? onPrevious;
if (channels != null && channels!.isNotEmpty) {
final currentIndex = channels!.indexWhere((c) {
try {
// Supports Channel/Movie (.streamId) and Episode (.id)
final cId = (c.streamId ?? c.id).toString();
return cId == streamId;
} catch (_) {
return false;
}
});
if (currentIndex != -1) {
void navigateTo(int index) {
final item = channels![index % channels!.length];
String nId = '';
String nTitle = '';
String nExt = containerExtension;
try {
// ID Extraction
nId = (item.streamId ?? item.id).toString();
// Title Extraction (name for channels/movies, title for episodes)
nTitle = item.name ?? item.title ?? 'Unknown';
// Format series title predictably
if (streamType == StreamType.series && title.contains(' - ')) {
final prefix = title.split(' - ').first;
nTitle = '$prefix - Episode ${item.episodeNum ?? ""}';
}
nExt = item.containerExtension ?? containerExtension;
} catch (_) {}
if (nId.isEmpty) return;
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => MobilePlayerScreen(
streamId: nId,
title: nTitle,
playlist: playlist,
streamType: streamType,
containerExtension: nExt,
channels: channels,
),
),
);
}
onNext = () => navigateTo(currentIndex + 1);
onPrevious = () => navigateTo(currentIndex - 1 + channels!.length);
}
}
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
title: Text(title, style: const TextStyle(color: Colors.white)),
title: Text(title,
style: const TextStyle(color: Colors.white, fontSize: 16)),
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.pop(context),
@@ -50,6 +112,8 @@ class MobilePlayerScreen extends ConsumerWidget {
child: LitePlayerView(
streamUrl: streamUrl,
isLive: streamType == StreamType.live,
onNext: onNext,
onPrevious: onPrevious,
),
),
);