用前置摄像头 + VisionKit/ROI 管线驱动开合计数,复用跟练舞台与连击粒子,并加固结束收尾避免末段卡死无响应。 Co-authored-by: Cursor <cursoragent@cursor.com>
108 lines
2.8 KiB
TypeScript
108 lines
2.8 KiB
TypeScript
import type { CameraFrame } from './types'
|
|
|
|
type FrameListener = {
|
|
start: () => void
|
|
stop: () => void
|
|
}
|
|
|
|
type CameraContextLike = {
|
|
onCameraFrame?: (cb: (frame: { width: number; height: number; data: ArrayBuffer }) => void) => FrameListener
|
|
}
|
|
|
|
function createContext(cameraId?: string): CameraContextLike | null {
|
|
try {
|
|
// uni-app 优先:可按 camera 组件 id 绑定
|
|
if (typeof uni !== 'undefined' && typeof uni.createCameraContext === 'function') {
|
|
return uni.createCameraContext(cameraId || 'gripCamera') as CameraContextLike
|
|
}
|
|
} catch (_) {}
|
|
|
|
// #ifdef MP-WEIXIN
|
|
try {
|
|
// @ts-ignore
|
|
if (typeof wx !== 'undefined' && typeof wx.createCameraContext === 'function') {
|
|
// @ts-ignore
|
|
return wx.createCameraContext() as CameraContextLike
|
|
}
|
|
} catch (_) {}
|
|
// #endif
|
|
return null
|
|
}
|
|
|
|
export function isCameraFrameSupported(): boolean {
|
|
try {
|
|
const ctx = createContext('gripCamera')
|
|
return typeof ctx?.onCameraFrame === 'function'
|
|
} catch (_) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 管理 CameraContext.onCameraFrame:只保留最新一帧,不入 Vue 响应式。
|
|
*/
|
|
export function createCameraFramePump(options?: { cameraId?: string }) {
|
|
let listener: FrameListener | null = null
|
|
let latest: CameraFrame | null = null
|
|
let running = false
|
|
let callbackCount = 0
|
|
let fpsWindowStart = Date.now()
|
|
let fps = 0
|
|
let onFrame: ((frame: CameraFrame) => void) | null = null
|
|
|
|
function start(cb: (frame: CameraFrame) => void) {
|
|
stop()
|
|
onFrame = cb
|
|
const ctx = createContext(options?.cameraId || 'gripCamera')
|
|
if (!ctx?.onCameraFrame) {
|
|
throw new Error('当前基础库不支持 onCameraFrame')
|
|
}
|
|
|
|
listener = ctx.onCameraFrame((raw) => {
|
|
if (!running) return
|
|
const frame: CameraFrame = {
|
|
width: raw.width,
|
|
height: raw.height,
|
|
data: raw.data,
|
|
ts: Date.now(),
|
|
}
|
|
latest = frame
|
|
callbackCount += 1
|
|
const now = Date.now()
|
|
if (now - fpsWindowStart >= 1000) {
|
|
fps = callbackCount
|
|
callbackCount = 0
|
|
fpsWindowStart = now
|
|
}
|
|
onFrame?.(frame)
|
|
})
|
|
running = true
|
|
listener.start()
|
|
}
|
|
|
|
function stop() {
|
|
running = false
|
|
try {
|
|
listener?.stop()
|
|
} catch (_) {}
|
|
listener = null
|
|
onFrame = null
|
|
}
|
|
|
|
function getLatest() {
|
|
return latest
|
|
}
|
|
|
|
function getFps() {
|
|
return fps
|
|
}
|
|
|
|
function isRunning() {
|
|
return running
|
|
}
|
|
|
|
return { start, stop, getLatest, getFps, isRunning }
|
|
}
|
|
|
|
export type CameraFramePump = ReturnType<typeof createCameraFramePump>
|