Skip to content

SDK Open 快速集成

本页给出可直接迁移到 Web/Electron 应用的 TypeScript 片段。

打开本地演示站点

已安装 runtime 时,可访问 http://127.0.0.1:17081 打开本地演示站点。在演示站点中填入自行申请的 API Key 并创建会话后,可体验该 API Key 已授权的部分 SDK 能力。

可调用的方法和剩余额度以会话返回的 capabilitiesquota_buckets 为准;未授权或额度不足的能力不会被受理。

构造 Command 请求

ts
export function buildCommandRequest(method: string, params: Record<string, unknown> = {}) {
  return {
    request_id: `${method.replace(/[^a-z0-9]+/gi, '-')}-${Date.now()}`,
    method,
    params,
    client: {
      source: 'your-app',
      protocol_version: '2.0.0',
      trace_id: `trc-${Date.now()}`
    }
  }
}

连接、授权和请求映射

ts
type Pending = {
  resolve: (value: any) => void
  reject: (reason: Error) => void
}

const command = new WebSocket('wss://sdk-runtime.localhost:18090')
const pending = new Map<string, Pending>()
let sessionToken = ''

function send<T>(method: string, params: Record<string, unknown> = {}): Promise<T> {
  const request = buildCommandRequest(method, params)
  command.send(JSON.stringify(request))
  return new Promise((resolve, reject) => {
    pending.set(request.request_id, { resolve, reject })
  })
}

command.addEventListener('message', (event) => {
  const payload = JSON.parse(event.data)
  if (payload.event) {
    console.log('runtime event', payload.event, payload.payload)
    return
  }
  const slot = pending.get(payload.request_id)
  if (!slot) return
  pending.delete(payload.request_id)
  payload.code === 0 ? slot.resolve(payload.data) : slot.reject(new Error(payload.message))
})

command.addEventListener('open', async () => {
  await send('system.ping')
  const auth = await send<{ session_token: string }>('auth.create_session', {
    token: 'sk-sq-v1-...'
  })
  sessionToken = auth.session_token
})

打开设备并启动视频

ts
const list = await send<{ devices: Array<{ device_id: string }> }>('device.list')
const deviceId = list.devices[0].device_id

await send('device.open', {
  device_id: deviceId,
  width: 1280,
  height: 720,
  fps: 15,
  pixel_format: 'mjpeg'
})

const stream = await send<{
  stream_id: string
  session_token: string
  width: number
  height: number
}>('video.start', {
  device_id: deviceId,
  width: 1280,
  height: 720,
  fps: 15,
  pixel_format: 'mjpeg'
})

渲染 MJPEG 视频帧

ts
const video = new WebSocket(
  `wss://sdk-runtime.localhost:18091?session_token=${encodeURIComponent(stream.session_token)}&stream_id=${encodeURIComponent(stream.stream_id)}`
)
video.binaryType = 'arraybuffer'

let frameMeta: { width: number; height: number; pixel_format: string } | null = null
const ctx = canvas.getContext('2d')

async function drawJpegFrame(buffer: ArrayBuffer) {
  if (!ctx || !frameMeta || frameMeta.pixel_format !== 'mjpeg') return
  const blob = new Blob([buffer], { type: 'image/jpeg' })
  if ('createImageBitmap' in window) {
    const bitmap = await createImageBitmap(blob)
    canvas.width = frameMeta.width || bitmap.width
    canvas.height = frameMeta.height || bitmap.height
    ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height)
    bitmap.close()
    return
  }

  const url = URL.createObjectURL(blob)
  const image = new Image()
  image.onload = () => {
    canvas.width = frameMeta?.width || image.naturalWidth
    canvas.height = frameMeta?.height || image.naturalHeight
    ctx.drawImage(image, 0, 0, canvas.width, canvas.height)
    URL.revokeObjectURL(url)
  }
  image.onerror = () => URL.revokeObjectURL(url)
  image.src = url
}

video.addEventListener('message', async (event) => {
  if (typeof event.data === 'string') {
    const parsed = JSON.parse(event.data)
    if (parsed.event === 'stream.frame_meta') frameMeta = parsed.payload
    return
  }

  await drawJpegFrame(event.data)
})

上传图片并轮询任务

ts
async function upload(file: File) {
  const form = new FormData()
  form.set('file', file)
  const res = await fetch('https://sdk-runtime.localhost:18082/api/uploads/images', {
    method: 'POST',
    headers: { Authorization: `Bearer ${sessionToken}` },
    body: form
  })
  return res.json() as Promise<{ upload_id: string }>
}

async function pollTask(taskId: string) {
  for (;;) {
    const data = await send<{ task: { status: string; progress: number } }>('image.enhance_get', { task_id: taskId })
    if (['completed', 'failed', 'cancelled'].includes(data.task.status)) return data.task
    await new Promise((resolve) => setTimeout(resolve, 800))
  }
}

CZUR Open Platform Documentation