不能直接用uni.chooseImage或组件实现实时水印,因其调用系统相机导致App切后台、UI失控且无法获取原始预览帧;live-pusher通过模拟直播流+cover-view叠加实现“伪实时”水印,需.nvue页面、FHD模式及canvas二次合成;原生插件则通过Camera/PreviewCallback或AVCaptureVideoDataOutput实现真帧级动态水印,但开发成本高。

uni-app 在 App 端(Android/iOS)无法靠纯前端 组件实现实时水印——因为系统相机界面不可控,水印只能“拍完再加”,做不到“所见即所得”。真正能实时叠加、且稳定上线的方案,只有两种:用原生插件接管相机流,或用 live-pusher 模拟预览+覆盖水印层。前者自由度高但开发成本大;后者兼容性好、落地快,是多数项目的实际选择。
为什么不能直接用 uni.chooseImage 或 加水印
这两个 API 都调用的是系统原生相机,App 会切到后台,UI 完全失控:
• uni.chooseImage 拍完返回临时路径,此时照片已生成,水印只能后加(延迟明显,且无法显示在取景框);
• 组件虽可叠加 ,但在 iOS 上 cover-view 常被遮挡或错位,Android 部分机型(尤其 MIUI、EMUI)根本无法响应点击或定位不准;
• 更关键的是:所有这些方式都**不触发摄像头帧回调**,你拿不到原始预览帧,也就没法做动态时间戳、地理坐标等实时更新的水印。
live-pusher 是当前最稳的“伪实时”方案
它本质是把摄像头画面当成直播流推出来,再用 cover-view 叠加文字/图标——虽然不是真帧级处理,但在用户感知上就是“边看边带水印”。注意几个硬性前提:
• 页面必须是 .nvue(不是 .vue),否则 live-pusher 不生效;
• live-pusher 的 mode 设为 "FHD" 或 "HD",避免低清下水印糊成一片;
• 水印内容(如 time、address)要用 setInterval 每秒更新,但别直接绑在 data 上频繁触发渲染,建议用 uni.$nextTick 节流或只更新 cover-text 的 text 属性;
• cover-view 的定位必须用 right/bottom 百分比或固定 px,绝对不用 flex 或 margin,否则在不同屏幕比例下偏移严重。
拍照时如何确保水印写进最终图片
live-pusher 本身不提供拍照能力,得靠截帧实现。关键点有三个:
• 调用 context.takePhoto()(不是 uni.chooseImage)获取当前帧的 base64 数据;
• 把 base64 解码为 Uint8Array,再用 uni.canvasToTempFilePath + canvas 绘制水印文字(字体大小按设备像素比缩放,比如 fontSize * uni.getSystemInfoSync().pixelRatio);
• 注意 iOS 和 Android 对 canvas 的 drawImage 支持差异:iOS 必须先 ctx.drawImage 图片,再 ctx.fillText 文字;Android 有些版本要求文字绘制前必须调用 ctx.setFontSize 两次才生效;
• 最终导出路径用 uni.saveFile 写入相册,别用 uni.uploadFile 直传——base64 过大会触发安卓 WebView 内存溢出。
原生插件才是真·实时水印的唯一出路
如果你的场景必须做到视频录制中每一帧都带跳动的时间戳(比如安防巡检、执法记录),那就绕不开原生开发:
• Android 侧必须用 Camera.PreviewCallback.onPreviewFrame 拿到 NV21 数据,用 BitmapFactory.decodeByteArray 转 Bitmap,再用 Canvas.drawXXX 绘制文字(字体用 Typeface.create("sans-serif", Typeface.BOLD),别用中文系统字体,免得某些 ROM 缺字);
• iOS 侧走 AVCaptureVideoDataOutput 的 captureOutput:didOutputSampleBuffer:fromConnection: 回调,用 CGBitmapContext 绘制,注意 Core Graphics 坐标系 Y 轴是反的;
• 两者都要做对象池(Object Pool)复用 Bitmap/CGContext,否则每帧 new 一次,30FPS 下 5 秒就 OOM;
• 插件接口暴露给 JS 的不能是图片路径,而是 base64 或 file:// URI,避免跨进程文件权限问题。
真正卡住大多数人的不是技术选型,而是误判“实时”的边界:你以为的“实时”,其实是用户看到水印和按下快门之间延迟 ≤ 200ms;而系统级帧处理要求的是单帧耗时 ≤ 33ms。这两者差了一个数量级。选方案前,先想清楚你的水印到底要“动”到什么程度。











