feat: Add initial IPTV support with a dedicated player, series detail screen, Xtream API service, and a streaming API handler.

This commit is contained in:
Michael committed 2025-12-15 22:09:21 +01:00
1 parent d1401711af
commit d6405f3471
4 files changed
+68 -15

No files matched your search

@@ -345,6 +345,22 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
overflow: TextOverflow.ellipsis,
),
),
// Mute button for Live TV
Material(
color: Colors.black54,
shape: const CircleBorder(),
child: InkWell(
onTap: _toggleMute,
customBorder: const CircleBorder(),
child: Padding(
padding: const EdgeInsets.all(12),
child: Icon(
_isMuted ? Icons.volume_off_rounded : Icons.volume_up_rounded,
color: Colors.white
),
),
),
),
],
),
),
@@ -616,6 +632,12 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
],
const Spacer(),
// Volume/Mute button
_buildTvControl(
_isMuted ? Icons.volume_off_rounded : Icons.volume_up_rounded,
_toggleMute
),
const SizedBox(width: 16),
_buildTvControl(Icons.subtitles_rounded, () {}),
const SizedBox(width: 16),
_buildTvControl(Icons.aspect_ratio_rounded, _toggleFullscreen),
@@ -267,10 +267,25 @@ class _SeriesDetailScreenState extends ConsumerState<SeriesDetailScreen> {
color: Colors.grey.shade800,
margin: const EdgeInsets.only(bottom: 8),
child: InkWell(
onTap: () {
onTap: () async {
// Mark as watched when playing
ref.read(watchHistoryProvider.notifier).markEpisodeWatched(episodeKey);
// Fetch actual duration from API if not available
Duration? episodeDuration;
if (episode.durationSecs != null && episode.durationSecs! > 0) {
episodeDuration = Duration(seconds: episode.durationSecs!);
} else {
// Try to fetch from API using getVodDuration (episodes are often queryable as VODs)
final service = ref.read(xtreamServiceProvider(widget.playlist));
final durationSecs = await service.getVodDuration(episode.id);
if (durationSecs != null && durationSecs > 0) {
episodeDuration = Duration(seconds: durationSecs);
}
}
if (!context.mounted) return;
Navigator.push(
context,
MaterialPageRoute(
@@ -280,7 +295,7 @@ class _SeriesDetailScreenState extends ConsumerState<SeriesDetailScreen> {
playlist: widget.playlist,
streamType: StreamType.series,
containerExtension: episode.containerExtension ?? 'mkv',
duration: episode.durationSecs != null ? Duration(seconds: episode.durationSecs!) : null,
duration: episodeDuration,
),
),
);
@@ -79,9 +79,9 @@ class XtreamService {
String getSeriesStreamUrl(String streamId, String containerExtension) {
if (_currentPlaylist == null) throw Exception('No playlist configured');
// Point to new dedicated VOD API (HLS playlist)
// Point to new dedicated VOD API (HLS playlist) with series type
final baseUrl = html.window.location.origin;
return '$baseUrl/api/vod/$streamId/playlist.m3u8';
return '$baseUrl/api/vod/$streamId/playlist.m3u8?type=series';
}
/// Authenticate and get server info