您好我正在开发一款带有视频录制功能的应用程序。我遇到过两个插件,能够捕获/保存图像作为文件,有颤动:
Camera v0.0.2和Image_picker v0.2.1,它们效果很好并且是正式的插件。
从本质上讲,我想构建一个具有抖动视频录制功能的迷你相机插件,可以无缝地与iOS和Android配合使用。
欢迎任何升级这些插件的建议,方向和方法。
答案 0 :(得分:8)
我们的团队现已在官方 camera plugin v0.2.0
上启用视频录制功能通过向存储库提交pull request。
此插件的示例应用使用其他插件path_provider
和video_player显示录制视频的样本。
希望这有助于其他扑克开发商的欢呼!
答案 1 :(得分:1)
Flutter提供了一个程序包“ camera”和“ video_player”。摄像头用于访问电话的摄像头,而video_plater则用于视频记录。您可以使用相机包装并录制视频。您可以在下面找到代码:
首先,您必须像这样更新pubspec文件:
dependencies:
camera: ^0.2.9+1
fluttertoast:
path_provider:
video_player:
fluttertoast是相机的吐司,而path_provider提供了保存视频的路径。然后,您必须将这些包导入到dart文件中并编写自己的实现。您可以在下面找到示例代码。它会打开手机中可用的摄像机列表,包括外部摄像机,以便您可以选择其中任何一个来录制视频。
import 'dart:async';
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:video_player/video_player.dart';
import 'package:fluttertoast/fluttertoast.dart';
class VideoRecorderExample extends StatefulWidget {
@override
_VideoRecorderExampleState createState() {
return _VideoRecorderExampleState();
}
}
class _VideoRecorderExampleState extends State<VideoRecorderExample> {
CameraController controller;
String videoPath;
List<CameraDescription> cameras;
int selectedCameraIdx;
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
@override
void initState() {
super.initState();
// Get the listonNewCameraSelected of available cameras.
// Then set the first camera as selected.
availableCameras()
.then((availableCameras) {
cameras = availableCameras;
if (cameras.length > 0) {
setState(() {
selectedCameraIdx = 0;
});
_onCameraSwitched(cameras[selectedCameraIdx]).then((void v) {});
}
})
.catchError((err) {
print('Error: $err.code\nError Message: $err.message');
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: const Text('Camera example'),
),
body: Column(
children: <Widget>[
Expanded(
child: Container(
child: Padding(
padding: const EdgeInsets.all(1.0),
child: Center(
child: _cameraPreviewWidget(),
),
),
decoration: BoxDecoration(
color: Colors.black,
border: Border.all(
color: controller != null && controller.value.isRecordingVideo
? Colors.redAccent
: Colors.grey,
width: 3.0,
),
),
),
),
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
_cameraTogglesRowWidget(),
_captureControlRowWidget(),
Expanded(
child: SizedBox(),
),
],
),
),
],
),
);
}
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;
default:
return Icons.device_unknown;
}
}
// Display 'Loading' text when the camera is still loading.
Widget _cameraPreviewWidget() {
if (controller == null || !controller.value.isInitialized) {
return const Text(
'Loading',
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
fontWeight: FontWeight.w900,
),
);
}
return AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: CameraPreview(controller),
);
}
/// Display a row of toggle to select the camera (or a message if no camera is available).
Widget _cameraTogglesRowWidget() {
if (cameras == null) {
return Row();
}
CameraDescription selectedCamera = cameras[selectedCameraIdx];
CameraLensDirection lensDirection = selectedCamera.lensDirection;
return Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: FlatButton.icon(
onPressed: _onSwitchCamera,
icon: Icon(
_getCameraLensIcon(lensDirection)
),
label: Text("${lensDirection.toString()
.substring(lensDirection.toString().indexOf('.')+1)}")
),
),
);
}
/// Display the control bar with buttons to record videos.
Widget _captureControlRowWidget() {
return Expanded(
child: Align(
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
IconButton(
icon: const Icon(Icons.videocam),
color: Colors.blue,
onPressed: controller != null &&
controller.value.isInitialized &&
!controller.value.isRecordingVideo
? _onRecordButtonPressed
: null,
),
IconButton(
icon: const Icon(Icons.stop),
color: Colors.red,
onPressed: controller != null &&
controller.value.isInitialized &&
controller.value.isRecordingVideo
? _onStopButtonPressed
: null,
)
],
),
),
);
}
String timestamp() => DateTime.now().millisecondsSinceEpoch.toString();
Future<void> _onCameraSwitched(CameraDescription cameraDescription) async {
if (controller != null) {
await controller.dispose();
}
controller = CameraController(cameraDescription, ResolutionPreset.high);
// If the controller is updated then update the UI.
controller.addListener(() {
if (mounted) {
setState(() {});
}
if (controller.value.hasError) {
Fluttertoast.showToast(
msg: 'Camera error ${controller.value.errorDescription}',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1,
backgroundColor: Colors.red,
textColor: Colors.white
);
}
});
try {
await controller.initialize();
} on CameraException catch (e) {
_showCameraException(e);
}
if (mounted) {
setState(() {});
}
}
void _onSwitchCamera() {
selectedCameraIdx = selectedCameraIdx < cameras.length - 1
? selectedCameraIdx + 1
: 0;
CameraDescription selectedCamera = cameras[selectedCameraIdx];
_onCameraSwitched(selectedCamera);
setState(() {
selectedCameraIdx = selectedCameraIdx;
});
}
void _onRecordButtonPressed() {
_startVideoRecording().then((String filePath) {
if (filePath != null) {
Fluttertoast.showToast(
msg: 'Recording video started',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1,
backgroundColor: Colors.grey,
textColor: Colors.white
);
}
});
}
void _onStopButtonPressed() {
_stopVideoRecording().then((_) {
if (mounted) setState(() {});
Fluttertoast.showToast(
msg: 'Video recorded to $videoPath',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1,
backgroundColor: Colors.grey,
textColor: Colors.white
);
});
}
Future<String> _startVideoRecording() async {
if (!controller.value.isInitialized) {
Fluttertoast.showToast(
msg: 'Please wait',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1,
backgroundColor: Colors.grey,
textColor: Colors.white
);
return null;
}
// Do nothing if a recording is on progress
if (controller.value.isRecordingVideo) {
return null;
}
final Directory appDirectory = await getApplicationDocumentsDirectory();
final String videoDirectory = '${appDirectory.path}/Videos';
await Directory(videoDirectory).create(recursive: true);
final String currentTime = DateTime.now().millisecondsSinceEpoch.toString();
final String filePath = '$videoDirectory/${currentTime}.mp4';
try {
await controller.startVideoRecording(filePath);
videoPath = filePath;
} on CameraException catch (e) {
_showCameraException(e);
return null;
}
return filePath;
}
Future<void> _stopVideoRecording() async {
if (!controller.value.isRecordingVideo) {
return null;
}
try {
await controller.stopVideoRecording();
} on CameraException catch (e) {
_showCameraException(e);
return null;
}
}
void _showCameraException(CameraException e) {
String errorText = 'Error: ${e.code}\nError Message: ${e.description}';
print(errorText);
Fluttertoast.showToast(
msg: 'Error: ${e.code}\n${e.description}',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1,
backgroundColor: Colors.red,
textColor: Colors.white
);
}
}
class VideoRecorderApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: VideoRecorderExample(),
);
}
}
Future<void> main() async {
runApp(VideoRecorderApp());
}