# Flutter Video Cache Flutter Video Cache (`video_cache`) is a Flutter plugin that provides transparent HTTP media caching for video playback on Android and iOS. It intercepts video URL requests by routing them through a local proxy server, enabling streamed content to be cached on-device so that repeated plays or interrupted streams do not re-download data over the network. The plugin is designed as a lightweight, drop-in wrapper around platform-native caching libraries: [AndroidVideoCache](https://github.com/danikula/AndroidVideoCache) (`HttpProxyCacheServer`) on Android and [KTVHTTPCache](https://github.com/ChangbaDevs/KTVHTTPCache) on iOS. The plugin exposes a single, focused public API — `convertToCacheProxyUrl` — that converts any remote video URL into a localhost proxy URL. Passing this proxy URL to any video player widget (such as `video_player` or `chewie`) transparently activates caching without any additional configuration. The Flutter–native bridge is generated via [Pigeon](https://pub.dev/packages/pigeon), ensuring type-safe, efficient communication between Dart and the host platform. The singleton `VideoCache` class is safe to use anywhere in the widget tree and manages the lifecycle of the underlying proxy server automatically. --- ## Installation Add `video_cache` to `pubspec.yaml` and import it in Dart files: ```yaml # pubspec.yaml dependencies: video_cache: ^0.0.1 video_player: ^2.0.0 # or any other video player ``` ```dart import 'package:video_cache/video_cache.dart'; ``` --- ## `VideoCache()` — Singleton Constructor `VideoCache` follows the singleton pattern. Every call to `VideoCache()` returns the same shared instance, which owns the long-lived native proxy server. There is no need to call any initialization method; the proxy server is started lazily on the first URL conversion request. ```dart import 'package:video_cache/video_cache.dart'; // Always returns the same instance — safe to call anywhere. final videoCache = VideoCache(); ``` --- ## `convertToCacheProxyUrl(String url)` — Convert a Remote URL to a Cache Proxy URL Converts a remote media URL into a `localhost` proxy URL that routes through the on-device caching layer. On Android, `HttpProxyCacheServer` handles the proxying and caching. On iOS, `KTVHTTPCache` starts its proxy service on first use and rewrites the URL. If the proxy cannot be started (e.g., port conflict on iOS), the original URL is returned unchanged so playback still proceeds without caching. ```dart import 'package:flutter/material.dart'; import 'package:video_cache/video_cache.dart'; import 'package:video_player/video_player.dart'; class CachedVideoPlayer extends StatefulWidget { final String videoUrl; const CachedVideoPlayer({super.key, required this.videoUrl}); @override State createState() => _CachedVideoPlayerState(); } class _CachedVideoPlayerState extends State { late VideoPlayerController _controller; bool _initialized = false; @override void initState() { super.initState(); _initPlayer(); } Future _initPlayer() async { // Step 1: convert the remote URL to a caching proxy URL. // Input: 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4' // Output: 'http://127.0.0.1:/https%3A%2F%2Fflutter.github.io%2F...' final String proxyUrl = await VideoCache().convertToCacheProxyUrl(widget.videoUrl); debugPrint('Proxy URL: $proxyUrl'); // Step 2: pass the proxy URL directly to VideoPlayerController. _controller = VideoPlayerController.networkUrl(Uri.parse(proxyUrl)); try { await _controller.initialize(); setState(() => _initialized = true); _controller.play(); } catch (e) { debugPrint('VideoPlayer initialization error: $e'); } } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { if (!_initialized) { return const Center(child: CircularProgressIndicator()); } return AspectRatio( aspectRatio: _controller.value.aspectRatio, child: VideoPlayer(_controller), ); } } // Usage: // CachedVideoPlayer( // videoUrl: 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', // ) ``` --- ## Integration with `chewie` — Full-Featured Player UI with Caching The plugin integrates seamlessly with the `chewie` package for a production-ready player UI with controls, full-screen support, and looping — all backed by the cache layer. ```dart import 'package:flutter/material.dart'; import 'package:video_cache/video_cache.dart'; import 'package:chewie/chewie.dart'; import 'package:video_player/video_player.dart'; class ChewieWithCache extends StatefulWidget { const ChewieWithCache({super.key}); @override State createState() => _ChewieWithCacheState(); } class _ChewieWithCacheState extends State { final _videoCache = VideoCache(); // singleton VideoPlayerController? _videoPlayerController; ChewieController? _chewieController; final List _playlist = [ 'http://aliuwmp3.changba.com/userdata/video/45F6BD5E445E4C029C33DC5901307461.mp4', 'http://aliuwmp3.changba.com/userdata/video/3B1DDE764577E0529C33DC5901307461.mp4', 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', ]; int _currentIndex = 0; @override void initState() { super.initState(); _loadVideo(_playlist[_currentIndex]); } Future _loadVideo(String url) async { // Dispose previous controllers before loading a new video. _chewieController?.dispose(); _videoPlayerController?.dispose(); // Convert remote URL → cache proxy URL. final String cachedUrl = await _videoCache.convertToCacheProxyUrl(url); debugPrint('Loading cached URL: $cachedUrl'); _videoPlayerController = VideoPlayerController.networkUrl(Uri.parse(cachedUrl)); await _videoPlayerController!.initialize(); _chewieController = ChewieController( videoPlayerController: _videoPlayerController!, autoPlay: true, looping: false, additionalOptions: (context) => [ OptionItem( onTap: _nextVideo, iconData: Icons.skip_next, title: 'Next Video', ), ], ); setState(() {}); } void _nextVideo() { _currentIndex = (_currentIndex + 1) % _playlist.length; _loadVideo(_playlist[_currentIndex]); } @override void dispose() { _chewieController?.dispose(); _videoPlayerController?.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Video Cache Demo')), body: _chewieController != null && _chewieController!.videoPlayerController.value.isInitialized ? Chewie(controller: _chewieController!) : const Center(child: CircularProgressIndicator()), ); } } ``` --- ## Platform Behavior Reference | Platform | Native Library | Proxy Mechanism | Fallback | |----------|---------------|-----------------|---------| | Android | `AndroidVideoCache` (`HttpProxyCacheServer`) | Lazy-initialized singleton proxy server; `getProxyUrl()` rewrites URL to `http://127.0.0.1:/...` | N/A — always starts | | iOS | `KTVHTTPCache` | `KTVHTTPCache.proxyStart()` called once; `proxyURL(withOriginalURL:)` rewrites URL | Returns original URL if proxy fails to start | --- ## `LXFVideoCacheHostApi` — Low-Level Pigeon Bridge (Advanced) The Pigeon-generated `LXFVideoCacheHostApi` class is the direct Flutter-to-native channel. It is used internally by `VideoCache` but can be instantiated independently (e.g., for testing with a custom `BinaryMessenger`). ```dart import 'package:flutter/services.dart'; import 'package:video_cache/plugin/pigeon.g.dart'; // Inject a mock messenger for unit testing. class MockBinaryMessenger extends BinaryMessenger { @override Future send(String channel, ByteData? message) async { // Return a fake proxy URL wrapped in the Pigeon list format. // The codec encodes ['http://127.0.0.1:8080/mocked'] as a StandardMessageCodec value. final WriteBuffer buffer = WriteBuffer(); const StandardMessageCodec().encodeMessage( ['http://127.0.0.1:8080/mocked'], // ignore: invalid_use_of_protected_member buffer, ); return buffer.done(); } @override void setMockMessageHandler(String channel, Future Function(ByteData? message)? handler) {} } Future testWithMock() async { final api = LXFVideoCacheHostApi( binaryMessenger: MockBinaryMessenger(), ); try { final String result = await api.convertToCacheProxyUrl( 'https://example.com/video.mp4', ); print('Result: $result'); // http://127.0.0.1:8080/mocked } on PlatformException catch (e) { print('Platform error [${e.code}]: ${e.message}'); } } ``` --- ## Summary Flutter Video Cache is purpose-built for mobile video applications that stream remote content and need transparent bandwidth optimization. Its primary use case is any feed or playlist-style app — social media, e-learning, news — where the same video clip is likely to be replayed. By inserting a single `await VideoCache().convertToCacheProxyUrl(url)` call before creating a `VideoPlayerController`, developers get automatic on-device caching with zero configuration, no changes to player logic, and graceful fallback to direct streaming on iOS when the proxy cannot start. For integration, the plugin works with any Flutter video player that accepts a URL string: `video_player`, `chewie`, `better_player`, `media_kit`, and others. The singleton nature of `VideoCache` means the proxy server is initialized once per app session and shared across all video instances, making it efficient for playlist or feed scenarios with multiple simultaneous or sequential video items. Developers switching between videos in a playlist should simply call `convertToCacheProxyUrl` for each new URL — the native cache layer handles deduplication and partial-content resumption automatically.