Products
Products
Video Hosting
Upload and manage your videos in a centralized video library.
Image Hosting
Upload and manage all your images in a centralized library.
Galleries
Choose from 100+templates to showcase your media in style.
Video Messaging
Record, and send personalized video messages.
CincoTube
Create your own community video hub your team, students or fans.
Pages
Create dedicated webpages to share your videos and images.
Live
Create dedicated webpages to share your videos and images.
For Developers
Video API
Build a unique video experience.
DeepUploader
Collect and store user content from anywhere with our file uploader.
Solutions
Solutions
Enterprise
Supercharge your business with secure, internal communication.
Townhall
Webinars
Team Collaboration
Learning & Development
Creative Professionals
Get creative with a built in-suite of editing and marketing tools.
eCommerce
Boost sales with interactive video and easy-embedding.
Townhall
Webinars
Team Collaboration
Learning & Development
eLearning & Training
Host and share course materials in a centralized portal.
Sales & Marketing
Attract, engage and convert with interactive tools and analytics.
"Cincopa helped my Enterprise organization collaborate better through video."
Book a Demo
Resources
Resources
Blog
Learn about the latest industry trends, tips & tricks.
Help Centre
Get access to help articles FAQs, and all things Cincopa.
Partners
Check out our valued list of partners.
Product Updates
Stay up-to-date with our latest greatest features.
Ebooks, Guides & More
Customer Stories
Hear how we've helped businesses succeed.
Boost Campaign Performance Through Video
Discover how to boost your next campaign by using video.
Download Now
Pricing
Watch a Demo
Demo
Login
Start Free Trial
The video_player package provides the foundational API for video playback in Flutter. While wrappers like Chewie or Better Player offer ready-made UIs, they limit customization and control. For applications that require a branded interface, multiple tracks, custom controls, or tighter memory optimization. Pre-Requisites Before building a custom player, ensure you have: Install Flutter SDK (preferably the latest stable release) Basic Flutter and Dart knowledge (widgets, state management) Familiarity with widget composition and lifecycle A working Flutter project (see the Flutter setup guide ) Installing video_player Add the video_player package to your pubspec.yaml : dependencies: video_player: ^2.9.1 Then, run: flutter pub get Initializing the Video Player Controller The VideoPlayerController is the heart of video playback. You must initialize it before use and dispose of it when done to free up native resources. Example : Creating a StatefulWidget-based Custom Video Player import 'package:video_player/video_player.dart'; import 'package:flutter/material.dart'; class CustomVideoPlayer extends StatefulWidget { final String url; const CustomVideoPlayer({Key? key, required this.url}) : super(key: key); @override State
createState() => _CustomVideoPlayerState(); } class _CustomVideoPlayerState extends State
{ late VideoPlayerController _controller; late Future
_initFuture; @override void initState() { super.initState(); _controller = VideoPlayerController.network(widget.url); _initFuture = _controller.initialize(); } @override void dispose() { _controller.dispose(); // Always dispose to free resources. super.dispose(); } @override Widget build(BuildContext context) { return FutureBuilder( future: _initFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { return AspectRatio( aspectRatio: _controller.value.aspectRatio, child: VideoPlayer(_controller), ); } else if (_controller.value.hasError) { return Center( child: Text('Error: ${_controller.value.errorDescription}'), ); } return const Center(child: CircularProgressIndicator()); }, ); } } Explanation : Use .asset() or .file() instead of .network() to load local videos. Wrap the player in a custom UI to fully control appearance and behavior. Building the Player UI Unlike wrappers, video_player does not provide built-in controls. This gives you the freedom to match your app’s branding, add gestures (e.g., double-tap to seek), and animate overlays or captions. Example : Reusable Play/Pause Button Widget buildControls() { return Stack( alignment: Alignment.bottomCenter, children: [ GestureDetector( onTap: () { setState(() { _controller.value.isPlaying ? _controller.pause() : _controller.play(); }); }, child: Center( child: Icon( _controller.value.isPlaying ? Icons.pause : Icons.play_arrow, size: 48, color: Colors.white, ), ), ), VideoProgressIndicator( _controller, allowScrubbing: true, colors: VideoProgressColors( playedColor: Colors.red, backgroundColor: Colors.grey, ), ), ], ); } By creating custom controls, you define the exact behavior and appearance, ensuring the player fits your app's design and provides the precise functionality your users need. Implementing Full-Screen Mode and Advanced Features Full-screen mode and features like subtitles and audio track switching are expected in professional players, as they elevate the user experience. Implementing full-screen by reusing the same controller avoids re-buffering, ensuring smooth transitions and continuous playback, thus maintaining user engagement. Full-Screen Mode To provide immersive playback, you’ll want full-screen mode. Reusing the same controller avoids re-buffering and allows seamless transitions. Example : Navigate to Full-Screen Player Future
_enterFullscreen() async { await Navigator.push( context, MaterialPageRoute( builder: (context) => Scaffold( body: Center( child: AspectRatio( aspectRatio: _controller.value.aspectRatio, child: VideoPlayer(_controller), ), ), ), ), ); } Adding Subtitles Subtitles enhance accessibility and UX. You can render subtitles manually based on the current playback position. Example : Basic Subtitle Widget Widget buildSubtitles(String text) { return Positioned( bottom: 40, child: Text( text, style: const TextStyle( color: Colors.white, backgroundColor: Colors.black54, fontSize: 16, ), ), ); } Parsing subtitle files such as .srt or .vtt and mapping lines to timestamps helps sync subtitle display with video progress. Switching Audio Tracks The video_player package doesn’t support multiple audio tracks natively. But you can simulate it by switching to a different video source at the same timestamp. Example : Reload Video with New Audio Future
switchTrack(String newUrl, Duration currentPosition) async { await _controller.pause(); await _controller.dispose(); _controller = VideoPlayerController.network(newUrl); await _controller.initialize(); await _controller.seekTo(currentPosition); await _controller.play(); } This technique highlights how building on top of video_player ’s flexibility enables handling features beyond its default capabilities. Error Handling and Background Playback The package doesn’t support background playback. You’ll need to use platform channels, implement Android MediaSession , and enable iOS AVAudioSession . if (_controller.value.hasError) { return Center( child: Text('Playback Error: ${_controller.value.errorDescription}'), ); } Using native capabilities like MediaSession on Android or AVPlayer background modes on iOS bridges this gap, enabling production-quality behavior. Resource Management and Performance Each VideoPlayerController consumes memory. If you're building feeds or lists, you must dispose of off-screen players. Example : Video Player in a ListView ListView.builder( itemCount: videoUrls.length, itemBuilder: (context, index) { return CustomVideoPlayer(url: videoUrls[index]); }, ); Use packages like visibility_detector to optimize player lifecycle. Performance, Testing & Debugging Testing on real devices under various network conditions helps uncover playback issues that simulators may miss. Automated unit and widget tests validate UI behavior, while manual testing checks for smoothness, buffering, and error handling in real scenarios. Monitoring controller states such as position, buffering, and errors provides actionable insights to debug and maintain your player. Extending the Player Modular design improves reusability and maintainability by letting you isolate features into components. For example, a reusable Play/Pause button simplifies UI construction and keeps consistent behavior across your app. Example : Reusable Play/Pause Button class PlayPauseButton extends StatelessWidget { final VideoPlayerController controller; const PlayPauseButton({Key? key, required this.controller}) : super(key: key); @override Widget build(BuildContext context) { return IconButton( icon: Icon( controller.value.isPlaying ? Icons.pause : Icons.play_arrow, ), onPressed: () { controller.value.isPlaying ? controller.pause() : controller.play(); }, ); } } Additional modular features like playback speed controls, volume sliders, analytics tracking, and DRM or adaptive streaming integrations enhance user control and secure content delivery.