Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 73 additions & 6 deletions example/lib/pages/advance_cache_management_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,14 @@ class _AdvanceCacheManagementPageState
maxNrOfCacheObjects: 20,
),
);
final _customMetadataStorage = _MemoryVideoPlayerMetadataStorage();
final _asyncPrefs = SharedPreferencesAsync();

int _selectedIndex = 0;
String _customKey = '';
bool _forceFetch = false;
bool _overrideCacheManager = false;
bool _overrideMetadataStorage = false;
bool _isLoading = false;
bool _isCaching = false;
bool _isClearing = false;
Expand Down Expand Up @@ -205,13 +208,52 @@ class _AdvanceCacheManagementPageState
],
),
const SizedBox(height: 12),
Row(
Wrap(
spacing: 12.0,
runSpacing: 12.0,
children: [
const Text('Force fetch latest:'),
const SizedBox(width: 12),
Switch.adaptive(
value: _forceFetch,
onChanged: (value) => setState(() => _forceFetch = value),
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Force fetch latest:'),
const SizedBox(width: 12),
Switch.adaptive(
value: _forceFetch,
onChanged: (value) => setState(() => _forceFetch = value),
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Override default cache manager:'),
const SizedBox(width: 12),
Switch.adaptive(
value: _overrideCacheManager,
onChanged: (value) {
CachedVideoPlayerPlus.cacheManager = value
? _customCacheManager
: CachedVideoPlayerPlus.defaultCacheManager;
setState(() => _overrideCacheManager = value);
},
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Override default metadata storage:'),
const SizedBox(width: 12),
Switch.adaptive(
value: _overrideMetadataStorage,
onChanged: (value) {
CachedVideoPlayerPlus.metadataStorage = value
? _customMetadataStorage
: CachedVideoPlayerPlus.defaultMetadataStorage;
setState(() => _overrideMetadataStorage = value);
},
),
],
),
],
),
Expand Down Expand Up @@ -328,3 +370,28 @@ class _SmallLoader extends StatelessWidget {
);
}
}

/// Stores video metadata in memory.
class _MemoryVideoPlayerMetadataStorage implements IVideoPlayerMetadataStorage {
final _data = <String, int>{};

@override
Future<int?> read(String key) {
return Future.value(_data[key]);
}

@override
Future<void> write(String key, int value) {
return Future.sync(() => _data[key] = value);
}

@override
Future<void> remove(String key) {
return Future.sync(() => _data.remove(key));
}

@override
Future<void> erase() {
return Future.sync(() => _data.clear());
}
}
3 changes: 3 additions & 0 deletions lib/cached_video_player_plus.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@
library;

export 'src/cached_video_player_plus.dart';
export 'src/i_video_player_metadata_storage.dart';
export 'src/video_cache_manager.dart';
export 'src/video_player_metadata_storage.dart';
101 changes: 77 additions & 24 deletions lib/src/cached_video_player_plus.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:video_player/video_player.dart';

import 'cache_key_helpers.dart';
import 'i_video_player_metadata_storage.dart';
import 'video_cache_manager.dart';
import 'video_player_storage.dart';
import 'video_player_metadata_storage.dart';

/// A video player that wraps [VideoPlayerController] with intelligent
/// caching capabilities using [flutter_cache_manager].
Expand Down Expand Up @@ -53,7 +54,8 @@ class CachedVideoPlayerPlus {
invalidateCacheIfOlderThan = Duration.zero,
skipCache = true,
_cacheKey = '',
_cacheManager = _defaultCacheManager;
_cacheManager = CachedVideoPlayerPlus.cacheManager,
_metadataStorage = CachedVideoPlayerPlus.metadataStorage;

/// Constructs a [CachedVideoPlayerPlus] playing a video from a network URL.
///
Expand Down Expand Up @@ -96,14 +98,17 @@ class CachedVideoPlayerPlus {
this.skipCache = false,
String? cacheKey,
CacheManager? cacheManager,
IVideoPlayerMetadataStorage? metadataStorage,
}) : dataSource = url.toString(),
dataSourceType = DataSourceType.network,
package = null,
_authHeaders = downloadHeaders ?? httpHeaders,
_cacheKey = cacheKey != null
? getCustomCacheKey(cacheKey)
: getCacheKey(url.toString()),
_cacheManager = cacheManager ?? _defaultCacheManager;
_cacheManager = cacheManager ?? CachedVideoPlayerPlus.cacheManager,
_metadataStorage =
metadataStorage ?? CachedVideoPlayerPlus.metadataStorage;

/// Constructs a [CachedVideoPlayerPlus] playing a video from a file.
///
Expand All @@ -126,7 +131,8 @@ class CachedVideoPlayerPlus {
invalidateCacheIfOlderThan = Duration.zero,
skipCache = true,
_cacheKey = '',
_cacheManager = _defaultCacheManager;
_cacheManager = CachedVideoPlayerPlus.cacheManager,
_metadataStorage = CachedVideoPlayerPlus.metadataStorage;

/// Constructs a [CachedVideoPlayerPlus] playing a video from a contentUri.
///
Expand All @@ -152,7 +158,8 @@ class CachedVideoPlayerPlus {
invalidateCacheIfOlderThan = Duration.zero,
skipCache = true,
_cacheKey = '',
_cacheManager = _defaultCacheManager;
_cacheManager = CachedVideoPlayerPlus.cacheManager,
_metadataStorage = CachedVideoPlayerPlus.metadataStorage;

/// The URI to the video file. This will be in different formats depending on
/// the [DataSourceType] of the original video.
Expand Down Expand Up @@ -218,6 +225,11 @@ class CachedVideoPlayerPlus {
/// Defaults to [VideoCacheManager] if not provided.
final CacheManager _cacheManager;

/// The [IVideoPlayerMetadataStorage] instance used for caching video metadata.
///
/// Defaults to [VideoPlayerMetadataStorage] if not provided.
final IVideoPlayerMetadataStorage _metadataStorage;

/// The underlying video player controller that handles actual video playback.
late VideoPlayerController _videoPlayerController;

Expand Down Expand Up @@ -255,12 +267,6 @@ class CachedVideoPlayerPlus {
return dataSourceType == DataSourceType.network && !kIsWeb && !skipCache;
}

/// The default cache manager for video file caching operations.
static final _defaultCacheManager = VideoCacheManager();

/// Default storage for cache metadata and expiration timestamps.
static final _storage = VideoPlayerStorage();

/// Initializes the video player and sets up caching if applicable.
///
/// This method must be called before accessing the [controller] or playing
Expand All @@ -287,7 +293,7 @@ class CachedVideoPlayerPlus {
_debugPrint('Cached video of [$dataSource] is: ${cachedFile?.file.path}');

if (cachedFile != null) {
final cachedElapsedMillis = await _storage.read(_cacheKey);
final cachedElapsedMillis = await _metadataStorage.read(_cacheKey);

bool isCacheExpired = true;
if (cachedElapsedMillis != null) {
Expand Down Expand Up @@ -316,7 +322,7 @@ class CachedVideoPlayerPlus {
_cacheManager
.downloadFile(dataSource, authHeaders: _authHeaders, key: _cacheKey)
.then((_) {
_storage.write(
_metadataStorage.write(
_cacheKey,
DateTime.timestamp().millisecondsSinceEpoch,
);
Expand Down Expand Up @@ -388,10 +394,28 @@ class CachedVideoPlayerPlus {
Future<void> removeFromCache() async {
await Future.wait([
_cacheManager.removeFile(_cacheKey),
_storage.remove(_cacheKey),
_metadataStorage.remove(_cacheKey),
]);
}

/// The default cache manager for video file caching operations.
static final defaultCacheManager = VideoCacheManager();

/// The globally used cache manager for video file caching operations.
///
/// Changing this will affect all [CachedVideoPlayerPlus] instances that use
/// the default cache manager.
static CacheManager cacheManager = defaultCacheManager;

/// Default storage for cache metadata and expiration timestamps.
static final defaultMetadataStorage = VideoPlayerMetadataStorage();

/// The globally used storage for video file metadata.
///
/// Changing this will affect all [CachedVideoPlayerPlus] instances that use
/// the default metadata storage.
static IVideoPlayerMetadataStorage metadataStorage = defaultMetadataStorage;

/// Removes the cached file for the specified [url] from the cache.
///
/// Use this static method to remove specific cached videos by their URL.
Expand All @@ -402,19 +426,25 @@ class CachedVideoPlayerPlus {
/// The [cacheManager] parameter allows specifying a custom [CacheManager]
/// instance. If not provided, the default [VideoCacheManager] will be used.
///
/// The [metadataStorage] parameter allows specifying a custom
/// [IVideoPlayerMetadataStorage] instance. If not provided, the default
/// [VideoPlayerMetadataStorage] will be used.
///
/// Both the cached video file and its expiration metadata are deleted.
static Future<void> removeFileFromCache(
Uri url, {
CacheManager? cacheManager,
IVideoPlayerMetadataStorage? metadataStorage,
}) async {
final urlString = url.toString();
final cacheKey = getCacheKey(urlString);

cacheManager ??= _defaultCacheManager;
cacheManager ??= CachedVideoPlayerPlus.cacheManager;
metadataStorage ??= CachedVideoPlayerPlus.metadataStorage;

await Future.wait([
cacheManager.removeFile(cacheKey),
_storage.remove(cacheKey),
metadataStorage.remove(cacheKey),
]);
}

Expand All @@ -428,18 +458,24 @@ class CachedVideoPlayerPlus {
/// The [cacheManager] parameter allows specifying a custom [CacheManager]
/// instance. If not provided, the default [VideoCacheManager] will be used.
///
/// The [metadataStorage] parameter allows specifying a custom
/// [IVideoPlayerMetadataStorage] instance. If not provided, the default
/// [VideoPlayerMetadataStorage] will be used.
///
/// Both the cached video file and its expiration metadata are deleted.
static Future<void> removeFileFromCacheByKey(
String cacheKey, {
CacheManager? cacheManager,
IVideoPlayerMetadataStorage? metadataStorage,
}) async {
cacheKey = getCustomCacheKey(cacheKey);

cacheManager ??= _defaultCacheManager;
cacheManager ??= CachedVideoPlayerPlus.cacheManager;
metadataStorage ??= CachedVideoPlayerPlus.metadataStorage;

await Future.wait([
cacheManager.removeFile(cacheKey),
_storage.remove(cacheKey),
metadataStorage.remove(cacheKey),
]);
}

Expand All @@ -452,12 +488,23 @@ class CachedVideoPlayerPlus {
/// The [cacheManager] parameter allows specifying a custom [CacheManager]
/// instance. If not provided, the default [VideoCacheManager] will be used.
///
/// The [metadataStorage] parameter allows specifying a custom
/// [IVideoPlayerMetadataStorage] instance. If not provided, the default
/// [VideoPlayerMetadataStorage] will be used.
///
/// This operation cannot be undone. All cached videos will need to be
/// re-downloaded from their original sources.
static Future<void> clearAllCache({CacheManager? cacheManager}) async {
cacheManager ??= _defaultCacheManager;
static Future<void> clearAllCache({
CacheManager? cacheManager,
IVideoPlayerMetadataStorage? metadataStorage,
}) async {
cacheManager ??= CachedVideoPlayerPlus.cacheManager;
metadataStorage ??= CachedVideoPlayerPlus.metadataStorage;

await Future.wait([cacheManager.emptyCache(), _storage.erase()]);
await Future.wait([
cacheManager.emptyCache(),
metadataStorage.erase(),
]);
}

/// Pre-caches a video file from the specified [url].
Expand All @@ -482,14 +529,20 @@ class CachedVideoPlayerPlus {
/// The [cacheManager] parameter allows providing a custom [CacheManager]
/// instance for caching operations. If not provided, the default
/// [VideoCacheManager] will be used.
///
/// The [metadataStorage] parameter allows specifying a custom
/// [IVideoPlayerMetadataStorage] for storing cache metadata. If not provided,
/// the default [VideoPlayerMetadataStorage] will be used.
static Future<void> preCacheVideo(
Uri url, {
Duration invalidateCacheIfOlderThan = const Duration(days: 69),
Map<String, String> downloadHeaders = const <String, String>{},
String? cacheKey,
CacheManager? cacheManager,
IVideoPlayerMetadataStorage? metadataStorage,
}) async {
cacheManager ??= _defaultCacheManager;
cacheManager ??= CachedVideoPlayerPlus.cacheManager;
metadataStorage ??= CachedVideoPlayerPlus.metadataStorage;

final effectiveCacheKey = cacheKey != null
? getCustomCacheKey(cacheKey)
Expand All @@ -501,7 +554,7 @@ class CachedVideoPlayerPlus {
);

if (cachedFile != null) {
final cachedElapsedMillis = await _storage.read(effectiveCacheKey);
final cachedElapsedMillis = await metadataStorage.read(effectiveCacheKey);

bool isCacheExpired = true;
if (cachedElapsedMillis != null) {
Expand Down Expand Up @@ -529,7 +582,7 @@ class CachedVideoPlayerPlus {
authHeaders: downloadHeaders,
);

await _storage.write(
await metadataStorage.write(
effectiveCacheKey,
DateTime.timestamp().millisecondsSinceEpoch,
);
Expand Down
20 changes: 20 additions & 0 deletions lib/src/i_video_player_metadata_storage.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/// An interface class for storing video metadata.
abstract interface class IVideoPlayerMetadataStorage {
/// Reads the cached video duration in milliseconds from storage.
///
/// Returns the stored value for the given [key], or null if not found.
Future<int?> read(String key);

/// Writes the video duration in milliseconds to storage with the given [key].
Future<void> write(String key, int value);

/// Removes a value from storage.
///
/// Deletes the value associated with the given [key].
Future<void> remove(String key);

/// Clears all cached video player metadata from storage.
///
/// This removes all keys that start with the video player prefix.
Future<void> erase();
}
Loading