101 lines
3.4 KiB
JavaScript
101 lines
3.4 KiB
JavaScript
/**
|
|
* webcamClient.js
|
|
* Kapselt alle Zugriffe auf den WebCam-Service.
|
|
* Basis-URL kommt von aussen (WEBCAM_URL) — kein Hartkodieren hier.
|
|
*
|
|
* Verwendung:
|
|
* import { WebcamClient } from './webcamClient.js';
|
|
* const wc = new WebcamClient(process.env.WEBCAM_URL);
|
|
* const cameras = await wc.getCameras();
|
|
* const response = await wc.getSnapshot('cam0'); // Response-Objekt, JPEG-Body
|
|
*/
|
|
|
|
const TIMEOUT_MS = 15_000;
|
|
|
|
export class WebcamClient {
|
|
/** @param {string} baseUrl z.B. "http://appRobotWebcam:8444" */
|
|
constructor(baseUrl) {
|
|
if (!baseUrl) throw new Error('WebcamClient: baseUrl ist erforderlich');
|
|
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
}
|
|
|
|
/**
|
|
* Liste aller Kameras mit Metadaten.
|
|
* `calibrationUrl` ist enthalten, wenn eine .npz unter data/calibration/{id}/ liegt.
|
|
* @returns {Promise<{cameras: CameraMeta[]}>}
|
|
*/
|
|
async getCameras() {
|
|
const res = await this.#get('/api/cameras');
|
|
if (!res.ok) throw new Error(`getCameras: HTTP ${res.status}`);
|
|
return res.json();
|
|
}
|
|
|
|
/**
|
|
* HD-JPEG einer Kamera als fetch-Response.
|
|
* Caller kann res.body direkt pipen (kein Buffering).
|
|
* @param {string} id Kamera-ID, z.B. "cam0"
|
|
* @param {boolean} hires true = /hires (Default), false = Live-Auflösung
|
|
* @returns {Promise<Response>}
|
|
*/
|
|
async getSnapshot(id, hires = true) {
|
|
const path = hires ? `/api/snapshot/${id}/hires` : `/api/snapshot/${id}`;
|
|
const res = await this.#get(path);
|
|
if (!res.ok) throw new Error(`getSnapshot(${id}): HTTP ${res.status}`);
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Kalibrierungsdatei (.npz) als ArrayBuffer.
|
|
* Kann direkt an den BodyTracker weitergereicht werden.
|
|
* Wirft bei 404 (noch keine Kalibrierung vorhanden).
|
|
* @param {string} id Kamera-ID
|
|
* @returns {Promise<ArrayBuffer>}
|
|
*/
|
|
async getCalibration(id) {
|
|
const res = await this.#get(`/api/cameras/${id}/calibration`);
|
|
if (res.status === 404) throw new Error(`Keine Kalibrierung für Kamera "${id}"`);
|
|
if (!res.ok) throw new Error(`getCalibration(${id}): HTTP ${res.status}`);
|
|
return res.arrayBuffer();
|
|
}
|
|
|
|
/**
|
|
* Gesundheitsstatus des WebCam-Service inkl. Kamera-Zustände.
|
|
* @returns {Promise<{status: string, cameras: CameraState[]}>}
|
|
*/
|
|
async health() {
|
|
const res = await this.#get('/health');
|
|
if (!res.ok) throw new Error(`health: HTTP ${res.status}`);
|
|
return res.json();
|
|
}
|
|
|
|
// ── intern ──────────────────────────────────────────────────────────────────
|
|
|
|
#get(path, options = {}) {
|
|
const url = `${this.baseUrl}${path}`;
|
|
return fetch(url, {
|
|
...options,
|
|
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @typedef {Object} CameraMeta
|
|
* @property {string} id
|
|
* @property {string} name
|
|
* @property {string} position "front" | "left" | "right"
|
|
* @property {boolean} stream
|
|
* @property {boolean} hires
|
|
* @property {string} encode
|
|
* @property {string|null} mseCodec
|
|
* @property {string} note Hardware-Serial (stable key)
|
|
* @property {string|null} [calibrationUrl] vorhanden wenn .npz existiert
|
|
*
|
|
* @typedef {Object} CameraState
|
|
* @property {string} id
|
|
* @property {string} name
|
|
* @property {string} device
|
|
* @property {string} state "running" | "idle" | "stopping"
|
|
* @property {boolean} hasFrame
|
|
*/
|