mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-11 17:30:00 +02:00
perf: accelerate metadata loading with parallel fetching and local storage caching
- Implement parallel fetching for categories and streams in Live, VOD, and Series - Add persistent local storage caching for instant UI population - Optimize JSON parsing logic for large datasets - Implement failover to local cache when network is unavailable
This commit is contained in:
1 parent
df4bcfd07f
commit
bb8978efaa
1 file changed
+103
-51
@@ -1,3 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:html' as html;
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
||||
import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_store.dart';
|
||||
@@ -190,45 +192,81 @@ class XtreamService {
|
||||
: _cacheOptions.toExtra();
|
||||
|
||||
try {
|
||||
// First, load categories to get the mapping
|
||||
final categoryMap = await _getLiveCategories();
|
||||
// Parallelize categories and streams fetching
|
||||
final results = await Future.wait([
|
||||
_getLiveCategories(),
|
||||
_dio.get(
|
||||
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
||||
queryParameters: {
|
||||
'username': _currentPlaylist!.username,
|
||||
'password': _currentPlaylist!.password,
|
||||
'action': 'get_live_streams',
|
||||
},
|
||||
options: Options(extra: options),
|
||||
),
|
||||
]);
|
||||
|
||||
final response = await _dio.get(
|
||||
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
||||
queryParameters: {
|
||||
'username': _currentPlaylist!.username,
|
||||
'password': _currentPlaylist!.password,
|
||||
'action': 'get_live_streams',
|
||||
},
|
||||
options: Options(extra: options),
|
||||
);
|
||||
final categoryMap = results[0] as Map<String, String>;
|
||||
final response = results[1] as Response;
|
||||
|
||||
final List<dynamic> streams = response.data as List<dynamic>;
|
||||
final Map<String, List<Channel>> groupedChannels = {};
|
||||
final groupedChannels = <String, List<Channel>>{};
|
||||
|
||||
for (final streamData in streams) {
|
||||
final data = streamData as Map<String, dynamic>;
|
||||
// Get category name from our mapping using category_id
|
||||
final categoryId = data['category_id']?.toString() ?? '';
|
||||
final categoryName = categoryMap[categoryId] ?? 'Uncategorized';
|
||||
|
||||
// Inject category_name into data before parsing
|
||||
data['category_name'] = categoryName;
|
||||
|
||||
final channel = Channel.fromJson(data);
|
||||
|
||||
if (!groupedChannels.containsKey(categoryName)) {
|
||||
groupedChannels[categoryName] = [];
|
||||
}
|
||||
groupedChannels[categoryName]!.add(channel);
|
||||
groupedChannels.putIfAbsent(categoryName, () => []).add(channel);
|
||||
}
|
||||
|
||||
// Save to local cache for instant display next time
|
||||
_saveToLocalCache('live_channels', groupedChannels);
|
||||
|
||||
return groupedChannels;
|
||||
} catch (e) {
|
||||
// Try to return from local cache if network fails
|
||||
final cached = _loadFromLocalCache<Channel>('live_channels', Channel.fromJson);
|
||||
if (cached.isNotEmpty) return cached;
|
||||
throw Exception('Failed to fetch live channels: $e');
|
||||
}
|
||||
}
|
||||
|
||||
void _saveToLocalCache(String key, Map<String, dynamic> data) {
|
||||
if (!kIsWeb) return;
|
||||
try {
|
||||
final jsonStr = jsonEncode(data);
|
||||
html.window.localStorage['xtream_cache_${_currentPlaylist?.id}_$key'] = jsonStr;
|
||||
} catch (e) {
|
||||
debugPrint('Error saving to local cache ($key): $e');
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, List<T>> _loadFromLocalCache<T>(
|
||||
String key, T Function(Map<String, dynamic>) fromJson) {
|
||||
if (!kIsWeb) return {};
|
||||
try {
|
||||
final jsonStr = html.window.localStorage['xtream_cache_${_currentPlaylist?.id}_$key'];
|
||||
if (jsonStr == null) return {};
|
||||
|
||||
final data = jsonDecode(jsonStr) as Map<String, dynamic>;
|
||||
final result = <String, List<T>>{};
|
||||
|
||||
data.forEach((cat, items) {
|
||||
if (items is List) {
|
||||
result[cat] = items
|
||||
.map((i) => fromJson(i as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
});
|
||||
return result;
|
||||
} catch (e) {
|
||||
debugPrint('Error loading from local cache ($key): $e');
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/// Load VOD categories mapping
|
||||
Future<Map<String, String>> _getVodCategories() async {
|
||||
if (_currentPlaylist == null) return {};
|
||||
@@ -269,17 +307,22 @@ class XtreamService {
|
||||
: _cacheOptions.toExtra();
|
||||
|
||||
try {
|
||||
final categoryMap = await _getVodCategories();
|
||||
// Parallelize VOD categories and streams fetching
|
||||
final results = await Future.wait([
|
||||
_getVodCategories(),
|
||||
_dio.get(
|
||||
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
||||
queryParameters: {
|
||||
'username': _currentPlaylist!.username,
|
||||
'password': _currentPlaylist!.password,
|
||||
'action': 'get_vod_streams',
|
||||
},
|
||||
options: Options(extra: options),
|
||||
),
|
||||
]);
|
||||
|
||||
final response = await _dio.get(
|
||||
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
||||
queryParameters: {
|
||||
'username': _currentPlaylist!.username,
|
||||
'password': _currentPlaylist!.password,
|
||||
'action': 'get_vod_streams',
|
||||
},
|
||||
options: Options(extra: options),
|
||||
);
|
||||
final categoryMap = results[0] as Map<String, String>;
|
||||
final response = results[1] as Response;
|
||||
|
||||
final List<dynamic> vods = response.data as List<dynamic>;
|
||||
final Map<String, List<VodItem>> groupedVods = {};
|
||||
@@ -291,15 +334,17 @@ class XtreamService {
|
||||
data['category_name'] = categoryName;
|
||||
|
||||
final vod = VodItem.fromJson(data);
|
||||
|
||||
if (!groupedVods.containsKey(categoryName)) {
|
||||
groupedVods[categoryName] = [];
|
||||
}
|
||||
groupedVods[categoryName]!.add(vod);
|
||||
groupedVods.putIfAbsent(categoryName, () => []).add(vod);
|
||||
}
|
||||
|
||||
// Save to local cache
|
||||
_saveToLocalCache('vod_items', groupedVods);
|
||||
|
||||
return groupedVods;
|
||||
} catch (e) {
|
||||
// Try to return from local cache if network fails
|
||||
final cached = _loadFromLocalCache<VodItem>('vod_items', VodItem.fromJson);
|
||||
if (cached.isNotEmpty) return cached;
|
||||
throw Exception('Failed to fetch VOD items: $e');
|
||||
}
|
||||
}
|
||||
@@ -344,17 +389,22 @@ class XtreamService {
|
||||
: _cacheOptions.toExtra();
|
||||
|
||||
try {
|
||||
final categoryMap = await _getSeriesCategories();
|
||||
// Parallelize Series categories and streams fetching
|
||||
final results = await Future.wait([
|
||||
_getSeriesCategories(),
|
||||
_dio.get(
|
||||
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
||||
queryParameters: {
|
||||
'username': _currentPlaylist!.username,
|
||||
'password': _currentPlaylist!.password,
|
||||
'action': 'get_series',
|
||||
},
|
||||
options: Options(extra: options),
|
||||
),
|
||||
]);
|
||||
|
||||
final response = await _dio.get(
|
||||
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
||||
queryParameters: {
|
||||
'username': _currentPlaylist!.username,
|
||||
'password': _currentPlaylist!.password,
|
||||
'action': 'get_series',
|
||||
},
|
||||
options: Options(extra: options),
|
||||
);
|
||||
final categoryMap = results[0] as Map<String, String>;
|
||||
final response = results[1] as Response;
|
||||
|
||||
final List<dynamic> seriesList = response.data as List<dynamic>;
|
||||
final Map<String, List<Series>> groupedSeries = {};
|
||||
@@ -366,15 +416,17 @@ class XtreamService {
|
||||
data['category_name'] = categoryName;
|
||||
|
||||
final series = Series.fromJson(data);
|
||||
|
||||
if (!groupedSeries.containsKey(categoryName)) {
|
||||
groupedSeries[categoryName] = [];
|
||||
}
|
||||
groupedSeries[categoryName]!.add(series);
|
||||
groupedSeries.putIfAbsent(categoryName, () => []).add(series);
|
||||
}
|
||||
|
||||
// Save to local cache
|
||||
_saveToLocalCache('series_items', groupedSeries);
|
||||
|
||||
return groupedSeries;
|
||||
} catch (e) {
|
||||
// Try to return from local cache if network fails
|
||||
final cached = _loadFromLocalCache<Series>('series_items', Series.fromJson);
|
||||
if (cached.isNotEmpty) return cached;
|
||||
throw Exception('Failed to fetch series: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user