HarmonyOS开发——uni-app 鸿蒙适配深坑:uni-app nvue 视频全屏无反应?详解鸿蒙端 createVideoContext 的正确打开方式
引言
最近在把一个 uni-app(Vue3 + nvue)多端工程适配鸿蒙(APP-HARMONY)时,课程详情页的视频点全屏按钮没反应,甚至误触发了暂停。本以为是个小交互 bug,结果绕了一大圈——最坑的地方在于:一开始我"确认"它是 ArkWeb 里跑 H5 <video>,于是所有修复都建立在"能操作 DOM"这个错误前提上。
这篇文章把整个排查链路、每次误判的原因、以及最终真正生效的方案记录下来。核心一句话:nvue 页面在鸿蒙是原生渲染,JS 层根本没有 document。
现象
某个课程详情页(.nvue)里自绘了视频控制栏:隐藏原生控件(:controls="false"、:show-fullscreen-btn="false"),用一个自定义按钮调全屏。表现是:
- 点全屏按钮 → 视频画面毫无变化;
- 有时点一下画面区域反而把播放暂停了。
为了在真机上定位,我在关键路径埋了 uni.showToast(鸿蒙真机 console.log 混在 hilog 里几乎看不见,调试期用 toast 比 console 靠谱),把"点击是否到达 / API 是否被调 / 全屏回调是否触发"三段分别弹出来。
阶段一(部分正确):点击穿透
先解决"点全屏却触发暂停"。最初按 H5 思路排查,怀疑插槽容器的 pointer-events。给自定义按钮和播放浮层补上:
/* 插槽内容默认继承容器 pointer-events:none,点击会穿透到 video 自身 */
.play-overlay,
.fullscreen-btn {
pointer-events: auto;
}
补上之后,点击确实能命中全屏按钮了(全屏处理函数开始被调用)。这一步看似印证了"是 H5 DOM 渲染",但其实是个误导:nvue 原生渲染支持 CSS 样式(含 pointer-events),但不代表它有可脚本化的 DOM。样式和 DOM API 是两回事,这是我后来才回过味来的关键点。
阶段二(错误前提崩塌):根本拿不到容器
命中了全屏按钮去调"全屏",仍然无效。原来的 DOM 方案长这样:
function harmonyEnterFullscreen() {
const doc = globalThis.document; // ← 这里就为 null
if (!doc) return false;
const el = doc.getElementById('courseVideo');
el?.requestFullscreen(); // 根本执行不到
}
用 toast 打印诊断后,真相浮出水面:
[FS-DEBUG] 步骤2 document不可用(非DOM渲染)
而且周边系统日志全是 Ace / AceOverlay(ArkUI 原生组件体系),并没有 ArkWeb 页面。结论很干脆:
这个
.nvue页在鸿蒙走 ArkUI 原生渲染,业务 JS 运行在没有document的层。 于是getElementById/querySelector/requestFullscreen全部无从谈起。
这也顺带解释了另一个从没暴露的问题:视频一直"能播",纯粹是靠 :autoplay 属性由原生播放器自动播放;基于 DOM 写的播放/暂停/拖动在鸿蒙其实一直是空转。"能播"的表象掩盖了 document 为 null 的事实,这是最容易骗过人的地方。
一个反直觉教训:不要因为在工程里看到类似 uni-video 的 CSS 样式文件,就假设页面跑在可脚本化的浏览器 DOM 里。判断渲染方式要用 typeof document !== 'undefined',而不是靠条件编译或猜测。
阶段三(真正生效):统一走 createVideoContext + proxy 作用域
nvue 的视频(iOS / Android / 鸿蒙三端都是原生组件)在 JS 层唯一的控制入口是 uni.createVideoContext。于是删掉整套 DOM 分支,改回标准 API。
但直接照旧写还是没反应:
const instance = getCurrentInstance();
const ctx = uni.createVideoContext('courseVideo', instance); // 全屏仍无效
坑在作用域参数。<script setup> 里 getCurrentInstance() 返回的是内部实例,传给 createVideoContext 定位不到目标组件,导致 requestFullScreen() 静默失败。改传组件代理实例 proxy 后立刻生效:
// 鸿蒙专用上下文:用 proxy 作为作用域
const harmonyCtx = ref(null);
onMounted(() => {
// #ifdef APP-HARMONY
harmonyCtx.value = uni.createVideoContext('courseVideo', instance?.proxy ?? instance);
// #endif
});
// 统一入口:播放 / 暂停 / 跳转 / 全屏都走上下文
function enterFullscreen() {
// #ifdef APP-HARMONY
harmonyCtx.value?.requestFullScreen({ direction: -90 }); // 横屏课程视频
// #endif
// #ifndef APP-HARMONY
videoCtx.value?.requestFullScreen({ direction: isIOS ? 0 : -90 });
// #endif
}
function exitFullscreen() {
// #ifdef APP-HARMONY
harmonyCtx.value?.exitFullScreen();
// #endif
// #ifndef APP-HARMONY
videoCtx.value?.exitFullScreen();
// #endif
}
全屏是否真的成功,不靠猜,靠 @fullscreenchange 回调(e.detail.fullScreen)作为唯一判据。回调一到、画面横屏铺满,问题闭环。同时因为不再依赖 DOM,播放/暂停/拖动也一并恢复正常。
整个改动都包在 #ifdef APP-HARMONY 里,iOS/Android 仍用原来的 videoCtx 路径——单端适配必须保证非目标端零行为变化。
最终改动清单
- 自定义按钮 / 浮层补
pointer-events: auto,修复点击穿透误触暂停。 - 删除基于
document的整套"DOM 直通"视频控制代码。 - 鸿蒙新增
instance.proxy作用域的createVideoContext专用上下文;播放/暂停/拖动/全屏统一走它。 - 以
@fullscreenchange作为全屏成功判据;requestFullScreen({ direction: -90 })适配横屏课程视频。 - 清理排查期的 toast 调试脚手架,保留必要
console日志。
经验总结
- 先证伪渲染方式,再选 API。 nvue 在鸿蒙是原生渲染、没有
document;vue(webview)页才有 DOM。两者控制媒体的手段完全不同,混用会写出"看起来执行了、其实没生效"的死代码。判断用typeof document。 - 警惕"表象正常"掩盖根因。 视频能播是因为
autoplay,让 DOM 方案长期"看着没问题",实际暂停/拖动都失效。验证要针对每个能力点单独确认,别用一个成功现象推断整条链路。 createVideoContext在<script setup>里要传instance.proxy,传内部instance会让原生组件方法静默失效——这个坑没有任何报错,极难发现。- 真机调试优先
uni.showToast,鸿蒙console.log常被系统日志淹没。 - 条件编译做单端隔离,适配鸿蒙时不动 iOS/Android 分支,用预处理后的可执行代码等值来确认"零影响"。
- 以事件回调为准做闭环判定,别把"调用了 API"当成"功能生效了"。
适用边界
本文结论针对 uni-app(Vue3)+ nvue + APP-HARMONY 的原生媒体组件场景。纯 H5、或 vue(webview)内嵌页面不适用"无 document"这一点,但"以 createVideoContext/事件回调为准、单端条件编译隔离"的排查方法同样适用。
浙公网安备 33010602011771号