import 'dart:convert';
import 'dart:html' as html;
import 'package:dio/dio.dart';
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
import 'package:flutter/foundation.dart';
import '../../../core/models/playlist_config.dart';
import '../../../core/models/iptv_models.dart';
import '../models/xtream_models.dart' as xm;
/// Xtream Codes API Service
///
/// Handles all communication with Xtream API servers
class XtreamService {
late final Dio _dio;
late final CacheOptions _cacheOptions;
PlaylistConfig? _currentPlaylist;
/// Flag to indicate that a playback is currently loading
/// Used to throttle background requests (EPG, images)
bool isPlaybackLoading = false;
/// Minimum delay for background requests when playback is loading
static const _throttleDelay = Duration(seconds: 8);
XtreamService() {
_dio = Dio(
BaseOptions(
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 60),
sendTimeout: const Duration(seconds: 60),
),
);
// Setup caching for API responses
// Use MemCacheStore for in-memory caching
_cacheOptions = CacheOptions(
store: MemCacheStore(),
policy: CachePolicy.forceCache, // Prioritize local cache
maxStale: const Duration(hours: 1), // Increase to 1h for stability
priority: CachePriority.high,
);
_dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
// Attach the backend session token: the Xtream gateway
// (/api/xtream-api) requires authentication and injects the IPTV
// credentials server-side.
_dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
if (kIsWeb) {
final token = html.window.localStorage['auth_token'];
if (token != null && token.isNotEmpty) {
options.headers['Authorization'] = 'Bearer $token';
}
}
handler.next(options);
},
),
);
}
/// GET on the authenticated Xtream gateway. Credentials are injected by
/// the backend; the client only passes the action and its parameters.
Future> _apiGet(
Map params, {
Options? options,
}) {
return _dio.get(
'$_backendBaseUrl/api/xtream-api',
queryParameters: params,
options: options ?? Options(extra: _cacheOptions.toExtra()),
);
}
String? _manualBackendUrl;
/// Get dynamic base URL for the local backend
/// Priority:
/// 1. Manual override from settings
/// 2. Detected origin (Web)
/// 3. Host machine IP (Emulator)
String get _backendBaseUrl {
if (_manualBackendUrl != null && _manualBackendUrl!.isNotEmpty) {
return _manualBackendUrl!;
}
if (kIsWeb) {
final origin = Uri.base.origin;
// If we are developing locally on a different port than the backend
if (origin.contains('localhost') && !origin.contains('8089')) {
return 'http://localhost:8089';
}
// On mobile devices, 'localhost' is the device itself.
// If origin is localhost but we are on mobile web, it might fail.
// We assume the user is accessing via an IP or hostname.
return origin;
}
// Android emulator bridge
return 'http://10.0.2.2:8089';
}
String get backendBaseUrl => _backendBaseUrl;
/// Set manual backend URL override
void setBackendUrl(String? url) {
_manualBackendUrl = url;
}
/// Update connection timeouts
void updateTimeouts(int seconds) {
_dio.options.connectTimeout = Duration(seconds: seconds);
_dio.options.receiveTimeout = Duration(seconds: seconds);
_dio.options.sendTimeout = Duration(seconds: seconds);
}
/// Initialize connection with a playlist
void setPlaylist(PlaylistConfig playlist) {
_currentPlaylist = playlist;
}
/// Generate stream URL for live TV (HLS)
/// [quality]: source | high | medium | low (server-side FFmpeg preset)
String getLiveStreamUrl(String streamId, {String quality = 'high'}) {
if (_currentPlaylist == null) throw Exception('No playlist configured');
return '$_backendBaseUrl/api/live/$streamId/$quality/playlist.m3u8';
}
/// Generate stream URL for live TV (Direct MPEG-TS)
/// Faster zapping on compatible players (mpegts.js)
String getLiveStreamUrlTs(String streamId) {
if (_currentPlaylist == null) throw Exception('No playlist configured');
// Backend route injects the Xtream credentials server-side
return '$_backendBaseUrl/api/live/$streamId.ts';
}
/// Generate stream URL for VOD (movies)
/// [quality]: source | high | medium | low (server-side FFmpeg preset)
String getVodStreamUrl(
String streamId,
String containerExtension, {
String quality = 'high',
}) {
if (_currentPlaylist == null) throw Exception('No playlist configured');
return '$_backendBaseUrl/api/vod/$streamId/$quality/playlist.m3u8';
}
/// Generate stream URL for series episodes
String getSeriesStreamUrl(
String streamId,
String containerExtension, {
String quality = 'high',
}) {
if (_currentPlaylist == null) throw Exception('No playlist configured');
return '$_backendBaseUrl/api/vod/$streamId/$quality/playlist.m3u8?type=series';
}
/// Authenticate and get server info
Future