ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Flutter×HarmonyOS视频控制栏实战:架构、通信与状态同步

Flutter×HarmonyOS视频控制栏实战:架构、通信与状态同步 做跨端播放器这段时间我最大的一个体会是Flutter × HarmonyOS 6.0 这种组合真正考验人的不是视频解码能力而是“视频控制栏”这一层看似轻薄的交互壳。进度条拖两下就卡、快进快退不同步、点按事件跟原生手势抢响应——这些才是让人熬夜的根因。这篇博文就围绕我写的这个「忆影播放器」项目把 Flutter 接入 HarmonyOS 6.0 后构建视频控制栏的完整思路、架构取舍、核心代码、踩坑过程全部摊开讲清楚。文章适合两类人看一类是打算在鸿蒙上做 Flutter 播放器、但不清楚原生层和 UI 层怎么分工的开发者另一类是已经跑通了视频画面却被控制栏交互搞得焦头烂额的同行。无论你属于哪一类我都尽量用工程实践说话把为什么这么做、踩了什么坑、最后怎么解决都写明白方便你直接参考复现。1. 切入正题控制栏为什么值得单独搞一套架构1.1 控制栏没你想的那么简单很多人觉得视频控制栏不就是播放暂停键、进度条、时间显示三件套么我一开始也这么想直到在鸿蒙上真机跑起来才发现问题全藏在“状态”两个字里。一个称得上能用的视频控制栏至少要同时维护这几类状态播放器的真实状态空闲、缓冲中、播放中、暂停、播放结束、控制栏自身的 UI 状态是否可见、当前显示的是横屏布局还是竖屏布局、拖动手势的临时状态正在拖动进度时要显示目标时间而不是当前播放时间、以及音量亮度这类系统状态的回调结果。这些状态交叠在一起用普通 setState 三下五除二很快就失控了。再加上 HarmonyOS 6.0 这一代鸿蒙系统本身用的是 ArkTS 生态Flutter 跑在上面本质上是一套适配过的 OHOS 引擎。视频解码走的是鸿蒙原生播放框架UI 却要用 Flutter 的 Widget 树渲染。说直白点控制栏的每个按钮按下去都要完成“Dart 事件 → Channel 通信 → ArkTS 原生调用 → 播放器回调 → 反向 Channel 事件 → Dart UI 刷新”这整整一圈闭环。这一圈闭环里任何一个环节慢了、断了、丢了用户感知就是“按钮没反应”“进度条自己乱跳”。所以这里我先把结论放在前面跨端播放器的控制栏技术难点不在控件画得漂不漂亮而在于状态模型是否统一、通信链路是否可靠、手势事件是否分得清。1.2 忆影播放器是什么、做了什么忆影播放器是我自己维护的一个本地视频播放器项目不是商业级产品但功能上一直在向主流播放器看齐。它基于 Flutter 编写全部 UI 层视频解码与渲染使用鸿蒙系统的原生能力通过 PlatformView 嵌入 Flutter 页面中。目前的版本已经实现了播放暂停、进度拖动、快进快退、倍速切换、横竖屏切换、亮度与音量手势控制、软硬解切换以及断点续播等基础能力。在 HarmonyOS 6.0 这个环境里这套方案踩通之后我有几个非常直观的感受。第一Flutter 侧的动画和布局能力比直接用 ArkUI 写控制栏要顺手太多尤其是进度条这种需要高频局部刷新的组件第二鸿蒙原生播放器对常见视频格式的硬解支持很完整省去自己折腾解码库的工夫第三两边通信如果设计不好体验会直接崩掉所以我的大部分精力都花在了 Channel 层的可靠性上。1.3 为什么一定要 Flutter 和鸿蒙配合而不是二选一坦白说如果只是做一个鸿蒙专用的小播放器完全用 ArkTS ArkUI 开发很多问题都不存在。但我做这个项目有一个隐藏目标同一套 Flutter 代码要能跑在 Android、iOS 以及鸿蒙上视频播放只做平台差异适配。这就要求我把 UI 层全部留在 Flutter把音视频底层放到各自系统原生侧。鸿蒙原生播放器要接进来无非两条路一条是只用视频输出也就是嵌一块原生 View 进来负责渲染画面另一条是把播放控制也全部交给原生Flutter 只发指令收状态。忆影播放器选择的是中间态渲染交给原生控制逻辑由 Flutter 主导。选择这种模式的原因是它在跨端一致性和交互灵活性之间最平衡你能用 Flutter 的动画和手势能力构建完全一致的控制栏体验又能享受到鸿蒙播放器对硬解、HDR、音频输出这些底层能力的原生支持。2. 架构设计能力边界、通信方案与状态模型2.1 原生渲染 Flutter UI能力边界的第一层拆分先说说我画的第一张架构图虽然是脑内构图但思路得先理清。整个播放器页面从下往上分三层最底层是鸿蒙原生视频渲染视图负责解码后的画面输出中间层是 Flutter 的 PlatformView 容器负责把原生视图挂进 Widget 树最上层是 Flutter 绘制的一整组控制栏浮层包括半透明渐变遮罩、播放按钮、进度条、时间文本、倍速菜单等。这个分层的核心思想是谁擅长什么就干什么。原生播放器负责解码渲染Flutter 负责所有 UI 展示与交互。控制栏按钮点击后走 MethodChannel 调用原生播放器内部状态变化后走 EventChannel 上报给 Flutter。两条通道各管各的方向明确不容易乱。能力边界一定要在一开始就划清楚。比如音量、亮度这种功能控制栏的手势可以负责“计算目标值”但真正调节必须交给原生系统能力比如视频的时间轴Flutter 可以负责“展示进度百分比”但 seek 之后真正的帧定位必须等待原生回抛事件来确认。谁抢了谁的活都会导致状态不一致。2.2 MethodChannel 与 EventChannel请求/响应与推流的分工Flutter 与原生通信有 MethodChannel、EventChannel、BasicMessageChannel 三种常用方式。播放器场景里我用了前两种理由是它们的语义恰好匹配需求通道类型语义播放器中的应用场景MethodChannel请求/响应一问一答播放、暂停、seek、设置倍速、切换画质EventChannel订阅/推送持续流式播放器状态变化、进度回调、缓冲进度、播放错误BasicMessageChannel双向消息本项目基本未使用MethodChannel 像打电话打通说一句对方回一句EventChannel 像听广播订阅之后只管接收不需要你主动问。播放器里的进度和时间状态天然是高频推送数据我总不可能每秒用 MethodChannel 去调一次原生层拿进度吧EventChannel 就是为这种场景设计的一次建立订阅原生侧持续往 Dart 侧推消息。具体到实现上Dart 侧建立一个服务类统一管理这两个通道避免页面里到处散落 MethodChannel 实例// player_channel_service.dart import package:flutter/services.dart; class PlayerChannelService { PlayerChannelService._(); static const MethodChannel _controlChannel MethodChannel(com.yiying.player/control); static const EventChannel _eventChannel EventChannel(com.yiying.player/events); static Futuredynamic invoke(String method, [Map? args]) { return _controlChannel.invokeMethod(method, args); } static StreamMapObject?, Object? eventStream() { return _eventChannel.receiveBroadcastStream() .castMapObject?, Object?(); } }这里我踩过的第一个坑先透露一下EventChannel 的receiveBroadcastStream()必须确保在页面真正加载完成后再调用否则会出现事件丢失。你调用得早原生侧可能还没 ready后面就算有状态推送Dart 侧也收不到。2.3 接入 Native 视图PlatformView 的鸿蒙实践在 Flutter 里嵌入原生视图Android 上是AndroidView/SurfaceAndroidViewiOS 上是UiKitView。到了鸿蒙Flutter 的 OHOS 适配分支提供了类似的平台视图接入能力。具体名字在不同 SDK 版本里有点差异但思路是共通的通过一个 Factory 类把原生 View 包一层再让 Flutter 侧通过PlatformViewLink或PlatformView组件挂到 Widget 树里。我在忆影播放器里没有采用 Flutter 官方 video_player 那类插件的做法而是自己接管原生渲染视图因为控制栏对渲染层和 UI 层之间的层级关系要求更高。结构上大致长这样Widget buildVideoArea() { return Stack( children: [ Positioned.fill( child: PlatformViewLink( viewType: com.yiying/native_player, onPlatformViewCreated: _onPlatformViewCreated, onCreatePlatformView: (params) PlatformViewsService.initSurfaceAndroidView( params, viewType: com.yiying/native_player, ), ), ), // 控制栏浮层 Positioned.fill( child: _buildControlsOverlay(), ), ], ); }需要注意鸿蒙侧的 PlatformView 有些版本只支持 Texture 合成有些支持 Surface 直通。这直接影响视频画面的渲染效率和旋转后的表现。我当时用 Surface 直通花了不少时间因为它能避免每帧从 GPU 拷贝纹理但缺点是竖屏旋转横屏时如果原生层没处理好画面会闪黑。如果你的系统版本支持建议优先 Surface 直通不支持就退回 Texture 合成稳定性优先。2.4 统一状态模型控制栏不卡顿的第二层地基通信方案定了之后接下来要解决状态模型。播放器控制栏最容易出现“手忙脚乱”的根源就在于多条状态流互相打架。比如正在拖动进度条时播放器内部也在回调进度如果你直接用回调进度刷新 UI进度条就会一边被你拖着、一边被原生推上来的值拉回去视觉上那个跳动感让人疯掉。我最终把状态模型拆成三层播放器真实状态来自 EventChannel只有 idle/preparing/playing/paused/buffering/completed/error 几个枚举UI 呈现状态来自用户交互与真实状态折叠比如控制栏显隐、播放按钮图标切换、锁屏按钮状态临时交互状态拖进度时的目标时间、松手后是否 seek、手势类型等只在交互期间生效。三层状态里播放器真实状态是唯一的数据源UI 状态由它派生临时交互状态只在手势生命周期里覆盖 UI 状态的一部分。等到视频控制栏真正实装时这个模型带来的好处比想象中大得多——后面第 4 节我再结合代码展开。3. 鸿蒙侧接入工程初始化与原生播放器插件改造3.1 环境准备SDK 版本与 Flutter OHOS 分支的匹配先讲环境匹配这环节最容易被忽略也是让我白花过最多时间的地方。HarmonyOS 6.0 这一代对应的 SDK 版本体系和 HarmonyOS NEXT 早期版本不完全一样。标题里提到 API 12 和 5.0.0(12)其实就是 HarmonyOS NEXT 5.0.0 对应的 API 12 基线到了 HarmonyOS 6.0API 版本继续演进但底层机制基本延续了这套 Stage 模型 ArkTS 系统播放框架的设计。要在鸿蒙上调试 Flutter 应用一个是 DevEco Studio一个是 Flutter SDK 的 OHOS 分支或者官方合入 OHOS 支持的版本。我这边的配置大致是组件版本参考DevEco Studio对应 HarmonyOS 6.0 的版本5.x 及以上HarmonyOS SDKAPI 12 及以上Flutter SDK带 OHOS 支持的分支3.22 之后的几个版本对鸿蒙支持逐渐完善Dart SDK随 Flutter SDK 自带无需单独安装项目语言模式ArkTSStage 模型这里给新入坑的读者一个硬建议不要用你电脑上现有的任何 Flutter 稳定版来建鸿蒙项目。必须去拉专门支持鸿蒙的 Flutter SDK 分支并配置到 IDE 里否则工程创建时根本看不到鸿蒙的编译目标。创建项目后习惯用 Android Studio 的同事会发现 如何创建 Flutter 项目 的流程都懂但工程里的ohos目录、module.json5、EntryAbility这些全新结构需要单独熟悉。3.2 鸿蒙工程里接线 Channel原生侧如何响应与上报鸿蒙侧的 Flutter 插件机制核心是继承FlutterPlugin并实现MethodCallHandler、EventChannel.StreamHandler等接口。原生侧注册的通道名必须和 Dart 侧完全一致否则会静默失败。我注册的通道有两个com.yiying.player/control用于接收 Dart 指令com.yiying.player/events用于上报播放器状态。ArkTS 侧核心逻辑示意如下以实际 SDK 为准但思路是成立的// PlayerPlugin.ets 示意代码 import { FlutterPlugin, MethodCall, MethodChannel, EventChannel } from ohos/flutter_ohos; export class PlayerPlugin implements FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler { private eventSink?: EventChannel.EventSink; private controller new AVPlayerController(); onAttach(flutterEngine: FlutterEngine) { const methodChannel new MethodChannel(flutterEngine.dartExecutor, com.yiying.player/control); methodChannel.setMethodCallHandler(this); const eventChannel new EventChannel(flutterEngine.dartExecutor, com.yiying.player/events); eventChannel.setStreamHandler(this); } onMethodCall(call: MethodCall, result: MethodChannel.Result) { switch (call.method) { case play: this.controller.play(); result.success(true); break; case seekTo: const posMs call.arguments as number; this.controller.seek(posMs, () { this.emitEvent(seekCompleted, posMs); result.success(posMs); }); break; // pause / setSpeed / setLoop 等类似 } } onListen(arguments: any, eventSink: EventChannel.EventSink) { this.eventSink eventSink; this.controller.onStateChange((state) { this.emitEvent(stateChanged, state); }); } private emitEvent(type: string, data: Object) { if (this.eventSink) { this.eventSink.success({ type: type, data: data }); } } }事件上报这里有个细节原生侧的回调线程和 Flutter 主线程不是同一个。如果你在播放器回调里直接调 eventSink可能会出现极低概率的消息乱序尤其在 seek 场景里旧回调和新回调的时间戳交叉Dart 侧收起来就错乱了。稳妥的作法是在 ArkTS 侧做一次串行化把同一时刻的事件按播放器时间戳排序后再推给 Flutter。3.3 页面销毁、断流与生命周期处理还有一个我一开始完全没注意、后来被用户反馈打得满头包的环节页面销毁。Flutter Navigator 切走页面后控制栏页面本身已经 dispose 了但原生播放器还在后台播放EventChannel 管道也已经断开了。返回页面时Dart 重新订阅 EventChannel原生却还持有旧状态导致黑屏但有声音、进度突然跳回开头这类问题。现在的处理方案是Flutter 侧在dispose里主动调用 MethodChannel 的release方法通知原生层暂停播放并释放视频资源原生层收到 release 后也要把 eventSink 置空防止继续往已断开的通道推消息。在didChangeAppLifecycleState里也做了类似处理切后台自动暂停回到前台按用户设置决定是否自动恢复。这里也回应一个很多人问过的问题Flutter Navigator 切换页面后播放器状态会丢失吗我明确回答默认一定会丢除非你做了状态保持。我用的是页面级状态提升——播放器实例提升到应用顶层单例持有页面切换只销毁 UI不销毁播放器对象再配合 PageStorageKey 保持页面滚动位置控制栏和播放进度才不会因为切入切出而重置。这也是忆影播放器断点续播实现的基础。4. 控制栏完整实装UI、手势、进度同步与控制联动4.1 控制栏 UI 布局与自动隐藏策略控制栏 UI 我按照观看场景分成两套布局竖屏时底栏常驻提供进度条、播放键、当前时间/总时长、全屏按钮横屏时切换成上层抽屉式控制栏左上角标题底栏增加倍速按钮、锁屏按钮、下一集按钮顶部下滑还能拉出更多设置。两套布局共用同一套 Widget 组件只是位置和显隐不同。自动隐藏这个功能看起来简单做起来有不少花样。我采用的控制逻辑是播放中且无交互时3 秒后自动隐藏控制栏暂停、缓冲、播放结束、拖进度、点击菜单时控制栏必须保持显示每次用户触碰屏幕时重置隐藏计时器隐藏和显示使用同一个 AnimatedOpacity AnimatedSlide 组合动画时间 220ms 左右避免生硬的闪现。有一点要注意自动隐藏计时器不能再 UI 每次刷新时重建。如果用 Timer 在 build 里重复创建屏幕刷新快的场景下计时器永远到不了点控制栏永远不会隐藏。我在实现里把计时器放在 ControlOverlay 的状态类里只在用户手势触发时 reset。4.2 手势区划分与事件优先级视频播放器的手势是最容易出事的区域。由于控制栏浮层是整个铺满屏幕的手势识别必须分区域、分状态。我的划分逻辑是手势区域手势类型触发动作全屏视频区域单击控制栏显隐切换全屏视频区域双击播放/暂停全屏左侧 1/3 垂直滑动垂直拖动亮度调节全屏右侧 1/3 垂直滑动垂直拖动音量调节全屏底部 1/4 水平滑动水平拖动进度 seek 预览控制栏按钮区域点击对应控制事件这些手势用 Flutter 的 GestureDetector 是不够的因为单击、双击、水平拖动、垂直拖动各有竞争关系。我做了一个手势仲裁层RawGestureDetector配合GestureRecognizer集合把水平拖动和垂直拖动分别指定到横竖两个 Axis 方向的 recognizer 上单击双击之间设置 200ms 的等待窗口任何拖动开始后取消单击和双击的注册主要避免拖动结束后误触发点击。这里有一个我真实遇到的线程冲突手势 recognizer 在手指离开屏幕后可能触发 onEnd但原生侧的状态推送恰好也在同一毫秒级时间点到达就会导致播放器先 seek 到目标点又因为旧的 updateProgress 回调把 UI 上的进度拉回去。这个问题最后是靠第 2 节说的“拖动态进度值不进播放器真实状态”解决的——我从数据源头隔离了交互状态和播放器推送状态。4.3 进度条拖动与请求帧同步进度条的实现我分成两个部分底部细进度条和横屏中央缩略预览进度条。其中缩略预览在鸿蒙原生侧也有配合拖动时显示当前拖动时间的视频帧缩略图。先说底部细进度条// video_progress_bar.dart 核心逻辑 class VideoProgressBar extends StatefulWidget { final double progress; // 0.0 - 1.0 final double buffered; // 0.0 - 1.0 final ValueChangeddouble? onSeek; final ValueChangeddouble? onDragging; } class _VideoProgressBarState extends StateVideoProgressBar { double? _dragValue; void _handleHorizontalDragUpdate(DragUpdateDetails details) { final box context.findRenderObject() as RenderBox; final width box.size.width; final dx details.localPosition.dx; final value (dx / width).clamp(0.0, 1.0); setState(() _dragValue value); widget.onDragging?.call(value); } // ... }拖动态时_dragValue覆盖widget.progress时间文本显示目标时间松手调onSeek后控制栏切回“等待 seek 完成”状态进度条停在指定位置直到原生侧回抛 seekCompleted 事件才把真实进度校准过来。这套流程我在最初版本里没做结果拖动进度条经常出现“拖完往后退回一段”的诡异现象——这就是前面反复强调的交互状态与真实状态分离问题。4.4 状态驱动的组件刷新与防抖控制栏并不是每 100ms 都要全部刷新一遍。播放器进度回调的推送频率通常是 200ms ~ 500ms 一次如果你的控制栏整体 build 成本高这种频率会导致丢帧。我做了几个优化首先把进度条区域、时间文本区域、控制按键区域拆成三个独立 Widget互不关联的状态更新只触发对应子树刷新。其次使用ValueNotifierValueListenableBuilder而不是 setState 刷新进度数据。最后对原生侧的高频事件做节流比如进度事件在 Dart 侧再包一层 100ms 节流避免极端情况下 UI 出现抖动。这些优化做完后真机上控制栏在 4K 视频播放时的 GPU 占用也没有明显上升手感上拖进度条非常跟手。还有一个容易忽视的控制联动问题进度条拖到尾部松手播放器如果已经走完流程进入 completed 状态此时再点播放键应该从头播放。我在控制逻辑里对 completed 状态单独处理seek 到 0 并重新 play而不是简单地调 play 方法不然用户会有点了播放没反应的错觉。5. 联调与排坑我在鸿蒙上踩过的那些真实深坑5.1 打包构建本地签名、Dart VM 初始化报错与 Gradle 无关项鸿蒙工程打包跟 Android 有很多相似处但也有自己独特的问题。首先是本地签名HarmonyOS 应用必须配置本地调试证书和 Profile否则安装不进真机。用 DevEco Studio 的自动签名功能可以生成但要注意每次重新生成后 bundleName 要保持一致不然会报签名不匹配。第二个高频报错是 Flutter 在鸿蒙上偶发的 Dart VM 初始化异常日志类似E/flutter (31173): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: ...这类报错本质不是 Dart VM 坏了而是平台通道在异常时序下被反复调用。我在查这种问题时一般先用堆栈定位到具体 channel再回到 ArkTS 侧看是否在插件 attach 之前就触发了方法调用。解决办法是在 Dart 侧加一个 channel 是否 ready 的布尔变量所有对原生侧的调用都先检查这个开关。第三个问题来自 Flutter 自身工具链。我之前在 Android 项目里见过java.lang.AssertionError这一类构建期错误在鸿蒙对接 Flutter 时如果误用了原来的 Flutter 工具链也会触发相似错误。解决的路径就是前面强调的确认使用的是 OHOS 适配分支并且flutter doctor能正确识别出鸿蒙工具链。5.2 EventChannel 事件丢、收不到、重复收到EventChannel 是播放器里最关键的通信方式也是坑最多的环节。我按实际遇到过的情况整理成速查表现象根因解决方式刚进入页面时状态推送全丢Dart 在页面 initState 就订阅但原生插件还没 attach在页面首个 frame 渲染完成后延迟订阅或让原生侧在有订阅后才开始推流切后台再回前台后收不到事件原生播放器被系统回收eventSink 失效但 Dart 还持有流监听 AppLifecycle 变化恢复前台时重新订阅并主动请求一次全量状态同一事件收到多份页面重建导致多次订阅没取消在dispose阶段先取消订阅再销毁页面使用同一个单例 StreamController 做事件分发带数据的事件收不到但无异常EventChannel 数据里包含 null 或超大整数ArkTS 侧把数据统一转成 number/string避免不兼容类型串扰其中“切后台再回前台”这组情况最坑。原生播放器可能会因为资源清理而自动重置但 Flutter 侧的 UI 还停留在暂停菜单然后用户一点播放又发现没有声音。现在的做法是在AppLifecycleState.resumed回调里主动通过 MethodChannel 询问原生侧“当前播放器状态、当前进度、缓冲进度”用这次全量同步来校正所有 UI 状态。这个方法也被复用在播放器从后台恢复的场景。5.3 PlatformView 黑屏、画面拉伸与图层问题PlatformView 在鸿蒙上最常见的表现就是黑屏。第一次接入时我一度以为视频源出问题后来才发现是原生表面没有正确插到平台上。排查思路是先打开开发者选项中的“显示布局边界”看有没有渲染区域再在 ArkTS 侧打日志确认 video view 是否 attach 成功最后确认 Flutter 侧的 PlatformView 容器尺寸是否为非零且没有在导航动画期间被裁剪。另一个大坑是画面比例。竖屏播放 16:9 视频控制栏底栏在屏幕底部视频画面如果直接填满整个 Stack就会发生拉伸变形人脸变宽。解决方式是使用 FittedBox AspectRatio 包裹平台视图区域但这里要注意平台视图的 surface 渲染不受 Flutter 布局约束需要原生侧同步设置视频 scaling mode。这个我最终在 ArkTS 侧给播放器设置了VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING按照视频比例裁剪填充在控制栏和视频画面之间有黑边时也做了背景遮罩处理。5.4 控制栏状态丢失问题Navigator 与状态保持文章开头提到的 Navigator 页面切换丢状态在实际测试中我自己试过三种路由方式Navigator.push默认模式切走再切回来播放器实例保持但控制栏 UI 因为页面重建会回到初始状态showDialog做的半屏面板切走时底层页面状态保持但不更新事件进入队列可能堆积底部导航切换 Tab如果 Tab 页面用IndexedStack包裹可以保持所有 Tab 状态但内存开销变高。我的最终方案是视频播放器实例不跟着页面走而是挂在一个全局的PlayerHolder单例上页面切走时保留播放器继续播放切回来时读取单例里的全量状态刷新 UI。能做到这一步的底气正是第 2 节中把播放器真实状态独立成模型层UI 只做展示和折叠因此页面重建只是重新订阅不会重新初始化播放器。5.5 关于 Impeller 与渲染引擎的一个补充热词里一直有人问 Flutter 在鸿蒙上的渲染引擎尤其是 Impeller 逐步成为新一代 Flutter 默认渲染引擎之后鸿蒙适配情况如何。我在忆影播放器里测试过带 OHOS 支持的 Flutter 分支已经在逐步跟进 Impeller 渲染但现阶段视频播放场景下PlatformView 的相关路径仍以 Skia 兼容链路更成熟。如果遇到视频画面和控制栏 UI 出现闪烁或边界锯齿先把渲染引擎切回兼容模式试一下再决定要不要继续排查其他因素。这个经验是实测出来的等项目后续版本把 Impeller 的视频合成路径吃透我大概率会再写一篇单独的测试记录。最后再分享一点个人心得。做 Flutter 和鸿蒙的混合开发最忌讳的就是把所有东西都往 Flutter 这边硬拉或者把所有逻辑都往原生塞。控制栏这套东西本质上是在两条技术栈之间竖起一座桥桥稳不稳取决于桥墩子也就是通信层碎不碎。这套方案里我反复打磨的就是 Channel 的可靠性、状态模型的单一数据源、以及事件时序的控制。后续如果继续扩展这个播放器我大概率会先把字幕解析和音轨切换接进来再把投屏相关的协议层放上来跑跑看。到时候再回来更新这篇实战记录也欢迎读者在实操过程中把你的踩坑结论分享出来这类跨端工程的疑难杂症往往越是具体越有参考价值。
返回列表