mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-19 13:08:05 +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/dio.dart';
|
||||||
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
||||||
import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_store.dart';
|
import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_store.dart';
|
||||||
@@ -190,45 +192,81 @@ class XtreamService {
|
|||||||
: _cacheOptions.toExtra();
|
: _cacheOptions.toExtra();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// First, load categories to get the mapping
|
// Parallelize categories and streams fetching
|
||||||
final categoryMap = await _getLiveCategories();
|
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(
|
final categoryMap = results[0] as Map<String, String>;
|
||||||
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
final response = results[1] as Response;
|
||||||
queryParameters: {
|
|
||||||
'username': _currentPlaylist!.username,
|
|
||||||
'password': _currentPlaylist!.password,
|
|
||||||
'action': 'get_live_streams',
|
|
||||||
},
|
|
||||||
options: Options(extra: options),
|
|
||||||
);
|
|
||||||
|
|
||||||
final List<dynamic> streams = response.data as List<dynamic>;
|
final groupedChannels = <String, List<Channel>>{};
|
||||||
final Map<String, List<Channel>> groupedChannels = {};
|
|
||||||
|
|
||||||
for (final streamData in streams) {
|
for (final streamData in streams) {
|
||||||
final data = streamData as Map<String, dynamic>;
|
final data = streamData as Map<String, dynamic>;
|
||||||
// Get category name from our mapping using category_id
|
|
||||||
final categoryId = data['category_id']?.toString() ?? '';
|
final categoryId = data['category_id']?.toString() ?? '';
|
||||||
final categoryName = categoryMap[categoryId] ?? 'Uncategorized';
|
final categoryName = categoryMap[categoryId] ?? 'Uncategorized';
|
||||||
|
|
||||||
// Inject category_name into data before parsing
|
|
||||||
data['category_name'] = categoryName;
|
data['category_name'] = categoryName;
|
||||||
|
|
||||||
final channel = Channel.fromJson(data);
|
final channel = Channel.fromJson(data);
|
||||||
|
groupedChannels.putIfAbsent(categoryName, () => []).add(channel);
|
||||||
if (!groupedChannels.containsKey(categoryName)) {
|
|
||||||
groupedChannels[categoryName] = [];
|
|
||||||
}
|
|
||||||
groupedChannels[categoryName]!.add(channel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save to local cache for instant display next time
|
||||||
|
_saveToLocalCache('live_channels', groupedChannels);
|
||||||
|
|
||||||
return groupedChannels;
|
return groupedChannels;
|
||||||
} catch (e) {
|
} 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');
|
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
|
/// Load VOD categories mapping
|
||||||
Future<Map<String, String>> _getVodCategories() async {
|
Future<Map<String, String>> _getVodCategories() async {
|
||||||
if (_currentPlaylist == null) return {};
|
if (_currentPlaylist == null) return {};
|
||||||
@@ -269,17 +307,22 @@ class XtreamService {
|
|||||||
: _cacheOptions.toExtra();
|
: _cacheOptions.toExtra();
|
||||||
|
|
||||||
try {
|
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(
|
final categoryMap = results[0] as Map<String, String>;
|
||||||
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
final response = results[1] as Response;
|
||||||
queryParameters: {
|
|
||||||
'username': _currentPlaylist!.username,
|
|
||||||
'password': _currentPlaylist!.password,
|
|
||||||
'action': 'get_vod_streams',
|
|
||||||
},
|
|
||||||
options: Options(extra: options),
|
|
||||||
);
|
|
||||||
|
|
||||||
final List<dynamic> vods = response.data as List<dynamic>;
|
final List<dynamic> vods = response.data as List<dynamic>;
|
||||||
final Map<String, List<VodItem>> groupedVods = {};
|
final Map<String, List<VodItem>> groupedVods = {};
|
||||||
@@ -291,15 +334,17 @@ class XtreamService {
|
|||||||
data['category_name'] = categoryName;
|
data['category_name'] = categoryName;
|
||||||
|
|
||||||
final vod = VodItem.fromJson(data);
|
final vod = VodItem.fromJson(data);
|
||||||
|
groupedVods.putIfAbsent(categoryName, () => []).add(vod);
|
||||||
if (!groupedVods.containsKey(categoryName)) {
|
|
||||||
groupedVods[categoryName] = [];
|
|
||||||
}
|
|
||||||
groupedVods[categoryName]!.add(vod);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save to local cache
|
||||||
|
_saveToLocalCache('vod_items', groupedVods);
|
||||||
|
|
||||||
return groupedVods;
|
return groupedVods;
|
||||||
} catch (e) {
|
} 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');
|
throw Exception('Failed to fetch VOD items: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -344,17 +389,22 @@ class XtreamService {
|
|||||||
: _cacheOptions.toExtra();
|
: _cacheOptions.toExtra();
|
||||||
|
|
||||||
try {
|
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(
|
final categoryMap = results[0] as Map<String, String>;
|
||||||
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
final response = results[1] as Response;
|
||||||
queryParameters: {
|
|
||||||
'username': _currentPlaylist!.username,
|
|
||||||
'password': _currentPlaylist!.password,
|
|
||||||
'action': 'get_series',
|
|
||||||
},
|
|
||||||
options: Options(extra: options),
|
|
||||||
);
|
|
||||||
|
|
||||||
final List<dynamic> seriesList = response.data as List<dynamic>;
|
final List<dynamic> seriesList = response.data as List<dynamic>;
|
||||||
final Map<String, List<Series>> groupedSeries = {};
|
final Map<String, List<Series>> groupedSeries = {};
|
||||||
@@ -366,15 +416,17 @@ class XtreamService {
|
|||||||
data['category_name'] = categoryName;
|
data['category_name'] = categoryName;
|
||||||
|
|
||||||
final series = Series.fromJson(data);
|
final series = Series.fromJson(data);
|
||||||
|
groupedSeries.putIfAbsent(categoryName, () => []).add(series);
|
||||||
if (!groupedSeries.containsKey(categoryName)) {
|
|
||||||
groupedSeries[categoryName] = [];
|
|
||||||
}
|
|
||||||
groupedSeries[categoryName]!.add(series);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save to local cache
|
||||||
|
_saveToLocalCache('series_items', groupedSeries);
|
||||||
|
|
||||||
return groupedSeries;
|
return groupedSeries;
|
||||||
} catch (e) {
|
} 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');
|
throw Exception('Failed to fetch series: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user