mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-12 01:36:20 +02:00
- Implemented DownloadService for managing download tasks, including start, pause, resume, and cancel functionalities. - Created SubtitleService for handling subtitle tracks, parsing SRT and WebVTT formats, and downloading subtitles. - Added ContinueWatchingWidget to display user's watch history with progress indicators. - Developed QualitySelectorWidget for selecting video quality during playback, including auto quality adjustment. - Introduced TrendingWidget and RecentlyAddedWidget to showcase trending and recently added content.
225 lines
5.4 KiB
Dart
225 lines
5.4 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
/// Optimized cache service for memory management
|
|
class CacheService {
|
|
static const _maxMemoryCacheSizeMb = 200;
|
|
static const _maxImageCacheItems = 500;
|
|
|
|
final Map<String, CacheEntry> _cache = {};
|
|
int _totalSizeBytes = 0;
|
|
|
|
/// Cache entry with metadata
|
|
class CacheEntry<T> {
|
|
final T data;
|
|
final DateTime timestamp;
|
|
final int sizeEstimate;
|
|
int accessCount;
|
|
DateTime lastAccess;
|
|
|
|
CacheEntry({
|
|
required this.data,
|
|
required this.sizeEstimate,
|
|
}) : timestamp = DateTime.now(),
|
|
lastAccess = DateTime.now(),
|
|
accessCount = 0;
|
|
|
|
bool isExpired({Duration ttl = const Duration(hours: 24)}) {
|
|
return DateTime.now().difference(timestamp) > ttl;
|
|
}
|
|
|
|
void markAccess() {
|
|
accessCount++;
|
|
lastAccess = DateTime.now();
|
|
}
|
|
}
|
|
|
|
/// Cache data with automatic size management
|
|
void cache<T>(String key, T value, {int estimatedSize = 1000}) {
|
|
// Remove old entry if exists
|
|
if (_cache.containsKey(key)) {
|
|
_totalSizeBytes -= _cache[key]!.sizeEstimate;
|
|
}
|
|
|
|
_cache[key] = CacheEntry(
|
|
data: value,
|
|
sizeEstimate: estimatedSize,
|
|
);
|
|
_totalSizeBytes += estimatedSize;
|
|
|
|
// Trim cache if over size limit
|
|
_trimCacheIfNeeded();
|
|
}
|
|
|
|
/// Get cached data
|
|
T? get<T>(String key) {
|
|
final entry = _cache[key] as CacheEntry?;
|
|
if (entry != null && !entry.isExpired()) {
|
|
entry.markAccess();
|
|
return entry.data as T;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Check if key exists and is valid
|
|
bool has(String key) {
|
|
final entry = _cache[key];
|
|
return entry != null && !entry.isExpired();
|
|
}
|
|
|
|
/// Get cache stats
|
|
Map<String, dynamic> getStats() => {
|
|
'totalSize': _totalSizeBytes,
|
|
'totalSizeMb': _totalSizeBytes / (1024 * 1024),
|
|
'itemCount': _cache.length,
|
|
'averageSizePerItem': _cache.isEmpty ? 0 : _totalSizeBytes ~/ _cache.length,
|
|
};
|
|
|
|
/// Clear all cache
|
|
void clear() {
|
|
_cache.clear();
|
|
_totalSizeBytes = 0;
|
|
}
|
|
|
|
/// Clear expired entries
|
|
void clearExpired() {
|
|
final now = DateTime.now();
|
|
final toRemove = _cache.entries
|
|
.where((e) => e.value.isExpired())
|
|
.map((e) => e.key)
|
|
.toList();
|
|
|
|
for (final key in toRemove) {
|
|
_totalSizeBytes -= _cache[key]!.sizeEstimate;
|
|
_cache.remove(key);
|
|
}
|
|
}
|
|
|
|
/// Trim cache by removing least recently used entries
|
|
void _trimCacheIfNeeded() {
|
|
final maxSize = _maxMemoryCacheSizeMb * 1024 * 1024;
|
|
if (_totalSizeBytes > maxSize) {
|
|
// Remove least recently accessed entries
|
|
final sorted = _cache.entries.toList()
|
|
..sort((a, b) => a.value.accessCount.compareTo(b.value.accessCount));
|
|
|
|
for (final entry in sorted) {
|
|
if (_totalSizeBytes <= maxSize * 0.8) break;
|
|
_totalSizeBytes -= entry.value.sizeEstimate;
|
|
_cache.remove(entry.key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Image cache with size limiting
|
|
class ImageCacheManager {
|
|
static const _maxImageMemoryMb = 100;
|
|
static const _maxImages = 500;
|
|
|
|
final Map<String, CachedImage> _imageCache = {};
|
|
int _totalMemoryBytes = 0;
|
|
|
|
class CachedImage {
|
|
final String url;
|
|
final int sizeBytes;
|
|
final DateTime cachedAt;
|
|
int accessCount = 0;
|
|
|
|
CachedImage({
|
|
required this.url,
|
|
required this.sizeBytes,
|
|
}) : cachedAt = DateTime.now();
|
|
}
|
|
|
|
/// Add image to cache
|
|
void cacheImage(String url, int sizeEstimateBytes) {
|
|
if (_imageCache.length >= _maxImages) {
|
|
_evictLeastUsed();
|
|
}
|
|
|
|
_imageCache[url] = CachedImage(
|
|
url: url,
|
|
sizeBytes: sizeEstimateBytes,
|
|
);
|
|
_totalMemoryBytes += sizeEstimateBytes;
|
|
|
|
// Trim if over size
|
|
while (_totalMemoryBytes > _maxImageMemoryMb * 1024 * 1024) {
|
|
_evictLeastUsed();
|
|
}
|
|
}
|
|
|
|
/// Check if image is cached
|
|
bool isCached(String url) => _imageCache.containsKey(url);
|
|
|
|
/// Mark image as accessed
|
|
void markAccessed(String url) {
|
|
if (_imageCache.containsKey(url)) {
|
|
_imageCache[url]!.accessCount++;
|
|
}
|
|
}
|
|
|
|
/// Evict least used image
|
|
void _evictLeastUsed() {
|
|
if (_imageCache.isEmpty) return;
|
|
|
|
final leastUsed = _imageCache.entries
|
|
.reduce((a, b) => a.value.accessCount < b.value.accessCount ? a : b);
|
|
|
|
_totalMemoryBytes -= leastUsed.value.sizeBytes;
|
|
_imageCache.remove(leastUsed.key);
|
|
}
|
|
|
|
/// Get cache stats
|
|
Map<String, dynamic> getStats() => {
|
|
'totalMemoryMb': _totalMemoryBytes / (1024 * 1024),
|
|
'cachedImages': _imageCache.length,
|
|
'maxImages': _maxImages,
|
|
};
|
|
|
|
/// Clear all image cache
|
|
void clear() {
|
|
_imageCache.clear();
|
|
_totalMemoryBytes = 0;
|
|
}
|
|
}
|
|
|
|
// Riverpod providers
|
|
|
|
final cacheServiceProvider = Provider<CacheService>((ref) {
|
|
return CacheService();
|
|
});
|
|
|
|
final imageCacheManagerProvider = Provider<ImageCacheManager>((ref) {
|
|
return ImageCacheManager();
|
|
});
|
|
|
|
/// Provider for cache statistics
|
|
final cacheStatsProvider = StateNotifierProvider<
|
|
CacheStatsNotifier,
|
|
Map<String, dynamic>>((ref) {
|
|
final cacheService = ref.watch(cacheServiceProvider);
|
|
return CacheStatsNotifier(cacheService);
|
|
});
|
|
|
|
class CacheStatsNotifier extends StateNotifier<Map<String, dynamic>> {
|
|
final CacheService _cacheService;
|
|
|
|
CacheStatsNotifier(this._cacheService)
|
|
: super(_cacheService.getStats());
|
|
|
|
void refreshStats() {
|
|
state = _cacheService.getStats();
|
|
}
|
|
|
|
void clearCache() {
|
|
_cacheService.clear();
|
|
state = _cacheService.getStats();
|
|
}
|
|
|
|
void clearExpired() {
|
|
_cacheService.clearExpired();
|
|
state = _cacheService.getStats();
|
|
}
|
|
}
|