把 ChatGPT 加入 Flutter 开发,会有怎样的体验?
前言
ChatGPT 最近一直都处于技术圈的讨论焦点。它除了可作为普通用户的日常 AI 助手,还可以帮助开发者加速开发进度。声网社区的一位开发者"小猿"就基于 ChatGPT 做了一场实验。仅 40 分钟就实现了一个互动直播 Demo。他是怎么做的呢?他将整个过程记录了下来。
(文章转载自开发者的个人博客,以下为正文)
“遇事不决,AI 力学” ~ ChatGPT 可以说是 2023 开年最热门的话题, 它不仅在极短时间内风靡了整个技术圈,更是病毒式地席卷了圈外的各个行业,并对各大企业都起到了实质性影响:
- 谷歌紧急推出 “Bard” 对抗 ChatGPT
- 微软发布新 Bing 集成 ChatGPT
- 复旦发布首个类 ChatGPT 模型 MOSS
- 国内阿里、百度、昆仑万维、网易、京东都开始新一轮 AI 军备
那 ChatGPT 究竟有什么魔力能让“群雄折腰”?这和 ChatGPT 的实现有很大关系:
与以往的统计模型不行,ChatGPT 不是那种「一切都从语料统计里学习」的 AI,相反 ChatGPT 具备有临场学习的能力,业内称之为 in-context learning ,这也是为什么 ChatGPT 可以在上下文中学习的原因。
ChatGPT 属于 AI 领域在商用技术上的重大突破,当然,本篇我们不是要讨论ChatGPT 的实现逻辑,而是 ChatGPT 会怎么样加速我们的开发?
PS:在此之前有人通过指示在 ChatGPT 界面下实现了一个虚拟机,虽然这是一个极端的例子,但是可以很直观地感受到:「ChatGPT 对我们开发的影响是肉眼可见」。
那 ChatGPT 在实际工作中是如何影响我们的开发?为了更直观,下面我们用一个开发场景来模拟这个流程。
基于 ChatGPT 开发
01 开发之前
假设我们现在有一个开发「直播」的需求,那我们可以直接求助 ChatGPT:
「开发一个直播app,是使用第三方SDK好还是自己从0开发好」?
如下图所示,从回答上可以看到,AI 建议我们根据团队实际情况去选择,而在知晓「我的团队只有 5 个人」的情况后,它建议我选择采用 “接入第三方 SDK” 的方式更合理。
那么选择 “接入第三方 SDK” ,接下来的问题就是:「选择做直播,在中国推荐使用哪些厂家的 SDK」?
如下图所示,这个问题 ChatGPT 同样提供了多个选项,从选项里看*声网、腾讯云和阿里云*好像都符合我们要求,而在接着的「优势问题」对比上看,这三个选项都“不相伯仲”,那我们就在再细化问题。
假设我们希望直播可以有更多“互动能力”,那么把问题修改为 「做互动直播,更推荐使用哪一个厂家的 SDK」 ,截图如下图所示,这次我们得到了更明确的答复,看来声网的 SDK 会更贴合我们的需求。
为了更放心这个选择,我们通过 「声网 SDK 的优势」 和 「什么产品使用了声网SDK」 两个问题进行提问,如下图所示,从回复上看声网作为一个全球化的厂家,在音视频领域还是值得相信。同时,还有包括小米、陌陌等产品都使用了声网的服务。那么就按照 AI 的建议选择声网 SDK 吧。
有没有发现,在获取资料的检索方式上,ChatGPT 确实比搜索引擎更直观且高效。
那么敲定完 SDK ,接下来我们需要选择应用的开发框架,我们把需求限定在 Android 和 iOS,更好是能兼容 Web,覆盖整个移动端 ,因为团队人数不多,所以我们希望采用跨平台开发来节约成本,那么问题就是:
「移动端哪个跨平台框架更适合做直播」?
如下图所示,得到的答案有 React Native 和 Flutter ,而恰好在 Flutter 回复里可以看到声网 SDK 的存在,所以我们可以敲定 App 开发框架就选 Flutter 了。
最后,在开发之前,我们还需要继续提问 「如何获取声网 SDK 」 和 「使用声网 SDK 需要做什么」,这样我们就可以在开始开发之前提前准备好需要的东西。
关于注册获取 App ID 等步骤这里就省略了,毕竟目前这部分 ChatGPT 也无能为力。
02 开始开发
那么到这里我们就假定大家已经准备好了开发环境,接下来可以直接进行开发。
我们还是继续面向 ChatGPT 开发,首先我们的提问是:「用声网的 Flutter SDK agora_rtc_engine 6.1.0 写一个视频通话页面,给我 dart 代码」 ,结果如下 GIF 所示,可以看到 ChatGPT 开始了疯狂的输出:
为什么关键词是「视频通话」?因为它比直播场景更精准简单,生成的代码更靠谱(经过提问测试),而基于视频通话部分,后面我们可以快速拓展为互动直播场景;而指定版本是为了避免 AI 使用旧版本 API。
从上门的代码生成可以看到,ChatGPT 生产的代码是自带中文注释,更贴心的是,如下图所示,在生成的代码末尾还给你解释了这段代码的实现逻辑,就像一个“知心大姐姐”。
从这里也可以感觉到 ,ChatGPT 不是一个单纯的完全只会基于语料答复整合的 AI 。
当然,直接复制生成的代码后会发现这段代码会报错,这和 ChatGPT 目前的模型数据版本有一定关系,所以针对生成的代码我们需要做一定手动调整,比如:
- 采用 createAgoraRtcEngine 和 initialize 创建和初始化 RtcEngine
- 将 setEventHandler 修改为最新的 registerEventHandler
- 将 AgoraRenderWidget 修改为 AgoraVideoView
最后修改代码如下,其中 80% 以上的逻辑都来自 ChatGPT 的自动生成,虽然没办法做到“直出”,这无疑大大提高了开发的生产力。
class VideoCallPage extends StatefulWidget { final String channelName; const VideoCallPage({Key? key, required this.channelName}) : super(key: key); @override _VideoCallPageState createState() => _VideoCallPageState(); } class _VideoCallPageState extends State<VideoCallPage> { late RtcEngine _engine; bool _localUserJoined = false; bool _remoteUserJoined = false; int? rUid; @override void initState() { super.initState(); initAgora(); } @override void dispose() { _engine.leaveChannel(); super.dispose(); } Future<void> initAgora() async { await [Permission.microphone, Permission.camera].request(); _engine = createAgoraRtcEngine(); await _engine.initialize(RtcEngineContext( appId: config.appId, channelProfile: ChannelProfileType.channelProfileLiveBroadcasting, )); _engine.registerEventHandler(RtcEngineEventHandler( onJoinChannelSuccess: (RtcConnection connection, int elapsed) { setState(() { _localUserJoined = true; }); }, onUserJoined: (connection, remoteUid, elapsed) { setState(() { _remoteUserJoined = true; rUid = remoteUid; }); }, onUserOffline: (RtcConnection connection, int remoteUid, UserOfflineReasonType reason) { setState(() { _remoteUserJoined = false; rUid = null; }); }, )); await _engine.enableVideo(); await _engine.startPreview(); await _engine.joinChannel( token: config.token, channelId: widget.channelName, uid: config.uid, options: const ChannelMediaOptions( channelProfile: ChannelProfileType.channelProfileLiveBroadcasting, clientRoleType: ClientRoleType.clientRoleBroadcaster, ), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("VideoCallPage"),), body: Center( child: Stack( children: [ _remoteUserJoined ? _remoteVideoView(rUid) : _placeholderView(), _localUserJoined ? _localVideoView() : _placeholderView(), ], ), ), ); } Widget _placeholderView() { return Container( color: Colors.black, ); } Widget _remoteVideoView(id) { return AgoraVideoView( controller: VideoViewController.remote( rtcEngine: _engine, canvas: VideoCanvas(uid: id), connection: RtcConnection(channelId: widget.channelName), ), ); } Widget _localVideoView() { return Positioned( right: 16, bottom: 16, width: 100, height: 160, child: AgoraVideoView( controller: VideoViewController( rtcEngine: _engine, canvas: const VideoCanvas(uid: 0), ), ), ); } }
接下来,如下图所示,在将项目运行到手机和 PC 端之后,可以看到我们就完成了最简单的直播视频场景,而基于我们打算做直播的念头仅仅过去了 40 分钟,这其中还包含了注册声网账号和申请 App ID 的过程,我们通过简单的提问、复制、粘贴、修改,就完成了一个直播需求的 demo。
红色方块是后期加上的打码~
那么到这里,虽然目前为止 demo 项目还不是互动直播,但是基于这个 demo 实现互动直播场景不会太难,因为你已经跑通了整个 SDK 的链路流程了。
03 进阶开发
那假设我们需要继续往互动直播的方向开发,那么我们肯定会遇到“互动”这个需求,比如「收到用户发送的一段内容后画面弹出一个动画」 这样的需求。
那么首先我们要知道声网 SDK 如何监听用户发送的内容,所以接下来我们继续提问:「如何使用声网的 agora_rtc_engine 6.1.0 监听别人发送的文本消息」 ?
这里为什么还强制写 agora_rtc_engine 6.1.0 ?因为如果不写,默认可能会输出 4.x 版本的老 API。
尽管得到的答案并不是 Dart 代码而是 OC ,但是关键词 registerEventHandler 和 Message 我们捕抓到了,简单对比一下,就是 Flutter SDK 里的 registerEventHandler 对象,可以发现平替的接口就是 onStreamMessage 回调。
那么接着就是弹出什么内容,因为我们没有素材,假设还没有设计师,那不如就让 ChatGPT 帮我们画一只兔子吧,不过测试结果并不好,如下图所示,从输出结果上看 ,这并不是我们想要的。
这里是我自己加的粉色,不然都是白色会糊成一坨,不得不说 ChatGPT 在绘制能力上“很抽象”。
所以 ChatGPT 有时候也不是很智能,可能目前在绘画理解上它还没那么成熟, 但是没问题, ChatGPT 是可以通过上下文学习“调教”的,比如我们觉得兔子的耳朵形状太离谱,那么我们可以让 ChatGPT 给我们调整。
如下所示,虽然调整之后依然不对,但是比起一开始是不是好很多了?
这就是 ChatGPT 在每次会话上下文里学习的表现。
然后我们在兔子耳朵的基础上再让 ChatGPT 补全兔子头,虽然最终的效果依然不理想,但是比起一开始已经进步了很多。
同时我们还让 ChatGPT 给我们画了一个“星星”,然后结合这两个 Canvas 绘制的素材,我们在代码里设置接收到 "兔子" 和 星星 文本的时候,就弹出一个放大动画效果。
最终运行后效果如下 GIF 所示,看起来很简陋,但是要知道,我们只是经过了简单的复制/粘贴就完成了这样的效果,这难道不是开发效率的极大提高?
源码在后面。
来自 ChatGPT 的兔子头代码:
class StarPaint extends StatelessWidget { @override Widget build(BuildContext context) { return CustomPaint( painter: HeartPainter(), size: Size(50, 50), ); } } class StarPaint extends CustomPainter { @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.red ..style = PaintingStyle.fill; final path = Path(); final halfWidth = size.width / 2; final halfHeight = size.height / 2; final radius = halfWidth; path.moveTo(halfWidth, halfHeight + radius); path.arcToPoint( Offset(halfWidth + radius, halfHeight), radius: Radius.circular(radius), clockwise: true, ); path.arcToPoint( Offset(halfWidth, halfHeight - radius), radius: Radius.circular(radius), clockwise: true, ); path.arcToPoint( Offset(halfWidth - radius, halfHeight), radius: Radius.circular(radius), clockwise: true, ); path.arcToPoint( Offset(halfWidth, halfHeight + radius), radius: Radius.circular(radius), clockwise: true, ); canvas.drawPath(path, paint); } @override bool shouldRepaint(covariant StarPaint oldDelegate) { return false; } }
来自 ChatGPT 的星星代码:
class StarPaint extends StatelessWidget { @override Widget build(BuildContext context) { return CustomPaint( painter: HeartPainter(), size: Size(50, 50), ); } } class StarPaint extends CustomPainter { @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.red ..style = PaintingStyle.fill; final path = Path(); final halfWidth = size.width / 2; final halfHeight = size.height / 2; final radius = halfWidth; path.moveTo(halfWidth, halfHeight + radius); path.arcToPoint( Offset(halfWidth + radius, halfHeight), radius: Radius.circular(radius), clockwise: true, ); path.arcToPoint( Offset(halfWidth, halfHeight - radius), radius: Radius.circular(radius), clockwise: true, ); path.arcToPoint( Offset(halfWidth - radius, halfHeight), radius: Radius.circular(radius), clockwise: true, ); path.arcToPoint( Offset(halfWidth, halfHeight + radius), radius: Radius.circular(radius), clockwise: true, ); canvas.drawPath(path, paint); } @override bool shouldRepaint(covariant StarPaint oldDelegate) { return false; } }
自己补充的监听文本、发送文本和动画效果代码:
onStreamMessage: (RtcConnection connection, int remoteUid, int streamId, Uint8List data, int length, int sentTs) { var message = utf8.decode(data); if (message == "兔子") { showDialog( context: context, builder: (context) { return AnimaWidget(Rabbit()); }); } else if (message == "星星") { showDialog( context: context, builder: (context) { return Center( child: AnimaWidget(StarPaint()), ); }); } Future.delayed(Duration(seconds: 3), () { Navigator.pop(context); }); }, Future<void> _onPressSend() async { try { final streamId = await _engine.createDataStream( const DataStreamConfig(syncWithAudio: false, ordered: false)); var txt = (Random().nextInt(10) % 2 == 0) ? "星星" : "兔子"; final data = Uint8List.fromList(utf8.encode(txt)); await _engine.sendStreamMessage( streamId: streamId, data: data, length: data.length); } catch (e) { print(e); } } class AnimaWidget extends StatefulWidget { final Widget child; const AnimaWidget(this.child); @override State<AnimaWidget> createState() => _AnimaWidgetState(); } class _AnimaWidgetState extends State<AnimaWidget> { double animaScale = 1; @override void initState() { super.initState(); Future.delayed(Duration(seconds: 1), () { animaScale = 5; setState(() {}); }); } @override Widget build(BuildContext context) { return AnimatedScale( scale: animaScale, duration: Duration(seconds: 1), curve: Curves.bounceIn, child: Container(child: widget.child)); } }
相信到这里大家应该可以感受到 ChatGPT 提高开发效率的魅力,甚至你还可以把 ChatGPT 集成到你的直播场景里,通过 Flutter 上的 chatgpt_api_client 插件,你可以在 App 里直接向 ChatGPT 提问,比如通过 OpenAI 的 API 实现一个可以互动的虚拟主播。
我怎么知道这个插件?肯定也是问 ChatGPT 的啊~
04 最后
到这里,相信大家应该能感受到,在使用 ChatGPT 之后,**整个开发效率能够得到很大的提升,特别是内容检索的高效和准确上比搜索引擎更加靠谱,**另外也能帮我们完成一些“体力活”形式的代码。
当然我们也看到了目前 ChatGPT 并不能完全替代人工,因为它在很多方面生成的内容并不完美,特别是很多代码还是需要我们人工调整,但是这并不影响 ChatGPT 的价值。
最后引用我曾经看到过的关于 ChatGPT 的一些评价:
「当你抱怨 ChatGPT鬼话连篇满嘴跑火车的时候,这可能有点像你看到一只猴子在沙滩上用石头写下1+1=3。它确实算错了,但这不是重点。它有一天会算对的。」
我相信 AI 并不是直接取代人类的方式,因为它对社会的挤压不是从水平上碾压,而是劣币驱逐良币,比如有位大佬就说过:「乙方最讨厌甲方什么都不懂还bb,但乙方的议价权恰恰来源于甲方什么都不懂还 bb」 ,而现在 ChatGPT 在慢慢消磨掉整个议价权。
总的来说「ChatGPT 只是一个产品,它不代表的整个技术的“上限” ,它代表的是技术已经到达商用的临界点」。
现在,它在慢慢成为开发圈子里的习惯,和曾经的 Copilot 一样,而同时它在其他领域如文字编排等的能力,甚至远超它在开发领域的价值。
欢迎开发者们也尝试体验声网 SDK,实现实时音视频互动场景。现注册声网账号下载 SDK,可获得每月免费 10000 分钟使用额度。如在开发过程中遇到疑问,可在声网开发者社区与官方工程师交流。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
拥抱下一代前端工具链-Vue老项目迁移Vite探索
作者:京东物流 邓道远 背景描述 随着项目的不断维护,代码越来越多,项目越来越大。调试代码的过程就变得极其痛苦,等待项目启动的时间也越来越长,尤其是需要处理紧急问题的时候,切换项目启动,等待的时间就会显得尤为的漫长。无法忍受这种开发效率的我,决定将老项目迁移至vite。 距离Vite工具发布到现在已经有了一些日子了,工具链与生态已经趋于稳定,最新版本已经更新到了3.0,既然念头已起,心动不如行动。 1、什么是Vite vite 发音为/vit/ 法语中就是快的意思,“人”如其名,就是快 一个开发服务器,它基于原生ES模块,提供了丰富的内建功能,如速度快到惊人的模块热更新(HRM) 一套构建指令,它使用rollop来打包你的代码,并且是预配置的,可输出用于生产环境的高度优化过的静态资源。 2、为什么快 众所周知,当冷启动服务器时,基于打包器的启动必须优先抓取并构建你的整个应用,然后才能提供服务,这一抓取构建的过程随着文件越来越多,时间也会越来越长。 而Vite却通过将应用中的木块区分为依赖和源码两类,从而优化了大量的服务器启动时间。 依赖大多为在开发时不会变动的纯 JavaScript。...
- 下一篇
查询性能较 Trino/Presto 3-10 倍提升!Apache Doris 极速数据湖分析深度解读
从上世纪 90 年代初 Bill Inmon 在《building the Data Warehouse》一书中正式提出数据仓库这一概念,至今已有超过三十年的时间。在最初的概念里,数据仓库被定义为「一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合,用于支持管理决策」,而数据湖最初是为了解决数仓无法存储海量且异构的数据而构建的集中式存储系统。 时代的发展与用户数据应用诉求的演进,催生了数据架构的不断革新,也衍生了更复杂的技术形态。可以清晰看到现代数据架构从计算到存储都在向着融合统一的方向发展,新的数据湖范式被提出,这也是 Lakehouse 诞生的背景。作为一种全新的、开放式的数据管理架构,Lakehouse 提供了更强的数据分析能力与更好的数据治理能力,也保留了数据湖的灵活性与开放式存储,为用户带来更多价值: 从存储的角度:统一数据集成,避免冗余存储以及跨系统间 ETL 带来的繁重工程和失败风险; 从治理的角度:支持 ACID、Schema Evolution 与 Snapshot,数据与元数据皆可治理; 从应用的角度:多引擎访问支持、可插拔,通过统一接口进行数据访问,同时适...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- MySQL8.0.19开启GTID主从同步CentOS8
- Mario游戏-低调大师作品
- CentOS7安装Docker,走上虚拟化容器引擎之路