Flutter Media Widgets: Working with Images, Video, and Audio
•24 min read
Media widgets are essential for creating engaging user experiences in Flutter applications. This comprehensive guide covers how to work with images, video, and audio content effectively.
1. Image Widgets
Basic Image Loading
Flutter provides multiple ways to load and display images.
class ImageExamples extends StatelessWidget { @override Widget build(BuildContext context) { return Column( children: [ // Network Image Image.network( 'https://example.com/image.jpg', width: 200, height: 200, fit: BoxFit.cover, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; return Center( child: CircularProgressIndicator( value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null, ), ); }, errorBuilder: (context, error, stackTrace) { return Container( width: 200, height: 200, color: Colors.grey[300], child: Icon(Icons.error), ); }, ), SizedBox(height: 16), // Asset Image Image.asset( 'assets/images/local_image.png', width: 200, height: 200, fit: BoxFit.contain, ), ], ); } }
Advanced Image Handling
Implement caching and placeholder strategies.
class CachedImageExample extends StatelessWidget { @override Widget build(BuildContext context) { return CachedNetworkImage( imageUrl: 'https://example.com/image.jpg', imageBuilder: (context, imageProvider) => Container( decoration: BoxDecoration( image: DecorationImage( image: imageProvider, fit: BoxFit.cover, colorFilter: ColorFilter.mode( Colors.blue.withOpacity(0.2), BlendMode.colorBurn, ), ), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 8, offset: Offset(0, 4), ), ], ), ), placeholder: (context, url) => ShimmerPlaceholder(), errorWidget: (context, url, error) => ErrorPlaceholder(), ); } } class ShimmerPlaceholder extends StatelessWidget { @override Widget build(BuildContext context) { return Shimmer.fromColors( baseColor: Colors.grey[300]!, highlightColor: Colors.grey[100]!, child: Container( width: 200, height: 200, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), ), ); } }
2. Video Widgets
Video Player Implementation
Implement video playback with controls.
class VideoPlayerExample extends StatefulWidget { @override _VideoPlayerExampleState createState() => _VideoPlayerExampleState(); } class _VideoPlayerExampleState extends State<VideoPlayerExample> { late VideoPlayerController _controller; late Future<void> _initializeVideoPlayerFuture; @override void initState() { super.initState(); _controller = VideoPlayerController.network( 'https://example.com/video.mp4', ); _initializeVideoPlayerFuture = _controller.initialize(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Column( children: [ FutureBuilder( future: _initializeVideoPlayerFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { return AspectRatio( aspectRatio: _controller.value.aspectRatio, child: Stack( alignment: Alignment.bottomCenter, children: [ VideoPlayer(_controller), VideoProgressIndicator( _controller, allowScrubbing: true, colors: VideoProgressColors( playedColor: Colors.blue, bufferedColor: Colors.grey, ), ), _buildControls(), ], ), ); } else { return Center(child: CircularProgressIndicator()); } }, ), ], ); } Widget _buildControls() { return Container( color: Colors.black26, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ IconButton( icon: Icon( _controller.value.isPlaying ? Icons.pause : Icons.play_arrow, color: Colors.white, ), onPressed: () { setState(() { _controller.value.isPlaying ? _controller.pause() : _controller.play(); }); }, ), IconButton( icon: Icon( _controller.value.volume > 0 ? Icons.volume_up : Icons.volume_off, color: Colors.white, ), onPressed: () { setState(() { _controller.setVolume( _controller.value.volume > 0 ? 0 : 1, ); }); }, ), ], ), ); } }
Custom Video Player
Create a custom video player with additional features.
class CustomVideoPlayer extends StatefulWidget { final String videoUrl; final bool autoPlay; final bool looping; CustomVideoPlayer({ required this.videoUrl, this.autoPlay = false, this.looping = false, }); @override _CustomVideoPlayerState createState() => _CustomVideoPlayerState(); } class _CustomVideoPlayerState extends State<CustomVideoPlayer> { late VideoPlayerController _controller; bool _isBuffering = false; @override void initState() { super.initState(); _initializeVideo(); } Future<void> _initializeVideo() async { _controller = VideoPlayerController.network(widget.videoUrl); await _controller.initialize(); _controller.addListener(() { final bool isBuffering = _controller.value.isBuffering; if (_isBuffering != isBuffering) { setState(() => _isBuffering = isBuffering); } }); if (widget.autoPlay) { _controller.play(); } if (widget.looping) { _controller.setLooping(true); } setState(() {}); } @override Widget build(BuildContext context) { if (!_controller.value.isInitialized) { return Center(child: CircularProgressIndicator()); } return GestureDetector( onTap: () { setState(() { _controller.value.isPlaying ? _controller.pause() : _controller.play(); }); }, child: Stack( alignment: Alignment.center, children: [ AspectRatio( aspectRatio: _controller.value.aspectRatio, child: VideoPlayer(_controller), ), if (_isBuffering) Container( decoration: BoxDecoration( color: Colors.black26, ), child: Center( child: CircularProgressIndicator(), ), ), if (!_controller.value.isPlaying) Container( decoration: BoxDecoration( color: Colors.black26, ), child: Icon( Icons.play_arrow, color: Colors.white, size: 48, ), ), ], ), ); } @override void dispose() { _controller.dispose(); super.dispose(); } }
3. Audio Widgets
Audio Player Implementation
Create an audio player with controls.
class AudioPlayerExample extends StatefulWidget { @override _AudioPlayerExampleState createState() => _AudioPlayerExampleState(); } class _AudioPlayerExampleState extends State<AudioPlayerExample> { final AudioPlayer _audioPlayer = AudioPlayer(); bool _isPlaying = false; Duration _duration = Duration(); Duration _position = Duration(); @override void initState() { super.initState(); _initAudioPlayer(); } Future<void> _initAudioPlayer() async { await _audioPlayer.setUrl('https://example.com/audio.mp3'); _audioPlayer.onDurationChanged.listen((Duration duration) { setState(() => _duration = duration); }); _audioPlayer.onPositionChanged.listen((Duration position) { setState(() => _position = position); }); _audioPlayer.onPlayerComplete.listen((_) { setState(() => _isPlaying = false); }); } String _formatDuration(Duration duration) { String twoDigits(int n) => n.toString().padLeft(2, '0'); final hours = twoDigits(duration.inHours); final minutes = twoDigits(duration.inMinutes.remainder(60)); final seconds = twoDigits(duration.inSeconds.remainder(60)); return duration.inHours > 0 ? '$hours:$minutes:$seconds' : '$minutes:$seconds'; } @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 8, offset: Offset(0, 4), ), ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( 'Audio Player', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(_formatDuration(_position)), Expanded( child: Slider( value: _position.inSeconds.toDouble(), min: 0.0, max: _duration.inSeconds.toDouble(), onChanged: (value) { final position = Duration(seconds: value.toInt()); _audioPlayer.seek(position); setState(() => _position = position); }, ), ), Text(_formatDuration(_duration)), ], ), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( icon: Icon(Icons.skip_previous), onPressed: () { _audioPlayer.seek(Duration.zero); }, ), IconButton( icon: Icon( _isPlaying ? Icons.pause : Icons.play_arrow, size: 32, ), onPressed: () { if (_isPlaying) { _audioPlayer.pause(); } else { _audioPlayer.resume(); } setState(() => _isPlaying = !_isPlaying); }, ), IconButton( icon: Icon(Icons.stop), onPressed: () { _audioPlayer.stop(); setState(() { _isPlaying = false; _position = Duration.zero; }); }, ), ], ), ], ), ); } @override void dispose() { _audioPlayer.dispose(); super.dispose(); } }
4. Best Practices
1. Media Asset Management
Organize and manage media assets effectively.
// pubspec.yaml flutter: assets: - assets/images/ - assets/videos/ - assets/audio/
2. Error Handling and Loading States
class MediaErrorHandler { static Widget handleLoadingState({ required bool isLoading, required Widget child, Widget? loadingWidget, }) { if (isLoading) { return loadingWidget ?? Center( child: CircularProgressIndicator(), ); } return child; } static Widget handleError({ required String errorMessage, VoidCallback? onRetry, }) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.error_outline, color: Colors.red, size: 48, ), SizedBox(height: 16), Text( errorMessage, style: TextStyle(color: Colors.red), ), if (onRetry != null) ...[ SizedBox(height: 16), ElevatedButton( onPressed: onRetry, child: Text('Retry'), ), ], ], ), ); } }
3. Performance Optimization
class MediaOptimization { static const int maxCacheSize = 100 * 1024 * 1024; // 100MB static const int maxCacheFileSize = 10 * 1024 * 1024; // 10MB static void initializeMediaCache() { PaintingBinding.instance.imageCache.maximumSize = 1000; PaintingBinding.instance.imageCache.maximumSizeBytes = maxCacheSize; } static String getOptimizedImageUrl(String url, { int? width, int? height, int quality = 85, }) { final Uri uri = Uri.parse(url); final Map<String, String> queryParams = { 'q': quality.toString(), if (width != null) 'w': width.toString(), if (height != null) 'h': height.toString(), }; return uri.replace(queryParameters: queryParams).toString(); } }
By following these guidelines and implementing the provided widgets correctly, you can create rich media experiences in your Flutter applications while maintaining good performance and user experience.