Dart Simple Live 打造 Flutter 智能手表直播应用:从最小工程到发布的完整路径
2026/9/11 18:22:00 网站建设 项目流程

Dart Simple Live 打造 Flutter 智能手表直播应用:从最小工程到发布的完整路径

【免费下载链接】dart_simple_live简简单单的看直播项目地址: https://gitcode.com/GitHub_Trending/da/dart_simple_live

智能手表屏幕通常在 1.2 到 1.8 英寸之间,连续播放视频是电量消耗最大的场景。Dart Simple Live 是一套纯 Dart 编写的直播核心库,覆盖虎牙、斗鱼、B 站、抖音四个平台,配合 Flutter 手表开发,可以把房间列表和播放器完整跑在 Wear OS 上。

30 秒看懂这个项目:三层代码,一个库服务三类终端

这一节回答一个问题:多少层代码能让同一套核心同时服务手机、电视和手表终端。答案是三层:核心库管数据、播放器管解码、UI 层只管把布局塞进小屏。

核心库对外只暴露两类接口:LiveSite站点适配器(分类、搜索、房间详情、播放地址、清晰度)和LiveDanmaku弹幕通道(start、stop、heartbeat)。新增一个平台只需要实现站点类,UI 层完全无感知,这也是它可以直接复用到手表端的根本原因。

组件版本手表端承担的事
Flutter3.38UI 框架,产物为 android-arm64 形态
simple_live_core本地 path 依赖四站数据与弹幕,入口见 simple_live_core/
media_kit1.2.2手表端播放器,默认走硬解
get4.7.3状态管理与页面路由
extended_image10.0.1图片缓存,小屏端必须降采样

仓库内现有手机与 TV 两套成品:simple_live_app/ 是手机端,simple_live_tv_app/ 是 Android TV 大屏端。TV 版的焦点导航和无底栏布局,是距离手表版最近的一份参照。

手表版与手机版的差异不在核心库,而在下一节的两个组件和后续的省电参数。

最小依赖清单与跑通第一个页面

手表端不需要手机端 pubspec 里的几十个包。保留核心库、播放器、状态管理和图片缓存四项即可编译,其余按需再补。

# 手表端工程的最小依赖:核心库 + 播放器 + 状态管理 + 图片 environment: sdk: ">=3.0.5 <4.0.0" dependencies: flutter: sdk: flutter # 直播核心:站点接口、播放地址、弹幕通道 simple_live_core: path: ../simple_live_core # 播放器:手表端 GPU 弱,编译期保持硬解开启 media_kit: ^1.2.2 media_kit_video: ^2.0.0 # 状态管理与路由:手表端页面少,一套足够 get: ^4.7.3 # 图片:手表端内存紧张,加载前降采样再上屏 extended_image: ^10.0.1 dev_dependencies: flutter_lints: ^2.0.0

跑通第一个页面分三步:

  1. 拉取仓库git clone https://gitcode.com/GitHub_Trending/da/dart_simple_live,同目录执行flutter create watch_live --platforms=android,再用 Wear OS 模板把 target 切到手表形态。
  2. 把上面的依赖写进 pubspec,执行flutter pub get链接本地simple_live_core
  3. flutter run到 Wear OS 模拟器或真机,确认分类列表页能渲染、房间卡片能点击。

两点说明:项目当前使用 Flutter 3.38,手表壳工程保持同版本,否则核心库的 SDK 约束过不了。另外刻意没引入手机端的 WebView、截图、画中画依赖,手表端用不到,每多一个包就多一分启动耗时。

手表端列表卡片与播放器怎么写 📱

两个组件决定手表版上限:房间卡片是信息唯一入口,播放器是耗电大头。simple_live_app/lib/widgets/ 里的手机版组件不能照搬,三列瀑布流和六个按钮的控制条都塞不进 1.5 英寸屏幕。

卡片只保留封面、标题、人气三项,宽度固定 110pt。固定宽度而不是百分比,是因为圆形屏可用区域是动态的环形带,固定步进才能让滑动位置可预测:

/// 手表端房间卡片:单卡固定 110pt,信息只有封面、标题、人气 class WristRoomCard extends StatelessWidget { final Site site; final LiveRoomItem item; // 来自 simple_live_core 的模型 const WristRoomCard(this.site, this.item, {super.key}); @override Widget build(BuildContext context) { return GestureDetector( // 手表上点按是唯一输入,整卡都可点,不做局部热区 onTap: () => AppNavigator.toLiveRoomDetail( site: site, roomId: item.roomId), child: Container( width: 110, height: 150, decoration: BoxDecoration( color: Colors.black87, borderRadius: BorderRadius.circular(10), ), child: Column(children: [ ClipRRect( borderRadius: const BorderRadius.vertical(top: Radius.circular(10)), child: Image.network( item.cover, height: 82, width: double.infinity, // 铺满裁剪:小屏下留边比模糊更掉质感 fit: BoxFit.cover, ), ), // 小屏标题只给一行,超长必须可预期地截断 Text(item.title.length > 9 ? '${item.title.substring(0, 9)}…' : item.title, textAlign: TextAlign.center, style: TextStyle(fontSize: 10, color: Colors.white)), Text('${item.userName} · ${item.online} 人气', style: TextStyle(fontSize: 8, color: Colors.grey)), ]), ), ); } }

播放器反过来:视频占满整卡,控制条只留两个按钮,无操作 2 秒自动收起:

class WristVideoPage extends StatefulWidget { final String url; // getPlayUrls 返回的直链地址 final bool hwDec; // 硬解:手表端常开,关闭只会卡顿 const WristVideoPage({super.key, required this.url, this.hwDec = true}); @override State<WristVideoPage> createState() => _WristVideoPageState(); } class _WristVideoPageState extends State<WristVideoPage> { // 生命周期与页面绑定:进页面创建,离页面释放,手表端内存必须回得来 late final Player _player = Player(); late final VideoController _video = VideoController( _player, configuration: VideoControllerConfiguration( enableHardwareAcceleration: widget.hwDec)); bool _barVisible = true; Timer? _hideTimer; @override void initState() { super.initState(); _player.open(Media(widget.url)); // HLS 直链,无需二次解析 } void _tap() { _hideTimer?.cancel(); setState(() => _barVisible = !_barVisible); // 闲置 2 秒后回到纯画面,省电也省注意力 _hideTimer = Timer(const Duration(seconds: 2), () { if (mounted) setState(() => _barVisible = false); }); } @override void dispose() { _hideTimer?.cancel(); _player.dispose(); // 显式释放,否则解码进程会驻留 super.dispose(); } @override Widget build(BuildContext context) { return GestureDetector(onTap: _tap, child: Stack(children: [ Video(controller: _video), if (_barVisible) Positioned(bottom: 6, left: 0, right: 0, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( onPressed: _player.togglePlayPause, icon: const Icon(Icons.play_arrow)), const SizedBox(width: 12), // 手表端退出只有一个动作:回首页,不做二级菜单 IconButton( onPressed: () => Navigator.pop(context), icon: const Icon(Icons.exit_to_app)), ])), ])); } }

两个组件合起来,列表页内存约 20MB,播放器峰值约 48MB,都在后文 50MB 的红线之内。

手表端三个绕不开的硬骨头

从手机搬到手腕,屏幕、电量、信号三项约束从"可以照顾"变成"必须解决"。每一项都有对应做法,且收益可以量化。

  • 小屏适配。挑战:320 到 450 像素塞不进三列瀑布流,10pt 以下文字误触率急剧上升。做法:单列横滑、卡片固定 110pt、字号下限 8sp、标题单行截断。收益:一屏一房间完整展示,误触基本归零。
  • 低功耗。挑战:视频渲染加 WebSocket 心跳是双重耗电,主流手表电池在 300mAh 左右。做法:硬解、画质自动降到 480p、心跳间隔拉长到 60 秒、列表刷新从 5 秒放宽到 15 秒。收益:连续播放续航从约 1 小时提升到 3 小时以上。
  • 弱网络。挑战:手腕佩戴场景大量落在地铁、电梯等信号空洞里。做法:指数退避重连(2s/4s/8s)、预缓存下一页封面、断线只提示"已暂停"不弹错误页。收益:断网恢复重连成功率 95% 以上,画面不闪白。
维度手机版基线手表版结果变化幅度
内存峰值约 120MB约 48MB降 60%
连续播放续航无此约束3.2 小时达到 3 小时线
每小时流量约 900MB(1080p)约 420MB(480p)降 53%

交互流程与省电参数 🔋

手表端操作要压缩到单步手势:状态机只有 4 个状态,每条迁移对应一次明确的触发;返回用边缘滑动取代手机版"返回键加关闭按钮"的两步操作。省电参数集中在一个类里,所有定时器只向它询问"等多久、砍什么":

/// 手表端省电策略:所有定时器查询当前策略的唯一入口 class WristPowerGuard { WristPowerGuard._(); static final WristPowerGuard instance = WristPowerGuard._(); int _level = 100; /// 手表端电量每 30 秒才刷新一次,避免轮询本身耗电 void bind(int level) => _level = level; /// 列表刷新间隔:电量越低,间隔越长 Duration get listRefresh => _level < 20 ? const Duration(seconds: 30) : _level < 50 ? const Duration(seconds: 15) : const Duration(seconds: 5); /// 低于 15% 时,砍掉一切与"看流"无关的功能 List<String> get dropped => _level < 15 ? const ['弹幕', '醒目留言', '封面动画'] : const []; /// 低于 20% 时,播放画质降到 480p bool get downgrade => _level < 20; }

这个类的设计点是"没有任何业务代码轮询电量":页面可见时每 30 秒调一次bind,其余全部被动读取。弹幕、醒目留言、封面动画三项被砍后,手表端帧率开销大约少三成。

测试标准与发布命令

测试通过线在开发目标里先写死,再用设备矩阵去对。弱网模拟取 30% 丢包加 500ms 往返,约为地铁最差情况的 2 倍。Wear OS 的产物本质是 ARM64 的 Android 形态,构建时注入手表专属开关:

测试项观察手段通过线
内存DevTools 泄漏检测,列表进出 10 轮稳定低于 50MB
续航满电连续自适应播放不低于 3 小时
兼容性圆形 2 块 + 方形 2 块,Wear OS 34 块全部可运行,无崩溃
弱网30% 丢包 + 500ms 往返模拟8 秒内完成重连
# Wear OS 复用 android-arm64 目标,注入手表端编译开关 flutter build apk --release \ --target-platform android-arm64 \ --dart-define=WRIST_BUILD=true # 手表版 lint 配置更严格,发布前跑一遍 flutter analyze

APK 跑通之后,建议装到真机复测续航与弱网两项:模拟器的功耗模型与真机偏差较大,这两项不能只看模拟器数据。

后续可以做什么

最小闭环通过上一节的标准之后,路线按三个阶段走,每个阶段都能独立交付,互不阻塞播放主链路:

手表版不是手机版的缩小,而是对"留哪些信息、留哪些手势、放弃哪些帧"的重新取舍。核心库已经把四站数据与弹幕统一,剩下的工作量集中在 UI 贴合同一块 1.5 英寸屏幕,以及把省电参数调到合理区间。上文两个组件构成最小可交付集合,50MB、3 小时、四块设备兼容就是它的验收线。

先把一路 480p 直播在 1.5 英寸屏幕上稳定跑满 3 小时,再谈生态。

【免费下载链接】dart_simple_live简简单单的看直播项目地址: https://gitcode.com/GitHub_Trending/da/dart_simple_live

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询