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