flutter_video_cache 0.1.8 copy "flutter_video_cache: ^0.1.8" to clipboard
flutter_video_cache: ^0.1.8 copied to clipboard

A Flutter plugin that enables video caching and offline playback alongside the video_player package. Supports downloads, playback from cache, and progress tracking.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_video_cache/flutter_video_cache.dart';
import 'dart:async';

import 'package:video_player/video_player.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Video Cache Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Video Cache Demo'),
          bottom: const TabBar(
            tabs: [
              Tab(text: 'Video List'),
              Tab(text: 'Custom Buttons'),
              Tab(text: 'Manual API'),
            ],
          ),
        ),
        body: const TabBarView(
          children: [VideoListTab(), CustomButtonsTab(), ManualApiTab()],
        ),
      ),
    );
  }
}

// Example 1: Video list with default cache buttons
class VideoListTab extends StatelessWidget {
  const VideoListTab({super.key});

  @override
  Widget build(BuildContext context) {
    final List<VideoExample> examples = [
      VideoExample(
        title: 'Big Buck Bunny',
        description: 'A short animated film by the Blender Foundation',
        url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8#uid=1234567',
        thumbnail:
            'https://storage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg',
      ),
      VideoExample(
        title: 'Лучшее лето',
        description:
            'В центре сюжета находится непростой подросток Дэниел, слушающий трэш-метал. Летом он планирует провести каникулы с отцом и его новой супругой в Америке, но планы срываются. Теперь герою предстоит проводить время с матерью, которая далека от интересов сына и вообще движения молодого поколения.',
        url:
            'https://meta.vcdn.biz/7a8fdb5c1a40f1a7ff1637e3ce44c77e_mgg/vod/hls/b/450_900_1350_1500_2000_5000/u_sid/0/o/100586581/rsid/6883e917-71a8-4c91-8122-6c25454bebc4/u_uid/1765872116/u_vod/4/u_device/24seven_uz/u_devicekey/_24seven_uz_web/u_did/MToxNzY1ODcyMTE2OjE3NDcxMzg4NjU6OjA1ZGEzNmE1Njc1OTg3MzVhZTc4MTVlZmMzNDA3NmVl/a/0/type.amlst/playlist.m3u8#uid=U5695762',
        thumbnail:
            'https://service.24seven.uz/uploads/video/jpg/1745587645876.jpg',
      ),
      VideoExample(
        title: 'Elephant Dream',
        description: 'Another animated film from the Blender Foundation',
        url:
            'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4',
        thumbnail:
            'https://storage.googleapis.com/gtv-videos-bucket/sample/images/ElephantsDream.jpg',
      ),
      VideoExample(
        title: 'For Bigger Blazes',
        description: 'A Google Chrome advertisement',
        url:
            'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4',
        thumbnail:
            'https://storage.googleapis.com/gtv-videos-bucket/sample/images/ForBiggerBlazes.jpg',
      ),
    ];

    return ListView.builder(
      itemCount: examples.length,
      itemBuilder: (context, index) {
        return VideoListItem(example: examples[index]);
      },
    );
  }
}

// Example 2: Custom button styles
class CustomButtonsTab extends StatelessWidget {
  const CustomButtonsTab({super.key});

  @override
  Widget build(BuildContext context) {
    const String videoUrl =
        'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4';

    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Button Customization Examples',
            style: Theme.of(context).textTheme.headlineSmall,
          ),
          const SizedBox(height: 20),

          // Default style
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'Default Style',
                    style: Theme.of(context).textTheme.titleMedium,
                  ),
                  const SizedBox(height: 16),
                  const Center(
                    child: CacheControlButton(
                      videoUrl: videoUrl,
                      showPlayButton: true,
                    ),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 20),

          // Custom icons
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'Custom Icons',
                    style: Theme.of(context).textTheme.titleMedium,
                  ),
                  const SizedBox(height: 16),
                  const Center(
                    child: CacheControlButton(
                      videoUrl: videoUrl,
                      showPlayButton: true,
                      downloadWidget: Icon(
                        Icons.cloud_download,
                        color: Colors.indigo,
                      ),
                      cancelWidget: Icon(
                        Icons.cancel_rounded,
                        color: Colors.red,
                      ),
                      deleteWidget: Icon(
                        Icons.delete_forever,
                        color: Colors.orange,
                      ),
                      playWidget: Icon(Icons.play_circle, color: Colors.green),
                    ),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 20),

          // With labels below
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'With Labels Below',
                    style: Theme.of(context).textTheme.titleMedium,
                  ),
                  const SizedBox(height: 16),
                  const Center(
                    child: CacheControlButton(
                      videoUrl: videoUrl,
                      showPlayButton: true,
                      downloadLabel: "Save",
                      cancelLabel: "Stop",
                      deleteLabel: "Remove",
                      playLabel: "Play",
                      labelPosition: LabelPosition.below,
                    ),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 20),

          // Labels on right
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'Labels on Right',
                    style: Theme.of(context).textTheme.titleMedium,
                  ),
                  const SizedBox(height: 16),
                  const Center(
                    child: CacheControlButton(
                      videoUrl: videoUrl,
                      showPlayButton: true,
                      downloadLabel: "Save Offline",
                      cancelLabel: "Cancel Download",
                      deleteLabel: "Delete from Device",
                      playLabel: "Play Video",
                      labelPosition: LabelPosition.right,
                      labelSpacing: 8,
                    ),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 20),

          // Fully customized
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'Fully Customized',
                    style: Theme.of(context).textTheme.titleMedium,
                  ),
                  const SizedBox(height: 16),
                  Center(
                    child: CacheControlButton(
                      videoUrl: videoUrl,
                      showPlayButton: true,
                      size: 48,
                      color: Colors.white,
                      backgroundColor: Colors.deepPurple,
                      downloadWidget: const Icon(
                        Icons.cloud_download,
                        color: Colors.white,
                      ),
                      cancelWidget: const Icon(
                        Icons.cancel_rounded,
                        color: Colors.white,
                      ),
                      deleteWidget: const Icon(
                        Icons.delete_forever,
                        color: Colors.white,
                      ),
                      playWidget: const Icon(
                        Icons.play_circle,
                        color: Colors.white,
                      ),
                      downloadLabel: "Download",
                      cancelLabel: "Cancel",
                      deleteLabel: "Delete",
                      playLabel: "Play",
                      labelPosition: LabelPosition.below,
                      labelStyle: const TextStyle(
                        color: Colors.deepPurple,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

// Example 3: Manual API usage
class ManualApiTab extends StatefulWidget {
  const ManualApiTab({super.key});

  @override
  State<ManualApiTab> createState() => _ManualApiTabState();
}

class _ManualApiTabState extends State<ManualApiTab> {
  final VideoCache _videoCache = VideoCache();
  final String _videoUrl =
      'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4';

  double _progress = 0.0;
  bool _isDownloading = false;
  bool _isCached = false;
  String? _cachedPath;
  VideoPlayerController? _controller;

  @override
  void initState() {
    super.initState();
    _checkCacheStatus();
  }

  @override
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }

  Future<void> _checkCacheStatus() async {
    final isCached = await _videoCache.isVideoCached(_videoUrl);
    final path =
        isCached ? await _videoCache.getCachedVideoPath(_videoUrl) : null;

    setState(() {
      _isCached = isCached;
      _cachedPath = path;
    });
  }

  Future<void> _startDownload() async {
    setState(() {
      _isDownloading = true;
      _progress = 0.0;
    });

    await _videoCache.startDownload(_videoUrl);

    // Listen to progress updates
    _videoCache
        .getDownloadProgressStream(_videoUrl)
        .listen(
          (progress) {
            setState(() {
              _progress = progress;
              if (progress >= 0.99) {
                _isDownloading = false;
                _checkCacheStatus();
              }
            });
          },
          onError: (error) {
            setState(() {
              _isDownloading = false;
            });
          },
        );
  }

  Future<void> _cancelDownload() async {
    await _videoCache.cancelDownload(_videoUrl);
    setState(() {
      _isDownloading = false;
      _progress = 0.0;
    });
  }

  Future<void> _removeDownload() async {
    await _videoCache.removeDownload(_videoUrl);
    setState(() {
      _isCached = false;
      _cachedPath = null;
    });
  }

  Future<void> _playVideo() async {
    if (_controller != null) {
      await _controller!.dispose();
    }

    _controller = await VideoCacheController.networkWithCaching(_videoUrl);
    await _controller!.initialize();
    await _controller!.play();
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Manual API Usage',
            style: Theme.of(context).textTheme.headlineSmall,
          ),
          const SizedBox(height: 16),

          // Video info
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'Video URL:',
                    style: Theme.of(context).textTheme.titleMedium,
                  ),
                  Text(
                    _videoUrl,
                    style: Theme.of(context).textTheme.bodyMedium,
                  ),
                  const SizedBox(height: 8),
                  Text(
                    'Status:',
                    style: Theme.of(context).textTheme.titleMedium,
                  ),
                  Text(
                    _isCached
                        ? 'Cached'
                        : _isDownloading
                        ? 'Downloading'
                        : 'Not cached',
                    style: Theme.of(context).textTheme.bodyMedium!.copyWith(
                      fontWeight: FontWeight.bold,
                      color:
                          _isCached
                              ? Colors.green
                              : _isDownloading
                              ? Colors.orange
                              : Colors.red,
                    ),
                  ),
                  if (_cachedPath != null) ...[
                    const SizedBox(height: 8),
                    Text(
                      'Cached Path:',
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    Text(
                      _cachedPath!,
                      style: Theme.of(context).textTheme.bodyMedium,
                    ),
                  ],
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),

          // Progress indicator
          if (_isDownloading)
            Column(
              children: [
                LinearProgressIndicator(
                  value: _progress,
                  backgroundColor: Colors.grey[300],
                ),
                const SizedBox(height: 8),
                Text('${(_progress * 100).toStringAsFixed(1)}%'),
                const SizedBox(height: 16),
              ],
            ),

          // Actions
          Wrap(
            spacing: 8,
            runSpacing: 8,
            children: [
              if (!_isDownloading && !_isCached)
                ElevatedButton.icon(
                  onPressed: _startDownload,
                  icon: const Icon(Icons.cloud_download),
                  label: const Text('Start Download'),
                ),

              if (_isDownloading)
                ElevatedButton.icon(
                  onPressed: _cancelDownload,
                  icon: const Icon(Icons.cancel),
                  label: const Text('Cancel Download'),
                  style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
                ),

              if (_isCached)
                ElevatedButton.icon(
                  onPressed: _removeDownload,
                  icon: const Icon(Icons.delete),
                  label: const Text('Remove Download'),
                  style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
                ),

              if (_isCached)
                ElevatedButton.icon(
                  onPressed: _playVideo,
                  icon: const Icon(Icons.play_arrow),
                  label: const Text('Play Video'),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.green,
                  ),
                ),

              if (!_isCached && !_isDownloading)
                ElevatedButton.icon(
                  onPressed: _checkCacheStatus,
                  icon: const Icon(Icons.refresh),
                  label: const Text('Check Cache Status'),
                ),
            ],
          ),

          // Video player
          if (_controller != null && _controller!.value.isInitialized)
            Expanded(
              child: Padding(
                padding: const EdgeInsets.only(top: 16),
                child: AspectRatio(
                  aspectRatio: _controller!.value.aspectRatio,
                  child: Stack(
                    alignment: Alignment.bottomCenter,
                    children: [
                      VideoPlayer(_controller!),
                      VideoProgressIndicator(
                        _controller!,
                        allowScrubbing: true,
                        padding: const EdgeInsets.symmetric(
                          vertical: 8,
                          horizontal: 12,
                        ),
                      ),
                      Positioned(
                        top: 8,
                        right: 8,
                        child: FloatingActionButton.small(
                          onPressed: () {
                            setState(() {
                              _controller!.value.isPlaying
                                  ? _controller!.pause()
                                  : _controller!.play();
                            });
                          },
                          child: Icon(
                            _controller!.value.isPlaying
                                ? Icons.pause
                                : Icons.play_arrow,
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
        ],
      ),
    );
  }
}

// Video list item component
class VideoListItem extends StatefulWidget {
  final VideoExample example;

  const VideoListItem({super.key, required this.example});

  @override
  State<VideoListItem> createState() => _VideoListItemState();
}

class _VideoListItemState extends State<VideoListItem> {
  VideoPlayerController? _controller;
  bool _isPlaying = false;
  bool _mounted = true;

  @override
  void initState() {
    super.initState();
    _mounted = true;
  }

  @override
  void dispose() {
    _mounted = false;
    _disposeController();
    super.dispose();
  }

  void _disposeController() {
    _controller?.pause();
    _controller?.dispose();
    _controller = null;
  }

  Future<void> _playVideo() async {
    if (!_mounted) return;

    setState(() {
      _isPlaying = true;
    });

    try {
      _controller = await VideoCacheController.networkWithCaching(
        widget.example.url,
      );

      if (!_mounted) {
        _controller?.dispose();
        return;
      }

      await _controller!.initialize();
      await _controller!.play();

      if (_mounted) {
        setState(() {});
      }
    } catch (e) {
      if (_mounted) {
        setState(() {
          _isPlaying = false;
        });

        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Error playing video: $e'),
            backgroundColor: Colors.red,
          ),
        );
      }
    }
  }

  void _stopVideo() {
    if (_controller != null) {
      _controller!.pause().then((_) {
        if (_mounted) {
          setState(() {
            _isPlaying = false;
          });
        }
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      clipBehavior: Clip.antiAlias,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // Video or thumbnail
          if (_isPlaying &&
              _controller != null &&
              _controller!.value.isInitialized)
            AspectRatio(
              aspectRatio: _controller!.value.aspectRatio,
              child: Stack(
                alignment: Alignment.bottomCenter,
                children: [
                  VideoPlayer(_controller!),
                  VideoProgressIndicator(
                    _controller!,
                    allowScrubbing: true,
                    padding: const EdgeInsets.symmetric(
                      vertical: 8,
                      horizontal: 12,
                    ),
                  ),
                ],
              ),
            )
          else
            Stack(
              alignment: Alignment.center,
              children: [
                // Thumbnail image
                AspectRatio(
                  aspectRatio: 16 / 9,
                  child: Image.network(
                    widget.example.thumbnail,
                    fit: BoxFit.cover,
                    errorBuilder: (context, error, stackTrace) {
                      return Container(
                        color: Colors.grey[300],
                        child: const Icon(Icons.image_not_supported, size: 50),
                      );
                    },
                  ),
                ),
                // Play icon overlay
                IconButton(
                  onPressed: _playVideo,
                  icon: const Icon(Icons.play_circle_fill, size: 50),
                  color: Colors.white.withOpacity(0.8),
                ),
              ],
            ),

          Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                // Title
                Text(
                  widget.example.title,
                  style: Theme.of(context).textTheme.titleLarge,
                ),
                const SizedBox(height: 8),
                // Description
                Text(
                  widget.example.description,
                  style: Theme.of(context).textTheme.bodyMedium,
                ),
                const SizedBox(height: 16),

                // Controls row
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    // Cache control button
                    CacheControlButton(
                      videoUrl: widget.example.url,
                      showPlayButton: true,
                      onPlayPressed: _playVideo,
                      size: 42,
                      onDownloadStarted: () {
                        ScaffoldMessenger.of(context).showSnackBar(
                          SnackBar(
                            content: Text(
                              'Started downloading: ${widget.example.title}',
                            ),
                            duration: const Duration(seconds: 2),
                          ),
                        );
                      },
                      onDownloadCompleted: () async {
                        ScaffoldMessenger.of(context).showSnackBar(
                          SnackBar(
                            content: Text(
                              'Download complete: ${widget.example.title}',
                            ),
                            duration: const Duration(seconds: 2),
                          ),
                        );
                      },
                    ),

                    // Stop button (only when playing)
                    if (_isPlaying)
                      ElevatedButton.icon(
                        onPressed: _stopVideo,
                        icon: const Icon(Icons.stop),
                        label: const Text('Stop'),
                        style: ElevatedButton.styleFrom(
                          backgroundColor: Theme.of(context).colorScheme.error,
                          foregroundColor:
                              Theme.of(context).colorScheme.onError,
                        ),
                      ),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// Data class for video examples
class VideoExample {
  final String title;
  final String description;
  final String url;
  final String thumbnail;

  const VideoExample({
    required this.title,
    required this.description,
    required this.url,
    required this.thumbnail,
  });
}
1
likes
150
points
62
downloads

Publisher

verified publisherflutterwithakmaljon.uz

Weekly Downloads

A Flutter plugin that enables video caching and offline playback alongside the video_player package. Supports downloads, playback from cache, and progress tracking.

Repository (GitHub)
View/report issues

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface, video_player, video_player_pip

More

Packages that depend on flutter_video_cache