reels_video_player 1.0.0
reels_video_player: ^1.0.0 copied to clipboard
A high-performance Flutter package for TikTok/Reels vertical video feeds with background pre-caching, session rotation, and 100% customizable UI.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:reels_video_player/reels_video_player.dart';
import 'package:video_player/video_player.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await ReelsCacheManager.init();
// Clear old session storage once to remove legacy test URLs
await SessionVideoStore.clearSessionStore();
runApp(const ReelsExampleApp());
}
/// Main entry point for the Reels video player example app.
class ReelsExampleApp extends StatelessWidget {
/// Creates a new [ReelsExampleApp] widget instance.
const ReelsExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Reels Video Player Example',
debugShowCheckedModeBanner: false,
theme: ThemeData.dark(useMaterial3: true),
home: const ReelsFeedScreen(),
);
}
}
/// Feed screen demonstrating [ReelsViewer] usage with custom social overlays.
class ReelsFeedScreen extends StatefulWidget {
/// Creates a new [ReelsFeedScreen] widget instance.
const ReelsFeedScreen({super.key});
@override
State<ReelsFeedScreen> createState() => _ReelsFeedScreenState();
}
class _ReelsFeedScreenState extends State<ReelsFeedScreen> {
final List<ReelsVideoItem> _sampleVideos = const <ReelsVideoItem>[
ReelsVideoItem(
id: 'reel_1',
videoUrl: 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4',
thumbnailUrl: 'https://images.unsplash.com/photo-1518709268805-4e9042af9f23?w=800',
metadata: <String, dynamic>{
'username': '@flutter_dev',
'caption': 'Honeybee buzzing on yellow flowers 🐝 #flutter #nature',
'likes': 1240,
'comments': 86,
},
),
ReelsVideoItem(
id: 'reel_2',
videoUrl: 'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4',
thumbnailUrl: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=800',
metadata: <String, dynamic>{
'username': '@nature_explorer',
'caption': 'Beautiful monarch butterfly in nature 🦋 #nature #travel',
'likes': 4520,
'comments': 312,
},
),
ReelsVideoItem(
id: 'reel_3',
videoUrl: 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4',
thumbnailUrl: 'https://images.unsplash.com/photo-1534447677768-be436bb09401?w=800',
metadata: <String, dynamic>{
'username': '@flower_power',
'caption': 'Blooming spring flowers macro video 🌸 #flowers #nature',
'likes': 8930,
'comments': 540,
},
),
ReelsVideoItem(
id: 'reel_4',
videoUrl: 'https://www.w3schools.com/html/mov_bbb.mp4',
thumbnailUrl: 'https://images.unsplash.com/photo-1518709268805-4e9042af9f23?w=800',
metadata: <String, dynamic>{
'username': '@animation_world',
'caption': 'Big Buck Bunny open animation clip 🐰 #animation #open_source',
'likes': 3100,
'comments': 195,
},
),
];
final Map<String, bool> _likedMap = <String, bool>{};
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Stack(
children: <Widget>[
ReelsViewer(
videos: _sampleVideos,
preCacheBefore: 1,
preCacheAfter: 5,
enableSessionRotation: true,
maxPersistedSessionVideos: 10,
onPageChanged: (int index) {
debugPrint('Active reel page changed to index: $index');
},
// Interactive Bottom Progress Bar Scrubber
progressBarBuilder: (BuildContext context, VideoPlayerController controller) {
return ReelsProgressBar(
controller: controller,
playedColor: Colors.redAccent,
bufferedColor: Colors.white38,
backgroundColor: Colors.white12,
barHeight: 4.0,
allowScrubbing: true,
);
},
// Social Overlay (Likes, Comments, Captions, Profile)
overlayBuilder: (BuildContext context, VideoPlayerController controller, ReelsVideoItem item) {
final Map<String, dynamic>? meta = item.metadata;
final String username = meta?['username'] as String? ?? '@user';
final String caption = meta?['caption'] as String? ?? '';
final int likes = meta?['likes'] as int? ?? 0;
final int comments = meta?['comments'] as int? ?? 0;
final bool isLiked = _likedMap[item.id] ?? false;
return Positioned.fill(
child: SafeArea(
child: Padding(
padding: const EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 36.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
// Left Column: User details and caption
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
const CircleAvatar(
radius: 18,
backgroundColor: Colors.white24,
child: Icon(Icons.person, color: Colors.white, size: 20),
),
const SizedBox(width: 10),
Text(
username,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
const SizedBox(width: 10),
OutlinedButton(
onPressed: () {},
style: OutlinedButton.styleFrom(
side: const BorderSide(color: Colors.white),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 0),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
child: const Text(
'Follow',
style: TextStyle(color: Colors.white, fontSize: 12),
),
),
],
),
const SizedBox(height: 10),
Text(
caption,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.white, fontSize: 14),
),
],
),
),
// Right Column: Social Action Buttons (Likes, Comments, Share)
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
// Like Button
GestureDetector(
onTap: () {
setState(() {
_likedMap[item.id] = !isLiked;
});
},
child: Column(
children: <Widget>[
Icon(
isLiked ? Icons.favorite : Icons.favorite_border,
color: isLiked ? Colors.redAccent : Colors.white,
size: 32,
),
const SizedBox(height: 4),
Text(
'${likes + (isLiked ? 1 : 0)}',
style: const TextStyle(color: Colors.white, fontSize: 12),
),
],
),
),
const SizedBox(height: 20),
// Comments Button
Column(
children: <Widget>[
const Icon(Icons.comment, color: Colors.white, size: 30),
const SizedBox(height: 4),
Text(
'$comments',
style: const TextStyle(color: Colors.white, fontSize: 12),
),
],
),
const SizedBox(height: 20),
// Share Button
const Column(
children: <Widget>[
Icon(Icons.share, color: Colors.white, size: 30),
SizedBox(height: 4),
Text(
'Share',
style: TextStyle(color: Colors.white, fontSize: 12),
),
],
),
const SizedBox(height: 24),
],
),
],
),
),
),
);
},
),
// Header Overlay Title
const Positioned(
top: 0,
left: 0,
right: 0,
child: SafeArea(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Reels Feed',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
shadows: <Shadow>[
Shadow(blurRadius: 4, color: Colors.black54),
],
),
),
],
),
),
),
),
],
),
);
}
}