更新记录

1.0.0(2026-10-28)

面向 uni-app x Android 的双摄合拍插件,支持 VDOM 与 Vapor(蒸汽模式)。插件 ID 为 lf-dual-camera,提供标准组件、拍摄 API 和完整 Demo。

双摄取景与画面布局

  • 支持 Android 内置前置与后置摄像头并发取景,将两个视角合成为一张预览、照片或视频。
  • 提供画中画、上下分屏、左右分屏三种布局,以及主次摄像头互换。
  • 支持小窗位置与大小设置、手势拖动、小窗圆角和前摄镜像。
  • 支持 0° / 90° / 180° / 270° 额外画面旋转;前后摄方向、镜像与裁剪统一参与合成。
  • 预览在组件区域内等比完整显示,合成画面各区域采用等比裁剪;屏幕比例与输出比例不同时可以留边,保持物体比例。
  • 支持文字水印和时间水印,随合成画面写入照片与视频。

合成照片与视频

  • 提供当前合成画面的 JPEG 照片拍摄,稳定录制期间也可拍照。
  • 提供 H.264 MP4 录像;有声模式包含 AAC 麦克风音轨,无声模式不采集麦克风。
  • 支持 720p / 1080p、横屏 / 竖屏输出,默认竖屏 720×1280,目标帧率为 30fps。
  • 1080p 按相机输入、并发会话与编码器能力开放,具体可用画质以设备检测和打开结果为准。
  • 预览、照片和视频共用同一套合成布局,保持主次画面、镜像、水印与布局设置一致。
  • 视频停止时等待 MP4 封装完成后返回文件结果,重复停止请求共享完成结果。

uni-app x 接入与组件能力

  • 提供采用 <script setup lang="uts"> 的标准组件,支持 VDOM 与蒸汽模式接入。
  • 通过 style / class 或父容器宽高控制预览区域,通过 pipRect 控制小窗,通过 profile / orientation 控制导出尺寸。
  • 提供 ready、相机状态、录制状态、录像完成、小窗拖动与错误事件,业务页面可据此管理交互状态。
  • 提供设备能力检测、打开与关闭相机、拍照、开始与停止录像、缩放、对焦、补光和相册保存 API。
  • API 使用一次性的 success、fail、complete 回调,支持按错误码与消息处理操作结果。
  • 提供显式前摄 / 后摄单摄模式;双摄不可用时,由业务或使用者选择拍摄方式。

全屏取景 Demo 与照片保存查看

  • 提供全屏取景容器,以及悬浮的顶部信息、设置入口和底部拍摄操作栏。
  • 提供画中画 / 分屏切换、照片 / 录像切换、主次互换、缩放、补光、声音开关和拍摄设置面板。
  • 提供图形快门、最近作品入口和操作状态提示;页面控件满足蒸汽模式的组件结构要求。
  • 拍照或录像结束后自动保存到系统相册,显示保存中、保存成功或保存失败状态。
  • 通过快门左侧“最近作品”查看本次页面内最近拍摄的照片或视频。
  • 作品页展示输出尺寸、文件大小、录像时长、声音状态和相册保存状态,支持预览重试、复制文件路径及相册保存重试。
  • 操作栏、快门、录制计时和状态标签不写入照片或视频,文字与时间水印按设置写入。

相机控制与页面生命周期

  • 提供点按对焦 / 测光、指定摄像头缩放和后摄持续补光,具体能力及范围以设备返回结果为准。
  • 录制期间可调整布局、小窗、主次画面、镜像、水印、缩放、对焦和补光。
  • 拍摄模式、画质、输出方向、额外旋转和声音设置在停止录制后调整;启动与收尾阶段通过状态控制拍摄操作。
  • 页面隐藏、返回及原生视图离开窗口时进行录像收尾与相机关闭,组件卸载时释放原生会话。
  • 支持初始化完成前退出页面的资源清理,返回拍摄页后由使用者显式开启预览。

文件保存与权限

  • 拍摄 API 生成应用私有文件,业务可通过 saveToAlbum 独立保存到系统相册;Demo 自动完成该步骤。
  • 照片相册目录为 Pictures/LingfuCamera,视频相册目录为 Movies/LingfuCamera,相册应用通常显示为 LingfuCamera 相簿。
  • 相册保存成功返回媒体 uri,path 保持为应用私有文件路径;相册写入失败时保留私有文件供查看或重试。
  • Demo 按文件路径处理录像完成回调,避免同一文件被重复保存到相册。
  • 打开相机时申请 CAMERA,开始有声录像时申请 RECORD_AUDIO;麦克风未授权时可继续预览,由使用者明确选择是否使用无声录像。
  • Android 6–9 保存相册时申请 WRITE_EXTERNAL_STORAGE;Android 10 及以上通过 MediaStore 保存应用创建的媒体。
  • 摄像头画面、麦克风音频、GPU 合成与文件处理在设备本地完成,不上传拍摄内容、不采集设备标识、不内置广告。

平台兼容性

uni-app x(5.26)

Chrome Safari Android Android插件版本 iOS 鸿蒙 微信小程序
× × 6.0 1.0.0 × × -

其他

多语言 暗黑模式 宽屏模式 蒸汽模式
× × × √

双摄合拍 UTS · uni-app x 使用说明

本插件用于 Android 手机内置前摄与后摄的同屏取景,提供画中画、上下分屏、左右分屏、合成照片和有声/无声录像。预览、照片与视频共用同一套合成布局,可用于双视角记录、讲解、采访等场景。

本插件面向 uni-app x 的 VDOM 与蒸汽模式(Vapor),安装 lf-dual-camera 即可接入组件和拍摄 API。

一、功能与支持范围

已提供的功能

  • 内置前后摄并发预览;设备不支持并发时,可由使用者明确选择前置或后置单摄。
  • 画中画、上下分屏、左右分屏,以及主次摄像头互换。
  • 小窗位置与大小设置、手势拖动、小窗圆角、前摄镜像和画面旋转。
  • 拍摄当前合成画面的 JPEG 照片,稳定录像期间也可拍照。
  • H.264 MP4 录像;有声模式包含 AAC 麦克风音频,无声模式不采集麦克风。
  • 根据设备能力开放缩放、点按对焦/测光、后摄持续补光。
  • 文字水印和时间水印,随合成画面写入照片与视频。
  • 保存到系统相册,以及完整的全屏取景 Demo、最近作品预览与失败重试入口。

运行环境

项目 要求或说明
工程 uni-app x
渲染模式 VDOM / 蒸汽模式(Vapor)
开发工具 HBuilderX 5.26 或更新版本
插件最低 Android 版本 Android 6.0 / API 23
前后摄并发双摄 Android 11 / API 30 及以上,且系统与硬件实际开放前后摄并发能力
单摄 Android 6.0 及以上;仍需满足相机输入、权限与编码器能力要求
iOS、鸿蒙、Web、小程序 不支持,相关 API 返回 UNSUPPORTED_PLATFORM
外接摄像头 不支持 USB/UVC 等外接摄像头

Android 11 以上不等于一定支持双摄,同品牌的不同机型也可能不同。 插件通过系统声明的并发组合检测能力,并在打开相机时继续检查实际配置;最终以 getCapabilities、openCamera 的结果为准。系统 Camera2 并发接口见 Android CameraManager。

双摄不可用时,插件不会在后台自动降为单摄。业务页面应明确提示原因,再由使用者选择 mode: 'back' 或 mode: 'front'。

二、Android已验证版本

Android 版本 当前状态
Android 16 已验证
Android 12 已验证

三、安装与运行 Demo

在业务工程中接入

  1. 将插件导入 uni-app x 工程的 uni_modules/lf-dual-camera。
  2. 在需要预览的 .uvue 页面中引入标准组件,给组件或其父容器设置明确宽高。
  3. 等待组件触发 ready,取得 sessionId。
  4. 调用 getCapabilities 检测能力,选择双摄或明确的单摄模式。
  5. 调用 openCamera,等待成功后再启用拍照、录像与相机控制。
  6. 页面隐藏时关闭相机;组件卸载会释放其原生绑定。

组件导入路径:

import LfDualCameraX from '@/uni_modules/lf-dual-camera/components/lf-dual-camera-x/lf-dual-camera-x.uvue'

API 和类型统一从插件根入口导入:

import { getCapabilities, openCamera, closeCamera, takePhoto,
  startRecording, stopRecording, saveToAlbum } from '@/uni_modules/lf-dual-camera'
import type { LfCameraOptions, LfCameraCapabilitiesOptions,
  LfCameraResult, LfCameraError } from '@/uni_modules/lf-dual-camera'

不要在业务页面中直接导入插件内部 utssdk 文件。VDOM 页面应显式导入所用参数类型,避免编译器将同名类型自动导入到错误的命名空间。

使用内置 Demo

Demo 路径为 uni_modules/lf-dual-camera/pages/demo/demo.uvue,最近作品页为 uni_modules/lf-dual-camera/pages/result/result.uvue。需要体验完整 Demo 时,在业务工程的 pages.json 中登记这两页,并使用自定义导航栏:

{
  "pages": [
    {
      "path": "uni_modules/lf-dual-camera/pages/demo/demo",
      "style": { "navigationStyle": "custom", "disableScroll": true, "backgroundColor": "#0B0F14" }
    },
    {
      "path": "uni_modules/lf-dual-camera/pages/result/result",
      "style": { "navigationStyle": "custom", "disableScroll": true, "backgroundColor": "#0B0F14" }
    }
  ]
}

原生基座注意事项

插件的 Android Manifest 和原生配置需要在打包时合并。标准调试基座仅能使用其已包含的原生配置;若缺少相机、麦克风或 Android 6–9 相册保存权限,应制作包含本插件的自定义调试基座。

四、最小预览示例

先取得会话、手动打开双摄预览及关闭相机。应先确认设备支持双摄;不支持时,可将打开参数明确改为 front 或 back。

<template>
  <view>
    <lf-dual-camera-x style="width:100%;height:480px"
      @ready="onCameraReady" @error="onCameraError" />
    <button :disabled="sessionId.length == 0 || opened" @click="openPreview">开启相机</button>
    <button :disabled="!opened" @click="closePreview">关闭相机</button>
  </view>
</template>

<script setup lang="uts">
import LfDualCameraX from '@/uni_modules/lf-dual-camera/components/lf-dual-camera-x/lf-dual-camera-x.uvue'
import { openCamera, closeCamera } from '@/uni_modules/lf-dual-camera'
import type { LfCameraOptions, LfCameraError } from '@/uni_modules/lf-dual-camera'

const sessionId = ref('')
const opened = ref(false)
let disposed = false

function showError(error: LfCameraError): void {
  if (!disposed) uni.showToast({ title: error.message, icon: 'none' })
}
function onCameraReady(event: UniNativeViewEvent): void {
  const detail = event.detail as UTSJSONObject
  sessionId.value = (detail['sessionId'] as string | null) ?? ''
}
function onCameraError(event: UniNativeViewEvent): void {
  const detail = event.detail as UTSJSONObject
  showError({ code: (detail['code'] as string | null) ?? 'UNKNOWN',
    message: (detail['message'] as string | null) ?? '相机暂不可用' })
}
function openPreview(): void {
  if (sessionId.value.length == 0 || opened.value) return
  const options: LfCameraOptions = {
    sessionId: sessionId.value, mode: 'dual', profile: '720p', orientation: 'portrait',
    success: (_) => { if (!disposed) opened.value = true },
    fail: (error) => showError(error)
  }
  openCamera(options)
}
function closePreview(): void {
  if (sessionId.value.length == 0) return
  const options: LfCameraOptions = {
    sessionId: sessionId.value,
    success: (_) => { if (!disposed) opened.value = false },
    fail: (error) => showError(error)
  }
  closeCamera(options)
}
onHide(() => closePreview())
onUnload(() => { disposed = true; sessionId.value = '' })
</script>

ready 仅代表预览载体已创建,不代表摄像头已打开。需等待 openCamera.success 后再拍摄。离开页面关闭相机后,返回页面需要显式重新打开。

五、设备能力检测

const options: LfCameraCapabilitiesOptions = {
  success: (capabilities) => {
    console.log('支持双摄:', capabilities.dualSupported)
    console.log('不可用原因:', capabilities.reason)
    console.log('可用画质:', capabilities.profiles)
    console.log('摄像头能力:', capabilities.cameras)
  },
  fail: (error) => console.error(error.code, error.message)
}
getCapabilities(options)
返回字段 类型 说明
dualSupported boolean 系统是否声明前后摄并发组合,实际打开仍可能失败
reason string 不支持双摄时的原因说明
profiles string[] 当前检测可用的 720p、1080p 画质
cameras LfCameraInfo[] 每颗摄像头的 ID、朝向、缩放和控制能力

LfCameraInfo 包含 id、facing、zoomMin、zoomMax、autofocus、torch。业务界面应按检测结果开放缩放、对焦与补光按钮。

未获得相机权限时,双摄画质检测采用保守结果,可能只显示 720p;授权后可重新检测。dualSupported: true 不保证所有画质都能启动,openCamera 还会检查会话配置和编码器能力。

六、预览尺寸、组件属性与事件

三种尺寸分别控制什么

设置方式 控制对象 是否改变导出尺寸
组件 style / class 或父容器宽高 页面中的预览显示区域 否
pipRect 合成画面中的小窗位置和大小 改变画面布局,不改变文件宽高
openCamera 的 profile / orientation 合成照片与视频的输出宽高 是

已有样式尺寸控制,无需额外增加 width / height 属性。例如动态尺寸:

<lf-dual-camera-x :style="'width:' + previewWidth + 'px;height:' + previewHeight + 'px'" />

使用百分比高度时,父容器也必须有明确高度。预览在区域内等比完整显示;容器与输出宽高比不同时可能出现黑边,不会为了铺满而拉伸画面。

组件属性

属性 类型 默认值 说明
layout string pip pip 画中画;split-vertical 上下分屏;split-horizontal 左右分屏
mainCamera string back 主画面/第一分屏的摄像头,back 或 front
pipRect UTSJSONObject {x:0.67,y:0.03,width:0.3,height:0.225} 以完整合成画布为基准的归一化小窗区域
frontMirror boolean true 前摄水平镜像,预览与导出一致
radius number 0.025 小窗圆角占合成画面短边的比例,范围 0–0.5
rotation number 0 额外画面旋转,允许 0/90/180/270,录像期间不能修改
watermarkText string 空字符串 文字水印;留空不显示文字水印
watermarkTime boolean false 是否叠加时间水印
gestures boolean true 小窗拖动、点按对焦/测光

pipRect 的原点在合成画面左上角,宽高至少为 0.05,并满足 x >= 0、y >= 0、x + width <= 1、y + height <= 1。分屏布局下不使用小窗区域,切回画中画后可继续使用保存的位置。

组件默认小窗位置与 Demo 的初始位置不同:Demo 为配合悬浮操作栏,初始使用 x:0.60, y:0.18。业务接入以自己传入的 pipRect 为准。

组件事件

事件数据从 event.detail 读取:

事件 关键字段 用途
ready sessionId 取得会话标识,之后可调用打开相机 API
statechange sessionId, state, mode, reason? 相机状态:opening/previewing/closing/closed/error
recordingchange sessionId, state, recording, reason? 录像状态:starting/recording/stopping/idle
recordcomplete sessionId, result, reason 获取完成封装的视频;result 包含文件信息
pipchange sessionId, pipRect 小窗被手势拖动后回写业务状态
error sessionId, code, message 原生相机或媒体处理错误

小窗被拖动后,应把事件中的 pipRect 更新到页面绑定的同名状态,避免后续属性更新将其恢复为旧位置。蒸汽模式中应使用索引读取:

const detail = event.detail as UTSJSONObject
const id = (detail['sessionId'] as string | null) ?? ''

recordingchange.recording 仅在状态为 recording 时为 true。启动和收尾阶段还需结合 state 禁用操作,不宜仅以该布尔值判断是否可以重新开录。

七、API 参数与结果

全部公开函数返回 void,通过一次性的 success、fail、complete 回调获取结果。fail 返回 {code, message}。完整类型见代码包中得 utssdk/interface.uts。

API 主要参数 行为说明
getCapabilities 回调 返回设备能力,不需要 sessionId
openCamera sessionId;mode 默认 dual;profile 默认 720p;orientation 默认 portrait mode 为 dual/front/back;profile 为 720p/1080p;方向为 portrait/landscape
closeCamera sessionId 关闭相机,必要时完成录像收尾;保留预览载体
takePhoto sessionId 保存当前合成帧为 JPEG;稳定录像期间允许拍照
startRecording sessionId;audio 默认 true 开始有声或无声录像;麦克风拒权时不自动改为无声
stopRecording sessionId 完成 MP4 封装后返回文件结果;重复停止请求共享完成结果
setZoom sessionId, camera, zoom camera 为 front/back;zoom 范围以能力检测为准
focusAt sessionId, x, y 合成画面上的 0–1 坐标,自动识别对应摄像头并映射到传感器
setTorch sessionId, enabled 控制后摄持续补光;仅在相应摄像头与设备能力支持时可用
saveToAlbum path 将插件生成的应用私有 JPEG/MP4 保存到相册,不需要相机会话

setZoom、focusAt、setTorch 等控制 API 可通过 success 确认操作成功,不应读取未在公开类型中声明的 zoom、closed、recording 等原生内部字段。

拍摄结果字段

拍照与停止录像的 success 使用 LfCameraResult。不同操作只填充相关字段,其余字段可能为空或为 0:

字段 含义
sessionId, mode, profile 当前操作关联的会话、相机模式和画质;按操作实际返回
width, height, fps 输出宽高与帧率;按操作实际返回
path 应用私有目录中的原始拍摄文件绝对路径
uri 保存到相册成功时返回的媒体 URI;普通拍摄结果通常为空
mime 照片 image/jpeg,视频 video/mp4
size 文件字节数
duration 视频时长,单位毫秒
hasAudio 视频是否包含麦克风音轨

输出画质

画质 竖屏 portrait 横屏 landscape
720p 720 × 1280 1280 × 720
1080p 1080 × 1920 1920 × 1080

目标帧率为 30fps,实际表现受设备相机、并发配置、编码器、温度和负载影响。照片是当前合成帧的快照,不是两颗传感器的全像素静态照片;前后两路也不保证硬件同步曝光。

八、拍照、录像与相册保存

拍摄 API 与 Demo 的区别

takePhoto / stopRecording 负责生成应用私有文件,不会自动保存系统相册。 本插件的 Demo 会在拍摄完成后继续调用 saveToAlbum,所以 Demo 中拍照和录像结束后会自动入相册。业务自行接入时,需要自行调用该保存 API。

以下代码假设已从 ready 取得 sessionId,并已成功打开预览:

function saveCapturedFile(result: LfCameraResult): void {
  const options: LfCameraOptions = {
    path: result.path,
    success: (saved) => console.log('相册媒体 URI:', saved.uri),
    fail: (error) => {
      console.error('相册保存失败:', error.message)
      console.log('可重试保存的原文件:', result.path)
    }
  }
  saveToAlbum(options)
}
function capturePhoto(): void {
  const options: LfCameraOptions = {
    sessionId: sessionId.value,
    success: (result) => saveCapturedFile(result)
  }
  takePhoto(options)
}
function beginRecording(): void {
  const options: LfCameraOptions = { sessionId: sessionId.value, audio: true }
  startRecording(options)
}
function finishRecording(): void {
  const options: LfCameraOptions = {
    sessionId: sessionId.value,
    success: (result) => saveCapturedFile(result)
  }
  stopRecording(options)
}

展示 API 调用关系。业务按钮还应根据启动、录制、收尾和保存状态禁用重复操作;完整处理可参考内置 Demo。

recordcomplete 与 stopRecording.success 可能指向同一个视频。若两处都触发相册保存,应按文件 path 去重,避免重复生成相册副本。Demo 已处理该情况。

文件保存到哪里

文件类型 应用内原文件 系统相册中的分类目录
JPEG 照片 应用私有 lf-dual-camera 目录 Pictures/LingfuCamera
MP4 视频 应用私有 lf-dual-camera 目录 Movies/LingfuCamera

系统相册通常显示为 LingfuCamera 相簿,具体展示位置取决于相册应用;也可通过相册的图片、视频或最近项目查看。saveToAlbum.success 的 uri 指向相册媒体,path 仍是应用内原文件路径,不会被替换成相册 URI。

相册保存失败会保留应用内原文件,可以重试。Demo 中可点击快门左侧的“最近作品”预览,并在作品页查看文件详情、复制原文件路径或重试保存。当前“最近作品”只保存本次页面内的最近拍摄结果,不提供跨重启的历史作品列表。应用私有文件也不等同于系统相册,卸载应用可能移除这些原文件。

九、权限与页面生命周期

权限 申请时机 用途
CAMERA 打开相机时 前摄、后摄预览和拍摄
RECORD_AUDIO 开始有声录像时 录制麦克风音轨;无声录像不采集麦克风
WRITE_EXTERNAL_STORAGE Android 6–9 保存到相册时 写入公共图片/视频目录;Manifest 限制 maxSdkVersion=28

Android 10 及以上通过 MediaStore 写入应用自己创建的媒体,不为相册保存申请广泛的媒体读取权限。用户拒绝麦克风权限后仍可继续预览;Demo 会让用户明确选择是否改为无声录像。

录像过程中可以调整布局、小窗、主次摄像头、镜像、水印、缩放、对焦与补光。切换单双摄、画质、输出方向、额外画面旋转或声音模式,应先停录并等待视频封装完成;录像启动/停止阶段不能再发起拍照。

切后台、页面隐藏或原生视图脱离窗口时会触发相机关闭与录像收尾。业务页面应处理 recordcomplete,组件卸载后原生绑定会被销毁;返回页面需重新创建或打开会话,不复用已经销毁的 sessionId。

业务自行处理“停止录像并退出”时,应先等待 stopRecording.success 取得完整文件,再处理相册保存和页面跳转。原生关闭逻辑会兜底收尾,但不要依赖已经卸载的页面继续接收事件或更新界面。

十、常见问题与错误处理

现象 / 错误码 处理方式
页面没有画面 检查组件/父容器宽高、是否收到 ready、是否调用并成功完成 openCamera、是否使用 Android App 真机
UNSUPPORTED_DUAL 当前设备没有可用的前后摄并发组合;提示原因并明确选择单摄
UNSUPPORTED_PROFILE 画质、并发组合或编码器不支持;尝试设备检测允许的 720p 配置
CAMERA_PERMISSION_DENIED 检查系统相机权限;被永久拒绝时引导用户到应用权限设置
AUDIO_PERMISSION_DENIED 保留预览;用户明确同意后改为 audio: false,或重新授权麦克风
STORAGE_PERMISSION_DENIED Android 6–9 保存相册时检查存储写权限
BUSY 等待当前打开、拍照、录像启动或收尾操作完成,避免连续重复调用
NOT_OPEN / SESSION_NOT_FOUND 等待相机打开,或使用新 ready 返回的有效 sessionId
CAMERA_TIMEOUT / CAMERA_UNAVAILABLE 检查其他应用是否占用相机、设备资源与系统状态,再重新打开
IO_ERROR 检查可用空间与相册写入结果;保留原文件,处理重试
拍摄成功但相册没有文件 确认业务代码调用了 saveToAlbum 并收到成功回调;拍摄 API 本身只生成私有文件
画面显示区域偏小或有黑边 调整预览容器样式;黑边来自容器与输出比例不同,不要用非等比缩放强行拉伸
蒸汽模式提示不支持选项式脚本 检查全部注册页面及依赖组件,使用 <script setup>
button 不能包含子元素 图形按钮使用 view,文字按钮使用纯文字 button,并在点击处理内检查禁用状态

fail 和组件 error 都应按实际返回的 code / message 提示。不要仅凭 Android 版本或品牌名判断相机能力。

十一、数据处理

摄像头画面、麦克风音频、GPU 合成和文件处理均在设备本地进行。插件不上传拍摄内容、不采集设备标识、不内置广告。

邮箱 lingfugroup@gmail.com。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

CAMERA;有声录像时 RECORD_AUDIO;Android 6–9 保存相册时 WRITE_EXTERNAL_STORAGE。

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

仅在设备本地处理摄像头及麦克风数据,用于合成预览、拍照录像与保存;不上传、不采集设备标识。

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

无

暂无用户评论。