作者 / Very Good Ventures Team
我们 (Very Good Ventures 团队) 与 Google 合作,在今年的 Google I/O 大会上推出了照相亭互动体验 (I/O Photo Booth)。您可以与深受喜爱的 Google 吉祥物合影: Flutter 的 Dash、Android Jetpack、Chrome 的 Dino 和 Firebase 的 Sparky,并用各种贴纸装饰照片,包括派对帽、披萨、时髦眼镜等。当然,您也可以通过社交媒体下载并分享,或者用作您的个人头像!
I/O Photo Booth
https://photobooth.flutter.cn/
Flutter Dash
https://flutter.cn/dash
△ Flutter 的 Dash、Firebase 的 Sparky、Android Jetpack 和 Chrome 的 Dino
我们使用 Flutter web 和 Firebase 构建了 I/O 照相亭。因为 Flutter 现在支持打造 Web 应用,我们认为这将是一个很好的方式,可以让世界各地的与会者在今年的线上 Google I/O 大会上轻松访问这一应用。Flutter web 消除了必须通过应用商店安装应用的障碍,同时用户还可以灵活选择运行应用的设备: 移动设备、桌面设备或平板电脑。因此,只要能使用浏览器,用户便可无需下载直接使用 I/O 照相亭。
Flutter web
https://flutter.cn/web
Firebase
https://firebase.google.com/
尽管 I/O 照相亭旨在提供 Web 体验,但所有代码均采用与平台无关的架构编写而成。当相机插件等原生功能的支持在各个平台就绪后,这套代码即可在所有平台 (桌面、Web 和移动设备) 通用。
使用 Flutter 构建虚拟照相亭
构建 Web 版 Flutter 相机插件
第一个挑战即在 Web 上为 Flutter 构建摄像头插件。最初,我们联系了 Baseflow 团队,因为他们负责维护现有的开源 Flutter 摄像头插件。Baseflow 致力于构建适用于 iOS 和 Android 的一流摄像头插件支持,我们也很乐于与其合作,使用联合插件 (federated plugin) 方法为插件提供 Web 支持。我们尽可能符合官方插件接口,以便我们可以在准备就绪时将其合并回官方插件。
Baseflow
https://www.baseflow.com/
Flutter 摄像头插件
https://github.com/Baseflow/flutter-plugins
联合插件
https://flutter.cn/docs/development/packages-and-plugins/developing-packages#federated-plugins
我们确定了两个对于在 Flutter 中构建 I/O 照相亭相机体验至关重要的 API。
初始化摄像头: 应用首先需要访问您的设备摄像头。对于桌面设备,访问的可能是网络摄像头,而对于移动设备,我们选择了访问前置摄像头。我们还提供了 1080p 的预期分辨率,以根据用户设备类型充分提高拍摄质量。
拍照: 我们使用了内置的 HtmlElementView,该控件使用平台视图将原生 Web 元素渲染为 Flutter widget。在此项目中,我们将 VideoElement 渲染为原生 HTML 元素,这便是您在拍照前会在屏幕上看到的内容。我们还使用了一个 CanvasElement,用于在您点击拍照按钮时从媒体流中捕获图像。
HtmlElementView
https://api.flutter.cn/flutter/widgets/HtmlElementView-class.html
VideoElement
https://api.flutter.cn/flutter/dart-html/VideoElement-class.html
CanvasElement
https://api.flutter.cn/flutter/dart-html/CanvasElement-class.html
Future<CameraImage> takePicture() async {
final videoWidth = videoElement.videoWidth;
final videoHeight = videoElement.videoHeight;
final canvas = html.CanvasElement(
width: videoWidth,
height: videoHeight,
);
canvas.context2D
..translate(videoWidth, 0)
..scale(-1, 1)
..drawImageScaled(videoElement, 0, 0, videoWidth, videoHeight);
final blob = await canvas.toBlob();
retur