flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
164.3k stars 27.11k forks source link

[WEB] [Camera] Incorrect recorded video duration issue in Chrome browser #121789

Open mr-sharmaji opened 1 year ago

mr-sharmaji commented 1 year ago

Description: When recording a video using the camera in a Flutter web application, the duration of the recorded video sometimes shows a negative value in the Google Chrome browser. However, the same code works perfectly fine in Mozilla Firefox.

Code sample ```dart Future startVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return; } if (cameraController.value.isRecordingVideo) { // A recording is already started, do nothing. return; } try { await cameraController.startVideoRecording(); } on CameraException catch (e) { _showCameraException(e); return; } } void onStopButtonPressed() async { XFile? file = await stopVideoRecording(); if (mounted) { setState(() {}); } if (file != null) { showInSnackBar('Video recorded'); videoFile = file; await _startVideoPlayer(); } } Future stopVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isRecordingVideo) { return null; } try { return cameraController.stopVideoRecording(); } on CameraException catch (e) { _showCameraException(e); return null; } } Widget _buildTimer(controller) { Duration position = controller.value.isInitialized ? controller.value.position : Duration.zero; Duration duration = controller.value.duration ?? Duration.zero; String positionText = position.toString().split('.').first; String durationText = duration.toString().split('.').first; return Positioned( right: 16.0, bottom: 24.0, child: Text( '$positionText / $durationText', style: const TextStyle( color: Colors.white, fontSize: 16.0, ), ), ); } ```

Expected output: The duration of the recorded video should always show a positive value in all browsers.

Output received: The duration of the recorded video sometimes shows a negative value in the Google Chrome browser.

Steps to reproduce:

Open the above code in a Flutter web application. Record a video using the camera. Check the duration of the recorded video.

Additionally, I have attached a screenshot that displays the negative duration issue on Google Chrome while the same code is giving the expected positive value when running on Mozilla Firefox.

Screenshot 2023-03-02 at 4 31 04 PM

Screenshot 2023-03-02 at 4 29 53 PM

danagbemava-nc commented 1 year ago

Hi @mr-sharmaji, the code sample you provided above is not complete. Kindly provide a complete runnable code sample so that investigate this.

Please also provide the version of camera & camera_web that you're using along with the output of flutter doctor -v

Thank you

mr-sharmaji commented 1 year ago

Here is the complete Runnable Code Sample:

Code sample ```dart import 'dart:async'; import 'dart:io'; import 'dart:math'; import 'package:camera/camera.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:video_player/video_player.dart'; import '../constants/Theme.dart'; class VideoCameraCheck extends StatefulWidget { final List cameras; const VideoCameraCheck({Key? key, required this.cameras}) : super(key: key); @override State createState() => _VideoCameraCheckState(); } class _VideoCameraCheckState extends State with WidgetsBindingObserver, TickerProviderStateMixin { CameraController? controller; XFile? videoFile; VideoPlayerController? videoController; VoidCallback? videoPlayerListener; bool enableAudio = true; bool _isPlaying = false; int _pointers = 0; String recordingTime = '00:00'; bool isRecording = false; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); if (widget.cameras.isEmpty) { SchedulerBinding.instance.addPostFrameCallback((_) { ScaffoldMessenger.of(context) .showSnackBar(const SnackBar(content: Text('No camera found'))); }); } else { onNewCameraSelected(widget.cameras.first); } } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override Widget build(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; double screenHeight = MediaQuery.of(context).size.height; return AnnotatedRegion( value: const SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.dark, systemNavigationBarColor: AppColors.defaultBgColor, systemNavigationBarIconBrightness: Brightness.dark, ), child: Scaffold( backgroundColor: AppColors.defaultBgColor, body: SafeArea( child: SingleChildScrollView( child: Container( margin: const EdgeInsets.only(top: 10), child: SafeArea( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( children: [ Container( height: 400, width: screenWidth * 0.4, decoration: const BoxDecoration( color: Colors.black, ), child: Padding( padding: const EdgeInsets.all(1.0), child: Stack( children: [ Center( child: _cameraPreviewWidget(), ), Positioned( right: 16.0, bottom: 24.0, child: Text( recordingTime, style: const TextStyle( color: Colors.red, fontSize: 16.0, ), ), ), ], ), ), ), _captureControlRowWidget(), ], ), _recordedVideoWidget(screenWidth * 0.4), ], ), ], ), ), ), ), ), ), ), ); } void recordTime() { var startTime = DateTime.now(); Timer.periodic(const Duration(seconds: 0), (Timer t) { var diff = DateTime.now().difference(startTime); recordingTime = '${diff.inHours == 0 ? '' : '${diff.inHours.toString().padLeft(2, "0")}:'}${(diff.inMinutes % 60).floor().toString().padLeft(2, "0")}:${(diff.inSeconds % 60).floor().toString().padLeft(2, '0')}'; if (!isRecording) { t.cancel(); } setState(() {}); }); } Widget _cameraPreviewWidget() { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { return const Text( 'Camera not initialized', style: TextStyle( color: Colors.white, fontSize: 24.0, fontWeight: FontWeight.w900, ), ); } else { return Listener( onPointerDown: (_) => _pointers++, onPointerUp: (_) => _pointers--, child: CameraPreview( controller!, ), ); } } Future onNewCameraSelected(CameraDescription cameraDescription) async { final CameraController? oldController = controller; if (oldController != null) { controller = null; await oldController.dispose(); } final CameraController cameraController = CameraController( cameraDescription, kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium, enableAudio: enableAudio, ); controller = cameraController; // If the controller is updated then update the UI. cameraController.addListener(() { if (mounted) { setState(() {}); } if (cameraController.value.hasError) { showInSnackBar( 'Camera error ${cameraController.value.errorDescription}'); } }); try { await cameraController.initialize(); } on CameraException catch (e) { switch (e.code) { case 'CameraAccessDenied': showInSnackBar('You have denied camera access.'); break; case 'CameraAccessDeniedWithoutPrompt': // iOS only showInSnackBar('Please go to Settings app to enable camera access.'); break; case 'CameraAccessRestricted': // iOS only showInSnackBar('Camera access is restricted.'); break; case 'AudioAccessDenied': showInSnackBar('You have denied audio access.'); break; case 'AudioAccessDeniedWithoutPrompt': // iOS only showInSnackBar('Please go to Settings app to enable audio access.'); break; case 'AudioAccessRestricted': // iOS only showInSnackBar('Audio access is restricted.'); break; default: _showCameraException(e); break; } } if (mounted) { setState(() {}); } } IconData getCameraLensIcon(CameraLensDirection direction) { switch (direction) { case CameraLensDirection.back: return Icons.camera_rear; case CameraLensDirection.front: return Icons.camera_front; case CameraLensDirection.external: return Icons.camera; } // ignore: dead_code return Icons.camera; } Widget _captureControlRowWidget() { final CameraController? cameraController = controller; return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ IconButton( icon: const Icon(Icons.videocam), color: AppColors.logoColor, onPressed: cameraController != null && cameraController.value.isInitialized && !cameraController.value.isRecordingVideo ? onVideoRecordButtonPressed : null, ), IconButton( icon: const Icon(Icons.stop), color: Colors.red, onPressed: cameraController != null && cameraController.value.isInitialized && cameraController.value.isRecordingVideo ? onStopButtonPressed : null, ), ], ); } Widget _recordedVideoWidget(double width) { final VideoPlayerController? localVideoController = videoController; return Row( mainAxisSize: MainAxisSize.min, children: [ if (localVideoController == null) Container( width: width, height: 400, decoration: BoxDecoration( color: Colors.black, border: Border.all(color: Colors.red), ), child: const Center( child: Text( 'No video recorded yet', style: TextStyle( color: Colors.white, fontSize: 24.0, fontWeight: FontWeight.w900, ), ), ), ) else Column( children: [ SizedBox( width: width, height: 400, child: Container( decoration: BoxDecoration( color: Colors.black, border: Border.all(color: Colors.red), ), child: Center( child: Stack( children: [ Center( child: AspectRatio( aspectRatio: localVideoController.value.size != null ? localVideoController.value.aspectRatio : 1.0, child: VideoPlayer(localVideoController), ), ), _buildTimer(localVideoController), ], ), ), ), ), // play pause video button _videoControlRowWidget(localVideoController), ], ), ], ); } Widget _buildTimer(controller) { Duration duration = controller.value.duration ?? Duration.zero; return Positioned( right: 16.0, bottom: 24.0, child: ValueListenableBuilder( valueListenable: controller, builder: (BuildContext context, VideoPlayerValue value, Widget? child) { String positionText = value.position.toString().split('.').first; String durationText = duration.toString().split('.').first; // remove hours positionText = positionText.substring(2); durationText = durationText.substring(2); return Text( '$positionText / $durationText', style: const TextStyle( color: Colors.red, fontSize: 16.0, ), ); }, ), ); } Widget _videoControlRowWidget(controller) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( icon: Icon( _isPlaying ? Icons.pause : Icons.play_arrow, ), onPressed: controller.value.isInitialized ? () { _isPlaying = !_isPlaying; setState(() { _isPlaying ? controller.play() : controller.pause(); }); } : null, ), ], ); } void onVideoRecordButtonPressed() { startVideoRecording().then((_) { recordTime(); setState(() { isRecording = true; }); if (mounted) { setState(() {}); } }); } Future startVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return; } if (cameraController.value.isRecordingVideo) { // A recording is already started, do nothing. return; } try { await cameraController.startVideoRecording(); } on CameraException catch (e) { _showCameraException(e); return; } } void onStopButtonPressed() async { XFile? file = await stopVideoRecording(); setState(() { isRecording = false; }); if (mounted) { setState(() {}); } if (file != null) { videoFile = file; String size = formatBytes(await file.length(), 2); showInSnackBar('Video recorded to ${file.path} having size $size'); await _startVideoPlayer(); } } Future stopVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isRecordingVideo) { return null; } try { XFile videoFile = await cameraController.stopVideoRecording(); return videoFile; } on CameraException catch (e) { _showCameraException(e); return null; } } Future _startVideoPlayer() async { if (videoFile == null) { return; } final VideoPlayerController vController = kIsWeb ? VideoPlayerController.network(videoFile!.path) : VideoPlayerController.file(File(videoFile!.path)); videoPlayerListener = () { if (videoController != null) { // Refreshing the state to update video player with the correct ratio. if (videoController?.value.isInitialized == true) { if (mounted) { setState(() {}); } } videoController!.removeListener(videoPlayerListener!); } }; vController.addListener(videoPlayerListener!); vController.addListener(() { if (vController.value.isPlaying && !_isPlaying) { setState(() { _isPlaying = true; }); } else if (vController.value.isPlaying == false && _isPlaying) { setState(() { _isPlaying = false; }); } }); await vController.setLooping(false); await vController.initialize(); await videoController?.dispose(); if (mounted) { setState(() { videoController = vController; }); } // await vController.play(); } String formatBytes(int bytes, int decimals) { if (bytes <= 0) return "0 B"; const suffixes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; var i = (log(bytes) / log(1024)).floor(); return '${(bytes / pow(1024, i)).toStringAsFixed(decimals)} ${suffixes[i]}'; } void showInSnackBar(String message) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(message))); } void _showCameraException(CameraException e) { _logError(e.code, e.description); showInSnackBar('Error: ${e.code}\n${e.description}'); } void _logError(String code, String? message) { // ignore: avoid_print print('Error: $code${message == null ? '' : '\nError Message: $message'}'); } } ```

camera version: 0.10.3+1

I am not using camera_web dependency, but this code works without it on web.

Here is the output of flutter doctor -v:

Terminal Output ```console [✓] Flutter (Channel stable, 3.7.0, on macOS 13.2.1 22D68 darwin-arm64, locale en-IN) • Flutter version 3.7.0 on channel stable at /Users/subh/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision b06b8b2710 (5 weeks ago), 2023-01-23 16:55:55 -0800 • Engine revision b24591ed32 • Dart version 2.19.0 • DevTools version 2.20.1 [✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0) • Android SDK at /Users/subh/Library/Android/sdk • Platform android-33, build-tools 33.0.0 • Java binary at: /Users/subh/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/213.7172.25.2113.9123335 /Android Studio.app/Contents/jre/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 11.0.13+0-b1751.21-8125866) • All Android licenses accepted. [!] Xcode - develop for iOS and macOS (Xcode 14.2) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 14C18 ! CocoaPods 1.10.1 out of date (1.11.0 is recommended). CocoaPods is used to retrieve the iOS and macOS platform side's plugin code that responds to your plugin usage on the Dart side. Without CocoaPods, plugins will not work on iOS or macOS. For more info, see https://flutter.dev/platform-plugins To upgrade see https://guides.cocoapods.org/using/getting-started.html#installation for instructions. [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2021.3) • Android Studio at /Users/subh/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/213.7172.25.2113.9123335 /Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 11.0.13+0-b1751.21-8125866) [✓] Android Studio (version 2021.3) • Android Studio at /Users/subh/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/213.7172.25.2113.9014738 /Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 11.0.13+0-b1751.21-8125866) [✓] IntelliJ IDEA Ultimate Edition (version 2022.3.1) • IntelliJ at /Users/subh/Applications/JetBrains Toolbox/IntelliJ IDEA Ultimate.app • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart [✓] IntelliJ IDEA Ultimate Edition (version 2022.3) • IntelliJ at /Users/subh/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/223.7571.182/IntelliJ IDEA.app • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart [✓] IntelliJ IDEA Ultimate Edition (version 2022.3.1) • IntelliJ at /Users/subh/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/223.8214.52/IntelliJ IDEA.app • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart [✓] VS Code (version 1.74.2) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension can be installed from: 🔨 https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter [✓] Connected device (4 available) • AC2001 (mobile) • adb-8a187736-P9zVl8._adb-tls-connect._tcp. • android-arm64 • Android 12 (API 31) • iPhone 14 Pro Max (mobile) • 3223C440-C43E-4592-AF60-3AF19DFD7DEC • ios • com.apple.CoreSimulator.SimRuntime.iOS-16-2 (simulator) • macOS (desktop) • macos • darwin-arm64 • macOS 13.2.1 22D68 darwin-arm64 • Chrome (web) • chrome • web-javascript • Google Chrome 110.0.5481.177 [✓] HTTP Host Availability • All required HTTP hosts are available ! Doctor found issues in 1 category. ```

Thank You.

danagbemava-nc commented 1 year ago

I can reproduce the issue using the sample provided above (complete sample pasted below). This seems to be affecting chromium-based browsers as I could reproduce it in both Arc & Chrome but not Safari or Firefox. This reproduces on both Chrome-desktop & chrome mobile. I'm not entirely sure if this is an issue with the camera plugin or if this is just an issue with chrome itself, labeling for further investigation.

screenshots ### firefox Screenshot 2023-03-03 at 07 01 19 ### safari Screenshot 2023-03-03 at 07 01 15 ### chrome Screenshot 2023-03-03 at 07 01 37
code sample ```dart import 'dart:async'; import 'dart:io'; import 'dart:math'; import 'package:camera/camera.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:video_player/video_player.dart'; late List cameras; Future main() async { cameras = await availableCameras(); runApp(const App()); } class App extends StatelessWidget { const App({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: VideoCameraCheck(cameras: cameras), ); } } class VideoCameraCheck extends StatefulWidget { final List cameras; const VideoCameraCheck({Key? key, required this.cameras}) : super(key: key); @override State createState() => _VideoCameraCheckState(); } class _VideoCameraCheckState extends State with WidgetsBindingObserver, TickerProviderStateMixin { CameraController? controller; XFile? videoFile; VideoPlayerController? videoController; VoidCallback? videoPlayerListener; bool enableAudio = true; bool _isPlaying = false; int _pointers = 0; String recordingTime = '00:00'; bool isRecording = false; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); if (widget.cameras.isEmpty) { SchedulerBinding.instance.addPostFrameCallback((_) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('No camera found'))); }); } else { onNewCameraSelected(widget.cameras.first); } } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override Widget build(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; double screenHeight = MediaQuery.of(context).size.height; return AnnotatedRegion( value: const SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.dark, systemNavigationBarIconBrightness: Brightness.dark, ), child: Scaffold( body: SafeArea( child: SingleChildScrollView( child: Container( margin: const EdgeInsets.only(top: 10), child: SafeArea( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( children: [ Container( height: 400, width: screenWidth * 0.4, decoration: const BoxDecoration( color: Colors.black, ), child: Padding( padding: const EdgeInsets.all(1.0), child: Stack( children: [ Center( child: _cameraPreviewWidget(), ), Positioned( right: 16.0, bottom: 24.0, child: Text( recordingTime, style: const TextStyle( color: Colors.red, fontSize: 16.0, ), ), ), ], ), ), ), _captureControlRowWidget(), ], ), _recordedVideoWidget(screenWidth * 0.4), ], ), ], ), ), ), ), ), ), ), ); } void recordTime() { var startTime = DateTime.now(); Timer.periodic(const Duration(seconds: 0), (Timer t) { var diff = DateTime.now().difference(startTime); recordingTime = '${diff.inHours == 0 ? '' : '${diff.inHours.toString().padLeft(2, "0")}:'}${(diff.inMinutes % 60).floor().toString().padLeft(2, "0")}:${(diff.inSeconds % 60).floor().toString().padLeft(2, '0')}'; if (!isRecording) { t.cancel(); } setState(() {}); }); } Widget _cameraPreviewWidget() { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { return const Text( 'Camera not initialized', style: TextStyle( color: Colors.white, fontSize: 24.0, fontWeight: FontWeight.w900, ), ); } else { return Listener( onPointerDown: (_) => _pointers++, onPointerUp: (_) => _pointers--, child: CameraPreview( controller!, ), ); } } Future onNewCameraSelected(CameraDescription cameraDescription) async { final CameraController? oldController = controller; if (oldController != null) { controller = null; await oldController.dispose(); } final CameraController cameraController = CameraController( cameraDescription, kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium, enableAudio: enableAudio, ); controller = cameraController; // If the controller is updated then update the UI. cameraController.addListener(() { if (mounted) { setState(() {}); } if (cameraController.value.hasError) { showInSnackBar('Camera error ${cameraController.value.errorDescription}'); } }); try { await cameraController.initialize(); } on CameraException catch (e) { switch (e.code) { case 'CameraAccessDenied': showInSnackBar('You have denied camera access.'); break; case 'CameraAccessDeniedWithoutPrompt': // iOS only showInSnackBar('Please go to Settings app to enable camera access.'); break; case 'CameraAccessRestricted': // iOS only showInSnackBar('Camera access is restricted.'); break; case 'AudioAccessDenied': showInSnackBar('You have denied audio access.'); break; case 'AudioAccessDeniedWithoutPrompt': // iOS only showInSnackBar('Please go to Settings app to enable audio access.'); break; case 'AudioAccessRestricted': // iOS only showInSnackBar('Audio access is restricted.'); break; default: _showCameraException(e); break; } } if (mounted) { setState(() {}); } } IconData getCameraLensIcon(CameraLensDirection direction) { switch (direction) { case CameraLensDirection.back: return Icons.camera_rear; case CameraLensDirection.front: return Icons.camera_front; case CameraLensDirection.external: return Icons.camera; } // ignore: dead_code return Icons.camera; } Widget _captureControlRowWidget() { final CameraController? cameraController = controller; return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ IconButton( icon: const Icon(Icons.videocam), color: Colors.blue, onPressed: cameraController != null && cameraController.value.isInitialized && !cameraController.value.isRecordingVideo ? onVideoRecordButtonPressed : null, ), IconButton( icon: const Icon(Icons.stop), color: Colors.red, onPressed: cameraController != null && cameraController.value.isInitialized && cameraController.value.isRecordingVideo ? onStopButtonPressed : null, ), ], ); } Widget _recordedVideoWidget(double width) { final VideoPlayerController? localVideoController = videoController; return Row( mainAxisSize: MainAxisSize.min, children: [ if (localVideoController == null) Container( width: width, height: 400, decoration: BoxDecoration( color: Colors.black, border: Border.all(color: Colors.red), ), child: const Center( child: Text( 'No video recorded yet', style: TextStyle( color: Colors.white, fontSize: 24.0, fontWeight: FontWeight.w900, ), ), ), ) else Column( children: [ SizedBox( width: width, height: 400, child: Container( decoration: BoxDecoration( color: Colors.black, border: Border.all(color: Colors.red), ), child: Center( child: Stack( children: [ Center( child: AspectRatio( aspectRatio: localVideoController.value.size != null ? localVideoController.value.aspectRatio : 1.0, child: VideoPlayer(localVideoController), ), ), _buildTimer(localVideoController), ], ), ), ), ), // play pause video button _videoControlRowWidget(localVideoController), ], ), ], ); } Widget _buildTimer(controller) { Duration duration = controller.value.duration ?? Duration.zero; return Positioned( right: 16.0, bottom: 24.0, child: ValueListenableBuilder( valueListenable: controller, builder: (BuildContext context, VideoPlayerValue value, Widget? child) { String positionText = value.position.toString().split('.').first; String durationText = duration.toString().split('.').first; // remove hours positionText = positionText.substring(2); durationText = durationText.substring(2); return Text( '$positionText / $durationText', style: const TextStyle( color: Colors.red, fontSize: 16.0, ), ); }, ), ); } Widget _videoControlRowWidget(controller) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( icon: Icon( _isPlaying ? Icons.pause : Icons.play_arrow, ), onPressed: controller.value.isInitialized ? () { _isPlaying = !_isPlaying; setState(() { _isPlaying ? controller.play() : controller.pause(); }); } : null, ), ], ); } void onVideoRecordButtonPressed() { startVideoRecording().then((_) { recordTime(); setState(() { isRecording = true; }); if (mounted) { setState(() {}); } }); } Future startVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return; } if (cameraController.value.isRecordingVideo) { // A recording is already started, do nothing. return; } try { await cameraController.startVideoRecording(); } on CameraException catch (e) { _showCameraException(e); return; } } void onStopButtonPressed() async { XFile? file = await stopVideoRecording(); setState(() { isRecording = false; }); if (mounted) { setState(() {}); } if (file != null) { videoFile = file; String size = formatBytes(await file.length(), 2); showInSnackBar('Video recorded to ${file.path} having size $size'); await _startVideoPlayer(); } } Future stopVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isRecordingVideo) { return null; } try { XFile videoFile = await cameraController.stopVideoRecording(); return videoFile; } on CameraException catch (e) { _showCameraException(e); return null; } } Future _startVideoPlayer() async { if (videoFile == null) { return; } final VideoPlayerController vController = kIsWeb ? VideoPlayerController.network(videoFile!.path) : VideoPlayerController.file(File(videoFile!.path)); videoPlayerListener = () { if (videoController != null) { // Refreshing the state to update video player with the correct ratio. if (videoController?.value.isInitialized == true) { if (mounted) { setState(() {}); } } videoController!.removeListener(videoPlayerListener!); } }; vController.addListener(videoPlayerListener!); vController.addListener(() { if (vController.value.isPlaying && !_isPlaying) { setState(() { _isPlaying = true; }); } else if (vController.value.isPlaying == false && _isPlaying) { setState(() { _isPlaying = false; }); } }); await vController.setLooping(false); await vController.initialize(); await videoController?.dispose(); if (mounted) { setState(() { videoController = vController; }); } // await vController.play(); } String formatBytes(int bytes, int decimals) { if (bytes <= 0) return "0 B"; const suffixes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; var i = (log(bytes) / log(1024)).floor(); return '${(bytes / pow(1024, i)).toStringAsFixed(decimals)} ${suffixes[i]}'; } void showInSnackBar(String message) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message))); } void _showCameraException(CameraException e) { _logError(e.code, e.description); showInSnackBar('Error: ${e.code}\n${e.description}'); } void _logError(String code, String? message) { // ignore: avoid_print print('Error: $code${message == null ? '' : '\nError Message: $message'}'); } } ```
flutter doctor -v ``` [✓] Flutter (Channel stable, 3.7.6, on macOS 13.2.1 22D68 darwin-arm64, locale en-GB) • Flutter version 3.7.6 on channel stable at /Users/nexus/dev/sdks/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 12cb4eb7a0 (2 days ago), 2023-03-01 10:29:26 -0800 • Engine revision ada363ee93 • Dart version 2.19.3 • DevTools version 2.20.1 [✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0) • Android SDK at /Users/nexus/Library/Android/sdk • Platform android-33, build-tools 33.0.0 • Java binary at: /Users/nexus/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/221.6008.13.2211.9477386/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 14.2) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 14C18 • CocoaPods version 1.11.3 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2022.1) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301) [✓] Android Studio (version 2022.1) • Android Studio at /Users/nexus/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/221.6008.13.2211.9477386/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301) [✓] IntelliJ IDEA Community Edition (version 2022.3.2) • IntelliJ at /Applications/IntelliJ IDEA CE.app • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart [✓] VS Code (version 1.76.0) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.60.0 [✓] Connected device (4 available) • M2007J20CG (mobile) • adb-5dd3be00-17AYzd._adb-tls-connect._tcp. • android-arm64 • Android 12 (API 31) • Nexus (mobile) • 00008020-001875E83A38002E • ios • iOS 16.3.1 20D67 • macOS (desktop) • macos • darwin-arm64 • macOS 13.2.1 22D68 darwin-arm64 • Chrome (web) • chrome • web-javascript • Google Chrome 110.0.5481.177 [✓] HTTP Host Availability • All required HTTP hosts are available • No issues found! ``` ``` [!] Flutter (Channel master, 3.9.0-1.0.pre.30, on macOS 13.2.1 22D68 darwin-arm64, locale en-GB) • Flutter version 3.9.0-1.0.pre.30 on channel master at /Users/nexus/dev/sdks/flutters ! Warning: `flutter` on your path resolves to /Users/nexus/dev/sdks/flutter/bin/flutter, which is not inside your current Flutter SDK checkout at /Users/nexus/dev/sdks/flutters. Consider adding /Users/nexus/dev/sdks/flutters/bin to the front of your path. ! Warning: `dart` on your path resolves to /Users/nexus/dev/sdks/flutter/bin/dart, which is not inside your current Flutter SDK checkout at /Users/nexus/dev/sdks/flutters. Consider adding /Users/nexus/dev/sdks/flutters/bin to the front of your path. • Upstream repository https://github.com/flutter/flutter.git • Framework revision 902dac4763 (5 hours ago), 2023-03-02 18:40:14 -0800 • Engine revision 2be41c6d01 • Dart version 3.0.0 (build 3.0.0-288.0.dev) • DevTools version 2.22.2 • If those were intentional, you can disregard the above warnings; however it is recommended to use "git" directly to perform update checks and upgrades. [✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0) • Android SDK at /Users/nexus/Library/Android/sdk • Platform android-33, build-tools 33.0.0 • Java binary at: /Users/nexus/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/221.6008.13.2211.9477386/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 14.2) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 14C18 • CocoaPods version 1.11.3 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2022.1) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301) [✓] Android Studio (version 2022.1) • Android Studio at /Users/nexus/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/221.6008.13.2211.9477386/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301) [✓] IntelliJ IDEA Community Edition (version 2022.3.2) • IntelliJ at /Applications/IntelliJ IDEA CE.app • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart [✓] VS Code (version 1.76.0) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.60.0 [✓] Connected device (4 available) • M2007J20CG (mobile) • adb-5dd3be00-17AYzd._adb-tls-connect._tcp. • android-arm64 • Android 12 (API 31) • Nexus (mobile) • 00008020-001875E83A38002E • ios • iOS 16.3.1 20D67 • macOS (desktop) • macos • darwin-arm64 • macOS 13.2.1 22D68 darwin-arm64 • Chrome (web) • chrome • web-javascript • Google Chrome 110.0.5481.177 [✓] Network resources • All expected network resources are available. ! Doctor found issues in 1 category. ```
wreppun commented 11 months ago

This appears to be a long-standing issue with Chrome:

https://bugs.chromium.org/p/chromium/issues/detail?id=642012

balint-kada commented 2 months ago

Are there any fixes for this issue?