import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:mime/mime.dart'; import 'package:video_player/video_player.dart'; class CamerExample extends StatefulWidget { const CamerExample({super.key}); @override State createState() => _CamerExampleState(); } class _CamerExampleState extends State { List? _mediaFileList; void _setImageFileListFromFile(XFile? value) { _mediaFileList = value == null ? null : [value]; } dynamic _pickImageError; bool isVideo = false; VideoPlayerController? _controller; VideoPlayerController? _toBeDisposed; String? _retrieveDataError; final ImagePicker _picker = ImagePicker(); final TextEditingController maxWidthController = TextEditingController(); final TextEditingController maxHeightController = TextEditingController(); final TextEditingController qualityController = TextEditingController(); Future _playVideo(XFile? file) async { if (file != null && mounted) { await _disposeVideoController(); late VideoPlayerController controller; if (kIsWeb) { controller = VideoPlayerController.networkUrl(Uri.parse(file.path)); } else { controller = VideoPlayerController.file(File(file.path)); } _controller = controller; // In web, most browsers won't honor a programmatic call to .play // if the video has a sound track (and is not muted). // Mute the video so it auto-plays in web! // This is not needed if the call to .play is the result of user // interaction (clicking on a "play" button, for example). const double volume = kIsWeb ? 0.0 : 1.0; await controller.setVolume(volume); await controller.initialize(); await controller.setLooping(true); await controller.play(); setState(() {}); } } Future _onImageButtonPressed( ImageSource source, { required BuildContext context, bool isMultiImage = false, bool isMedia = false, }) async { if (_controller != null) { await _controller!.setVolume(0.0); } if (context.mounted) { if (isVideo) { final XFile? file = await _picker.pickVideo( source: source, maxDuration: const Duration(seconds: 10)); await _playVideo(file); } else if (isMultiImage) { await _displayPickImageDialog(context, (double? maxWidth, double? maxHeight, int? quality) async { try { final List pickedFileList = isMedia ? await _picker.pickMultipleMedia( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: quality, ) : await _picker.pickMultiImage( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: quality, ); setState(() { _mediaFileList = pickedFileList; }); } catch (e) { setState(() { _pickImageError = e; }); } }); } else if (isMedia) { await _displayPickImageDialog(context, (double? maxWidth, double? maxHeight, int? quality) async { try { final List pickedFileList = []; final XFile? media = await _picker.pickMedia( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: quality, ); if (media != null) { pickedFileList.add(media); setState(() { _mediaFileList = pickedFileList; }); } } catch (e) { setState(() { _pickImageError = e; }); } }); } else { await _displayPickImageDialog(context, (double? maxWidth, double? maxHeight, int? quality) async { try { final XFile? pickedFile = await _picker.pickImage( source: source, maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: quality, ); setState(() { _setImageFileListFromFile(pickedFile); }); } catch (e) { setState(() { _pickImageError = e; }); } }); } } } @override void deactivate() { if (_controller != null) { _controller!.setVolume(0.0); _controller!.pause(); } super.deactivate(); } @override void dispose() { _disposeVideoController(); maxWidthController.dispose(); maxHeightController.dispose(); qualityController.dispose(); super.dispose(); } Future _disposeVideoController() async { if (_toBeDisposed != null) { await _toBeDisposed!.dispose(); } _toBeDisposed = _controller; _controller = null; } Widget _previewVideo() { final Text? retrieveError = _getRetrieveErrorWidget(); if (retrieveError != null) { return retrieveError; } if (_controller == null) { return const Text( 'You have not yet picked a video', textAlign: TextAlign.center, ); } return Padding( padding: const EdgeInsets.all(10.0), child: AspectRatioVideo(_controller), ); } Widget _previewImages() { final Text? retrieveError = _getRetrieveErrorWidget(); if (retrieveError != null) { return retrieveError; } if (_mediaFileList != null) { return Semantics( label: 'image_picker_example_picked_images', child: ListView.builder( key: UniqueKey(), itemBuilder: (BuildContext context, int index) { final String? mime = lookupMimeType(_mediaFileList![index].path); // Why network for web? // See https://pub.dev/packages/image_picker_for_web#limitations-on-the-web-platform return Semantics( label: 'image_picker_example_picked_image', child: kIsWeb ? Image.network(_mediaFileList![index].path) : (mime == null || mime.startsWith('image/') ? Image.file( File(_mediaFileList![index].path), errorBuilder: (BuildContext context, Object error, StackTrace? stackTrace) { return const Center( child: Text('This image type is not supported')); }, ) : _buildInlineVideoPlayer(index)), ); }, itemCount: _mediaFileList!.length, ), ); } else if (_pickImageError != null) { return Text( 'Pick image error: $_pickImageError', textAlign: TextAlign.center, ); } else { return const Text( 'You have not yet picked an image.', textAlign: TextAlign.center, ); } } Widget _buildInlineVideoPlayer(int index) { final VideoPlayerController controller = VideoPlayerController.file(File(_mediaFileList![index].path)); const double volume = kIsWeb ? 0.0 : 1.0; controller.setVolume(volume); controller.initialize(); controller.setLooping(true); controller.play(); return Center(child: AspectRatioVideo(controller)); } Widget _handlePreview() { if (isVideo) { return _previewVideo(); } else { return _previewImages(); } } Future retrieveLostData() async { final LostDataResponse response = await _picker.retrieveLostData(); if (response.isEmpty) { return; } if (response.file != null) { if (response.type == RetrieveType.video) { isVideo = true; await _playVideo(response.file); } else { isVideo = false; setState(() { if (response.files == null) { _setImageFileListFromFile(response.file); } else { _mediaFileList = response.files; } }); } } else { _retrieveDataError = response.exception!.code; } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Camera example"), ), body: Center( child: !kIsWeb && defaultTargetPlatform == TargetPlatform.android ? FutureBuilder( future: retrieveLostData(), builder: (BuildContext context, AsyncSnapshot snapshot) { switch (snapshot.connectionState) { case ConnectionState.none: case ConnectionState.waiting: return const Text( 'You have not yet picked an image.', textAlign: TextAlign.center, ); case ConnectionState.done: return _handlePreview(); case ConnectionState.active: if (snapshot.hasError) { return Text( 'Pick image/video error: ${snapshot.error}}', textAlign: TextAlign.center, ); } else { return const Text( 'You have not yet picked an image.', textAlign: TextAlign.center, ); } } }, ) : _handlePreview(), ), floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ Semantics( label: 'image_picker_example_from_gallery', child: FloatingActionButton( onPressed: () { isVideo = false; _onImageButtonPressed(ImageSource.gallery, context: context); }, heroTag: 'image0', tooltip: 'Pick Image from gallery', child: const Icon(Icons.photo), ), ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( onPressed: () { isVideo = false; _onImageButtonPressed( ImageSource.gallery, context: context, isMultiImage: true, isMedia: true, ); }, heroTag: 'multipleMedia', tooltip: 'Pick Multiple Media from gallery', child: const Icon(Icons.photo_library), ), ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( onPressed: () { isVideo = false; _onImageButtonPressed( ImageSource.gallery, context: context, isMedia: true, ); }, heroTag: 'media', tooltip: 'Pick Single Media from gallery', child: const Icon(Icons.photo_library), ), ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( onPressed: () { isVideo = false; _onImageButtonPressed( ImageSource.gallery, context: context, isMultiImage: true, ); }, heroTag: 'image1', tooltip: 'Pick Multiple Image from gallery', child: const Icon(Icons.photo_library), ), ), if (_picker.supportsImageSource(ImageSource.camera)) Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( onPressed: () { isVideo = false; _onImageButtonPressed(ImageSource.camera, context: context); }, heroTag: 'image2', tooltip: 'Take a Photo', child: const Icon(Icons.camera_alt), ), ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( backgroundColor: Colors.red, onPressed: () { isVideo = true; _onImageButtonPressed(ImageSource.gallery, context: context); }, heroTag: 'video0', tooltip: 'Pick Video from gallery', child: const Icon(Icons.video_library), ), ), if (_picker.supportsImageSource(ImageSource.camera)) Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( backgroundColor: Colors.red, onPressed: () { isVideo = true; _onImageButtonPressed(ImageSource.camera, context: context); }, heroTag: 'video1', tooltip: 'Take a Video', child: const Icon(Icons.videocam), ), ), ], ), ); } Text? _getRetrieveErrorWidget() { if (_retrieveDataError != null) { final Text result = Text(_retrieveDataError!); _retrieveDataError = null; return result; } return null; } Future _displayPickImageDialog( BuildContext context, OnPickImageCallback onPick) async { return showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Add optional parameters'), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: maxWidthController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration( hintText: 'Enter maxWidth if desired'), ), TextField( controller: maxHeightController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration( hintText: 'Enter maxHeight if desired'), ), TextField( controller: qualityController, keyboardType: TextInputType.number, decoration: const InputDecoration( hintText: 'Enter quality if desired'), ), ], ), actions: [ TextButton( child: const Text('CANCEL'), onPressed: () { Navigator.of(context).pop(); }, ), TextButton( child: const Text('PICK'), onPressed: () { final double? width = maxWidthController.text.isNotEmpty ? double.parse(maxWidthController.text) : null; final double? height = maxHeightController.text.isNotEmpty ? double.parse(maxHeightController.text) : null; final int? quality = qualityController.text.isNotEmpty ? int.parse(qualityController.text) : null; onPick(width, height, quality); Navigator.of(context).pop(); }), ], ); }); } } typedef OnPickImageCallback = void Function( double? maxWidth, double? maxHeight, int? quality); class AspectRatioVideo extends StatefulWidget { const AspectRatioVideo(this.controller, {super.key}); final VideoPlayerController? controller; @override AspectRatioVideoState createState() => AspectRatioVideoState(); } class AspectRatioVideoState extends State { VideoPlayerController? get controller => widget.controller; bool initialized = false; void _onVideoControllerUpdate() { if (!mounted) { return; } if (initialized != controller!.value.isInitialized) { initialized = controller!.value.isInitialized; setState(() {}); } } @override void initState() { super.initState(); controller!.addListener(_onVideoControllerUpdate); } @override void dispose() { controller!.removeListener(_onVideoControllerUpdate); super.dispose(); } @override Widget build(BuildContext context) { if (initialized) { return Center( child: AspectRatio( aspectRatio: controller!.value.aspectRatio, child: VideoPlayer(controller!), ), ); } else { return Container(); } } }