A
Air官方
【Air 系列 · 鸿蒙 PC 适配全记录】31 / 56
■ 应用:AirPlayer(from VLC)(AirPlayer)
■ 定位:VLC 4 移植,全能视频播放器
■ 上游:VLC 4.x (master) | 路线:Qt6 6.11.1+QPA | 版本:1.1.1
■ 开源: https://gitcode.com/Andy88/Harmony-VLC
■ 市场状态:已上架 AGC 应用页: https://appgallery.huawei.com/app/6917615707101761498
■ 官网(免费下载): https://www.shunidc.cn/
————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/
PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:https://gitcode.com/Andy88/Harmony-VLC
环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743
▍一、为什么要适配 AirPlayer
VLC media player 是开源桌面媒体播放领域使用最广的程序之一,VLC 4.x(master 线)把整个界面迁移到 Qt Quick/QML,解码核心 libvlc 配合 ffmpeg 覆盖了从 MPEG 系、H.264 到 ProRes 的庞大格式集合,同时带完整的媒体库、播放列表、字幕与音轨管理。把这样一个重型的 Qt 6 应用带到 HarmonyOS PC 上,考验的不是某个控件的重绘,而是整套"Qt 运行时 + 原生引擎 + 桌面窗口语义"能否在沙箱环境里立起来。
AirPlayer(工程内部曾用名 AirVlc,目录与包名已对齐)就是这个问题的回答。工程采用 AirShark 模式路线:自建 Qt 6.2.4 源码栈与 ohos QPA 平台插件,把 VLC 的 Qt GUI 模块从可执行程序改造成 SHARED 库,由鸿蒙侧的 ArkTS 壳与 NAPI 桥加载运行。PORTING.md 记载的验证设备覆盖 MatePad 11.5、MateBook Pro(2in1)、MatePad Mini 悦读版与 Pura X,横跨平板、PC 浮窗与横屏形态。
当前工程的版本为 1.1.0,包名 com.airplayer.app,deviceTypes 覆盖 phone、tablet 与 2in1,播放 Ability 固定横屏,launchType 为 specified,配合模块级 AppAbilityStage 实现自由多窗下的多实例分配。仓库 entry/libs/arm64-v8a/ 携带 61 个运行时库,其中 vlc/plugins/ 目录下有 295 个 VLC 插件随包部署,目标 ABI 为 arm64-v8a。
▍二、先划清边界:不把编译通过当成适配完成
桌面 VLC 的一切都建立在完整的桌面系统之上:X11/Wayland 窗口、compositor、ALSA/PulseAudio 音频、QFileDialog 桌面对话框、由启动器进程传递的命令行参数。这些在鸿蒙沙箱里都不存在。如果只把 libvlc 编译成动态库就算完成,得到的将是一个能解码却无处显示、无声、无窗口的产品。
因此工程在 PORTING.md 中把每层能力都落到了明确的替代实现上,"编译通过"从来不是验收项,可安装、可播放、可交互才是。
层次 原项目实现(桌面 VLC 4.x) 鸿蒙侧当前方案
应用入口 vlc/winvlc/darwinvlc 启动器 + libvlc_new Stage 模型 EntryAbility + AppAbilityStage(specified 多实例)
窗口承载 X11/Wayland 窗口系统与 compositor ArkTS XComponent(TEXTURE 型)+ ohos QPA 软件渲染
播放器界面 Qt Quick QML 完整 UI,独立进程运行 libvlc_gui.so 改 SHARED 库进程内运行,QML 语义全量保留
视频输出 OpenGL/VDPAU 等 vout display 模块族 软件 vout 帧回调送入 Qt Quick video surface 上屏
音频输出 ALSA/PulseAudio 等音频模块 自研 ohaudio 模块(NDK OHAudio C API),补齐 MuteSet
文件选择 Qt 桌面 QFileDialog QPA 拦截转 DocumentViewPicker,URI 拷入沙箱
全屏语义 vlc_player_vout_ToggleFullscreen 窗口操作 壳层沉浸:窗口最大化/全屏布局 + 顶栏隐藏
解码后端 ffmpeg 全量 + 硬件加速 ffmpeg 全量内置(禁硬件加速与外部库),ProRes 串流实测
其中"全屏由壳层实现"是理解这个工程的钥匙:Qt 窗口被 QPA 钉在 XComponent 区域内,VLC 内部的全屏通路作用于一个无形的 vout 窗口,因此全屏必须在 ArkTS 侧调用窗口 API 完成,Qt 侧只维护状态副本。
▍三、鸿蒙版的整体架构
工程是标准的 Air 双层架构:ArkTS 壳负责 Ability 生命周期、沉浸式、系统 Picker、分享接收与菜单栏;NAPI 薄壳 libvlcbridge.so 只链系统库;libairvlc_core.so 承载 Qt 宿主线程与 libvlc 全家,Qt GUI 以共享库形态在进程内运行。
text
EntryAbility / AppAbilityStage
├── 沉浸式窗口、Want 接收(sendData / file uri / stream 三来源)
├── ArkTS 顶栏菜单栏(七大菜单 + 关于,系统 Menu 承载)
└── Index.ets / XComponent(TEXTURE)
└── NAPI:libvlcbridge.so(薄壳,只链系统库)
└── libairvlc_core.so(Qt 宿主线程 + 环境变量/gettext/SIGSEGV handler)
├── Qt 6.2.4(qtbase/qtsvg/qttools/qtdeclarative)+ ohos QPA
├── libvlc(meson 交叉编译)
│ ├── ffmpeg 全量(解码)
│ ├── ohaudio 音频输出模块(OHAudio C API)
│ ├── medialibrary + sqlite3(媒体库,进程内 preparser)
│ └── gnutls 插件(https 串流 + Mozilla CA 束)
└── libvlc_gui.so(modules/gui/qt 改 SHARED,AIRVLC_AS_LIB 守卫)
主要目录如下:
text
AirPlayer/
├── AppScope/app.json5 # 包名 com.airplayer.app、版本 1.1.0
├── entry/src/main/
│ ├── ets/
│ │ ├── pages/Index.ets # 壳层主页:菜单栏、splash、Picker、分享
│ │ ├── managers/ImmersiveManager.ets # 全屏/浮动窗口状态机
│ │ ├── managers/ShareManager.ets # 碰一碰注册与分享收发
│ │ └── myabilitystage/AppAbilityStage.ets # specified 多实例 key 分配
│ ├── cpp/napi_init.cpp # NAPI 桥:注入、拾取、指针样式、dlopen 预热
│ └── resources/rawfile/ # mo/qm 翻译、CA 证书束等随包数据
├── entry/libs/arm64-v8a/ # 61 库 + vlc/plugins/ 295 插件(拓扑序 dlopen)
└── native/
├── core/airvlc_core.cpp # Qt 宿主线程、键值映射、命令通道(单文件直编)
├── scripts/deploy_vlc.sh # 库部署与 ELF 规整管线
├── scripts/elf_edit.py # NEEDED 去版本号 / 去 rpath / 去 libc.so
├── src-qt/ # VLC Qt GUI 补丁源(menus/player_controller 等)
├── qt_platform_ohos/ # ohos QPA 平台插件
├── compat/ # glib 系弱符号 gcompat 桩
└── tls/ # gnutls/nettle/hogweed/gmp 交叉产物
▍四、核心适配过程
### 1. GUI 转共享库:让 VLC 4.x 的 Qt 界面在进程内启动
VLC 的 Qt 界面原本编译为独立可执行程序。工程按 AirShark 的 GUI→SHARED 范式,把 modules/gui/qt 改为 SHARED 库 libvlc_gui.so,入口函数带 AIRVLC_AS_LIB 守卫。libairvlc_core.so 在 Qt 宿主线程里创建 QApplication、加载 QML 主界面,并把 libvlc 的播放控制对象暴露给 QML。视频输出走软件 vout 回调链:解码帧经 vmem 型输出进入 Qt Quick 的 video surface,由 QPA 的软件合成路径上屏,这条路线规避了 VLC 4 compositor 无 OHOS 后端的风险(风险登记册 R1/R2 的对策)。
### 2. 解码、音频与媒体库的全量补齐
ffmpeg 从"55 个白名单解码器"升级为全量内置构建,libavcodec 从 7MB 增至 12.4MB,ProRes URL 串流随之起播。音频输出是自研的 modules/audio_output/ohaudio.c,直接使用 NDK 的 OHAudio C API,并补写了桌面后端不存在对口的 MuteSet 实现(SetVolume(0) + 记忆音量 + aout_MuteReport 上报)。媒体库按 contrib 版本补编 sqlite3(必须 -DSQLITE_ENABLE_FTS3)与 medialibrary 0.13.2,libvlc 启动参数显式传 --media-library,XDG_DATA_HOME 指进沙箱,VLC4 的外部 preparser 因沙箱禁 exec 改为 __OHOS__ 分支的进程内实现。https 串流则复用 AirFtp 工程的 gnutls 交叉产物补编 libgnutls_plugin.so,CA 信任以 192 张 Mozilla 证书束随 rawfile 部署。
### 3. 输入链路:从 ArkTS 键值到 VLC 快捷键
物理键盘链路为:系统键盘 → XComponent.onKeyEvent → NAPI injectKey → airvlc_core.cpp 的 ohos_key_to_qt → Qt 键值 → VLC hotkeys。键值表按 InputKit 权威定义重写(字母 A-Z=2017-2042、Space=2050、Enter=2054、F1-F12=2090-2101),静音、停止、全屏、空格暂停等快捷键在 MateBook 上实测通。触摸、鼠标、滚轮在 NAPI 层分三通道注入 QPA,IME 文本输入采用"隐形 TextInput onChange 差分提交"协议,与上次快照求公共前缀后只提交增量,解决全量重放导致的字符成倍重复。光标样式由 QPA 的 QOhosCursor 把 16 种 Qt 形状映射到鸿蒙 PointerStyle,壳层以 120ms 轮询取走并 setPointerStyleSync。
### 4. 壳层能力:菜单栏、分享与多实例
顶栏菜单栏是"菜单树导出 + 命令回传"标准模式:glue 层在 Qt 侧重建上游七大菜单(与 QmlMenuBar::setupMenuEntry 同源),遍历 QAction 树序列化为 JSON(含勾选态、禁用态、分隔符与助记符清洗),ArkTS 侧用系统 Menu 组件渲染,点击经 menutrigger 回注上游 QAction,打开类动作自然走进 QPA 拦截的系统 Picker。分享收发注册了 39 条 UTD 的 sendData/viewData skills,接收侧经 systemShare.getSharedData 拷沙箱后自动起播,MatePad 实测 2/2 通过。自由多窗下由 AppAbilityStage.onAcceptWant 按内容返回唯一 key 弹新窗口,进程单例内核的接管副作用以 superseded 事件治理——旧实例还原窗口并提示"已在新窗口中打开"。
▍五、适配中遇到的几个难点
### 难点一:musl 下的 ELF 纪律,一处漏改就"核心引擎启动失败"
hvigor 打包闭包会丢运行时 dlopen 的模块,且 musl 加载器对 SONAME 严格匹配。libairvlc_core.so 每次重编后必须以 elf_edit.py 把 libQt6*.so.6 版本号 NEEDED 改为部署名、去除 rpath 与 libc.so 依赖,再拷入 entry/libs 重打 HAP。工程两次踩中"手工挑参数必漏"的坑后,把流程固化为"readelf 扫 NEEDED → 逐条 --replace-needed"的数组化脚本;patchelf 因改动段布局会触发 musl relocation 段错误而被禁用,全部 ELF 修改走原位编辑。部署 .so 一律不预 strip,strip:false 与 compressNativeLibs:false 双配置闭合 hvigor 路径。
### 难点二:插件双扫描导致快捷键自相抵消
M 静音、F 全屏"按了没反应"的根因不在注入链——KEYDIAG 探针实证动作照常发出。真正的根因是 VLC 的插件目录被双通道各扫一遍:运行时把 PKGLIBDIR 嫁接到 bundle libs 后,默认路径与 VLC_PLUGIN_PATH 对同一目录重复注册,autorun 起了两个 hotkeys 实例,toggle 类动作执行两次自抵(静音 true→false 归零、快跳量翻倍),空格"生效"只是双 Pause 恰好幂等。修复为撤掉 VLC_PLUGIN_PATH、改设 VLC_LIB_PATH 单通道注册,候选数从 2 归 1。这个案例说明插件加载失败不报错,只会让配置项"消失"成 unknown option。
### 难点三:musl 的 fclose 让所有 ES 注册无声失败
MP4 播放闪退的终极根因极为隐蔽:OHOS musl 对 open_memstream() 创建的 FILE 执行 fclose() 恒返回 EBADF。VLC 的 vlc_memstream 全家(ES str_id、URL 解析等)走这条 libc 路径,导致 EsOutCreateStrId 恒 NULL、所有流注册失败,timeshift 包装层带着 NULL 内层转发时崩溃。修复是在 src/text/memstream.c 的 __OHOS__ 分支 undef HAVE_OPEN_MEMSTREAM,启用 VLC 内置的 realloc/vsnprintf 回退。同族的还有 timeshift 包装层吞掉下游 es_out_Add 失败的上游盲区,工程补齐了失败传播与转发判空。
### 难点四:触摸语义与上游的触屏门控互相纠缠
MainCtx.usingTouch 是 VLC QML 界面的触屏模式开关,上游由全局 TouchBegin 事件驱动。在 QWSI 注入管线加子线程 QApplication 的组合下该开关实测不触发,导致全部可滚动列表的鼠标过滤被禁、Flickable 滚动全灭;反过来恒为 true 又会劣化合成鼠标路径、吞掉控制条点击。终案是在 __OHOS__ 下令 usingTouch() 恒 true(触摸是唯一指针源,语义上即触屏设备),滚动由四个站点无条件 setFiltersChildMouseEvents 独立解决。软渲染的 glyph 残影也属同类:四层治理(Text.NativeRendering 基类化、ScrollBar 关抗锯齿、QPA 活跃期全量 Expose 自愈)把残影压到约 0.006% 屏的微残余。
### 难点五:QML 修改的验证盲区与假成功
QML 经 rcc 压缩嵌入 .so,strings 查不到内容,是否入包只能真机实测;ninja 失败后 deploy/hvigor 照跑会把旧库打进新包,工程为此确立"多环节链每环节必须 mtime 闸"的纪律。v23 启动提速中还有一例:dlopen 预热放在 NAPI 模块注册阶段会与 ArkTS 运行时的模块加载并发撞 musl 装载器锁导致 SIGABRT,挪到首个桥调用后稳定。三项措施(rawfile 拷贝 12 并发、dlopen 后台预热、转发时序收紧)把热启动 e2e 压到实测 640ms,首装 530 个文件拷贝仅 308ms。
▍六、编译、安装与启动
工程构建分两段:先交叉编译 VLC 侧产物,再打 HAP。VLC/libvlc 经 meson 交叉构建(meson setup 必须显式 --prefix=install),ffmpeg 由 native/scripts/build_ffmpeg.sh 全量构建;native/core/airvlc_core.cpp 采用 NDK clang++ 单文件直编配方定稿(注意禁用 -fvisibility=hidden,否则桥接 vlc_core_* dlsym 契约全部失联,ArkTS 表现为"核心引擎启动失败"兜底页)。所有产物经 deploy_vlc.sh 部署,脚本内以 elf_edit.py 完成 NEEDED 去版本号、去 rpath、去 libc.so 的规整。随后清理 outputs、touch module.json5 强制重打包:
bash
export JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk
hvigorw --mode module -p module=entry assembleHap --no-daemon
签名产物位于 entry/build/default/outputs/default/entry-default-signed.hap。连接设备后安装并启动:
bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airplayer.app
PORTING.md 记载的回归基准可作参照:MatePad 11.5 上系统 Picker 选片起播、全屏往返、分享面板接收自动播放(2/2)、八大菜单全中文、ProRes URL 串流与媒体库卡片入库全链通过;MateBook Pro 浮窗形态与 Pura X 横屏布局同样部署验证过。
▍七、当前功能边界
按 PORTING.md 与代码现状,当前版本已覆盖:
- 可安装 HAP、Stage 模型入口、XComponent 与 ohos QPA 软件渲染窗口;
- 播放/暂停/进度/音量/停止、空格与字母快捷键、双击全屏、控制条点按切换;
- ffmpeg 全量解码(ProRes/vp9 等实测)、ohaudio 音频输出、静音与音量记忆;
- https/http URL 串流(gnutls + CA 束)、Range 服务器兼容的 demux 探测;
- 媒体库形态(分区主页、卡片入库、继续观看、jpeg 缩略图编码器);
- 字幕(freetype 渲染链)与音轨/宽高比/缩放/裁剪菜单全量;
- 七大中文菜单 + 关于卡片(含开源地址)、系统 Menu 弹层、勾选与级联;
- 系统 Picker 打开/保存、分享面板接收自动播放、多实例与接管治理;
- 2in1 浮窗/全屏、平板沉浸、横屏(Pura X)三形态,深浅色自适应。
尚未进入当前产物的能力有明确清单:SVG 族图像插件(libsvg/libxml/libsvgdec)因无健康依赖库而有意弃用;应用内右键菜单的桌面语义(Open Containing Folder 等)因 vlc-qt 构建树已删不可改,走系统"打开方式/分享"侧实现;旧媒体条目的缩略图占位不回填(新扫描条目生效);S9 剪贴板读权限因 profile ACL 限制未声明;碰一碰真贴屏属用户手动便利项。因此当前版本更准确的定位是"VLC 4.x 鸿蒙 PC 原生播放基础版":播放主线与壳层桌面能力已经闭环,剩余项集中在图像栈补全与长尾桌面语义。
▍八、总结
AirPlayer 的适配说明,VLC 这一量级的 Qt 6 应用在鸿蒙 PC 上不是"能不能编译"的问题,而是把窗口、输入、音频、文件、插件五套桌面假设逐一替换为沙箱等价物的系统工程。GUI 转共享库让 QML 界面原样保留,ohos QPA 与软件渲染解决了显示,ohaudio 与 gnutls 补齐了音频与串流,ArkTS 壳层接管了菜单、分享、多实例与全屏这些必须贴近系统窗口管理的能力。
后续工作的重心在 PORTING.md 的遗留记录里已经很具体:SVG 图像族待自建健康依赖后启用,缩略图与媒体库展示细节继续对齐上游,Home 页滚动叠影等软渲染残余按专项治理。每一步都以可构建的 HAP 与真机上的实际播放行为为验收依据,这也是这个工程二十余轮迭代始终维持的节奏。
■ 应用:AirPlayer(from VLC)(AirPlayer)
■ 定位:VLC 4 移植,全能视频播放器
■ 上游:VLC 4.x (master) | 路线:Qt6 6.11.1+QPA | 版本:1.1.1
■ 开源: https://gitcode.com/Andy88/Harmony-VLC
■ 市场状态:已上架 AGC 应用页: https://appgallery.huawei.com/app/6917615707101761498
■ 官网(免费下载): https://www.shunidc.cn/
————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/
PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:https://gitcode.com/Andy88/Harmony-VLC
环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743
▍一、为什么要适配 AirPlayer
VLC media player 是开源桌面媒体播放领域使用最广的程序之一,VLC 4.x(master 线)把整个界面迁移到 Qt Quick/QML,解码核心 libvlc 配合 ffmpeg 覆盖了从 MPEG 系、H.264 到 ProRes 的庞大格式集合,同时带完整的媒体库、播放列表、字幕与音轨管理。把这样一个重型的 Qt 6 应用带到 HarmonyOS PC 上,考验的不是某个控件的重绘,而是整套"Qt 运行时 + 原生引擎 + 桌面窗口语义"能否在沙箱环境里立起来。
AirPlayer(工程内部曾用名 AirVlc,目录与包名已对齐)就是这个问题的回答。工程采用 AirShark 模式路线:自建 Qt 6.2.4 源码栈与 ohos QPA 平台插件,把 VLC 的 Qt GUI 模块从可执行程序改造成 SHARED 库,由鸿蒙侧的 ArkTS 壳与 NAPI 桥加载运行。PORTING.md 记载的验证设备覆盖 MatePad 11.5、MateBook Pro(2in1)、MatePad Mini 悦读版与 Pura X,横跨平板、PC 浮窗与横屏形态。
当前工程的版本为 1.1.0,包名 com.airplayer.app,deviceTypes 覆盖 phone、tablet 与 2in1,播放 Ability 固定横屏,launchType 为 specified,配合模块级 AppAbilityStage 实现自由多窗下的多实例分配。仓库 entry/libs/arm64-v8a/ 携带 61 个运行时库,其中 vlc/plugins/ 目录下有 295 个 VLC 插件随包部署,目标 ABI 为 arm64-v8a。
▍二、先划清边界:不把编译通过当成适配完成
桌面 VLC 的一切都建立在完整的桌面系统之上:X11/Wayland 窗口、compositor、ALSA/PulseAudio 音频、QFileDialog 桌面对话框、由启动器进程传递的命令行参数。这些在鸿蒙沙箱里都不存在。如果只把 libvlc 编译成动态库就算完成,得到的将是一个能解码却无处显示、无声、无窗口的产品。
因此工程在 PORTING.md 中把每层能力都落到了明确的替代实现上,"编译通过"从来不是验收项,可安装、可播放、可交互才是。
层次 原项目实现(桌面 VLC 4.x) 鸿蒙侧当前方案
应用入口 vlc/winvlc/darwinvlc 启动器 + libvlc_new Stage 模型 EntryAbility + AppAbilityStage(specified 多实例)
窗口承载 X11/Wayland 窗口系统与 compositor ArkTS XComponent(TEXTURE 型)+ ohos QPA 软件渲染
播放器界面 Qt Quick QML 完整 UI,独立进程运行 libvlc_gui.so 改 SHARED 库进程内运行,QML 语义全量保留
视频输出 OpenGL/VDPAU 等 vout display 模块族 软件 vout 帧回调送入 Qt Quick video surface 上屏
音频输出 ALSA/PulseAudio 等音频模块 自研 ohaudio 模块(NDK OHAudio C API),补齐 MuteSet
文件选择 Qt 桌面 QFileDialog QPA 拦截转 DocumentViewPicker,URI 拷入沙箱
全屏语义 vlc_player_vout_ToggleFullscreen 窗口操作 壳层沉浸:窗口最大化/全屏布局 + 顶栏隐藏
解码后端 ffmpeg 全量 + 硬件加速 ffmpeg 全量内置(禁硬件加速与外部库),ProRes 串流实测
其中"全屏由壳层实现"是理解这个工程的钥匙:Qt 窗口被 QPA 钉在 XComponent 区域内,VLC 内部的全屏通路作用于一个无形的 vout 窗口,因此全屏必须在 ArkTS 侧调用窗口 API 完成,Qt 侧只维护状态副本。
▍三、鸿蒙版的整体架构
工程是标准的 Air 双层架构:ArkTS 壳负责 Ability 生命周期、沉浸式、系统 Picker、分享接收与菜单栏;NAPI 薄壳 libvlcbridge.so 只链系统库;libairvlc_core.so 承载 Qt 宿主线程与 libvlc 全家,Qt GUI 以共享库形态在进程内运行。
text
EntryAbility / AppAbilityStage
├── 沉浸式窗口、Want 接收(sendData / file uri / stream 三来源)
├── ArkTS 顶栏菜单栏(七大菜单 + 关于,系统 Menu 承载)
└── Index.ets / XComponent(TEXTURE)
└── NAPI:libvlcbridge.so(薄壳,只链系统库)
└── libairvlc_core.so(Qt 宿主线程 + 环境变量/gettext/SIGSEGV handler)
├── Qt 6.2.4(qtbase/qtsvg/qttools/qtdeclarative)+ ohos QPA
├── libvlc(meson 交叉编译)
│ ├── ffmpeg 全量(解码)
│ ├── ohaudio 音频输出模块(OHAudio C API)
│ ├── medialibrary + sqlite3(媒体库,进程内 preparser)
│ └── gnutls 插件(https 串流 + Mozilla CA 束)
└── libvlc_gui.so(modules/gui/qt 改 SHARED,AIRVLC_AS_LIB 守卫)
主要目录如下:
text
AirPlayer/
├── AppScope/app.json5 # 包名 com.airplayer.app、版本 1.1.0
├── entry/src/main/
│ ├── ets/
│ │ ├── pages/Index.ets # 壳层主页:菜单栏、splash、Picker、分享
│ │ ├── managers/ImmersiveManager.ets # 全屏/浮动窗口状态机
│ │ ├── managers/ShareManager.ets # 碰一碰注册与分享收发
│ │ └── myabilitystage/AppAbilityStage.ets # specified 多实例 key 分配
│ ├── cpp/napi_init.cpp # NAPI 桥:注入、拾取、指针样式、dlopen 预热
│ └── resources/rawfile/ # mo/qm 翻译、CA 证书束等随包数据
├── entry/libs/arm64-v8a/ # 61 库 + vlc/plugins/ 295 插件(拓扑序 dlopen)
└── native/
├── core/airvlc_core.cpp # Qt 宿主线程、键值映射、命令通道(单文件直编)
├── scripts/deploy_vlc.sh # 库部署与 ELF 规整管线
├── scripts/elf_edit.py # NEEDED 去版本号 / 去 rpath / 去 libc.so
├── src-qt/ # VLC Qt GUI 补丁源(menus/player_controller 等)
├── qt_platform_ohos/ # ohos QPA 平台插件
├── compat/ # glib 系弱符号 gcompat 桩
└── tls/ # gnutls/nettle/hogweed/gmp 交叉产物
▍四、核心适配过程
### 1. GUI 转共享库:让 VLC 4.x 的 Qt 界面在进程内启动
VLC 的 Qt 界面原本编译为独立可执行程序。工程按 AirShark 的 GUI→SHARED 范式,把 modules/gui/qt 改为 SHARED 库 libvlc_gui.so,入口函数带 AIRVLC_AS_LIB 守卫。libairvlc_core.so 在 Qt 宿主线程里创建 QApplication、加载 QML 主界面,并把 libvlc 的播放控制对象暴露给 QML。视频输出走软件 vout 回调链:解码帧经 vmem 型输出进入 Qt Quick 的 video surface,由 QPA 的软件合成路径上屏,这条路线规避了 VLC 4 compositor 无 OHOS 后端的风险(风险登记册 R1/R2 的对策)。
### 2. 解码、音频与媒体库的全量补齐
ffmpeg 从"55 个白名单解码器"升级为全量内置构建,libavcodec 从 7MB 增至 12.4MB,ProRes URL 串流随之起播。音频输出是自研的 modules/audio_output/ohaudio.c,直接使用 NDK 的 OHAudio C API,并补写了桌面后端不存在对口的 MuteSet 实现(SetVolume(0) + 记忆音量 + aout_MuteReport 上报)。媒体库按 contrib 版本补编 sqlite3(必须 -DSQLITE_ENABLE_FTS3)与 medialibrary 0.13.2,libvlc 启动参数显式传 --media-library,XDG_DATA_HOME 指进沙箱,VLC4 的外部 preparser 因沙箱禁 exec 改为 __OHOS__ 分支的进程内实现。https 串流则复用 AirFtp 工程的 gnutls 交叉产物补编 libgnutls_plugin.so,CA 信任以 192 张 Mozilla 证书束随 rawfile 部署。
### 3. 输入链路:从 ArkTS 键值到 VLC 快捷键
物理键盘链路为:系统键盘 → XComponent.onKeyEvent → NAPI injectKey → airvlc_core.cpp 的 ohos_key_to_qt → Qt 键值 → VLC hotkeys。键值表按 InputKit 权威定义重写(字母 A-Z=2017-2042、Space=2050、Enter=2054、F1-F12=2090-2101),静音、停止、全屏、空格暂停等快捷键在 MateBook 上实测通。触摸、鼠标、滚轮在 NAPI 层分三通道注入 QPA,IME 文本输入采用"隐形 TextInput onChange 差分提交"协议,与上次快照求公共前缀后只提交增量,解决全量重放导致的字符成倍重复。光标样式由 QPA 的 QOhosCursor 把 16 种 Qt 形状映射到鸿蒙 PointerStyle,壳层以 120ms 轮询取走并 setPointerStyleSync。
### 4. 壳层能力:菜单栏、分享与多实例
顶栏菜单栏是"菜单树导出 + 命令回传"标准模式:glue 层在 Qt 侧重建上游七大菜单(与 QmlMenuBar::setupMenuEntry 同源),遍历 QAction 树序列化为 JSON(含勾选态、禁用态、分隔符与助记符清洗),ArkTS 侧用系统 Menu 组件渲染,点击经 menutrigger 回注上游 QAction,打开类动作自然走进 QPA 拦截的系统 Picker。分享收发注册了 39 条 UTD 的 sendData/viewData skills,接收侧经 systemShare.getSharedData 拷沙箱后自动起播,MatePad 实测 2/2 通过。自由多窗下由 AppAbilityStage.onAcceptWant 按内容返回唯一 key 弹新窗口,进程单例内核的接管副作用以 superseded 事件治理——旧实例还原窗口并提示"已在新窗口中打开"。
▍五、适配中遇到的几个难点
### 难点一:musl 下的 ELF 纪律,一处漏改就"核心引擎启动失败"
hvigor 打包闭包会丢运行时 dlopen 的模块,且 musl 加载器对 SONAME 严格匹配。libairvlc_core.so 每次重编后必须以 elf_edit.py 把 libQt6*.so.6 版本号 NEEDED 改为部署名、去除 rpath 与 libc.so 依赖,再拷入 entry/libs 重打 HAP。工程两次踩中"手工挑参数必漏"的坑后,把流程固化为"readelf 扫 NEEDED → 逐条 --replace-needed"的数组化脚本;patchelf 因改动段布局会触发 musl relocation 段错误而被禁用,全部 ELF 修改走原位编辑。部署 .so 一律不预 strip,strip:false 与 compressNativeLibs:false 双配置闭合 hvigor 路径。
### 难点二:插件双扫描导致快捷键自相抵消
M 静音、F 全屏"按了没反应"的根因不在注入链——KEYDIAG 探针实证动作照常发出。真正的根因是 VLC 的插件目录被双通道各扫一遍:运行时把 PKGLIBDIR 嫁接到 bundle libs 后,默认路径与 VLC_PLUGIN_PATH 对同一目录重复注册,autorun 起了两个 hotkeys 实例,toggle 类动作执行两次自抵(静音 true→false 归零、快跳量翻倍),空格"生效"只是双 Pause 恰好幂等。修复为撤掉 VLC_PLUGIN_PATH、改设 VLC_LIB_PATH 单通道注册,候选数从 2 归 1。这个案例说明插件加载失败不报错,只会让配置项"消失"成 unknown option。
### 难点三:musl 的 fclose 让所有 ES 注册无声失败
MP4 播放闪退的终极根因极为隐蔽:OHOS musl 对 open_memstream() 创建的 FILE 执行 fclose() 恒返回 EBADF。VLC 的 vlc_memstream 全家(ES str_id、URL 解析等)走这条 libc 路径,导致 EsOutCreateStrId 恒 NULL、所有流注册失败,timeshift 包装层带着 NULL 内层转发时崩溃。修复是在 src/text/memstream.c 的 __OHOS__ 分支 undef HAVE_OPEN_MEMSTREAM,启用 VLC 内置的 realloc/vsnprintf 回退。同族的还有 timeshift 包装层吞掉下游 es_out_Add 失败的上游盲区,工程补齐了失败传播与转发判空。
### 难点四:触摸语义与上游的触屏门控互相纠缠
MainCtx.usingTouch 是 VLC QML 界面的触屏模式开关,上游由全局 TouchBegin 事件驱动。在 QWSI 注入管线加子线程 QApplication 的组合下该开关实测不触发,导致全部可滚动列表的鼠标过滤被禁、Flickable 滚动全灭;反过来恒为 true 又会劣化合成鼠标路径、吞掉控制条点击。终案是在 __OHOS__ 下令 usingTouch() 恒 true(触摸是唯一指针源,语义上即触屏设备),滚动由四个站点无条件 setFiltersChildMouseEvents 独立解决。软渲染的 glyph 残影也属同类:四层治理(Text.NativeRendering 基类化、ScrollBar 关抗锯齿、QPA 活跃期全量 Expose 自愈)把残影压到约 0.006% 屏的微残余。
### 难点五:QML 修改的验证盲区与假成功
QML 经 rcc 压缩嵌入 .so,strings 查不到内容,是否入包只能真机实测;ninja 失败后 deploy/hvigor 照跑会把旧库打进新包,工程为此确立"多环节链每环节必须 mtime 闸"的纪律。v23 启动提速中还有一例:dlopen 预热放在 NAPI 模块注册阶段会与 ArkTS 运行时的模块加载并发撞 musl 装载器锁导致 SIGABRT,挪到首个桥调用后稳定。三项措施(rawfile 拷贝 12 并发、dlopen 后台预热、转发时序收紧)把热启动 e2e 压到实测 640ms,首装 530 个文件拷贝仅 308ms。
▍六、编译、安装与启动
工程构建分两段:先交叉编译 VLC 侧产物,再打 HAP。VLC/libvlc 经 meson 交叉构建(meson setup 必须显式 --prefix=install),ffmpeg 由 native/scripts/build_ffmpeg.sh 全量构建;native/core/airvlc_core.cpp 采用 NDK clang++ 单文件直编配方定稿(注意禁用 -fvisibility=hidden,否则桥接 vlc_core_* dlsym 契约全部失联,ArkTS 表现为"核心引擎启动失败"兜底页)。所有产物经 deploy_vlc.sh 部署,脚本内以 elf_edit.py 完成 NEEDED 去版本号、去 rpath、去 libc.so 的规整。随后清理 outputs、touch module.json5 强制重打包:
bash
export JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk
hvigorw --mode module -p module=entry assembleHap --no-daemon
签名产物位于 entry/build/default/outputs/default/entry-default-signed.hap。连接设备后安装并启动:
bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airplayer.app
PORTING.md 记载的回归基准可作参照:MatePad 11.5 上系统 Picker 选片起播、全屏往返、分享面板接收自动播放(2/2)、八大菜单全中文、ProRes URL 串流与媒体库卡片入库全链通过;MateBook Pro 浮窗形态与 Pura X 横屏布局同样部署验证过。
▍七、当前功能边界
按 PORTING.md 与代码现状,当前版本已覆盖:
- 可安装 HAP、Stage 模型入口、XComponent 与 ohos QPA 软件渲染窗口;
- 播放/暂停/进度/音量/停止、空格与字母快捷键、双击全屏、控制条点按切换;
- ffmpeg 全量解码(ProRes/vp9 等实测)、ohaudio 音频输出、静音与音量记忆;
- https/http URL 串流(gnutls + CA 束)、Range 服务器兼容的 demux 探测;
- 媒体库形态(分区主页、卡片入库、继续观看、jpeg 缩略图编码器);
- 字幕(freetype 渲染链)与音轨/宽高比/缩放/裁剪菜单全量;
- 七大中文菜单 + 关于卡片(含开源地址)、系统 Menu 弹层、勾选与级联;
- 系统 Picker 打开/保存、分享面板接收自动播放、多实例与接管治理;
- 2in1 浮窗/全屏、平板沉浸、横屏(Pura X)三形态,深浅色自适应。
尚未进入当前产物的能力有明确清单:SVG 族图像插件(libsvg/libxml/libsvgdec)因无健康依赖库而有意弃用;应用内右键菜单的桌面语义(Open Containing Folder 等)因 vlc-qt 构建树已删不可改,走系统"打开方式/分享"侧实现;旧媒体条目的缩略图占位不回填(新扫描条目生效);S9 剪贴板读权限因 profile ACL 限制未声明;碰一碰真贴屏属用户手动便利项。因此当前版本更准确的定位是"VLC 4.x 鸿蒙 PC 原生播放基础版":播放主线与壳层桌面能力已经闭环,剩余项集中在图像栈补全与长尾桌面语义。
▍八、总结
AirPlayer 的适配说明,VLC 这一量级的 Qt 6 应用在鸿蒙 PC 上不是"能不能编译"的问题,而是把窗口、输入、音频、文件、插件五套桌面假设逐一替换为沙箱等价物的系统工程。GUI 转共享库让 QML 界面原样保留,ohos QPA 与软件渲染解决了显示,ohaudio 与 gnutls 补齐了音频与串流,ArkTS 壳层接管了菜单、分享、多实例与全屏这些必须贴近系统窗口管理的能力。
后续工作的重心在 PORTING.md 的遗留记录里已经很具体:SVG 图像族待自建健康依赖后启用,缩略图与媒体库展示细节继续对齐上游,Home 页滚动叠影等软渲染残余按专项治理。每一步都以可构建的 HAP 与真机上的实际播放行为为验收依据,这也是这个工程二十余轮迭代始终维持的节奏。
楼主
8 天前