更新记录

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

隐私、权限声明

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

iOS: NSCameraUsageDescription, NSMicrophoneUsageDescription, NSPhotoLibraryAddUsageDescription; Android: 相机、麦克风、存储权限

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

插件不采集任何数据

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

暂无用户评论。