feat: Implement Xtream IPTV integration including providers, a dedicated player screen, EPG overlay, and live TV channel browsing.

This commit is contained in:
Michael committed 2025-12-06 09:45:22 +01:00
1 parent 210b6a81ca
commit e5eca6ebac
4 files changed
+40 -3

No files matched your search

@@ -61,3 +61,28 @@ final epgProvider = FutureProvider.family.autoDispose((ref, String streamId) asy
final service = ref.watch(activeXtreamServiceProvider); final service = ref.watch(activeXtreamServiceProvider);
return await service.getShortEpg(streamId); return await service.getShortEpg(streamId);
}); });
/// EPG request key for family provider (combines playlist and streamId)
class EpgRequestKey {
final PlaylistConfig playlist;
final String streamId;
const EpgRequestKey({required this.playlist, required this.streamId});
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is EpgRequestKey &&
runtimeType == other.runtimeType &&
playlist == other.playlist &&
streamId == other.streamId;
@override
int get hashCode => playlist.hashCode ^ streamId.hashCode;
}
/// Provider for EPG with playlist context
final epgByPlaylistProvider = FutureProvider.family.autoDispose((ref, EpgRequestKey key) async {
final service = ref.watch(xtreamServiceProvider(key.playlist));
return await service.getShortEpg(key.streamId);
});
+8 -2
View File
@@ -5,6 +5,7 @@ import 'package:chewie/chewie.dart';
import '../providers/xtream_provider.dart'; import '../providers/xtream_provider.dart';
import '../widgets/epg_overlay.dart'; import '../widgets/epg_overlay.dart';
import '../../../core/models/iptv_models.dart'; import '../../../core/models/iptv_models.dart';
import '../../../core/models/playlist_config.dart';
/// Stream type enum for player /// Stream type enum for player
enum StreamType { live, vod, series } enum StreamType { live, vod, series }
@@ -15,11 +16,13 @@ class PlayerScreen extends ConsumerStatefulWidget {
final String title; final String title;
final StreamType streamType; final StreamType streamType;
final String containerExtension; final String containerExtension;
final PlaylistConfig playlist;
const PlayerScreen({ const PlayerScreen({
super.key, super.key,
required this.streamId, required this.streamId,
required this.title, required this.title,
required this.playlist,
this.streamType = StreamType.live, this.streamType = StreamType.live,
this.containerExtension = 'm3u8', this.containerExtension = 'm3u8',
}); });
@@ -42,7 +45,7 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
Future<void> _initializePlayer() async { Future<void> _initializePlayer() async {
try { try {
final xtreamService = ref.read(activeXtreamServiceProvider); final xtreamService = ref.read(xtreamServiceProvider(widget.playlist));
// Generate stream URL based on type // Generate stream URL based on type
late String streamUrl; late String streamUrl;
@@ -177,7 +180,10 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
bottom: 80, bottom: 80,
left: 0, left: 0,
right: 0, right: 0,
child: EpgOverlay(streamId: widget.streamId), child: EpgOverlay(
streamId: widget.streamId,
playlist: widget.playlist,
),
), ),
], ],
), ),
+6 -1
View File
@@ -1,19 +1,24 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/models/playlist_config.dart';
import '../providers/xtream_provider.dart'; import '../providers/xtream_provider.dart';
/// EPG overlay widget showing "Now" and "Next" programs /// EPG overlay widget showing "Now" and "Next" programs
class EpgOverlay extends ConsumerWidget { class EpgOverlay extends ConsumerWidget {
final String streamId; final String streamId;
final PlaylistConfig playlist;
const EpgOverlay({ const EpgOverlay({
super.key, super.key,
required this.streamId, required this.streamId,
required this.playlist,
}); });
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final epgAsync = ref.watch(epgProvider(streamId)); final epgAsync = ref.watch(epgByPlaylistProvider(
EpgRequestKey(playlist: playlist, streamId: streamId),
));
return epgAsync.when( return epgAsync.when(
data: (epgEntries) { data: (epgEntries) {
@@ -255,6 +255,7 @@ class _LiveTVTabState extends ConsumerState<LiveTVTab>
builder: (context) => PlayerScreen( builder: (context) => PlayerScreen(
streamId: channel.streamId, streamId: channel.streamId,
title: channel.name, title: channel.name,
playlist: widget.playlist,
streamType: StreamType.live, streamType: StreamType.live,
), ),
), ),