更新记录
1.0.1(2026-02-16)
新功能
- 自定义相机预览
- 拍照功能(支持质量配置)
- 视频录制(支持时长限制)
- 前后摄像头切换
- 闪光灯控制
- 音频处理:回声消除、噪声抑制、自动增益控制
- 线程安全的状态管理
- iOS/Android 双平台支持
1.0.0(2026-02-16)
新功能
- 自定义相机预览
- 拍照功能(支持质量配置)
- 视频录制(支持时长限制)
- 前后摄像头切换
- 闪光灯控制
- 音频处理:回声消除、噪声抑制、自动增益控制
- 线程安全的状态管理
平台兼容性
uni-app(4.81)
| Vue2 |
Vue3 |
Chrome |
Safari |
app-vue |
app-nvue |
Android |
iOS |
鸿蒙 |
| √ |
√ |
- |
- |
× |
√ |
√ |
√ |
- |
| 微信小程序 |
支付宝小程序 |
抖音小程序 |
百度小程序 |
快手小程序 |
京东小程序 |
鸿蒙元服务 |
QQ小程序 |
飞书小程序 |
小红书小程序 |
快应用-华为 |
快应用-联盟 |
| - |
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
rd-camera 自定义相机插件
高性能自定义相机 UTS 插件,支持 iOS 和 Android 平台。
功能特性
- 自定义相机预览
- 拍照功能(支持质量配置)
- 视频录制(支持时长限制)
- 前后摄像头切换
- 闪光灯控制
- 音频处理:回声消除、噪声抑制、自动增益控制
- 线程安全的状态管理
- iOS/Android 双平台支持
安装
将 uni_modules/rd-camera 目录复制到项目的 uni_modules 目录下。
使用方法
请查看 changelog.md 获取完整的使用示例。
权限要求
iOS
NSCameraUsageDescription - 相机权限
NSMicrophoneUsageDescription - 麦克风权限
NSPhotoLibraryAddUsageDescription - 相册保存权限
Android
CAMERA - 相机权限
RECORD_AUDIO - 录音权限
WRITE_EXTERNAL_STORAGE - 存储权限 (Android 9 及以下)
API
事件
| 事件名 |
说明 |
onCameraReady |
相机初始化完成 |
onPhotoCaptured |
拍照完成 |
onRecordingFinished |
录制完成 |
onRecordingDurationUpdate |
录制时长更新 |
onError |
错误回调 |
方法
| 方法名 |
说明 |
capturePhoto(saveToAlbum, quality) |
拍照 |
startRecording(...) |
开始录制 |
stopRecording() |
停止录制 |
getRecordingStatus() |
获取录制状态 |
switchCamera() |
切换摄像头 |
toggleFlash() |
切换闪光灯 |
setFlashMode(mode) |
设置闪光灯模式 |
平台特性
| 特性 |
iOS |
Android |
| 相机预览 |
✅ AVCaptureSession |
✅ CameraX |
| 拍照 |
✅ AVCapturePhotoOutput |
✅ ImageCapture |
| 视频录制 |
✅ AVCaptureMovieFileOutput |
✅ VideoCapture |
| 线程安全 |
✅ os_unfair_lock |
✅ AtomicBoolean |
| 音频处理 |
✅ AVAudioSession |
✅ AudioEffect API |
使用示例
Vue2 使用示例
<template>
<view class="container">
<!-- 相机组件 -->
<rd-camera
ref="camera"
class="camera-preview"
@onCameraReady="onCameraReady"
@onPhotoCaptured="onPhotoCaptured"
@onRecordingFinished="onRecordingFinished"
@onError="onError"
@onRecordingDurationUpdate="onRecordingDurationUpdate"
/>
<!-- 控制按钮 -->
<view class="controls">
<button @click="capturePhoto">拍照</button>
<button @click="startRecording">开始录制</button>
<button @click="stopRecording">停止录制</button>
<button @click="switchCamera">切换摄像头</button>
<button @click="toggleFlash">闪光灯</button>
</view>
<!-- 录制时长显示 -->
<text v-if="isRecording">录制中: {{ recordingDuration }}秒</text>
</view>
</template>
<script>
export default {
data() {
return {
isRecording: false,
recordingDuration: 0,
isCameraReady: false
}
},
methods: {
// 相机就绪回调
onCameraReady(e) {
console.log('相机已就绪', e.detail.message)
this.isCameraReady = true
},
// 拍照
capturePhoto() {
if (!this.isCameraReady) {
uni.showToast({ title: '相机未就绪', icon: 'none' })
return
}
// saveToAlbum: 是否保存到相册
// quality: "low" | "medium" | "high" | "photo"
this.$refs.camera.capturePhoto(true, 'high')
},
// 拍照结果回调
onPhotoCaptured(e) {
const { success, message, path } = e.detail
if (success) {
console.log('拍照成功,路径:', path)
uni.showToast({ title: '拍照成功', icon: 'success' })
} else {
console.error('拍照失败:', message)
uni.showToast({ title: message, icon: 'none' })
}
},
// 开始录制
startRecording() {
if (!this.isCameraReady) {
uni.showToast({ title: '相机未就绪', icon: 'none' })
return
}
// 检查是否正在录制
if (this.$refs.camera.getRecordingStatus()) {
uni.showToast({ title: '正在录制中', icon: 'none' })
return
}
this.recordingDuration = 0
// 参数说明:
// saveToAlbum: 是否保存到相册
// quality: "low" | "medium" | "high" | "photo"
// recordAudio: 是否录制音频
// maxDuration: 最大时长(秒),0表示不限制
// echoCancellation: 回声消除
// noiseSuppression: 噪声抑制
// autoGainControl: 自动增益控制
this.$refs.camera.startRecording(
true, // saveToAlbum
'high', // quality
true, // recordAudio
60, // maxDuration (60秒)
true, // echoCancellation
true, // noiseSuppression
true // autoGainControl
)
this.isRecording = true
},
// 停止录制
stopRecording() {
this.$refs.camera.stopRecording()
this.isRecording = false
},
// 录制完成回调
onRecordingFinished(e) {
const { success, message, path } = e.detail
this.isRecording = false
if (success) {
console.log('录制成功,路径:', path)
uni.showToast({ title: '录制成功', icon: 'success' })
} else {
console.error('录制失败:', message)
uni.showToast({ title: message, icon: 'none' })
}
},
// 录制时长更新
onRecordingDurationUpdate(e) {
this.recordingDuration = Math.floor(e.detail.duration)
},
// 切换摄像头
switchCamera() {
this.$refs.camera.switchCamera()
},
// 切换闪光灯
toggleFlash() {
const result = this.$refs.camera.toggleFlash()
uni.showToast({
title: result ? '闪光灯已开启' : '闪光灯已关闭',
icon: 'none'
})
},
// 错误回调
onError(e) {
console.error('相机错误:', e.detail.message)
uni.showToast({ title: e.detail.message, icon: 'none' })
}
}
}
</script>
<style>
.container {
flex: 1;
}
.camera-preview {
width: 100%;
height: 500rpx;
}
.controls {
display: flex;
flex-wrap: wrap;
justify-content: center;
padding: 20rpx;
}
.controls button {
margin: 10rpx;
font-size: 28rpx;
}
</style>
Vue3 使用示例
<template>
<view class="container">
<!-- 相机组件 -->
<rd-camera
ref="cameraRef"
class="camera-preview"
@onCameraReady="onCameraReady"
@onPhotoCaptured="onPhotoCaptured"
@onRecordingFinished="onRecordingFinished"
@onError="onError"
@onRecordingDurationUpdate="onRecordingDurationUpdate"
/>
<!-- 控制按钮 -->
<view class="controls">
<button @click="capturePhoto">拍照</button>
<button @click="handleStartRecording">
{{ isRecording ? '录制中...' : '开始录制' }}
</button>
<button @click="handleStopRecording" :disabled="!isRecording">停止录制</button>
<button @click="switchCamera">切换摄像头</button>
<button @click="toggleFlash">闪光灯</button>
</view>
<!-- 录制时长显示 -->
<text v-if="isRecording" class="duration-text">
录制中: {{ recordingDuration }}秒
</text>
<!-- 预览图 -->
<image v-if="lastPhotoPath" :src="lastPhotoPath" class="preview-image" mode="aspectFit" />
</view>
</template>
<script setup>
import { ref } from 'vue'
// refs
const cameraRef = ref(null)
const isRecording = ref(false)
const recordingDuration = ref(0)
const isCameraReady = ref(false)
const lastPhotoPath = ref('')
// 相机就绪
const onCameraReady = (e) => {
console.log('相机已就绪', e.detail.message)
isCameraReady.value = true
}
// 拍照
const capturePhoto = () => {
if (!isCameraReady.value) {
uni.showToast({ title: '相机未就绪', icon: 'none' })
return
}
cameraRef.value.capturePhoto(true, 'high')
}
// 拍照结果
const onPhotoCaptured = (e) => {
const { success, message, path } = e.detail
if (success) {
console.log('拍照成功,路径:', path)
lastPhotoPath.value = path
uni.showToast({ title: '拍照成功', icon: 'success' })
} else {
console.error('拍照失败:', message)
uni.showToast({ title: message, icon: 'none' })
}
}
// 开始录制
const handleStartRecording = () => {
if (!isCameraReady.value) {
uni.showToast({ title: '相机未就绪', icon: 'none' })
return
}
if (cameraRef.value.getRecordingStatus()) {
uni.showToast({ title: '正在录制中', icon: 'none' })
return
}
recordingDuration.value = 0
// 完整参数示例
cameraRef.value.startRecording(
true, // saveToAlbum - 保存到相册
'high', // quality - 视频质量
true, // recordAudio - 录制音频
60, // maxDuration - 最大60秒
true, // echoCancellation - 回声消除
true, // noiseSuppression - 噪声抑制
true // autoGainControl - 自动增益控制
)
isRecording.value = true
}
// 停止录制
const handleStopRecording = () => {
cameraRef.value.stopRecording()
}
// 录制完成
const onRecordingFinished = (e) => {
const { success, message, path } = e.detail
isRecording.value = false
if (success) {
console.log('录制成功,路径:', path)
uni.showToast({ title: '录制成功', icon: 'success' })
} else {
console.error('录制失败:', message)
uni.showToast({ title: message, icon: 'none' })
}
}
// 录制时长更新
const onRecordingDurationUpdate = (e) => {
recordingDuration.value = Math.floor(e.detail.duration)
}
// 切换摄像头
const switchCamera = () => {
cameraRef.value.switchCamera()
}
// 闪光灯
const toggleFlash = () => {
const result = cameraRef.value.toggleFlash()
uni.showToast({
title: result ? '闪光灯已开启' : '闪光灯已关闭',
icon: 'none'
})
}
// 错误处理
const onError = (e) => {
console.error('相机错误:', e.detail.message)
uni.showToast({ title: e.detail.message, icon: 'none' })
}
</script>
<style scoped>
.container {
flex: 1;
background-color: #000;
}
.camera-preview {
width: 100%;
height: 500rpx;
}
.controls {
display: flex;
flex-wrap: wrap;
justify-content: center;
padding: 20rpx;
background-color: #fff;
}
.controls button {
margin: 10rpx;
font-size: 28rpx;
padding: 15rpx 30rpx;
}
.duration-text {
color: #fff;
text-align: center;
padding: 20rpx;
font-size: 32rpx;
}
.preview-image {
width: 100%;
height: 300rpx;
margin-top: 20rpx;
}
</style>
API 文档
事件 (Events)
| 事件名 |
说明 |
回调参数 |
onCameraReady |
相机初始化完成 |
{ detail: { message } } |
onPhotoCaptured |
拍照完成 |
{ detail: { success, message, path } } |
onRecordingFinished |
录制完成 |
{ detail: { success, message, path } } |
onRecordingDurationUpdate |
录制时长更新 |
{ detail: { duration } } |
onError |
错误回调 |
{ detail: { message } } |
方法 (Methods)
| 方法名 |
参数 |
说明 |
capturePhoto(saveToAlbum, quality) |
saveToAlbum: boolean, quality: string |
拍照 |
startRecording(saveToAlbum, quality, recordAudio, maxDuration, echoCancellation, noiseSuppression, autoGainControl) |
见下方说明 |
开始录制 |
stopRecording() |
- |
停止录制 |
getRecordingStatus() |
- |
获取录制状态 |
switchCamera() |
- |
切换摄像头 |
toggleFlash() |
- |
切换闪光灯 |
setFlashMode(mode) |
mode: "on" | "off" |
设置闪光灯模式 |
startCamera() |
- |
启动相机 |
stopCamera() |
- |
停止相机 |
参数说明
quality (质量)
"low" - 低质量
"medium" - 中等质量
"high" - 高质量
"photo" - 照片质量
| startRecording 参数 |
参数 |
类型 |
说明 |
| saveToAlbum |
boolean |
是否保存到相册 |
| quality |
string |
视频质量 |
| recordAudio |
boolean |
是否录制音频 |
| maxDuration |
number |
最大时长(秒),0表示不限制 |
| echoCancellation |
boolean |
回声消除 |
| noiseSuppression |
boolean |
噪声抑制 |
| autoGainControl |
boolean |
自动增益控制 |
权限要求
iOS
在 Info.plist 中添加以下权限描述:
<key>NSCameraUsageDescription</key>
<string>需要访问相机进行拍照和录制视频</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要访问麦克风录制视频声音</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>需要访问相册保存照片和视频</string>
Android
在 AndroidManifest.xml 中添加以下权限:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
技术实现
iOS (Swift)
- 使用
AVCaptureSession 进行相机预览
AVCapturePhotoOutput 拍照
AVCaptureMovieFileOutput 视频录制
os_unfair_lock 实现线程安全
AVAudioSession 音频处理配置
Android (Kotlin)
- 使用
CameraX 进行相机预览
ImageCapture 拍照
VideoCapture 视频录制
AtomicBoolean / AtomicReference 实现线程安全
- 支持
AcousticEchoCanceler / NoiseSuppressor / AutomaticGainControl