mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-20 05:28:05 +02:00
Déconnecter Auto NBA sur une IPTV la déconnectait sur l'autre : les deux tombaient en fait sur la même règle. L'en-tête X-Playlist-Id était absent, donc le serveur repliait sur la playlist la plus récente, pour la lecture comme pour l'écriture. AuthedHttp lit localStorage['selected_playlist'], écrit jusqu'ici par le seul ref.listen de playlistPersistenceProvider. Or ce listener n'existe qu'une fois activeXtreamServiceProvider lu, c'est-à-dire une fois le tableau de bord construit : le premier choix d'une session changeait l'état avant que quiconque n'écoute, et n'était jamais écrit. - persistSelectedPlaylist écrit l'entrée ; le listener l'appelle. - selectPlaylist persiste puis pose l'état ; les deux écrans de sélection (web et mobile) passent par lui. Non vérifié localement : le SDK Flutter de cette machine (3.10.3) est antérieur au ^3.13.0 exigé par le projet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
166 lines
5.8 KiB
Dart
166 lines
5.8 KiB
Dart
import 'dart:convert';
|
|
import 'dart:html' as html;
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../../../core/models/playlist_config.dart';
|
|
import '../services/xtream_service.dart';
|
|
import 'settings_provider.dart';
|
|
|
|
/// Family provider for Xtream service - one instance per playlist
|
|
final xtreamServiceProvider =
|
|
Provider.family<XtreamService, PlaylistConfig>((ref, playlist) {
|
|
final service = XtreamService();
|
|
final settings = ref.read(iptvSettingsProvider);
|
|
service.setBackendUrl(settings.backendUrl);
|
|
service.updateTimeouts(settings.timeoutSeconds);
|
|
service.setPlaylist(playlist);
|
|
|
|
// Update instance without rebuilding the provider to keep connectivity active
|
|
ref.listen(iptvSettingsProvider, (previous, next) {
|
|
service.setBackendUrl(next.backendUrl);
|
|
service.updateTimeouts(next.timeoutSeconds);
|
|
});
|
|
|
|
// ref.onDispose(() => service.dispose()); // Removed to prevent premature Dio destruction
|
|
return service;
|
|
});
|
|
|
|
/// Singleton Xtream service provider (legacy, for activeXtreamServiceProvider)
|
|
final _singletonXtreamServiceProvider = Provider<XtreamService>((ref) {
|
|
final service = XtreamService();
|
|
final settings = ref.read(iptvSettingsProvider);
|
|
service.setBackendUrl(settings.backendUrl);
|
|
service.updateTimeouts(settings.timeoutSeconds);
|
|
|
|
// Update instance without rebuilding the provider
|
|
ref.listen(iptvSettingsProvider, (previous, next) {
|
|
service.setBackendUrl(next.backendUrl);
|
|
service.updateTimeouts(next.timeoutSeconds);
|
|
});
|
|
|
|
// ref.onDispose(() => service.dispose()); // Removed to prevent premature Dio destruction
|
|
return service;
|
|
});
|
|
|
|
/// Current selected playlist provider with persistence for Web
|
|
final selectedPlaylistProvider = StateProvider<PlaylistConfig?>((ref) {
|
|
// Restore from localStorage if on web
|
|
try {
|
|
final stored = html.window.localStorage['selected_playlist'];
|
|
if (stored != null) {
|
|
final data = jsonDecode(stored) as Map<String, dynamic>;
|
|
return PlaylistConfig.fromJson(data);
|
|
}
|
|
} catch (e) {
|
|
debugPrint('Error restoring playlist: $e');
|
|
}
|
|
return null;
|
|
});
|
|
|
|
/// Écrit l'IPTV courante là où `AuthedHttp` la relit pour poser l'en-tête
|
|
/// `X-Playlist-Id`.
|
|
void persistSelectedPlaylist(PlaylistConfig? playlist) {
|
|
if (playlist != null) {
|
|
html.window.localStorage['selected_playlist'] =
|
|
jsonEncode(playlist.toJson());
|
|
} else {
|
|
html.window.localStorage.remove('selected_playlist');
|
|
}
|
|
}
|
|
|
|
/// Choisit l'IPTV courante et la persiste dans le même temps.
|
|
///
|
|
/// Écrire par le seul `playlistPersistenceProvider` ne suffit pas : son
|
|
/// `ref.listen` n'existe qu'une fois `activeXtreamServiceProvider` lu, c'est
|
|
/// à dire une fois le tableau de bord construit. Le tout premier choix d'une
|
|
/// session partait donc sans être écrit, `AuthedHttp` n'envoyait aucun
|
|
/// en-tête, et le serveur repliait sur la playlist la plus récente — les deux
|
|
/// IPTV retombaient alors sur la même, d'où une seule règle Auto NBA pour
|
|
/// les deux.
|
|
void selectPlaylist(WidgetRef ref, PlaylistConfig playlist) {
|
|
persistSelectedPlaylist(playlist);
|
|
ref.read(selectedPlaylistProvider.notifier).state = playlist;
|
|
}
|
|
|
|
// Listener to save selected playlist
|
|
final playlistPersistenceProvider = Provider<void>((ref) {
|
|
ref.listen(selectedPlaylistProvider, (previous, next) {
|
|
persistSelectedPlaylist(next);
|
|
});
|
|
});
|
|
|
|
/// Provider that watches for playlist changes and updates Xtream service
|
|
final activeXtreamServiceProvider = Provider<XtreamService>((ref) {
|
|
final service = ref.watch(_singletonXtreamServiceProvider);
|
|
final playlist = ref.watch(selectedPlaylistProvider);
|
|
|
|
// Ensure persistence listener is active
|
|
ref.read(playlistPersistenceProvider);
|
|
|
|
if (playlist != null) {
|
|
service.setPlaylist(playlist);
|
|
final settings = ref.read(iptvSettingsProvider);
|
|
service.updateTimeouts(settings.timeoutSeconds);
|
|
}
|
|
|
|
return service;
|
|
});
|
|
|
|
/// Provider for live TV channels grouped by category
|
|
final liveChannelsProvider = FutureProvider.autoDispose((ref) async {
|
|
final service = ref.watch(activeXtreamServiceProvider);
|
|
return await service.getLiveChannels();
|
|
});
|
|
|
|
/// Provider for live TV channels by playlist (family provider)
|
|
final liveChannelsByPlaylistProvider =
|
|
FutureProvider.family.autoDispose((ref, PlaylistConfig playlist) async {
|
|
final service = ref.watch(xtreamServiceProvider(playlist));
|
|
return await service.getLiveChannels();
|
|
});
|
|
|
|
/// Provider for VOD items (movies) grouped by category
|
|
final vodItemsProvider = FutureProvider.autoDispose((ref) async {
|
|
final service = ref.watch(activeXtreamServiceProvider);
|
|
return await service.getVodItems();
|
|
});
|
|
|
|
/// Provider for series grouped by category
|
|
final seriesProvider = FutureProvider.autoDispose((ref) async {
|
|
final service = ref.watch(activeXtreamServiceProvider);
|
|
return await service.getSeries();
|
|
});
|
|
|
|
/// Provider for EPG of a specific stream
|
|
final epgProvider =
|
|
FutureProvider.family.autoDispose((ref, String streamId) async {
|
|
final service = ref.watch(activeXtreamServiceProvider);
|
|
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);
|
|
});
|