Flutter 中的视频录制和回放
原文链接:Tutorial: Video Recording and Replay with Flutter - 原文作者 Stefan
本文采用意译的方式
在 Flutter 中,视频录制和预览视频很容易做到。这个教程为我们展示怎么使用 camera 和 video_player 包来创建录制和播放回放视频。
介绍
在这个教程中,我们将使用 camera 和 video_player 包来创建带有视频录制和视频回放功能的Flutter 应用。记录下来的视频可以被使用 - 比如,上传到远程服务器。
首先,我们将创建一个页面,用来展示摄像头的输入和录制一个视频。
在视频录制之后,我们将打开另外一个页面来回放视频,这允许用户观看或者关闭视频。
本教程的相关代码放在
GitHub的 github.com/bettercodin…
前置条件
首先,我们创建一个新的 Flutter 项目。
接下来,我们将清空 pubspec.yaml 文件,然后添加必要的依赖。pubspec.yaml 文件内容应该如下:
name: flutter_video
description: Flutter Video Flow
publish_to: 'none' # 如果我们希望发布到 pub.dev ,则移除该行
version: 1.0.0+1
enviroment:
sdk: ">=2.12.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
camera: ^0.9.4+3
video_player: ^2.2.6
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^1.0.0
flutter
uses-material-design: true
正如你所看到,我们添加了包 camera 和 video_player。
iOS 专属前置条件
为了让包能够在 iOS 中工作,我们需要在文件 ios/Runner/Info.plist 文件中,插入下面的内容:
<key>NSCameraUsageDescription</key>
<string>Needed for recording videos.</string> <key>NSMicrophoneUsageDescription</key>
<string>Needed for recording videos.</string> <key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
确保在 dict 代码块中(在 </dict>) 之前添加这些代码。
Android 专属前置条件
我们���需要更改 Android 专属文件。为了让插件在 Android 上工作。我们需要将 minSdkVersion 升到 21。我们在 android/app/build.gradle 文件中完成。如果我们只是搜索 minSdkVersion 关键字来替换,请区分 android/build.gradle 和 android/app/build.gradle。
在更改 pubspec.yaml 文件之后,记得运行 pub get 命令行。
在我们的 main.dart 文件中,我们将用下面的代码来替换旧的代码。
import 'package:flutter/material.dart';
import 'package:flutter_video/camera_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: CameraPage(),
);
}
}
需要注意的是,现在 CameraPage 还不存在,但是我们接下来会创建它。
使用 camera 插件展示摄像头输入
接下来,我们将实现 CameraPage 页面来展示摄像头引入和我们怎么使用来录制视频。为了实现这个,我们创建一个名为 camera_page.dart 的文件,然后创建一个 StatefulWidget。如下:
import 'package:flutter/material.dart';
class CameraPage extends StatefulWidget {
const CameraPage({Key? key}) : super(key: key);
@override
_CameraPageState createState() => _CameraPageState();
}
class _CameraPageState extends State<CameraPage> {
@override
Widget build(BuildContext context) {
return Container();
}
}
因为在我们访问相机之前需要进行一些初始化,我们需要在初始化过程中显示一些加载状态。
为了实现这个,我们在 _CameraPageState 中创建一个变量 _isLoading,并在 build 函数中检查该状态。
class _CameraPageState extends State<CameraPage> {
bool _isLoading = true;
late CameraController _cameraController;
@override
void dispose() {
_cameraController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if(_isLoading) {
return Container(
color: Colors.white,
child: const Center(
child: CircularProgressIndicator(),
),
);
} else {
return CameraPreview(_cameraController);
}
}
}
我们也添加了类型为 CameraController 变量。然后在加载完成之后,展示 CameraPreview。除非 _cameraController 已初始化,否则这将不起作用,还是会显示加载这步。
初始化 CameraController
首先,我们创建一个名为 _initCamera 的函数。
_initCamera() async {
final cameras = await availableCameras();
final front = cameras.firstWhere((camera) => camera.lensDirection == CameraLensDirection.front);
_cameraController = CameraController(front, ResolutionPreset.max);
await _cameraController.initialize();
setState(() => _isloading = false);
}
- 第 2 行代码:从
camera插件中请求所有可以获取到的摄像头 - 第 3 行代码:选择前置摄像头
- 第 4 行代码:创建一个
CameraController实例。我们使用前置摄像头的CameraDescription然后设置视频的分辨率到最大 - 第 5 行代码:设置参数初始化控制器
- 第 6 行代码:在初始化之后,设置
_isLoading的值为false
最后,我们在 initState() 中调用 _initCamera() 函数。
@override
void initState() {
super.initState();
_initCamera();
}
至此,当我们开启应用,我们应该可以看到前置摄像头的预览。
录制视频
为了开启录制视频,我添加一个录制按钮。这个按钮应该在视频的上层,所以,我们将 CameraPreview 和这个按钮在 Stack 挂件中。
简单替换 else 分支的内容为如下代码:
return Center(
child: Stack(
alignment: Alignment.bottomCenter,
children: [
CameraPreview(_cameraController),
Padding(
padding: const EdgeInsets.all(25),
child: FloatingActionButton(
backgroundColor: Colors.red,
child: Icon(_isRecording ? Icons.stop : Icons.circle),
onPressed: () => _recordVideo(),
),
),
],
),
);
现在,我们需要创建一个新的状态变量 _isRecording 并赋值为 false。
bool _isRecording = false;
最后,我们创建一个 _recordVideo() 函数来处理按钮的点击。这个函数如下:
_recordVideo() async {
if (_isRecording) {
final file = await _cameraController.stopVideoRecording();
setState(() => isRecording = false);
final route = MaterialPageRoute(
fullscreenDialog: true,
builder: (_) => VideoPage(filePath: file.path),
);
Navigator.push(context, route);
} else {
await _cameraController.prepareForVideoRecording();
await _cameraController.startVideoRecording();
setState(() => _isRecording = true);
}
}
- 第 2 行代码:我们需要处理开始和停止记录的行为,因此我们需要检查视频当前的记录的状态。
- 第 3 行代码:如果视频正在录制,我们将暂停它。
stopVideoRecording()函数返回一个包含录制视频的文件。 - 第 4 行代码:我们更新
_isRecording的状态为false。 - 第 5-9 行代码:最后,获得了录制的视频文件,我们将打开
VideoPage,然后让用户检查录制的视频。(备注:VideoPage目前还不存在,我们稍后会添加) - 第 11 行代码:如果视频还没有录制,我们将告诉
CameraController准备录制。 - 第 12 行代码:一旦准备完毕,我们将开启记录
- 第 13 行代码:最后,我们设置
_isRecording的状态为true
回放录制的视频
为了回放录制视频,创建一个新的文件,名为 video_page.dart,然后添加一个 StatefulWidget,正如我们在 CameraPage 所做的那样。
VideoPage 需要接受录制视频文件路径。所以,我们添加一个新的属性 filePage:
class VideoPage extends StatefulWidget {
final String filePath;
const VideoPage({ Key? key, required this.filePath }): super(key: key);
@override
_VideoPageState createState() => _VideoPageState();
}
在 _VideoPageState 类中,我们通过创建一个 VideoController 开始,和一个函数来初始化它。
class _VideoPageState extends State<VideoPage> {
late VideoPlayerController _videoPlayerController;
@override
void dispose() {
_videoPlayerController.dispose();
super.dispose();
}
Future _initVideoPlayer() async {
_videoPlayerController = VideoPlayerController.file(File(widget.filePath));
await _videoPlayerController.initialize();
await _videoPlayerController.setLooping(true);
await _videoPlayerController.play();
}
@override
Widget build(BuilderContext context) {
return Container();
}
}
- 第 2 行代码:我们添加了一个
_videoPlayerController变量 - 第 4-8 行代码:一旦
State已经被销毁了,别忘记销毁VideoPlayerController - 第 11 行代码:从我们传递给挂件的文件路径,来创建一个新的
VideoPlayerController - 第 12 行代码:在我们可以调用之前,初始化
VideoPlayerController - 第 13 行代码:为了能够重复播放视频,我们开启循环
- 第 14 行代码:最后,开启视频
最后一步,我们来设计自己的 UI。我们应该有一个 AppBar 来进入或者销毁视频。嗯,然后,包含一个 VideoPlayer 挂件来回放视频。这个挂件中的 build 函数可能像这样:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Preview'),
elevation: 0,
backgroundColor: Colors.black26,
actions: [
IconButton(
icon: const Icon(Icons.check),
onPressed: () {
print('do something with the file');
},
),
]
),
extendBodyBehindAppBar: true,
body: FutureBuilder(
future: _initVideoPlayer(),
builder: (context, state) {
if(state.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else {
return VideoPlayer(_videoPlayerController);
}
}
),
);
}
- 第 3 行代码:我们使用
Scafflod来创建我们的页面 - 第 4-16 行代码:添加一个透明的
AppBar,并包含额外的OK操作。在这里,我们可以实现额外的功能,比如从服务端获取视频。 - 第 17 行代码:这个参数是设置,是为了拉伸视频到
AppBar后面 - 第 18-27 行代码:我们使用
FutureBuilder,当正在初始化,则显示CircularProgressIndicator。当初始化完成,则显示VideoPlayer, 并播放回放视频
嗯,至此,我们已经全部完成。
我们现在可以启动应用,来录制并回放视频。

总结
在这个教程中,在 Flutter 中,我们使用 camera 和 video_player 包来创建一个录制和回放视频的流程。
转载自:https://juejin.cn/post/7398736473090490405