更新记录
1.0.1(2026-09-28)
- 优化鸿蒙端
1.0.0(2026-09-28)
- 新版发布
平台兼容性
uni-app x(5.01)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| × | × | √ | √ | √ | × |
yt-unix-camera
特别提醒
- 购买本插件前,请先试用,请先试用,请先试用,确认满足需求之后再行购买。虚拟物品一旦购买之后无法退款。
- 如有使用上的疑问、bug,可以进交流群联系作者;
- 请在合法范围内使用,若使用本插件做非法开发,本方概不负责;
- 可扫描右侧二维码安装Android示例demo,体验插件功能
- iOS使用示例demo测试时,要先修改manifest中项目名称后再打包(名称长度尽量短一点),不然会因为名称过长,导致App启动不了。
- 插件需先引入再打自定义基座后使用
- 修改插件原生代码(如 UTS / Kotlin / Swift)后,需重新制作自定义调试基座,热更新通常不生效
- 本插件是标准式组件必须在uniappx项目使用,uniapp项目看这里
主要能力:
- 自定义尺寸的原生相机预览
- 普通拍照、拍照后裁剪/旋转编辑
- 视频录制,可选择是否录制麦克风声音、是否保存到相册
- 指定 2160p、1080p、720p、480p 分辨率和目标帧率
- 请求的分辨率/帧率组合不可用时自动降级
- 切换前后摄像头、开关手电筒
- 点击预览对焦、双指缩放、接口控制缩放倍率
- 删除插件生成的沙盒照片或视频文件
- Android 拍照方向兼容处理(读取并校正 EXIF 方向)
运行要求
- HBuilderX 4.31 或更高版本
- uni-app x App Android / App iOS / App HarmonyOS
- 本插件包含原生配置与依赖,调试时应制作自定义基座
- 标准式 UTS 组件不支持旧版 uni-app/nvue 项目
模块目录名、插件 ID 和组件名均为 yt-unix-camera。
最小示例
组件通过 easycom 自动注册,无需手动写入 components:
<template>
<yt-unix-camera
ref="cameraRef"
style="width: 750rpx; height: 1000rpx;"
camera-position="back"
@capturePhotoHandle="onCapturePhoto"
@recordingHandle="onRecordingFinished"
@recordingDurationUpdateHandler="onRecordingDuration"
></yt-unix-camera>
</template>
<script setup lang="uts">
const cameraRef = ref<YtUnixCameraComponentPublicInstance | null>(null)
function takePhoto() {
// 参数一:是否保存到系统相册
// 参数二:是否在拍照后进入编辑页
cameraRef.value?.capturePhotoAction(true, false)
}
function takeEditablePhoto() {
cameraRef.value?.capturePhotoAction(true, true)
}
function onCapturePhoto(event: UniNativeViewEvent) {
// event.detail: { status, msg, path }
console.log(event.detail)
}
function onRecordingFinished(event: UniNativeViewEvent) {
// 指定画质录像完成时还会返回 resolution、frameRate
console.log(event.detail)
}
function onRecordingDuration(event: UniNativeViewEvent) {
// event.detail.durationString,例如 00:00:08
console.log(event.detail)
}
</script>
组件必须设置明确的宽高,否则 native-view 没有显示相机预览的布局区域。
属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
cameraPosition |
string |
back |
初始摄像头;支持 back、front。仅在原生相机创建时读取,运行中请调用切换方法。 |
拍照
普通拍照和可编辑拍照已合并为一个方法:
// 普通拍照
cameraRef.value?.capturePhotoAction(true, false)
// 拍照后显示裁剪/旋转编辑页;完成后仍通过 capturePhotoHandle 返回
cameraRef.value?.capturePhotoAction(true, true)
// 第二个参数默认 false,因此普通拍照也可以只传一个参数
cameraRef.value?.capturePhotoAction(true)
方法签名:
capturePhotoAction(saveToAlbum: boolean, enableEdit: boolean = false): void
saveToAlbum:是否把最终照片同时保存到系统相册。enableEdit:false为普通拍照;true为编辑完成后返回照片。- 编辑页取消后不会触发成功回调,可以继续拍照。
- 照片沙盒路径通过
capturePhotoHandle的event.detail.path返回。
录像
默认录像
第一次调用开始录像,再次调用停止录像:
const saveToAlbum = true
const recordAudio = true
cameraRef.value?.toggleRecordingAction(saveToAlbum, recordAudio)
Android 默认请求 720p、由系统选择帧率;HarmonyOS 默认请求 720p 并优先使用 30fps,设备不支持时自动降级;iOS 沿用 high 会话预设并由系统选择帧率。
指定输出格式
cameraRef.value?.toggleRecordingWithOptions(true, true, 'mp4')
- Android、HarmonyOS 仅支持
mp4。 - iOS 支持
mov、mp4。
指定分辨率和帧率
// 请求 1080p / 60fps;设备不支持时自动降级。
cameraRef.value?.toggleRecordingWithResolution(
true, // 是否保存到相册
true, // 是否录制麦克风
'1080p', // 2160p、1080p、720p、480p
'mp4', // Android/HarmonyOS: mp4;iOS: mov 或 mp4
60 // 0=系统选择;常用 24、30、60
)
resolution 和 frameRate 是目标值,并不保证成片一定使用该组合。插件会优先保留请求帧率并逐级降低分辨率;仍不可用时继续选择设备可用的较低组合。
录像完成后应以回调中的实际参数为准:
function onRecordingFinished(event: UniNativeViewEvent) {
const detail = event.detail
// detail.resolution:实际分辨率档位
// detail.frameRate:实际/目标帧率;0 表示由系统选择
console.log(detail)
}
其他组件方法
页面通过 YtUnixCameraComponentPublicInstance 调用下列公开方法:
| 方法 | 参数 | 说明 |
|---|---|---|
switchCameraAction |
无 | 切换前后摄像头 |
capturePhotoAction |
saveToAlbum, enableEdit=false |
普通/可编辑拍照统一入口 |
toggleTorchAction |
无 | 开关手电筒 |
toggleRecordingAction |
saveToAlbum, recordAudio |
默认参数录像/停止 |
toggleRecordingWithOptions |
saveToAlbum, recordAudio, outputFormat |
指定封装格式录像/停止 |
toggleRecordingWithResolution |
saveToAlbum, recordAudio, resolution, outputFormat, frameRate |
指定画质录像/停止 |
startRunning |
无 | 恢复采集;iOS/HarmonyOS 显式启动,Android 跟随 Activity 生命周期 |
stopRunning |
无 | 暂停采集;iOS/HarmonyOS 显式停止,Android 跟随 Activity 生命周期 |
setZoomFactorAction |
factor, animated |
设置缩放倍率,原生层自动限制到设备范围 |
getZoomFactorAction |
无 | 在控制台输出当前倍率 |
getMinZoomFactorAction |
无 | 在控制台输出设备最小倍率 |
getMaxZoomFactorAction |
无 | 在控制台输出设备最大倍率 |
resetZoomFactorAction |
animated |
恢复 1 倍缩放 |
事件
标准式组件在 Android/iOS/HarmonyOS 使用完全相同的事件字段,不需要条件编译三套处理函数。
注意:不同平台的 event.detail 运行时对象类型可能不同。跨端代码不要调用
event.detail.getString() 或把 detail 强制转换成自定义 class;可直接读取公开字段,或先用
JSON.stringify/JSON.parse 转成纯 UTS type。项目示例采用后一种方式。
capturePhotoHandle
event.detail:
{
status: '0', // 0 成功,1 失败
msg: '拍照成功',
path: '/.../photo.jpg'
}
recordingHandle
event.detail:
{
status: '0',
msg: '录制成功',
path: '/.../video.mp4',
resolution: '1080p', // 指定画质录像时返回实际档位
frameRate: 60 // 0 表示系统选择
}
recordingDurationUpdateHandler
event.detail:
{
durationString: '00:00:08'
}
缩放、对焦和手电筒
cameraRef.value?.switchCameraAction()
cameraRef.value?.toggleTorchAction()
cameraRef.value?.setZoomFactorAction(2.0, true)
cameraRef.value?.resetZoomFactorAction(true)
- 预览区域默认支持单击对焦和双指捏合缩放。
- 前置摄像头或部分设备没有可用闪光灯,调用手电筒方法可能不会改变硬件状态。
- 切换摄像头后,可用分辨率、帧率和缩放范围都可能改变。
删除沙盒文件
import { deleteFile } from '@/uni_modules/yt-unix-camera'
deleteFile({
path: '/absolute/path/photo.jpg',
success: result => {
console.log(result.message)
},
fail: error => {
console.log(error.errMsg)
},
complete: result => {
console.log(result)
}
})
删除的是回调返回的本地沙盒文件;如果拍摄时同时保存到了系统相册,删除沙盒文件不会删除相册副本。
页面生命周期建议
onReady(() => {
cameraRef.value?.startRunning()
})
onShow(() => {
cameraRef.value?.startRunning()
})
onHide(() => {
cameraRef.value?.stopRunning()
})
组件卸载时会自动释放原生相机、录像、编辑页和相关回调,不需要页面手动销毁。
权限
插件原生配置声明了下列权限:
- Android:
android.permission.CAMERA、android.permission.RECORD_AUDIO - iOS:
NSCameraUsageDescription、NSMicrophoneUsageDescription、NSPhotoLibraryAddUsageDescription - HarmonyOS:
ohos.permission.CAMERA、ohos.permission.MICROPHONE
相机权限在组件初始化时申请;录制声音时才会使用麦克风权限。HarmonyOS 保存系统相册使用系统安全资产创建弹窗,不声明 WRITE_IMAGEVIDEO。系统相册权限行为取决于系统版本。
完整示例代码
<template>
<view class="page">
<!--
标准式组件通过 easycom 自动引入,无需在页面 components 中注册。
组件必须设置明确的宽高,否则 native-view 没有可用于显示预览的区域。
-->
<!--
标准组件启用样式隔离后,页面 class 不一定会作用到组件内部根节点。
因此使用普通 view 固定预览区域,再通过内联样式明确设置组件自身宽高,
避免 native-view 被 flex 拉伸并把下面的操作区挤出屏幕。
-->
<view class="camera-container">
<yt-unix-camera
ref="cameraRef"
style="width: 750rpx; height: 760rpx;"
camera-position="back"
@capturePhotoHandle="onCapturePhoto"
@recordingHandle="onRecordingFinished"
@recordingDurationUpdateHandler="onRecordingDuration"
></yt-unix-camera>
</view>
<!-- 录像计时来自原生层,每秒更新一次。 -->
<view v-if="isRecording" class="recording-badge">
<view class="recording-dot"></view>
<text class="recording-time">{{ recordingTime }}</text>
</view>
<scroll-view class="control-panel" scroll-y="true">
<text class="section-title">拍照</text>
<view class="button-row">
<button class="action-button" size="mini" @click="capturePhoto(false)">普通拍照</button>
<button class="action-button" size="mini" @click="capturePhoto(true)">拍照后编辑</button>
</view>
<text class="section-title">录像</text>
<view class="button-row">
<button class="action-button" size="mini" @click="toggleDefaultRecording">
{{ isRecording ? '停止录像' : '默认录像' }}
</button>
<button class="action-button primary-button" size="mini" @click="toggle1080p60Recording">
{{ isRecording ? '停止录像' : '1080p / 60fps' }}
</button>
</view>
<text class="hint">
1080p/60fps 是目标值;设备或当前摄像头不支持时,插件会自动降级,实际参数在录像完成回调中返回。
</text>
<text class="section-title">相机控制</text>
<view class="button-row">
<button class="action-button" size="mini" @click="switchCamera">切换摄像头</button>
<button class="action-button" size="mini" @click="toggleTorch">
{{ torchEnabled ? '关闭手电筒' : '打开手电筒' }}
</button>
</view>
<view class="button-row">
<button class="action-button" size="mini" @click="zoomOut">缩小</button>
<button class="action-button" size="mini" @click="resetZoom">重置 1x</button>
<button class="action-button" size="mini" @click="zoomIn">放大</button>
</view>
<text class="hint">当前缩放:{{ currentZoom }}x;也可以直接在预览区域双指缩放、点击对焦。</text>
<view v-if="lastFilePath.length > 0" class="result-card">
<text class="section-title">最近一次结果</text>
<text class="result-text">{{ lastMessage }}</text>
<text class="path-text">{{ lastFilePath }}</text>
<image
v-if="lastResultIsPhoto"
class="photo-preview"
:src="lastFileUrl"
mode="aspectFit"
></image>
<button class="delete-button" size="mini" @click="deleteLastFile">删除本地文件</button>
</view>
</scroll-view>
</view>
</template>
<script lang="uts">
import { deleteFile } from '@/uni_modules/yt-unix-camera'
/**
* 三端 native-view 事件的可序列化数据结构。
*
* Android 的 event.detail 在运行时可能是 UTSJSONObject,iOS 则是普通对象;不能直接调用
* getString/getNumber,也不能强制转换成自定义 class。统一 JSON 序列化后再解析成 type,
* 可以同时兼容 Android、iOS 和 HarmonyOS。
*/
type CameraNativeEventDetail = {
status?: string
msg?: string
path?: string
resolution?: string
frameRate?: number
durationString?: string
albumUri?: string
}
export default {
data() {
return {
isRecording: false,
recordingTime: '00:00:00',
torchEnabled: false,
currentZoom: 1.0,
lastFilePath: '',
lastFileUrl: '',
lastMessage: '尚无结果',
lastResultIsPhoto: false
}
},
/** 页面首次渲染完成后确保 iOS 采集会话处于运行状态。 */
onReady() {
(this.$refs['cameraRef'] as YtUnixCameraComponentPublicInstance | null)?.startRunning()
},
/** 页面重新显示时恢复预览。Android 由 Activity 生命周期自动恢复。 */
onShow() {
(this.$refs['cameraRef'] as YtUnixCameraComponentPublicInstance | null)?.startRunning()
},
/** 页面隐藏时停止 iOS 采集,避免后台继续占用摄像头。 */
onHide() {
(this.$refs['cameraRef'] as YtUnixCameraComponentPublicInstance | null)?.stopRunning()
},
methods: {
/**
* 普通拍照和可编辑拍照已经合并为同一个方法。
* enableEdit=false:普通拍照;enableEdit=true:拍照后进入编辑页。
*/
capturePhoto(enableEdit: boolean) {
const saveToAlbum = true
;(this.$refs['cameraRef'] as YtUnixCameraComponentPublicInstance | null)?.capturePhotoAction(
saveToAlbum,
enableEdit
)
},
/** 兼容旧录像调用:不指定分辨率和帧率。再次调用同一方法会停止录像。 */
toggleDefaultRecording() {
const saveToAlbum = true
const recordAudio = true
this.isRecording = !this.isRecording
;(this.$refs['cameraRef'] as YtUnixCameraComponentPublicInstance | null)?.toggleRecordingAction(
saveToAlbum,
recordAudio
)
},
/**
* 请求 1080p/60fps 录像。
* Android 仅支持 mp4;iOS 同时支持 mov/mp4,所以跨端示例统一使用 mp4。
* 设备不支持 1080p+60fps 组合时会自动降级,不能只用请求值判断成片参数。
*/
toggle1080p60Recording() {
const saveToAlbum = true
const recordAudio = true
const resolution = '1080p'
const outputFormat = 'mp4'
const frameRate = 60
this.isRecording = !this.isRecording
;(this.$refs['cameraRef'] as YtUnixCameraComponentPublicInstance | null)?.toggleRecordingWithResolution(
saveToAlbum,
recordAudio,
resolution,
outputFormat,
frameRate
)
},
switchCamera() {
;(this.$refs['cameraRef'] as YtUnixCameraComponentPublicInstance | null)?.switchCameraAction()
// 切换摄像头后不假设新摄像头也有手电筒,重置页面显示状态。
this.torchEnabled = false
},
toggleTorch() {
;(this.$refs['cameraRef'] as YtUnixCameraComponentPublicInstance | null)?.toggleTorchAction()
this.torchEnabled = !this.torchEnabled
},
/** 示例把按钮缩放范围限制为 1x~5x;原生层仍会按设备真实范围再次限制。 */
zoomIn() {
this.currentZoom = Math.min(this.currentZoom + 0.5, 5.0)
;(this.$refs['cameraRef'] as YtUnixCameraComponentPublicInstance | null)?.setZoomFactorAction(
this.currentZoom,
true
)
},
zoomOut() {
this.currentZoom = Math.max(this.currentZoom - 0.5, 1.0)
;(this.$refs['cameraRef'] as YtUnixCameraComponentPublicInstance | null)?.setZoomFactorAction(
this.currentZoom,
true
)
},
resetZoom() {
this.currentZoom = 1.0
;(this.$refs['cameraRef'] as YtUnixCameraComponentPublicInstance | null)?.resetZoomFactorAction(true)
},
/**
* 将 native-view 的平台运行时对象转换成纯 UTS type。
* JSON 转换只包含字符串/数字字段,不会携带任何平台原生实例。
*/
parseEventDetail(event: UniNativeViewEvent): CameraNativeEventDetail {
try {
const detail = JSON.parse<CameraNativeEventDetail>(JSON.stringify(event.detail))
if (detail != null) {
return detail
}
const emptyDetail: CameraNativeEventDetail = {}
return emptyDetail
} catch (_error) {
return {}
}
},
/** 保存最近结果,并用 toast 告知成功或失败。 */
showResult(detail: CameraNativeEventDetail, isPhoto: boolean) {
const status = detail.status ?? '1'
const message = detail.msg ?? '操作失败'
const path = detail.path ?? ''
this.lastMessage = message
this.lastResultIsPhoto = isPhoto && status == '0'
if (path.length > 0) {
this.lastFilePath = path
this.lastFileUrl = path.startsWith('file://') ? path : `file://${path}`
}
uni.showToast({
title: message,
icon: status == '0' ? 'success' : 'none'
})
},
/** 三端事件先经过 JSON 标准化,避免依赖某个平台的 detail 运行时类型。 */
onCapturePhoto(event: UniNativeViewEvent) {
this.showResult(this.parseEventDetail(event), true)
},
onRecordingFinished(event: UniNativeViewEvent) {
const detail = this.parseEventDetail(event)
this.isRecording = false
this.recordingTime = '00:00:00'
this.showResult(detail, false)
const status = detail.status ?? '1'
const resolution = detail.resolution ?? ''
if (status == '0' && resolution.length > 0) {
const actualFrameRate = detail.frameRate ?? 0
const frameRateText = actualFrameRate == 0 ? '系统默认帧率' : `${actualFrameRate}fps`
console.log(`实际录制参数:${resolution}/${frameRateText}`)
}
},
onRecordingDuration(event: UniNativeViewEvent) {
this.recordingTime = this.parseEventDetail(event).durationString ?? '00:00:00'
},
/** deleteFile 是模块 API,可删除拍照或录像回调返回的沙盒文件。 */
deleteLastFile() {
if (this.lastFilePath.length == 0) return
deleteFile({
path: this.lastFilePath,
success: result => {
uni.showToast({ title: result.message, icon: 'success' })
this.lastFilePath = ''
this.lastFileUrl = ''
this.lastResultIsPhoto = false
},
fail: error => {
uni.showToast({ title: error.errMsg, icon: 'none' })
}
})
}
}
}
</script>
<style>
.page {
flex: 1;
flex-direction: column;
background-color: #111827;
}
.camera-container {
width: 750rpx;
height: 760rpx;
flex-shrink: 0;
background-color: #000000;
}
.recording-badge {
position: absolute;
top: 24rpx;
left: 275rpx;
width: 200rpx;
height: 64rpx;
flex-direction: row;
align-items: center;
justify-content: center;
border-radius: 32rpx;
background-color: rgba(0, 0, 0, 0.65);
}
.recording-dot {
width: 18rpx;
height: 18rpx;
margin-right: 14rpx;
border-radius: 9rpx;
background-color: #ef4444;
}
.recording-time {
font-size: 28rpx;
color: #ffffff;
}
.control-panel {
flex: 1;
width: 750rpx;
padding: 24rpx;
background-color: #f3f4f6;
}
.section-title {
margin-top: 18rpx;
margin-bottom: 14rpx;
font-size: 30rpx;
font-weight: 600;
color: #111827;
}
.button-row {
width: 702rpx;
margin-bottom: 14rpx;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.action-button {
min-width: 206rpx;
margin: 0;
color: #111827;
background-color: #ffffff;
}
.primary-button {
color: #ffffff;
background-color: #2563eb;
}
.hint {
margin-bottom: 18rpx;
font-size: 24rpx;
line-height: 36rpx;
color: #6b7280;
}
.result-card {
margin-top: 18rpx;
margin-bottom: 80rpx;
padding: 20rpx;
border-radius: 16rpx;
background-color: #ffffff;
}
.result-text {
font-size: 26rpx;
color: #111827;
}
.path-text {
margin-top: 10rpx;
font-size: 22rpx;
line-height: 30rpx;
color: #6b7280;
}
.photo-preview {
width: 662rpx;
height: 360rpx;
margin-top: 18rpx;
background-color: #111827;
}
.delete-button {
margin-top: 18rpx;
margin-left: 0;
color: #ffffff;
background-color: #dc2626;
}
</style>
更多好用实惠插件
- 高德定位连续定位后台定位保活定位
- 百度汽车摩托车导航插件
- 百度鹰眼轨迹插件支持后台采集、保活
- 百度定位插件、连续定位、保活、坐标系转换、支持双端
- 计步器插件,支持Android、iOS双端
- uts经典蓝牙插件、蓝牙电子秤
- 获取唯一标识、ServiceID、卸载更新不变iOS+Android
- Android经典蓝牙
- 华为ScanKit统一扫码插件支持iOS+Android原生插件
- 【华为扫码】统一扫码插件支持多码连续扫码支持半屏扫码uts插件iOS+Android+HarmonyOS
- 截屏、录屏、防截屏、录屏iOS、Android
- 人脸采集插件 最新百度SDK 离线人脸采集、活体检测
- 页面截长图、截取WebView内容,生成长截图Android+iOS
- 百度OCR识别插件
- Android无预览拍照、录制、静默拍照、静默录制、抓拍插件支持
- uni高德地图功能拓展地图截图
- 科大讯飞离线合成插件支持iOS、android
- iOS保活Android保活鸿蒙保活定位插件系统定位
- 高德定位、猎鹰轨迹插件
- 海康威视综合安防平台视频播放插件
- 支持NFC读写功能检测支持Android iOS HarmonyOS

收藏人数:
购买源码授权版(
试用
赞赏(0)
下载 634
赞赏 15
下载 12643877
赞赏 1951
赞赏
京公网安备:11010802035340号