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 日志。

经验总结

  1. 先证伪渲染方式,再选 API。 nvue 在鸿蒙是原生渲染、没有 document;vue(webview)页才有 DOM。两者控制媒体的手段完全不同,混用会写出"看起来执行了、其实没生效"的死代码。判断用 typeof document。
  2. 警惕"表象正常"掩盖根因。 视频能播是因为 autoplay,让 DOM 方案长期"看着没问题",实际暂停/拖动都失效。验证要针对每个能力点单独确认,别用一个成功现象推断整条链路。
  3. createVideoContext 在 <script setup> 里要传 instance.proxy,传内部 instance 会让原生组件方法静默失效——这个坑没有任何报错,极难发现。
  4. 真机调试优先 uni.showToast,鸿蒙 console.log 常被系统日志淹没。
  5. 条件编译做单端隔离,适配鸿蒙时不动 iOS/Android 分支,用预处理后的可执行代码等值来确认"零影响"。
  6. 以事件回调为准做闭环判定,别把"调用了 API"当成"功能生效了"。

适用边界

本文结论针对 uni-app(Vue3)+ nvue + APP-HARMONY 的原生媒体组件场景。纯 H5、或 vue(webview)内嵌页面不适用"无 document"这一点,但"以 createVideoContext/事件回调为准、单端条件编译隔离"的排查方法同样适用。

posted on 2026-10-14 15:54  码间留白  阅读(6)  评论(0)    收藏  举报

导航