camstreamerlib
Version:
Helper library for CamStreamer ACAP applications.
206 lines (205 loc) • 9.22 kB
JavaScript
import { z } from 'zod';
import { ProxyClient } from './internal/ProxyClient';
import { audioFileListSchema, storageListSchema, streamSchema, } from './types/CamStreamerAPI/CamStreamerAPI';
import { ErrorWithResponse, UtcTimeFetchError, WsAuthorizationError, MigrationError, ParsingBlobError, } from './errors/errors';
import { oldStringStreamSchema, oldStringStreamSchemaWithId, } from './types/CamStreamerAPI/oldStreamSchema';
import { paramToUrl } from './internal/utils';
const BASE_PATH = '/local/camstreamer';
export class CamStreamerAPI {
client;
constructor(client) {
this.client = client;
}
static getProxyPath = () => `${BASE_PATH}/proxy.cgi`;
static getWsEventsPath = () => `${BASE_PATH}/events`;
getClient(proxyParams) {
return proxyParams ? new ProxyClient(this.client, proxyParams) : this.client;
}
async checkAPIAvailable(options) {
await this._getJson(`${BASE_PATH}/api_check.cgi`, undefined, options);
}
async wsAuthorization(options) {
const res = await this._getJson(`${BASE_PATH}/ws_authorization.cgi`, undefined, options);
if (res.status !== 200) {
throw new WsAuthorizationError(res.message);
}
return z.string().parse(res.data);
}
async getUtcTime(options) {
const res = await this._getJson(`${BASE_PATH}/get_utc_time.cgi`, undefined, options);
if (res.status !== 200) {
throw new UtcTimeFetchError(res.message);
}
return z.number().parse(res.data);
}
async getMaxFps(source = 0, options) {
return await this._getJson(`${BASE_PATH}/get_max_framerate.cgi`, { video_source: source.toString() }, options);
}
async isCSPassValid(pass, options) {
const res = await this._getJson(`${BASE_PATH}/check_pass.cgi`, { pass }, options);
return res.data === '1';
}
async getStreamList(options) {
const res = await this._getJson(`${BASE_PATH}/stream_list.cgi`, { action: 'get' }, options);
const oldStreamListRecord = z.record(z.string(), oldStringStreamSchema).safeParse(res.data);
if (oldStreamListRecord.success) {
const data = Object.entries(oldStreamListRecord.data).map(([id, streamData]) => ({
id: parseInt(id),
...parseCameraStreamResponse(streamData),
}));
throw new MigrationError([], data);
}
const newStreamData = [];
const oldStreamData = [];
const invalidStreamData = [];
for (const streamData of res.data.streamList) {
const newStreamParse = streamSchema.safeParse(streamData);
if (newStreamParse.success) {
newStreamData.push(newStreamParse.data);
continue;
}
const oldStreamParse = oldStringStreamSchemaWithId.safeParse(streamData);
if (oldStreamParse.success) {
oldStreamData.push({
id: parseInt(oldStreamParse.data.id),
...parseCameraStreamResponse(oldStreamParse.data),
});
continue;
}
invalidStreamData.push(streamData);
}
if (oldStreamData.length > 0 || invalidStreamData.length > 0) {
throw new MigrationError(newStreamData, oldStreamData, invalidStreamData);
}
return newStreamData;
}
async setStreamList(streamList, options) {
await this._postJsonEncoded(`${BASE_PATH}/stream_list.cgi`, JSON.stringify({ streamList }), {
action: 'set',
}, options);
}
async getStream(streamId, options) {
const res = await this._getJson(`${BASE_PATH}/stream_list.cgi`, { action: 'get', stream_id: streamId }, options);
const newStream = streamSchema.safeParse(res.data);
if (newStream.success) {
return newStream.data;
}
const oldStream = oldStringStreamSchema.passthrough().parse(res.data);
throw new MigrationError([], [{ id: streamId, ...parseCameraStreamResponse(oldStream) }]);
}
async setStream(streamId, streamData, options) {
await this._postJsonEncoded(`${BASE_PATH}/stream_list.cgi`, JSON.stringify(streamData), {
action: 'set',
stream_id: streamId,
}, options);
}
async isStreaming(streamId, options) {
const res = await this._getJson(`${BASE_PATH}/get_streamstat.cgi`, { stream_id: streamId }, options);
return res.data.is_streaming === 1;
}
async setStreamEnabled(streamId, enabled, options) {
await this._postUrlEncoded(`${BASE_PATH}/set_stream_enabled.cgi`, { stream_id: streamId, enabled: enabled ? 1 : 0 }, options);
}
async setStreamActive(streamId, active, options) {
await this._postUrlEncoded(`${BASE_PATH}/set_stream_active.cgi`, { stream_id: streamId, active: active ? 1 : 0 }, options);
}
async listFiles(options) {
const res = await this._getJson(`${BASE_PATH}/upload_audio.cgi`, { action: 'list' }, options);
return audioFileListSchema.parse(res.data);
}
async uploadFile(formData, storage, options) {
await this._post(`${BASE_PATH}/upload_audio.cgi`, formData, {
action: 'upload',
storage: storage,
}, options);
}
async removeFile(fileParams, options) {
await this._postUrlEncoded(`${BASE_PATH}/upload_audio.cgi`, { action: 'remove', ...fileParams }, options, undefined);
}
async getFileStorage(options) {
const res = await this._getJson(`${BASE_PATH}/upload_audio.cgi`, { action: 'get_storage' }, options);
return storageListSchema.parse(res.data);
}
async getFileFromCamera(path, options) {
return await this._getBlob(`${BASE_PATH}/audio.cgi`, { path }, options);
}
async _getJson(path, parameters, options) {
const agent = this.getClient(options?.proxyParams);
const res = await agent.get({ path, parameters, timeout: options?.timeout });
if (res.ok) {
return await res.json();
}
else {
throw new ErrorWithResponse(res);
}
}
async _getBlob(path, parameters, options) {
const agent = this.getClient(options?.proxyParams);
const res = await agent.get({ path, parameters, timeout: options?.timeout });
if (res.ok) {
return await this.parseBlobResponse(res);
}
else {
throw new ErrorWithResponse(res);
}
}
async parseBlobResponse(response) {
try {
return (await response.blob());
}
catch (err) {
throw new ParsingBlobError(err);
}
}
async _postUrlEncoded(path, parameters, options, headers) {
const data = paramToUrl(parameters);
const baseHeaders = { 'Content-Type': 'application/x-www-form-urlencoded' };
return this._post(path, data, undefined, options, { ...baseHeaders, ...headers });
}
async _postJsonEncoded(path, data, parameters, options, headers) {
const agent = this.getClient(options?.proxyParams);
const baseHeaders = { 'Accept': 'application/json', 'Content-Type': 'application/json' };
return agent.post({
path,
data,
parameters,
timeout: options?.timeout,
headers: { ...baseHeaders, ...headers },
});
}
async _post(path, data, parameters, options, headers) {
const agent = this.getClient(options?.proxyParams);
const res = await agent.post({ path, data, parameters, headers, timeout: options?.timeout });
if (res.ok) {
return await res.json();
}
else {
throw new ErrorWithResponse(res);
}
}
}
export const parseCameraStreamResponse = (cameraStreamData) => {
return {
enabled: parseInt(cameraStreamData.enabled),
active: parseInt(cameraStreamData.active),
audioSource: cameraStreamData.audioSource,
avSyncMsec: parseInt(cameraStreamData.avSyncMsec),
internalVapixParameters: cameraStreamData.internalVapixParameters,
userVapixParameters: cameraStreamData.userVapixParameters,
outputParameters: cameraStreamData.outputParameters,
outputType: cameraStreamData.outputType,
mediaServerUrl: cameraStreamData.mediaServerUrl,
inputType: cameraStreamData.inputType,
inputUrl: cameraStreamData.inputUrl,
forceStereo: parseInt(cameraStreamData.forceStereo),
streamDelay: isNaN(parseInt(cameraStreamData.streamDelay)) ? null : parseInt(cameraStreamData.streamDelay),
statusLed: parseInt(cameraStreamData.statusLed),
statusPort: cameraStreamData.statusPort,
callApi: parseInt(cameraStreamData.callApi),
trigger: cameraStreamData.trigger,
schedule: cameraStreamData.schedule,
prepareAhead: parseInt(cameraStreamData.prepareAhead),
startTime: isNaN(parseInt(cameraStreamData.startTime)) ? null : parseInt(cameraStreamData.startTime),
stopTime: isNaN(parseInt(cameraStreamData.stopTime)) ? null : parseInt(cameraStreamData.stopTime),
};
};