@camstack/addon-go2rtc 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +35 -60
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +32 -57
- package/dist/index.mjs.map +1 -1
- package/package.json +33 -7
package/dist/index.js
CHANGED
|
@@ -106,9 +106,9 @@ var Go2rtcEngine = class {
|
|
|
106
106
|
};
|
|
107
107
|
|
|
108
108
|
// src/go2rtc.addon.ts
|
|
109
|
-
var
|
|
110
|
-
var
|
|
111
|
-
var
|
|
109
|
+
var import_node_child_process = require("child_process");
|
|
110
|
+
var import_node_fs = require("fs");
|
|
111
|
+
var import_node_path = require("path");
|
|
112
112
|
|
|
113
113
|
// src/go2rtc-config-generator.ts
|
|
114
114
|
function generateGo2rtcConfig(config) {
|
|
@@ -129,14 +129,10 @@ streams: {}
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
// src/go2rtc-downloader.ts
|
|
132
|
-
var
|
|
133
|
-
var import_node_path = require("path");
|
|
134
|
-
var import_promises = require("stream/promises");
|
|
135
|
-
var import_node_stream = require("stream");
|
|
136
|
-
var import_node_child_process = require("child_process");
|
|
132
|
+
var import_core = require("@camstack/core");
|
|
137
133
|
var GO2RTC_VERSION = "1.9.14";
|
|
138
134
|
var BASE_URL = `https://github.com/AlexxIT/go2rtc/releases/download/v${GO2RTC_VERSION}`;
|
|
139
|
-
function
|
|
135
|
+
function getGo2rtcDownloadUrl() {
|
|
140
136
|
const os = process.platform;
|
|
141
137
|
const rawArch = process.arch;
|
|
142
138
|
let arch;
|
|
@@ -158,54 +154,25 @@ function getPlatformFilename() {
|
|
|
158
154
|
}
|
|
159
155
|
switch (os) {
|
|
160
156
|
case "darwin":
|
|
161
|
-
return {
|
|
157
|
+
return { url: `${BASE_URL}/go2rtc_mac_${arch}.zip`, isArchive: true, archiveFormat: "zip" };
|
|
162
158
|
case "linux":
|
|
163
|
-
return {
|
|
159
|
+
return { url: `${BASE_URL}/go2rtc_linux_${arch}`, isArchive: false };
|
|
164
160
|
case "win32":
|
|
165
|
-
return {
|
|
161
|
+
return { url: `${BASE_URL}/go2rtc_win_${arch}.zip`, isArchive: true, archiveFormat: "zip" };
|
|
166
162
|
default:
|
|
167
163
|
throw new Error(`Unsupported platform: ${os}`);
|
|
168
164
|
}
|
|
169
165
|
}
|
|
170
166
|
async function ensureGo2rtcBinary(targetDir, logger) {
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return "go2rtc";
|
|
181
|
-
} catch {
|
|
182
|
-
}
|
|
183
|
-
const { filename, isZip } = getPlatformFilename();
|
|
184
|
-
const url = `${BASE_URL}/${filename}`;
|
|
185
|
-
logger.info(`Downloading go2rtc v${GO2RTC_VERSION} for ${process.platform}/${process.arch}...`);
|
|
186
|
-
logger.info(` Source: ${url}`);
|
|
187
|
-
(0, import_node_fs.mkdirSync)(targetDir, { recursive: true });
|
|
188
|
-
const response = await fetch(url, { redirect: "follow" });
|
|
189
|
-
if (!response.ok || !response.body) {
|
|
190
|
-
throw new Error(`Failed to download go2rtc: ${response.status} ${response.statusText}`);
|
|
191
|
-
}
|
|
192
|
-
if (isZip) {
|
|
193
|
-
const tmpPath = (0, import_node_path.join)(targetDir, `go2rtc-download.zip`);
|
|
194
|
-
const nodeStream = import_node_stream.Readable.fromWeb(response.body);
|
|
195
|
-
await (0, import_promises.pipeline)(nodeStream, (0, import_node_fs.createWriteStream)(tmpPath));
|
|
196
|
-
try {
|
|
197
|
-
(0, import_node_child_process.execFileSync)("unzip", ["-o", "-q", tmpPath, "-d", targetDir], { stdio: "pipe" });
|
|
198
|
-
} catch {
|
|
199
|
-
(0, import_node_child_process.execFileSync)("tar", ["-xf", tmpPath, "-C", targetDir], { stdio: "pipe" });
|
|
200
|
-
}
|
|
201
|
-
(0, import_node_fs.unlinkSync)(tmpPath);
|
|
202
|
-
} else {
|
|
203
|
-
const nodeStream = import_node_stream.Readable.fromWeb(response.body);
|
|
204
|
-
await (0, import_promises.pipeline)(nodeStream, (0, import_node_fs.createWriteStream)(targetPath));
|
|
205
|
-
}
|
|
206
|
-
(0, import_node_fs.chmodSync)(targetPath, 493);
|
|
207
|
-
logger.info(`go2rtc v${GO2RTC_VERSION} downloaded to ${targetPath}`);
|
|
208
|
-
return targetPath;
|
|
167
|
+
const { url, isArchive, archiveFormat } = getGo2rtcDownloadUrl();
|
|
168
|
+
return (0, import_core.ensureBinary)({
|
|
169
|
+
name: "go2rtc",
|
|
170
|
+
targetDir,
|
|
171
|
+
downloadUrl: url,
|
|
172
|
+
logger,
|
|
173
|
+
isArchive,
|
|
174
|
+
archiveFormat
|
|
175
|
+
});
|
|
209
176
|
}
|
|
210
177
|
|
|
211
178
|
// src/go2rtc.addon.ts
|
|
@@ -230,7 +197,7 @@ var Go2rtcAddon = class {
|
|
|
230
197
|
};
|
|
231
198
|
async initialize(context) {
|
|
232
199
|
const dataPath = context.locationPaths.data;
|
|
233
|
-
const binDir = (0,
|
|
200
|
+
const binDir = (0, import_node_path.join)(dataPath, "bin");
|
|
234
201
|
const resolvedBinaryPath = await ensureGo2rtcBinary(binDir, context.logger);
|
|
235
202
|
const processConfig = {
|
|
236
203
|
apiPort: context.addonConfig.apiPort ?? this.currentConfig.apiPort,
|
|
@@ -240,10 +207,10 @@ var Go2rtcAddon = class {
|
|
|
240
207
|
};
|
|
241
208
|
this.currentConfig = processConfig;
|
|
242
209
|
const configYaml = generateGo2rtcConfig(processConfig);
|
|
243
|
-
const configPath = (0,
|
|
244
|
-
(0,
|
|
245
|
-
(0,
|
|
246
|
-
this.process = (0,
|
|
210
|
+
const configPath = (0, import_node_path.join)(dataPath, "go2rtc.yaml");
|
|
211
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(configPath), { recursive: true });
|
|
212
|
+
(0, import_node_fs.writeFileSync)(configPath, configYaml);
|
|
213
|
+
this.process = (0, import_node_child_process.spawn)(processConfig.binaryPath, ["-config", configPath], {
|
|
247
214
|
stdio: ["pipe", "pipe", "pipe"]
|
|
248
215
|
});
|
|
249
216
|
this.process.on("exit", (code, signal) => {
|
|
@@ -415,7 +382,7 @@ var Go2rtcAddon = class {
|
|
|
415
382
|
return {
|
|
416
383
|
sections: [
|
|
417
384
|
{
|
|
418
|
-
id: "info",
|
|
385
|
+
id: "go2rtc-info",
|
|
419
386
|
title: "go2rtc",
|
|
420
387
|
fields: [
|
|
421
388
|
{
|
|
@@ -428,15 +395,23 @@ var Go2rtcAddon = class {
|
|
|
428
395
|
]
|
|
429
396
|
},
|
|
430
397
|
{
|
|
431
|
-
id: "ports",
|
|
398
|
+
id: "go2rtc-ports",
|
|
432
399
|
title: "Network Ports",
|
|
400
|
+
description: "Port configuration for the go2rtc restreamer. Changes require a restart.",
|
|
433
401
|
columns: 3,
|
|
434
402
|
fields: [
|
|
403
|
+
{
|
|
404
|
+
type: "info",
|
|
405
|
+
key: "restart-note",
|
|
406
|
+
label: "Restart required",
|
|
407
|
+
content: "Port changes require restarting go2rtc.",
|
|
408
|
+
variant: "warning"
|
|
409
|
+
},
|
|
435
410
|
{
|
|
436
411
|
type: "number",
|
|
437
412
|
key: "apiPort",
|
|
438
413
|
label: "API Port",
|
|
439
|
-
description: "HTTP API
|
|
414
|
+
description: "go2rtc HTTP API",
|
|
440
415
|
min: 1024,
|
|
441
416
|
max: 65535,
|
|
442
417
|
step: 1
|
|
@@ -445,7 +420,7 @@ var Go2rtcAddon = class {
|
|
|
445
420
|
type: "number",
|
|
446
421
|
key: "rtspPort",
|
|
447
422
|
label: "RTSP Port",
|
|
448
|
-
description: "
|
|
423
|
+
description: "RTSP restream output",
|
|
449
424
|
min: 1024,
|
|
450
425
|
max: 65535,
|
|
451
426
|
step: 1
|
|
@@ -454,7 +429,7 @@ var Go2rtcAddon = class {
|
|
|
454
429
|
type: "number",
|
|
455
430
|
key: "webrtcPort",
|
|
456
431
|
label: "WebRTC Port",
|
|
457
|
-
description: "
|
|
432
|
+
description: "WebRTC UDP/TCP",
|
|
458
433
|
min: 1024,
|
|
459
434
|
max: 65535,
|
|
460
435
|
step: 1
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/go2rtc-engine.ts","../src/go2rtc.addon.ts","../src/go2rtc-config-generator.ts","../src/go2rtc-downloader.ts"],"sourcesContent":["export { Go2rtcEngine } from './go2rtc-engine'\nexport type { Go2rtcConfig } from './go2rtc-engine'\nexport { Go2rtcAddon } from './go2rtc.addon'\nexport type { Go2rtcProcessConfig } from './go2rtc.addon'\nexport { generateGo2rtcConfig } from './go2rtc-config-generator'\nexport type { Go2rtcGeneratedConfig } from './go2rtc-config-generator'\n","import type {\n IStreamingEngine,\n StreamFormat,\n StreamInfo,\n StreamingSource,\n StreamStatus,\n} from '@camstack/types'\n\nexport interface Go2rtcConfig {\n readonly apiPort: number\n readonly rtspPort: number\n readonly webrtcPort: number\n readonly binaryPath?: string\n}\n\nexport class Go2rtcEngine implements IStreamingEngine {\n private readonly baseUrl: string\n private readonly streams = new Map<string, StreamingSource>()\n\n constructor(private readonly config: Go2rtcConfig) {\n this.baseUrl = `http://127.0.0.1:${config.apiPort}`\n }\n\n async initialize(_config?: Record<string, unknown>): Promise<void> {\n try {\n const res = await fetch(`${this.baseUrl}/api/streams`)\n if (!res.ok) {\n throw new Error(`go2rtc API returned ${res.status}`)\n }\n } catch (err) {\n throw new Error(\n `Failed to reach go2rtc at ${this.baseUrl}: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n }\n\n async shutdown(): Promise<void> {\n this.streams.clear()\n }\n\n async registerStream(streamId: string, source: StreamingSource): Promise<void> {\n const url = `${this.baseUrl}/api/streams?src=${encodeURIComponent(streamId)}&url=${encodeURIComponent(source.url)}`\n const res = await fetch(url, { method: 'PUT' })\n if (!res.ok) {\n throw new Error(`go2rtc registerStream failed: ${res.status}`)\n }\n this.streams.set(streamId, source)\n }\n\n async unregisterStream(streamId: string): Promise<void> {\n const url = `${this.baseUrl}/api/streams?src=${encodeURIComponent(streamId)}`\n const res = await fetch(url, { method: 'DELETE' })\n if (!res.ok) {\n throw new Error(`go2rtc unregisterStream failed: ${res.status}`)\n }\n this.streams.delete(streamId)\n }\n\n getStreamUrl(streamId: string, format: StreamFormat): string | null {\n const encoded = encodeURIComponent(streamId)\n switch (format) {\n case 'webrtc':\n return `${this.baseUrl}/api/webrtc?src=${encoded}`\n case 'hls':\n return `${this.baseUrl}/api/stream.m3u8?src=${encoded}`\n case 'mjpeg':\n return `${this.baseUrl}/api/frame.jpeg?src=${encoded}`\n case 'rtsp':\n return `rtsp://127.0.0.1:${this.config.rtspPort}/${streamId}`\n default:\n return null\n }\n }\n\n async proxyWhepOffer(streamId: string, sdpOffer: string): Promise<string> {\n const url = `${this.baseUrl}/api/webrtc?src=${encodeURIComponent(streamId)}`\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/sdp' },\n body: sdpOffer,\n })\n if (!res.ok) {\n throw new Error(`go2rtc WHEP failed: ${res.status}`)\n }\n return res.text()\n }\n\n listStreams(): StreamInfo[] {\n return [...this.streams.entries()].map(([id, source]) => ({\n streamId: id,\n source,\n formats: ['webrtc', 'hls', 'mjpeg', 'rtsp'] as StreamFormat[],\n }))\n }\n\n getStreamStatus(streamId: string): StreamStatus | null {\n if (!this.streams.has(streamId)) return null\n return { active: true, viewers: 0 }\n }\n}\n","import { spawn, type ChildProcess } from 'node:child_process'\nimport { mkdirSync, writeFileSync } from 'node:fs'\nimport { join, dirname } from 'node:path'\nimport type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n IConfigurable,\n ConfigUISchema,\n CapabilityProviderMap,\n IRestreamer,\n RegisteredStream,\n RestreamerExposedResource as ExposedResource,\n EncodedPacket,\n} from '@camstack/types'\nimport type { IWebRtcProvider } from './webrtc-provider'\nimport { Go2rtcEngine } from './go2rtc-engine'\nimport type { Go2rtcConfig } from './go2rtc-engine'\nimport { generateGo2rtcConfig } from './go2rtc-config-generator'\nimport { ensureGo2rtcBinary } from './go2rtc-downloader'\n\nexport interface Go2rtcProcessConfig {\n readonly apiPort: number\n readonly rtspPort: number\n readonly webrtcPort: number\n readonly binaryPath: string\n}\n\ninterface DeviceRegistration {\n readonly deviceId: string\n readonly streams: readonly RegisteredStream[]\n /** The original source URL registered with go2rtc (for cleanup) */\n readonly streamIds: readonly string[]\n}\n\nexport class Go2rtcAddon implements ICamstackAddon, IConfigurable, IRestreamer, IWebRtcProvider {\n readonly manifest: AddonManifest = {\n id: 'go2rtc',\n name: 'go2rtc Streaming Engine',\n version: '1.0.0',\n capabilities: ['streaming-engine'],\n }\n\n /** IRestreamer identity */\n readonly id = 'go2rtc'\n readonly name = 'go2rtc Restreamer'\n\n private engine: Go2rtcEngine | null = null\n private process: ChildProcess | null = null\n private readonly registeredDevices = new Map<string, DeviceRegistration>()\n private currentConfig: Go2rtcProcessConfig = {\n apiPort: 1984,\n rtspPort: 8554,\n webrtcPort: 8555,\n binaryPath: 'go2rtc',\n }\n\n async initialize(context: AddonContext): Promise<void> {\n // Path comes from StorageLocationManager via context.locationPaths\n const dataPath = context.locationPaths.data\n const binDir = join(dataPath, 'bin')\n\n // Auto-download go2rtc binary if not found\n const resolvedBinaryPath = await ensureGo2rtcBinary(binDir, context.logger)\n\n const processConfig: Go2rtcProcessConfig = {\n apiPort: (context.addonConfig.apiPort as number) ?? this.currentConfig.apiPort,\n rtspPort: (context.addonConfig.rtspPort as number) ?? this.currentConfig.rtspPort,\n webrtcPort: (context.addonConfig.webrtcPort as number) ?? this.currentConfig.webrtcPort,\n binaryPath: resolvedBinaryPath,\n }\n this.currentConfig = processConfig\n\n // Generate config file\n const configYaml = generateGo2rtcConfig(processConfig)\n const configPath = join(dataPath, 'go2rtc.yaml')\n mkdirSync(dirname(configPath), { recursive: true })\n writeFileSync(configPath, configYaml)\n\n // Start go2rtc as a child process\n this.process = spawn(processConfig.binaryPath, ['-config', configPath], {\n stdio: ['pipe', 'pipe', 'pipe'],\n })\n\n this.process.on('exit', (code, signal) => {\n context.logger.warn(`go2rtc process exited (code: ${code}, signal: ${signal})`)\n })\n\n this.process.stderr?.on('data', (data: Buffer) => {\n const msg = data.toString().trim()\n if (msg) context.logger.debug(`go2rtc stderr: ${msg}`)\n })\n\n this.process.stdout?.on('data', (data: Buffer) => {\n const msg = data.toString().trim()\n if (msg) context.logger.debug(`go2rtc stdout: ${msg}`)\n })\n\n // Wait for API to be ready\n const apiUrl = `http://127.0.0.1:${processConfig.apiPort}/api/streams`\n await this.waitForReady(apiUrl, 10_000)\n\n // Create engine\n const engineConfig: Go2rtcConfig = {\n apiPort: processConfig.apiPort,\n rtspPort: processConfig.rtspPort,\n webrtcPort: processConfig.webrtcPort,\n binaryPath: processConfig.binaryPath,\n }\n this.engine = new Go2rtcEngine(engineConfig)\n await this.engine.initialize()\n\n context.logger.info(`go2rtc started (PID: ${this.process.pid}, API: ${processConfig.apiPort})`)\n }\n\n async shutdown(): Promise<void> {\n await this.engine?.shutdown()\n this.engine = null\n this.registeredDevices.clear()\n\n if (this.process) {\n this.process.kill('SIGTERM')\n this.process = null\n }\n }\n\n getEngine(): Go2rtcEngine {\n if (!this.engine) throw new Error('go2rtc not initialized')\n return this.engine\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === 'streaming-engine' && this.engine) {\n return this.engine as unknown as CapabilityProviderMap[K]\n }\n if (name === 'restreamer') {\n return this as unknown as CapabilityProviderMap[K]\n }\n if (name === 'webrtc') {\n return this as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n // --- IRestreamer implementation ---\n\n async registerDevice(deviceId: string, streams: readonly RegisteredStream[]): Promise<void> {\n if (!this.engine) {\n throw new Error('go2rtc not initialized — cannot register device')\n }\n\n const streamIds: string[] = []\n\n for (const stream of streams) {\n streamIds.push(stream.streamId)\n\n // Register the stream with go2rtc using the broker's local TCP URL.\n // The broker is the sole reader from the camera; go2rtc consumes the\n // broker's TCP pipe instead of connecting to the camera directly.\n if (stream.sourceUrl) {\n await this.engine.registerStream(stream.streamId, {\n url: `ffmpeg:${stream.sourceUrl}#video=${stream.codec}`,\n protocol: 'rtsp',\n deviceId,\n providerId: 'broker',\n })\n }\n }\n\n this.registeredDevices.set(deviceId, {\n deviceId,\n streams,\n streamIds,\n })\n }\n\n pushPacket(_streamId: string, _packet: EncodedPacket): void {\n // Packet push from the broker is a no-op — go2rtc reads directly from\n // the broker's local TCP pipe server instead.\n }\n\n async unregisterDevice(deviceId: string): Promise<void> {\n const registration = this.registeredDevices.get(deviceId)\n if (!registration) {\n return\n }\n\n if (this.engine) {\n for (const streamId of registration.streamIds) {\n try {\n await this.engine.unregisterStream(streamId)\n } catch {\n // Best-effort cleanup\n }\n }\n }\n\n this.registeredDevices.delete(deviceId)\n }\n\n getExposedResources(deviceId: string): readonly ExposedResource[] {\n const registration = this.registeredDevices.get(deviceId)\n if (!registration || !this.engine) {\n return []\n }\n\n const resources: ExposedResource[] = []\n\n for (const streamId of registration.streamIds) {\n const webrtcUrl = this.engine.getStreamUrl(streamId, 'webrtc')\n if (webrtcUrl) {\n resources.push({ type: 'url', format: 'webrtc', value: webrtcUrl })\n }\n\n const hlsUrl = this.engine.getStreamUrl(streamId, 'hls')\n if (hlsUrl) {\n resources.push({ type: 'url', format: 'hls', value: hlsUrl })\n }\n\n const mjpegUrl = this.engine.getStreamUrl(streamId, 'mjpeg')\n if (mjpegUrl) {\n resources.push({ type: 'url', format: 'mjpeg', value: mjpegUrl })\n }\n\n const rtspUrl = this.engine.getStreamUrl(streamId, 'rtsp')\n if (rtspUrl) {\n resources.push({ type: 'url', format: 'rtsp', value: rtspUrl })\n }\n }\n\n return resources\n }\n\n async proxyWhepOffer(streamId: string, sdpOffer: string): Promise<string> {\n if (!this.engine) {\n throw new Error('go2rtc not initialized — cannot proxy WHEP')\n }\n return this.engine.proxyWhepOffer(streamId, sdpOffer)\n }\n\n // --- End IRestreamer ---\n\n // --- IWebRtcProvider implementation ---\n\n async handleOffer(streamId: string, sdpOffer: string): Promise<string> {\n if (!this.engine) {\n throw new Error('go2rtc not initialized — cannot handle WebRTC offer')\n }\n return this.engine.proxyWhepOffer(streamId, sdpOffer)\n }\n\n supportsStream(streamId: string): boolean {\n const deviceId = streamId.split('/')[0]\n return deviceId !== undefined && this.registeredDevices.has(deviceId)\n }\n\n async registerStream(streamId: string, _codec: string): Promise<void> {\n // Stream registration is handled via registerDevice (IRestreamer).\n // This is a no-op since go2rtc manages streams through its own API.\n const deviceId = streamId.split('/')[0]\n if (deviceId && !this.registeredDevices.has(deviceId)) {\n this.registeredDevices.set(deviceId, {\n deviceId,\n streams: [{ streamId, label: streamId, codec: _codec, type: 'video' }],\n streamIds: [streamId],\n })\n }\n }\n\n async unregisterStream(streamId: string): Promise<void> {\n if (!this.engine) {\n return\n }\n try {\n await this.engine.unregisterStream(streamId)\n } catch {\n // Best-effort cleanup\n }\n }\n\n // --- End IWebRtcProvider ---\n\n /** Exposed for testing */\n async waitForReady(url: string, timeoutMs: number): Promise<void> {\n const start = Date.now()\n while (Date.now() - start < timeoutMs) {\n try {\n const res = await fetch(url)\n if (res.ok) return\n } catch {\n // Not ready yet\n }\n await new Promise((r) => setTimeout(r, 500))\n }\n throw new Error(`go2rtc did not become ready within ${timeoutMs}ms`)\n }\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'info',\n title: 'go2rtc',\n fields: [\n {\n type: 'info',\n key: 'binary-info',\n label: 'Binary',\n content: 'go2rtc is automatically downloaded on first boot. The binary is stored in the data directory.',\n variant: 'info',\n },\n ],\n },\n {\n id: 'ports',\n title: 'Network Ports',\n columns: 3,\n fields: [\n {\n type: 'number',\n key: 'apiPort',\n label: 'API Port',\n description: 'HTTP API port for go2rtc management',\n min: 1024,\n max: 65535,\n step: 1,\n },\n {\n type: 'number',\n key: 'rtspPort',\n label: 'RTSP Port',\n description: 'Port for RTSP stream output',\n min: 1024,\n max: 65535,\n step: 1,\n },\n {\n type: 'number',\n key: 'webrtcPort',\n label: 'WebRTC Port',\n description: 'Port for WebRTC ICE/STUN negotiations',\n min: 1024,\n max: 65535,\n step: 1,\n },\n ],\n },\n ],\n }\n }\n\n getConfig(): Record<string, unknown> {\n return { ...this.currentConfig }\n }\n\n async onConfigChange(config: Record<string, unknown>): Promise<void> {\n this.currentConfig = {\n binaryPath: (config.binaryPath as string) ?? this.currentConfig.binaryPath,\n apiPort: (config.apiPort as number) ?? this.currentConfig.apiPort,\n rtspPort: (config.rtspPort as number) ?? this.currentConfig.rtspPort,\n webrtcPort: (config.webrtcPort as number) ?? this.currentConfig.webrtcPort,\n }\n }\n}\n","export interface Go2rtcGeneratedConfig {\n api: { listen: string }\n rtsp: { listen: string }\n webrtc: { listen: string }\n streams: Record<string, string[]>\n}\n\nexport function generateGo2rtcConfig(config: {\n apiPort: number\n rtspPort: number\n webrtcPort: number\n}): string {\n const yaml = `\napi:\n listen: \":${config.apiPort}\"\n\nrtsp:\n listen: \":${config.rtspPort}\"\n\nwebrtc:\n listen: \":${config.webrtcPort}\"\n ice_servers: []\n\nstreams: {}\n`\n return yaml.trim()\n}\n","/**\n * Auto-download go2rtc binary for the current platform.\n * Checks if binary exists at the target path. If not, downloads from GitHub releases.\n */\n\nimport { existsSync, mkdirSync, chmodSync, createWriteStream, unlinkSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { pipeline } from 'node:stream/promises'\nimport { Readable } from 'node:stream'\nimport { execFileSync } from 'node:child_process'\nimport type { IScopedLogger } from '@camstack/types'\n\nconst GO2RTC_VERSION = '1.9.14'\nconst BASE_URL = `https://github.com/AlexxIT/go2rtc/releases/download/v${GO2RTC_VERSION}`\n\nfunction getPlatformFilename(): { filename: string; isZip: boolean } {\n const os = process.platform\n const rawArch = process.arch\n\n let arch: string\n switch (rawArch) {\n case 'x64': arch = 'amd64'; break\n case 'arm64': arch = 'arm64'; break\n case 'arm': arch = 'arm'; break\n case 'ia32': arch = 'i386'; break\n default: throw new Error(`Unsupported architecture: ${rawArch}`)\n }\n\n switch (os) {\n case 'darwin':\n return { filename: `go2rtc_mac_${arch}.zip`, isZip: true }\n case 'linux':\n return { filename: `go2rtc_linux_${arch}`, isZip: false }\n case 'win32':\n return { filename: `go2rtc_win_${arch}.zip`, isZip: true }\n default:\n throw new Error(`Unsupported platform: ${os}`)\n }\n}\n\n/**\n * Ensure go2rtc binary is available at `targetPath`.\n * Downloads from GitHub releases if not present.\n * Returns the absolute path to the binary.\n */\nexport async function ensureGo2rtcBinary(targetDir: string, logger: IScopedLogger): Promise<string> {\n const binaryName = process.platform === 'win32' ? 'go2rtc.exe' : 'go2rtc'\n const targetPath = join(targetDir, binaryName)\n\n // Already downloaded?\n if (existsSync(targetPath)) {\n logger.debug(`go2rtc binary found at ${targetPath}`)\n return targetPath\n }\n\n // Check if it's in PATH\n try {\n execFileSync('go2rtc', ['--version'], { stdio: 'pipe', timeout: 5000 })\n logger.info('go2rtc found in system PATH')\n return 'go2rtc'\n } catch {\n // Not in PATH — download it\n }\n\n const { filename, isZip } = getPlatformFilename()\n const url = `${BASE_URL}/${filename}`\n\n logger.info(`Downloading go2rtc v${GO2RTC_VERSION} for ${process.platform}/${process.arch}...`)\n logger.info(` Source: ${url}`)\n\n mkdirSync(targetDir, { recursive: true })\n\n const response = await fetch(url, { redirect: 'follow' })\n if (!response.ok || !response.body) {\n throw new Error(`Failed to download go2rtc: ${response.status} ${response.statusText}`)\n }\n\n if (isZip) {\n // Download zip to temp, extract\n const tmpPath = join(targetDir, `go2rtc-download.zip`)\n const nodeStream = Readable.fromWeb(response.body as any)\n await pipeline(nodeStream, createWriteStream(tmpPath))\n\n // Use system unzip\n try {\n execFileSync('unzip', ['-o', '-q', tmpPath, '-d', targetDir], { stdio: 'pipe' })\n } catch {\n // Try tar on systems without unzip\n execFileSync('tar', ['-xf', tmpPath, '-C', targetDir], { stdio: 'pipe' })\n }\n unlinkSync(tmpPath)\n } else {\n // Direct binary download\n const nodeStream = Readable.fromWeb(response.body as any)\n await pipeline(nodeStream, createWriteStream(targetPath))\n }\n\n chmodSync(targetPath, 0o755)\n logger.info(`go2rtc v${GO2RTC_VERSION} downloaded to ${targetPath}`)\n\n return targetPath\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACeO,IAAM,eAAN,MAA+C;AAAA,EAIpD,YAA6B,QAAsB;AAAtB;AAC3B,SAAK,UAAU,oBAAoB,OAAO,OAAO;AAAA,EACnD;AAAA,EALiB;AAAA,EACA,UAAU,oBAAI,IAA6B;AAAA,EAM5D,MAAM,WAAW,SAAkD;AACjE,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,cAAc;AACrD,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,IAAI,MAAM,uBAAuB,IAAI,MAAM,EAAE;AAAA,MACrD;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,6BAA6B,KAAK,OAAO,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MAChG;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,WAA0B;AAC9B,SAAK,QAAQ,MAAM;AAAA,EACrB;AAAA,EAEA,MAAM,eAAe,UAAkB,QAAwC;AAC7E,UAAM,MAAM,GAAG,KAAK,OAAO,oBAAoB,mBAAmB,QAAQ,CAAC,QAAQ,mBAAmB,OAAO,GAAG,CAAC;AACjH,UAAM,MAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,MAAM,CAAC;AAC9C,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,iCAAiC,IAAI,MAAM,EAAE;AAAA,IAC/D;AACA,SAAK,QAAQ,IAAI,UAAU,MAAM;AAAA,EACnC;AAAA,EAEA,MAAM,iBAAiB,UAAiC;AACtD,UAAM,MAAM,GAAG,KAAK,OAAO,oBAAoB,mBAAmB,QAAQ,CAAC;AAC3E,UAAM,MAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,SAAS,CAAC;AACjD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,mCAAmC,IAAI,MAAM,EAAE;AAAA,IACjE;AACA,SAAK,QAAQ,OAAO,QAAQ;AAAA,EAC9B;AAAA,EAEA,aAAa,UAAkB,QAAqC;AAClE,UAAM,UAAU,mBAAmB,QAAQ;AAC3C,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,eAAO,GAAG,KAAK,OAAO,mBAAmB,OAAO;AAAA,MAClD,KAAK;AACH,eAAO,GAAG,KAAK,OAAO,wBAAwB,OAAO;AAAA,MACvD,KAAK;AACH,eAAO,GAAG,KAAK,OAAO,uBAAuB,OAAO;AAAA,MACtD,KAAK;AACH,eAAO,oBAAoB,KAAK,OAAO,QAAQ,IAAI,QAAQ;AAAA,MAC7D;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAAA,EAEA,MAAM,eAAe,UAAkB,UAAmC;AACxE,UAAM,MAAM,GAAG,KAAK,OAAO,mBAAmB,mBAAmB,QAAQ,CAAC;AAC1E,UAAM,MAAM,MAAM,MAAM,KAAK;AAAA,MAC3B,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,kBAAkB;AAAA,MAC7C,MAAM;AAAA,IACR,CAAC;AACD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,uBAAuB,IAAI,MAAM,EAAE;AAAA,IACrD;AACA,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA,EAEA,cAA4B;AAC1B,WAAO,CAAC,GAAG,KAAK,QAAQ,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,MAAM,OAAO;AAAA,MACxD,UAAU;AAAA,MACV;AAAA,MACA,SAAS,CAAC,UAAU,OAAO,SAAS,MAAM;AAAA,IAC5C,EAAE;AAAA,EACJ;AAAA,EAEA,gBAAgB,UAAuC;AACrD,QAAI,CAAC,KAAK,QAAQ,IAAI,QAAQ,EAAG,QAAO;AACxC,WAAO,EAAE,QAAQ,MAAM,SAAS,EAAE;AAAA,EACpC;AACF;;;ACnGA,IAAAA,6BAAyC;AACzC,IAAAC,kBAAyC;AACzC,IAAAC,oBAA8B;;;ACKvB,SAAS,qBAAqB,QAI1B;AACT,QAAM,OAAO;AAAA;AAAA,cAED,OAAO,OAAO;AAAA;AAAA;AAAA,cAGd,OAAO,QAAQ;AAAA;AAAA;AAAA,cAGf,OAAO,UAAU;AAAA;AAAA;AAAA;AAAA;AAK7B,SAAO,KAAK,KAAK;AACnB;;;ACrBA,qBAAgF;AAChF,uBAAqB;AACrB,sBAAyB;AACzB,yBAAyB;AACzB,gCAA6B;AAG7B,IAAM,iBAAiB;AACvB,IAAM,WAAW,wDAAwD,cAAc;AAEvF,SAAS,sBAA4D;AACnE,QAAM,KAAK,QAAQ;AACnB,QAAM,UAAU,QAAQ;AAExB,MAAI;AACJ,UAAQ,SAAS;AAAA,IACf,KAAK;AAAO,aAAO;AAAS;AAAA,IAC5B,KAAK;AAAS,aAAO;AAAS;AAAA,IAC9B,KAAK;AAAO,aAAO;AAAO;AAAA,IAC1B,KAAK;AAAQ,aAAO;AAAQ;AAAA,IAC5B;AAAS,YAAM,IAAI,MAAM,6BAA6B,OAAO,EAAE;AAAA,EACjE;AAEA,UAAQ,IAAI;AAAA,IACV,KAAK;AACH,aAAO,EAAE,UAAU,cAAc,IAAI,QAAQ,OAAO,KAAK;AAAA,IAC3D,KAAK;AACH,aAAO,EAAE,UAAU,gBAAgB,IAAI,IAAI,OAAO,MAAM;AAAA,IAC1D,KAAK;AACH,aAAO,EAAE,UAAU,cAAc,IAAI,QAAQ,OAAO,KAAK;AAAA,IAC3D;AACE,YAAM,IAAI,MAAM,yBAAyB,EAAE,EAAE;AAAA,EACjD;AACF;AAOA,eAAsB,mBAAmB,WAAmB,QAAwC;AAClG,QAAM,aAAa,QAAQ,aAAa,UAAU,eAAe;AACjE,QAAM,iBAAa,uBAAK,WAAW,UAAU;AAG7C,UAAI,2BAAW,UAAU,GAAG;AAC1B,WAAO,MAAM,0BAA0B,UAAU,EAAE;AACnD,WAAO;AAAA,EACT;AAGA,MAAI;AACF,gDAAa,UAAU,CAAC,WAAW,GAAG,EAAE,OAAO,QAAQ,SAAS,IAAK,CAAC;AACtE,WAAO,KAAK,6BAA6B;AACzC,WAAO;AAAA,EACT,QAAQ;AAAA,EAER;AAEA,QAAM,EAAE,UAAU,MAAM,IAAI,oBAAoB;AAChD,QAAM,MAAM,GAAG,QAAQ,IAAI,QAAQ;AAEnC,SAAO,KAAK,uBAAuB,cAAc,QAAQ,QAAQ,QAAQ,IAAI,QAAQ,IAAI,KAAK;AAC9F,SAAO,KAAK,aAAa,GAAG,EAAE;AAE9B,gCAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAExC,QAAM,WAAW,MAAM,MAAM,KAAK,EAAE,UAAU,SAAS,CAAC;AACxD,MAAI,CAAC,SAAS,MAAM,CAAC,SAAS,MAAM;AAClC,UAAM,IAAI,MAAM,8BAA8B,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AAAA,EACxF;AAEA,MAAI,OAAO;AAET,UAAM,cAAU,uBAAK,WAAW,qBAAqB;AACrD,UAAM,aAAa,4BAAS,QAAQ,SAAS,IAAW;AACxD,cAAM,0BAAS,gBAAY,kCAAkB,OAAO,CAAC;AAGrD,QAAI;AACF,kDAAa,SAAS,CAAC,MAAM,MAAM,SAAS,MAAM,SAAS,GAAG,EAAE,OAAO,OAAO,CAAC;AAAA,IACjF,QAAQ;AAEN,kDAAa,OAAO,CAAC,OAAO,SAAS,MAAM,SAAS,GAAG,EAAE,OAAO,OAAO,CAAC;AAAA,IAC1E;AACA,mCAAW,OAAO;AAAA,EACpB,OAAO;AAEL,UAAM,aAAa,4BAAS,QAAQ,SAAS,IAAW;AACxD,cAAM,0BAAS,gBAAY,kCAAkB,UAAU,CAAC;AAAA,EAC1D;AAEA,gCAAU,YAAY,GAAK;AAC3B,SAAO,KAAK,WAAW,cAAc,kBAAkB,UAAU,EAAE;AAEnE,SAAO;AACT;;;AFlEO,IAAM,cAAN,MAAyF;AAAA,EACrF,WAA0B;AAAA,IACjC,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,cAAc,CAAC,kBAAkB;AAAA,EACnC;AAAA;AAAA,EAGS,KAAK;AAAA,EACL,OAAO;AAAA,EAER,SAA8B;AAAA,EAC9B,UAA+B;AAAA,EACtB,oBAAoB,oBAAI,IAAgC;AAAA,EACjE,gBAAqC;AAAA,IAC3C,SAAS;AAAA,IACT,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,EACd;AAAA,EAEA,MAAM,WAAW,SAAsC;AAErD,UAAM,WAAW,QAAQ,cAAc;AACvC,UAAM,aAAS,wBAAK,UAAU,KAAK;AAGnC,UAAM,qBAAqB,MAAM,mBAAmB,QAAQ,QAAQ,MAAM;AAE1E,UAAM,gBAAqC;AAAA,MACzC,SAAU,QAAQ,YAAY,WAAsB,KAAK,cAAc;AAAA,MACvE,UAAW,QAAQ,YAAY,YAAuB,KAAK,cAAc;AAAA,MACzE,YAAa,QAAQ,YAAY,cAAyB,KAAK,cAAc;AAAA,MAC7E,YAAY;AAAA,IACd;AACA,SAAK,gBAAgB;AAGrB,UAAM,aAAa,qBAAqB,aAAa;AACrD,UAAM,iBAAa,wBAAK,UAAU,aAAa;AAC/C,uCAAU,2BAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AAClD,uCAAc,YAAY,UAAU;AAGpC,SAAK,cAAU,kCAAM,cAAc,YAAY,CAAC,WAAW,UAAU,GAAG;AAAA,MACtE,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC;AAED,SAAK,QAAQ,GAAG,QAAQ,CAAC,MAAM,WAAW;AACxC,cAAQ,OAAO,KAAK,gCAAgC,IAAI,aAAa,MAAM,GAAG;AAAA,IAChF,CAAC;AAED,SAAK,QAAQ,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AAChD,YAAM,MAAM,KAAK,SAAS,EAAE,KAAK;AACjC,UAAI,IAAK,SAAQ,OAAO,MAAM,kBAAkB,GAAG,EAAE;AAAA,IACvD,CAAC;AAED,SAAK,QAAQ,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AAChD,YAAM,MAAM,KAAK,SAAS,EAAE,KAAK;AACjC,UAAI,IAAK,SAAQ,OAAO,MAAM,kBAAkB,GAAG,EAAE;AAAA,IACvD,CAAC;AAGD,UAAM,SAAS,oBAAoB,cAAc,OAAO;AACxD,UAAM,KAAK,aAAa,QAAQ,GAAM;AAGtC,UAAM,eAA6B;AAAA,MACjC,SAAS,cAAc;AAAA,MACvB,UAAU,cAAc;AAAA,MACxB,YAAY,cAAc;AAAA,MAC1B,YAAY,cAAc;AAAA,IAC5B;AACA,SAAK,SAAS,IAAI,aAAa,YAAY;AAC3C,UAAM,KAAK,OAAO,WAAW;AAE7B,YAAQ,OAAO,KAAK,wBAAwB,KAAK,QAAQ,GAAG,UAAU,cAAc,OAAO,GAAG;AAAA,EAChG;AAAA,EAEA,MAAM,WAA0B;AAC9B,UAAM,KAAK,QAAQ,SAAS;AAC5B,SAAK,SAAS;AACd,SAAK,kBAAkB,MAAM;AAE7B,QAAI,KAAK,SAAS;AAChB,WAAK,QAAQ,KAAK,SAAS;AAC3B,WAAK,UAAU;AAAA,IACjB;AAAA,EACF;AAAA,EAEA,YAA0B;AACxB,QAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,MAAM,wBAAwB;AAC1D,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,sBACE,MACiC;AACjC,QAAI,SAAS,sBAAsB,KAAK,QAAQ;AAC9C,aAAO,KAAK;AAAA,IACd;AACA,QAAI,SAAS,cAAc;AACzB,aAAO;AAAA,IACT;AACA,QAAI,SAAS,UAAU;AACrB,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAIA,MAAM,eAAe,UAAkB,SAAqD;AAC1F,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI,MAAM,sDAAiD;AAAA,IACnE;AAEA,UAAM,YAAsB,CAAC;AAE7B,eAAW,UAAU,SAAS;AAC5B,gBAAU,KAAK,OAAO,QAAQ;AAK9B,UAAI,OAAO,WAAW;AACpB,cAAM,KAAK,OAAO,eAAe,OAAO,UAAU;AAAA,UAChD,KAAK,UAAU,OAAO,SAAS,UAAU,OAAO,KAAK;AAAA,UACrD,UAAU;AAAA,UACV;AAAA,UACA,YAAY;AAAA,QACd,CAAC;AAAA,MACH;AAAA,IACF;AAEA,SAAK,kBAAkB,IAAI,UAAU;AAAA,MACnC;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,WAAW,WAAmB,SAA8B;AAAA,EAG5D;AAAA,EAEA,MAAM,iBAAiB,UAAiC;AACtD,UAAM,eAAe,KAAK,kBAAkB,IAAI,QAAQ;AACxD,QAAI,CAAC,cAAc;AACjB;AAAA,IACF;AAEA,QAAI,KAAK,QAAQ;AACf,iBAAW,YAAY,aAAa,WAAW;AAC7C,YAAI;AACF,gBAAM,KAAK,OAAO,iBAAiB,QAAQ;AAAA,QAC7C,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAEA,SAAK,kBAAkB,OAAO,QAAQ;AAAA,EACxC;AAAA,EAEA,oBAAoB,UAA8C;AAChE,UAAM,eAAe,KAAK,kBAAkB,IAAI,QAAQ;AACxD,QAAI,CAAC,gBAAgB,CAAC,KAAK,QAAQ;AACjC,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,YAA+B,CAAC;AAEtC,eAAW,YAAY,aAAa,WAAW;AAC7C,YAAM,YAAY,KAAK,OAAO,aAAa,UAAU,QAAQ;AAC7D,UAAI,WAAW;AACb,kBAAU,KAAK,EAAE,MAAM,OAAO,QAAQ,UAAU,OAAO,UAAU,CAAC;AAAA,MACpE;AAEA,YAAM,SAAS,KAAK,OAAO,aAAa,UAAU,KAAK;AACvD,UAAI,QAAQ;AACV,kBAAU,KAAK,EAAE,MAAM,OAAO,QAAQ,OAAO,OAAO,OAAO,CAAC;AAAA,MAC9D;AAEA,YAAM,WAAW,KAAK,OAAO,aAAa,UAAU,OAAO;AAC3D,UAAI,UAAU;AACZ,kBAAU,KAAK,EAAE,MAAM,OAAO,QAAQ,SAAS,OAAO,SAAS,CAAC;AAAA,MAClE;AAEA,YAAM,UAAU,KAAK,OAAO,aAAa,UAAU,MAAM;AACzD,UAAI,SAAS;AACX,kBAAU,KAAK,EAAE,MAAM,OAAO,QAAQ,QAAQ,OAAO,QAAQ,CAAC;AAAA,MAChE;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,eAAe,UAAkB,UAAmC;AACxE,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI,MAAM,iDAA4C;AAAA,IAC9D;AACA,WAAO,KAAK,OAAO,eAAe,UAAU,QAAQ;AAAA,EACtD;AAAA;AAAA;AAAA,EAMA,MAAM,YAAY,UAAkB,UAAmC;AACrE,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI,MAAM,0DAAqD;AAAA,IACvE;AACA,WAAO,KAAK,OAAO,eAAe,UAAU,QAAQ;AAAA,EACtD;AAAA,EAEA,eAAe,UAA2B;AACxC,UAAM,WAAW,SAAS,MAAM,GAAG,EAAE,CAAC;AACtC,WAAO,aAAa,UAAa,KAAK,kBAAkB,IAAI,QAAQ;AAAA,EACtE;AAAA,EAEA,MAAM,eAAe,UAAkB,QAA+B;AAGpE,UAAM,WAAW,SAAS,MAAM,GAAG,EAAE,CAAC;AACtC,QAAI,YAAY,CAAC,KAAK,kBAAkB,IAAI,QAAQ,GAAG;AACrD,WAAK,kBAAkB,IAAI,UAAU;AAAA,QACnC;AAAA,QACA,SAAS,CAAC,EAAE,UAAU,OAAO,UAAU,OAAO,QAAQ,MAAM,QAAQ,CAAC;AAAA,QACrE,WAAW,CAAC,QAAQ;AAAA,MACtB,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAM,iBAAiB,UAAiC;AACtD,QAAI,CAAC,KAAK,QAAQ;AAChB;AAAA,IACF;AACA,QAAI;AACF,YAAM,KAAK,OAAO,iBAAiB,QAAQ;AAAA,IAC7C,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,KAAa,WAAkC;AAChE,UAAM,QAAQ,KAAK,IAAI;AACvB,WAAO,KAAK,IAAI,IAAI,QAAQ,WAAW;AACrC,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,GAAG;AAC3B,YAAI,IAAI,GAAI;AAAA,MACd,QAAQ;AAAA,MAER;AACA,YAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAAA,IAC7C;AACA,UAAM,IAAI,MAAM,sCAAsC,SAAS,IAAI;AAAA,EACrE;AAAA,EAEA,kBAAkC;AAChC,WAAO;AAAA,MACL,UAAU;AAAA,QACR;AAAA,UACE,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,QAAQ;AAAA,YACN;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,SAAS;AAAA,cACT,SAAS;AAAA,YACX;AAAA,UACF;AAAA,QACF;AAAA,QACA;AAAA,UACE,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,SAAS;AAAA,UACT,QAAQ;AAAA,YACN;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,YACR;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,YACR;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,YAAqC;AACnC,WAAO,EAAE,GAAG,KAAK,cAAc;AAAA,EACjC;AAAA,EAEA,MAAM,eAAe,QAAgD;AACnE,SAAK,gBAAgB;AAAA,MACnB,YAAa,OAAO,cAAyB,KAAK,cAAc;AAAA,MAChE,SAAU,OAAO,WAAsB,KAAK,cAAc;AAAA,MAC1D,UAAW,OAAO,YAAuB,KAAK,cAAc;AAAA,MAC5D,YAAa,OAAO,cAAyB,KAAK,cAAc;AAAA,IAClE;AAAA,EACF;AACF;","names":["import_node_child_process","import_node_fs","import_node_path"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/go2rtc-engine.ts","../src/go2rtc.addon.ts","../src/go2rtc-config-generator.ts","../src/go2rtc-downloader.ts"],"sourcesContent":["export { Go2rtcEngine } from './go2rtc-engine'\nexport type { Go2rtcConfig } from './go2rtc-engine'\nexport { Go2rtcAddon } from './go2rtc.addon'\nexport type { Go2rtcProcessConfig } from './go2rtc.addon'\nexport { generateGo2rtcConfig } from './go2rtc-config-generator'\nexport type { Go2rtcGeneratedConfig } from './go2rtc-config-generator'\n","import type {\n IStreamingEngine,\n StreamFormat,\n StreamInfo,\n StreamingSource,\n StreamStatus,\n} from '@camstack/types'\n\nexport interface Go2rtcConfig {\n readonly apiPort: number\n readonly rtspPort: number\n readonly webrtcPort: number\n readonly binaryPath?: string\n}\n\nexport class Go2rtcEngine implements IStreamingEngine {\n private readonly baseUrl: string\n private readonly streams = new Map<string, StreamingSource>()\n\n constructor(private readonly config: Go2rtcConfig) {\n this.baseUrl = `http://127.0.0.1:${config.apiPort}`\n }\n\n async initialize(_config?: Record<string, unknown>): Promise<void> {\n try {\n const res = await fetch(`${this.baseUrl}/api/streams`)\n if (!res.ok) {\n throw new Error(`go2rtc API returned ${res.status}`)\n }\n } catch (err) {\n throw new Error(\n `Failed to reach go2rtc at ${this.baseUrl}: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n }\n\n async shutdown(): Promise<void> {\n this.streams.clear()\n }\n\n async registerStream(streamId: string, source: StreamingSource): Promise<void> {\n const url = `${this.baseUrl}/api/streams?src=${encodeURIComponent(streamId)}&url=${encodeURIComponent(source.url)}`\n const res = await fetch(url, { method: 'PUT' })\n if (!res.ok) {\n throw new Error(`go2rtc registerStream failed: ${res.status}`)\n }\n this.streams.set(streamId, source)\n }\n\n async unregisterStream(streamId: string): Promise<void> {\n const url = `${this.baseUrl}/api/streams?src=${encodeURIComponent(streamId)}`\n const res = await fetch(url, { method: 'DELETE' })\n if (!res.ok) {\n throw new Error(`go2rtc unregisterStream failed: ${res.status}`)\n }\n this.streams.delete(streamId)\n }\n\n getStreamUrl(streamId: string, format: StreamFormat): string | null {\n const encoded = encodeURIComponent(streamId)\n switch (format) {\n case 'webrtc':\n return `${this.baseUrl}/api/webrtc?src=${encoded}`\n case 'hls':\n return `${this.baseUrl}/api/stream.m3u8?src=${encoded}`\n case 'mjpeg':\n return `${this.baseUrl}/api/frame.jpeg?src=${encoded}`\n case 'rtsp':\n return `rtsp://127.0.0.1:${this.config.rtspPort}/${streamId}`\n default:\n return null\n }\n }\n\n async proxyWhepOffer(streamId: string, sdpOffer: string): Promise<string> {\n const url = `${this.baseUrl}/api/webrtc?src=${encodeURIComponent(streamId)}`\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/sdp' },\n body: sdpOffer,\n })\n if (!res.ok) {\n throw new Error(`go2rtc WHEP failed: ${res.status}`)\n }\n return res.text()\n }\n\n listStreams(): StreamInfo[] {\n return [...this.streams.entries()].map(([id, source]) => ({\n streamId: id,\n source,\n formats: ['webrtc', 'hls', 'mjpeg', 'rtsp'] as StreamFormat[],\n }))\n }\n\n getStreamStatus(streamId: string): StreamStatus | null {\n if (!this.streams.has(streamId)) return null\n return { active: true, viewers: 0 }\n }\n}\n","import { spawn, type ChildProcess } from 'node:child_process'\nimport { mkdirSync, writeFileSync } from 'node:fs'\nimport { join, dirname } from 'node:path'\nimport type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n IConfigurable,\n ConfigUISchema,\n CapabilityProviderMap,\n IRestreamer,\n RegisteredStream,\n RestreamerExposedResource as ExposedResource,\n EncodedPacket,\n} from '@camstack/types'\nimport type { IWebRtcProvider } from './webrtc-provider'\nimport { Go2rtcEngine } from './go2rtc-engine'\nimport type { Go2rtcConfig } from './go2rtc-engine'\nimport { generateGo2rtcConfig } from './go2rtc-config-generator'\nimport { ensureGo2rtcBinary } from './go2rtc-downloader'\n\nexport interface Go2rtcProcessConfig {\n readonly apiPort: number\n readonly rtspPort: number\n readonly webrtcPort: number\n readonly binaryPath: string\n}\n\ninterface DeviceRegistration {\n readonly deviceId: string\n readonly streams: readonly RegisteredStream[]\n /** The original source URL registered with go2rtc (for cleanup) */\n readonly streamIds: readonly string[]\n}\n\nexport class Go2rtcAddon implements ICamstackAddon, IConfigurable, IRestreamer, IWebRtcProvider {\n readonly manifest: AddonManifest = {\n id: 'go2rtc',\n name: 'go2rtc Streaming Engine',\n version: '1.0.0',\n capabilities: ['streaming-engine'],\n }\n\n /** IRestreamer identity */\n readonly id = 'go2rtc'\n readonly name = 'go2rtc Restreamer'\n\n private engine: Go2rtcEngine | null = null\n private process: ChildProcess | null = null\n private readonly registeredDevices = new Map<string, DeviceRegistration>()\n private currentConfig: Go2rtcProcessConfig = {\n apiPort: 1984,\n rtspPort: 8554,\n webrtcPort: 8555,\n binaryPath: 'go2rtc',\n }\n\n async initialize(context: AddonContext): Promise<void> {\n // Path comes from StorageLocationManager via context.locationPaths\n const dataPath = context.locationPaths.data\n const binDir = join(dataPath, 'bin')\n\n // Auto-download go2rtc binary if not found\n const resolvedBinaryPath = await ensureGo2rtcBinary(binDir, context.logger)\n\n const processConfig: Go2rtcProcessConfig = {\n apiPort: (context.addonConfig.apiPort as number) ?? this.currentConfig.apiPort,\n rtspPort: (context.addonConfig.rtspPort as number) ?? this.currentConfig.rtspPort,\n webrtcPort: (context.addonConfig.webrtcPort as number) ?? this.currentConfig.webrtcPort,\n binaryPath: resolvedBinaryPath,\n }\n this.currentConfig = processConfig\n\n // Generate config file\n const configYaml = generateGo2rtcConfig(processConfig)\n const configPath = join(dataPath, 'go2rtc.yaml')\n mkdirSync(dirname(configPath), { recursive: true })\n writeFileSync(configPath, configYaml)\n\n // Start go2rtc as a child process\n this.process = spawn(processConfig.binaryPath, ['-config', configPath], {\n stdio: ['pipe', 'pipe', 'pipe'],\n })\n\n this.process.on('exit', (code, signal) => {\n context.logger.warn(`go2rtc process exited (code: ${code}, signal: ${signal})`)\n })\n\n this.process.stderr?.on('data', (data: Buffer) => {\n const msg = data.toString().trim()\n if (msg) context.logger.debug(`go2rtc stderr: ${msg}`)\n })\n\n this.process.stdout?.on('data', (data: Buffer) => {\n const msg = data.toString().trim()\n if (msg) context.logger.debug(`go2rtc stdout: ${msg}`)\n })\n\n // Wait for API to be ready\n const apiUrl = `http://127.0.0.1:${processConfig.apiPort}/api/streams`\n await this.waitForReady(apiUrl, 10_000)\n\n // Create engine\n const engineConfig: Go2rtcConfig = {\n apiPort: processConfig.apiPort,\n rtspPort: processConfig.rtspPort,\n webrtcPort: processConfig.webrtcPort,\n binaryPath: processConfig.binaryPath,\n }\n this.engine = new Go2rtcEngine(engineConfig)\n await this.engine.initialize()\n\n context.logger.info(`go2rtc started (PID: ${this.process.pid}, API: ${processConfig.apiPort})`)\n }\n\n async shutdown(): Promise<void> {\n await this.engine?.shutdown()\n this.engine = null\n this.registeredDevices.clear()\n\n if (this.process) {\n this.process.kill('SIGTERM')\n this.process = null\n }\n }\n\n getEngine(): Go2rtcEngine {\n if (!this.engine) throw new Error('go2rtc not initialized')\n return this.engine\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === 'streaming-engine' && this.engine) {\n return this.engine as unknown as CapabilityProviderMap[K]\n }\n if (name === 'restreamer') {\n return this as unknown as CapabilityProviderMap[K]\n }\n if (name === 'webrtc') {\n return this as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n // --- IRestreamer implementation ---\n\n async registerDevice(deviceId: string, streams: readonly RegisteredStream[]): Promise<void> {\n if (!this.engine) {\n throw new Error('go2rtc not initialized — cannot register device')\n }\n\n const streamIds: string[] = []\n\n for (const stream of streams) {\n streamIds.push(stream.streamId)\n\n // Register the stream with go2rtc using the broker's local TCP URL.\n // The broker is the sole reader from the camera; go2rtc consumes the\n // broker's TCP pipe instead of connecting to the camera directly.\n if (stream.sourceUrl) {\n await this.engine.registerStream(stream.streamId, {\n url: `ffmpeg:${stream.sourceUrl}#video=${stream.codec}`,\n protocol: 'rtsp',\n deviceId,\n providerId: 'broker',\n })\n }\n }\n\n this.registeredDevices.set(deviceId, {\n deviceId,\n streams,\n streamIds,\n })\n }\n\n pushPacket(_streamId: string, _packet: EncodedPacket): void {\n // Packet push from the broker is a no-op — go2rtc reads directly from\n // the broker's local TCP pipe server instead.\n }\n\n async unregisterDevice(deviceId: string): Promise<void> {\n const registration = this.registeredDevices.get(deviceId)\n if (!registration) {\n return\n }\n\n if (this.engine) {\n for (const streamId of registration.streamIds) {\n try {\n await this.engine.unregisterStream(streamId)\n } catch {\n // Best-effort cleanup\n }\n }\n }\n\n this.registeredDevices.delete(deviceId)\n }\n\n getExposedResources(deviceId: string): readonly ExposedResource[] {\n const registration = this.registeredDevices.get(deviceId)\n if (!registration || !this.engine) {\n return []\n }\n\n const resources: ExposedResource[] = []\n\n for (const streamId of registration.streamIds) {\n const webrtcUrl = this.engine.getStreamUrl(streamId, 'webrtc')\n if (webrtcUrl) {\n resources.push({ type: 'url', format: 'webrtc', value: webrtcUrl })\n }\n\n const hlsUrl = this.engine.getStreamUrl(streamId, 'hls')\n if (hlsUrl) {\n resources.push({ type: 'url', format: 'hls', value: hlsUrl })\n }\n\n const mjpegUrl = this.engine.getStreamUrl(streamId, 'mjpeg')\n if (mjpegUrl) {\n resources.push({ type: 'url', format: 'mjpeg', value: mjpegUrl })\n }\n\n const rtspUrl = this.engine.getStreamUrl(streamId, 'rtsp')\n if (rtspUrl) {\n resources.push({ type: 'url', format: 'rtsp', value: rtspUrl })\n }\n }\n\n return resources\n }\n\n async proxyWhepOffer(streamId: string, sdpOffer: string): Promise<string> {\n if (!this.engine) {\n throw new Error('go2rtc not initialized — cannot proxy WHEP')\n }\n return this.engine.proxyWhepOffer(streamId, sdpOffer)\n }\n\n // --- End IRestreamer ---\n\n // --- IWebRtcProvider implementation ---\n\n async handleOffer(streamId: string, sdpOffer: string): Promise<string> {\n if (!this.engine) {\n throw new Error('go2rtc not initialized — cannot handle WebRTC offer')\n }\n return this.engine.proxyWhepOffer(streamId, sdpOffer)\n }\n\n supportsStream(streamId: string): boolean {\n const deviceId = streamId.split('/')[0]\n return deviceId !== undefined && this.registeredDevices.has(deviceId)\n }\n\n async registerStream(streamId: string, _codec: string): Promise<void> {\n // Stream registration is handled via registerDevice (IRestreamer).\n // This is a no-op since go2rtc manages streams through its own API.\n const deviceId = streamId.split('/')[0]\n if (deviceId && !this.registeredDevices.has(deviceId)) {\n this.registeredDevices.set(deviceId, {\n deviceId,\n streams: [{ streamId, label: streamId, codec: _codec, type: 'video' }],\n streamIds: [streamId],\n })\n }\n }\n\n async unregisterStream(streamId: string): Promise<void> {\n if (!this.engine) {\n return\n }\n try {\n await this.engine.unregisterStream(streamId)\n } catch {\n // Best-effort cleanup\n }\n }\n\n // --- End IWebRtcProvider ---\n\n /** Exposed for testing */\n async waitForReady(url: string, timeoutMs: number): Promise<void> {\n const start = Date.now()\n while (Date.now() - start < timeoutMs) {\n try {\n const res = await fetch(url)\n if (res.ok) return\n } catch {\n // Not ready yet\n }\n await new Promise((r) => setTimeout(r, 500))\n }\n throw new Error(`go2rtc did not become ready within ${timeoutMs}ms`)\n }\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'go2rtc-info',\n title: 'go2rtc',\n fields: [\n {\n type: 'info',\n key: 'binary-info',\n label: 'Binary',\n content: 'go2rtc is automatically downloaded on first boot. The binary is stored in the data directory.',\n variant: 'info',\n },\n ],\n },\n {\n id: 'go2rtc-ports',\n title: 'Network Ports',\n description: 'Port configuration for the go2rtc restreamer. Changes require a restart.',\n columns: 3,\n fields: [\n {\n type: 'info',\n key: 'restart-note',\n label: 'Restart required',\n content: 'Port changes require restarting go2rtc.',\n variant: 'warning',\n },\n {\n type: 'number',\n key: 'apiPort',\n label: 'API Port',\n description: 'go2rtc HTTP API',\n min: 1024,\n max: 65535,\n step: 1,\n },\n {\n type: 'number',\n key: 'rtspPort',\n label: 'RTSP Port',\n description: 'RTSP restream output',\n min: 1024,\n max: 65535,\n step: 1,\n },\n {\n type: 'number',\n key: 'webrtcPort',\n label: 'WebRTC Port',\n description: 'WebRTC UDP/TCP',\n min: 1024,\n max: 65535,\n step: 1,\n },\n ],\n },\n ],\n }\n }\n\n getConfig(): Record<string, unknown> {\n return { ...this.currentConfig }\n }\n\n async onConfigChange(config: Record<string, unknown>): Promise<void> {\n this.currentConfig = {\n binaryPath: (config.binaryPath as string) ?? this.currentConfig.binaryPath,\n apiPort: (config.apiPort as number) ?? this.currentConfig.apiPort,\n rtspPort: (config.rtspPort as number) ?? this.currentConfig.rtspPort,\n webrtcPort: (config.webrtcPort as number) ?? this.currentConfig.webrtcPort,\n }\n }\n}\n","export interface Go2rtcGeneratedConfig {\n api: { listen: string }\n rtsp: { listen: string }\n webrtc: { listen: string }\n streams: Record<string, string[]>\n}\n\nexport function generateGo2rtcConfig(config: {\n apiPort: number\n rtspPort: number\n webrtcPort: number\n}): string {\n const yaml = `\napi:\n listen: \":${config.apiPort}\"\n\nrtsp:\n listen: \":${config.rtspPort}\"\n\nwebrtc:\n listen: \":${config.webrtcPort}\"\n ice_servers: []\n\nstreams: {}\n`\n return yaml.trim()\n}\n","/**\n * Auto-download go2rtc binary for the current platform.\n * Refactored to use shared BinaryDownloader from @camstack/core.\n */\nimport { join } from 'node:path'\nimport type { IScopedLogger } from '@camstack/types'\nimport { ensureBinary } from '@camstack/core'\n\nconst GO2RTC_VERSION = '1.9.14'\nconst BASE_URL = `https://github.com/AlexxIT/go2rtc/releases/download/v${GO2RTC_VERSION}`\n\nfunction getGo2rtcDownloadUrl(): { url: string; isArchive: boolean; archiveFormat?: 'zip' } {\n const os = process.platform\n const rawArch = process.arch\n\n let arch: string\n switch (rawArch) {\n case 'x64': arch = 'amd64'; break\n case 'arm64': arch = 'arm64'; break\n case 'arm': arch = 'arm'; break\n case 'ia32': arch = 'i386'; break\n default: throw new Error(`Unsupported architecture: ${rawArch}`)\n }\n\n switch (os) {\n case 'darwin':\n return { url: `${BASE_URL}/go2rtc_mac_${arch}.zip`, isArchive: true, archiveFormat: 'zip' }\n case 'linux':\n return { url: `${BASE_URL}/go2rtc_linux_${arch}`, isArchive: false }\n case 'win32':\n return { url: `${BASE_URL}/go2rtc_win_${arch}.zip`, isArchive: true, archiveFormat: 'zip' }\n default:\n throw new Error(`Unsupported platform: ${os}`)\n }\n}\n\n/**\n * Ensure go2rtc binary is available.\n * Returns the absolute path to the binary.\n */\nexport async function ensureGo2rtcBinary(targetDir: string, logger: IScopedLogger): Promise<string> {\n const { url, isArchive, archiveFormat } = getGo2rtcDownloadUrl()\n\n return ensureBinary({\n name: 'go2rtc',\n targetDir,\n downloadUrl: url,\n logger,\n isArchive,\n archiveFormat,\n })\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -79,8 +79,8 @@ var Go2rtcEngine = class {
|
|
|
79
79
|
|
|
80
80
|
// src/go2rtc.addon.ts
|
|
81
81
|
import { spawn } from "child_process";
|
|
82
|
-
import { mkdirSync
|
|
83
|
-
import { join
|
|
82
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
83
|
+
import { join, dirname } from "path";
|
|
84
84
|
|
|
85
85
|
// src/go2rtc-config-generator.ts
|
|
86
86
|
function generateGo2rtcConfig(config) {
|
|
@@ -101,14 +101,10 @@ streams: {}
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
// src/go2rtc-downloader.ts
|
|
104
|
-
import {
|
|
105
|
-
import { join } from "path";
|
|
106
|
-
import { pipeline } from "stream/promises";
|
|
107
|
-
import { Readable } from "stream";
|
|
108
|
-
import { execFileSync } from "child_process";
|
|
104
|
+
import { ensureBinary } from "@camstack/core";
|
|
109
105
|
var GO2RTC_VERSION = "1.9.14";
|
|
110
106
|
var BASE_URL = `https://github.com/AlexxIT/go2rtc/releases/download/v${GO2RTC_VERSION}`;
|
|
111
|
-
function
|
|
107
|
+
function getGo2rtcDownloadUrl() {
|
|
112
108
|
const os = process.platform;
|
|
113
109
|
const rawArch = process.arch;
|
|
114
110
|
let arch;
|
|
@@ -130,54 +126,25 @@ function getPlatformFilename() {
|
|
|
130
126
|
}
|
|
131
127
|
switch (os) {
|
|
132
128
|
case "darwin":
|
|
133
|
-
return {
|
|
129
|
+
return { url: `${BASE_URL}/go2rtc_mac_${arch}.zip`, isArchive: true, archiveFormat: "zip" };
|
|
134
130
|
case "linux":
|
|
135
|
-
return {
|
|
131
|
+
return { url: `${BASE_URL}/go2rtc_linux_${arch}`, isArchive: false };
|
|
136
132
|
case "win32":
|
|
137
|
-
return {
|
|
133
|
+
return { url: `${BASE_URL}/go2rtc_win_${arch}.zip`, isArchive: true, archiveFormat: "zip" };
|
|
138
134
|
default:
|
|
139
135
|
throw new Error(`Unsupported platform: ${os}`);
|
|
140
136
|
}
|
|
141
137
|
}
|
|
142
138
|
async function ensureGo2rtcBinary(targetDir, logger) {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return "go2rtc";
|
|
153
|
-
} catch {
|
|
154
|
-
}
|
|
155
|
-
const { filename, isZip } = getPlatformFilename();
|
|
156
|
-
const url = `${BASE_URL}/${filename}`;
|
|
157
|
-
logger.info(`Downloading go2rtc v${GO2RTC_VERSION} for ${process.platform}/${process.arch}...`);
|
|
158
|
-
logger.info(` Source: ${url}`);
|
|
159
|
-
mkdirSync(targetDir, { recursive: true });
|
|
160
|
-
const response = await fetch(url, { redirect: "follow" });
|
|
161
|
-
if (!response.ok || !response.body) {
|
|
162
|
-
throw new Error(`Failed to download go2rtc: ${response.status} ${response.statusText}`);
|
|
163
|
-
}
|
|
164
|
-
if (isZip) {
|
|
165
|
-
const tmpPath = join(targetDir, `go2rtc-download.zip`);
|
|
166
|
-
const nodeStream = Readable.fromWeb(response.body);
|
|
167
|
-
await pipeline(nodeStream, createWriteStream(tmpPath));
|
|
168
|
-
try {
|
|
169
|
-
execFileSync("unzip", ["-o", "-q", tmpPath, "-d", targetDir], { stdio: "pipe" });
|
|
170
|
-
} catch {
|
|
171
|
-
execFileSync("tar", ["-xf", tmpPath, "-C", targetDir], { stdio: "pipe" });
|
|
172
|
-
}
|
|
173
|
-
unlinkSync(tmpPath);
|
|
174
|
-
} else {
|
|
175
|
-
const nodeStream = Readable.fromWeb(response.body);
|
|
176
|
-
await pipeline(nodeStream, createWriteStream(targetPath));
|
|
177
|
-
}
|
|
178
|
-
chmodSync(targetPath, 493);
|
|
179
|
-
logger.info(`go2rtc v${GO2RTC_VERSION} downloaded to ${targetPath}`);
|
|
180
|
-
return targetPath;
|
|
139
|
+
const { url, isArchive, archiveFormat } = getGo2rtcDownloadUrl();
|
|
140
|
+
return ensureBinary({
|
|
141
|
+
name: "go2rtc",
|
|
142
|
+
targetDir,
|
|
143
|
+
downloadUrl: url,
|
|
144
|
+
logger,
|
|
145
|
+
isArchive,
|
|
146
|
+
archiveFormat
|
|
147
|
+
});
|
|
181
148
|
}
|
|
182
149
|
|
|
183
150
|
// src/go2rtc.addon.ts
|
|
@@ -202,7 +169,7 @@ var Go2rtcAddon = class {
|
|
|
202
169
|
};
|
|
203
170
|
async initialize(context) {
|
|
204
171
|
const dataPath = context.locationPaths.data;
|
|
205
|
-
const binDir =
|
|
172
|
+
const binDir = join(dataPath, "bin");
|
|
206
173
|
const resolvedBinaryPath = await ensureGo2rtcBinary(binDir, context.logger);
|
|
207
174
|
const processConfig = {
|
|
208
175
|
apiPort: context.addonConfig.apiPort ?? this.currentConfig.apiPort,
|
|
@@ -212,8 +179,8 @@ var Go2rtcAddon = class {
|
|
|
212
179
|
};
|
|
213
180
|
this.currentConfig = processConfig;
|
|
214
181
|
const configYaml = generateGo2rtcConfig(processConfig);
|
|
215
|
-
const configPath =
|
|
216
|
-
|
|
182
|
+
const configPath = join(dataPath, "go2rtc.yaml");
|
|
183
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
217
184
|
writeFileSync(configPath, configYaml);
|
|
218
185
|
this.process = spawn(processConfig.binaryPath, ["-config", configPath], {
|
|
219
186
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -387,7 +354,7 @@ var Go2rtcAddon = class {
|
|
|
387
354
|
return {
|
|
388
355
|
sections: [
|
|
389
356
|
{
|
|
390
|
-
id: "info",
|
|
357
|
+
id: "go2rtc-info",
|
|
391
358
|
title: "go2rtc",
|
|
392
359
|
fields: [
|
|
393
360
|
{
|
|
@@ -400,15 +367,23 @@ var Go2rtcAddon = class {
|
|
|
400
367
|
]
|
|
401
368
|
},
|
|
402
369
|
{
|
|
403
|
-
id: "ports",
|
|
370
|
+
id: "go2rtc-ports",
|
|
404
371
|
title: "Network Ports",
|
|
372
|
+
description: "Port configuration for the go2rtc restreamer. Changes require a restart.",
|
|
405
373
|
columns: 3,
|
|
406
374
|
fields: [
|
|
375
|
+
{
|
|
376
|
+
type: "info",
|
|
377
|
+
key: "restart-note",
|
|
378
|
+
label: "Restart required",
|
|
379
|
+
content: "Port changes require restarting go2rtc.",
|
|
380
|
+
variant: "warning"
|
|
381
|
+
},
|
|
407
382
|
{
|
|
408
383
|
type: "number",
|
|
409
384
|
key: "apiPort",
|
|
410
385
|
label: "API Port",
|
|
411
|
-
description: "HTTP API
|
|
386
|
+
description: "go2rtc HTTP API",
|
|
412
387
|
min: 1024,
|
|
413
388
|
max: 65535,
|
|
414
389
|
step: 1
|
|
@@ -417,7 +392,7 @@ var Go2rtcAddon = class {
|
|
|
417
392
|
type: "number",
|
|
418
393
|
key: "rtspPort",
|
|
419
394
|
label: "RTSP Port",
|
|
420
|
-
description: "
|
|
395
|
+
description: "RTSP restream output",
|
|
421
396
|
min: 1024,
|
|
422
397
|
max: 65535,
|
|
423
398
|
step: 1
|
|
@@ -426,7 +401,7 @@ var Go2rtcAddon = class {
|
|
|
426
401
|
type: "number",
|
|
427
402
|
key: "webrtcPort",
|
|
428
403
|
label: "WebRTC Port",
|
|
429
|
-
description: "
|
|
404
|
+
description: "WebRTC UDP/TCP",
|
|
430
405
|
min: 1024,
|
|
431
406
|
max: 65535,
|
|
432
407
|
step: 1
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/go2rtc-engine.ts","../src/go2rtc.addon.ts","../src/go2rtc-config-generator.ts","../src/go2rtc-downloader.ts"],"sourcesContent":["import type {\n IStreamingEngine,\n StreamFormat,\n StreamInfo,\n StreamingSource,\n StreamStatus,\n} from '@camstack/types'\n\nexport interface Go2rtcConfig {\n readonly apiPort: number\n readonly rtspPort: number\n readonly webrtcPort: number\n readonly binaryPath?: string\n}\n\nexport class Go2rtcEngine implements IStreamingEngine {\n private readonly baseUrl: string\n private readonly streams = new Map<string, StreamingSource>()\n\n constructor(private readonly config: Go2rtcConfig) {\n this.baseUrl = `http://127.0.0.1:${config.apiPort}`\n }\n\n async initialize(_config?: Record<string, unknown>): Promise<void> {\n try {\n const res = await fetch(`${this.baseUrl}/api/streams`)\n if (!res.ok) {\n throw new Error(`go2rtc API returned ${res.status}`)\n }\n } catch (err) {\n throw new Error(\n `Failed to reach go2rtc at ${this.baseUrl}: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n }\n\n async shutdown(): Promise<void> {\n this.streams.clear()\n }\n\n async registerStream(streamId: string, source: StreamingSource): Promise<void> {\n const url = `${this.baseUrl}/api/streams?src=${encodeURIComponent(streamId)}&url=${encodeURIComponent(source.url)}`\n const res = await fetch(url, { method: 'PUT' })\n if (!res.ok) {\n throw new Error(`go2rtc registerStream failed: ${res.status}`)\n }\n this.streams.set(streamId, source)\n }\n\n async unregisterStream(streamId: string): Promise<void> {\n const url = `${this.baseUrl}/api/streams?src=${encodeURIComponent(streamId)}`\n const res = await fetch(url, { method: 'DELETE' })\n if (!res.ok) {\n throw new Error(`go2rtc unregisterStream failed: ${res.status}`)\n }\n this.streams.delete(streamId)\n }\n\n getStreamUrl(streamId: string, format: StreamFormat): string | null {\n const encoded = encodeURIComponent(streamId)\n switch (format) {\n case 'webrtc':\n return `${this.baseUrl}/api/webrtc?src=${encoded}`\n case 'hls':\n return `${this.baseUrl}/api/stream.m3u8?src=${encoded}`\n case 'mjpeg':\n return `${this.baseUrl}/api/frame.jpeg?src=${encoded}`\n case 'rtsp':\n return `rtsp://127.0.0.1:${this.config.rtspPort}/${streamId}`\n default:\n return null\n }\n }\n\n async proxyWhepOffer(streamId: string, sdpOffer: string): Promise<string> {\n const url = `${this.baseUrl}/api/webrtc?src=${encodeURIComponent(streamId)}`\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/sdp' },\n body: sdpOffer,\n })\n if (!res.ok) {\n throw new Error(`go2rtc WHEP failed: ${res.status}`)\n }\n return res.text()\n }\n\n listStreams(): StreamInfo[] {\n return [...this.streams.entries()].map(([id, source]) => ({\n streamId: id,\n source,\n formats: ['webrtc', 'hls', 'mjpeg', 'rtsp'] as StreamFormat[],\n }))\n }\n\n getStreamStatus(streamId: string): StreamStatus | null {\n if (!this.streams.has(streamId)) return null\n return { active: true, viewers: 0 }\n }\n}\n","import { spawn, type ChildProcess } from 'node:child_process'\nimport { mkdirSync, writeFileSync } from 'node:fs'\nimport { join, dirname } from 'node:path'\nimport type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n IConfigurable,\n ConfigUISchema,\n CapabilityProviderMap,\n IRestreamer,\n RegisteredStream,\n RestreamerExposedResource as ExposedResource,\n EncodedPacket,\n} from '@camstack/types'\nimport type { IWebRtcProvider } from './webrtc-provider'\nimport { Go2rtcEngine } from './go2rtc-engine'\nimport type { Go2rtcConfig } from './go2rtc-engine'\nimport { generateGo2rtcConfig } from './go2rtc-config-generator'\nimport { ensureGo2rtcBinary } from './go2rtc-downloader'\n\nexport interface Go2rtcProcessConfig {\n readonly apiPort: number\n readonly rtspPort: number\n readonly webrtcPort: number\n readonly binaryPath: string\n}\n\ninterface DeviceRegistration {\n readonly deviceId: string\n readonly streams: readonly RegisteredStream[]\n /** The original source URL registered with go2rtc (for cleanup) */\n readonly streamIds: readonly string[]\n}\n\nexport class Go2rtcAddon implements ICamstackAddon, IConfigurable, IRestreamer, IWebRtcProvider {\n readonly manifest: AddonManifest = {\n id: 'go2rtc',\n name: 'go2rtc Streaming Engine',\n version: '1.0.0',\n capabilities: ['streaming-engine'],\n }\n\n /** IRestreamer identity */\n readonly id = 'go2rtc'\n readonly name = 'go2rtc Restreamer'\n\n private engine: Go2rtcEngine | null = null\n private process: ChildProcess | null = null\n private readonly registeredDevices = new Map<string, DeviceRegistration>()\n private currentConfig: Go2rtcProcessConfig = {\n apiPort: 1984,\n rtspPort: 8554,\n webrtcPort: 8555,\n binaryPath: 'go2rtc',\n }\n\n async initialize(context: AddonContext): Promise<void> {\n // Path comes from StorageLocationManager via context.locationPaths\n const dataPath = context.locationPaths.data\n const binDir = join(dataPath, 'bin')\n\n // Auto-download go2rtc binary if not found\n const resolvedBinaryPath = await ensureGo2rtcBinary(binDir, context.logger)\n\n const processConfig: Go2rtcProcessConfig = {\n apiPort: (context.addonConfig.apiPort as number) ?? this.currentConfig.apiPort,\n rtspPort: (context.addonConfig.rtspPort as number) ?? this.currentConfig.rtspPort,\n webrtcPort: (context.addonConfig.webrtcPort as number) ?? this.currentConfig.webrtcPort,\n binaryPath: resolvedBinaryPath,\n }\n this.currentConfig = processConfig\n\n // Generate config file\n const configYaml = generateGo2rtcConfig(processConfig)\n const configPath = join(dataPath, 'go2rtc.yaml')\n mkdirSync(dirname(configPath), { recursive: true })\n writeFileSync(configPath, configYaml)\n\n // Start go2rtc as a child process\n this.process = spawn(processConfig.binaryPath, ['-config', configPath], {\n stdio: ['pipe', 'pipe', 'pipe'],\n })\n\n this.process.on('exit', (code, signal) => {\n context.logger.warn(`go2rtc process exited (code: ${code}, signal: ${signal})`)\n })\n\n this.process.stderr?.on('data', (data: Buffer) => {\n const msg = data.toString().trim()\n if (msg) context.logger.debug(`go2rtc stderr: ${msg}`)\n })\n\n this.process.stdout?.on('data', (data: Buffer) => {\n const msg = data.toString().trim()\n if (msg) context.logger.debug(`go2rtc stdout: ${msg}`)\n })\n\n // Wait for API to be ready\n const apiUrl = `http://127.0.0.1:${processConfig.apiPort}/api/streams`\n await this.waitForReady(apiUrl, 10_000)\n\n // Create engine\n const engineConfig: Go2rtcConfig = {\n apiPort: processConfig.apiPort,\n rtspPort: processConfig.rtspPort,\n webrtcPort: processConfig.webrtcPort,\n binaryPath: processConfig.binaryPath,\n }\n this.engine = new Go2rtcEngine(engineConfig)\n await this.engine.initialize()\n\n context.logger.info(`go2rtc started (PID: ${this.process.pid}, API: ${processConfig.apiPort})`)\n }\n\n async shutdown(): Promise<void> {\n await this.engine?.shutdown()\n this.engine = null\n this.registeredDevices.clear()\n\n if (this.process) {\n this.process.kill('SIGTERM')\n this.process = null\n }\n }\n\n getEngine(): Go2rtcEngine {\n if (!this.engine) throw new Error('go2rtc not initialized')\n return this.engine\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === 'streaming-engine' && this.engine) {\n return this.engine as unknown as CapabilityProviderMap[K]\n }\n if (name === 'restreamer') {\n return this as unknown as CapabilityProviderMap[K]\n }\n if (name === 'webrtc') {\n return this as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n // --- IRestreamer implementation ---\n\n async registerDevice(deviceId: string, streams: readonly RegisteredStream[]): Promise<void> {\n if (!this.engine) {\n throw new Error('go2rtc not initialized — cannot register device')\n }\n\n const streamIds: string[] = []\n\n for (const stream of streams) {\n streamIds.push(stream.streamId)\n\n // Register the stream with go2rtc using the broker's local TCP URL.\n // The broker is the sole reader from the camera; go2rtc consumes the\n // broker's TCP pipe instead of connecting to the camera directly.\n if (stream.sourceUrl) {\n await this.engine.registerStream(stream.streamId, {\n url: `ffmpeg:${stream.sourceUrl}#video=${stream.codec}`,\n protocol: 'rtsp',\n deviceId,\n providerId: 'broker',\n })\n }\n }\n\n this.registeredDevices.set(deviceId, {\n deviceId,\n streams,\n streamIds,\n })\n }\n\n pushPacket(_streamId: string, _packet: EncodedPacket): void {\n // Packet push from the broker is a no-op — go2rtc reads directly from\n // the broker's local TCP pipe server instead.\n }\n\n async unregisterDevice(deviceId: string): Promise<void> {\n const registration = this.registeredDevices.get(deviceId)\n if (!registration) {\n return\n }\n\n if (this.engine) {\n for (const streamId of registration.streamIds) {\n try {\n await this.engine.unregisterStream(streamId)\n } catch {\n // Best-effort cleanup\n }\n }\n }\n\n this.registeredDevices.delete(deviceId)\n }\n\n getExposedResources(deviceId: string): readonly ExposedResource[] {\n const registration = this.registeredDevices.get(deviceId)\n if (!registration || !this.engine) {\n return []\n }\n\n const resources: ExposedResource[] = []\n\n for (const streamId of registration.streamIds) {\n const webrtcUrl = this.engine.getStreamUrl(streamId, 'webrtc')\n if (webrtcUrl) {\n resources.push({ type: 'url', format: 'webrtc', value: webrtcUrl })\n }\n\n const hlsUrl = this.engine.getStreamUrl(streamId, 'hls')\n if (hlsUrl) {\n resources.push({ type: 'url', format: 'hls', value: hlsUrl })\n }\n\n const mjpegUrl = this.engine.getStreamUrl(streamId, 'mjpeg')\n if (mjpegUrl) {\n resources.push({ type: 'url', format: 'mjpeg', value: mjpegUrl })\n }\n\n const rtspUrl = this.engine.getStreamUrl(streamId, 'rtsp')\n if (rtspUrl) {\n resources.push({ type: 'url', format: 'rtsp', value: rtspUrl })\n }\n }\n\n return resources\n }\n\n async proxyWhepOffer(streamId: string, sdpOffer: string): Promise<string> {\n if (!this.engine) {\n throw new Error('go2rtc not initialized — cannot proxy WHEP')\n }\n return this.engine.proxyWhepOffer(streamId, sdpOffer)\n }\n\n // --- End IRestreamer ---\n\n // --- IWebRtcProvider implementation ---\n\n async handleOffer(streamId: string, sdpOffer: string): Promise<string> {\n if (!this.engine) {\n throw new Error('go2rtc not initialized — cannot handle WebRTC offer')\n }\n return this.engine.proxyWhepOffer(streamId, sdpOffer)\n }\n\n supportsStream(streamId: string): boolean {\n const deviceId = streamId.split('/')[0]\n return deviceId !== undefined && this.registeredDevices.has(deviceId)\n }\n\n async registerStream(streamId: string, _codec: string): Promise<void> {\n // Stream registration is handled via registerDevice (IRestreamer).\n // This is a no-op since go2rtc manages streams through its own API.\n const deviceId = streamId.split('/')[0]\n if (deviceId && !this.registeredDevices.has(deviceId)) {\n this.registeredDevices.set(deviceId, {\n deviceId,\n streams: [{ streamId, label: streamId, codec: _codec, type: 'video' }],\n streamIds: [streamId],\n })\n }\n }\n\n async unregisterStream(streamId: string): Promise<void> {\n if (!this.engine) {\n return\n }\n try {\n await this.engine.unregisterStream(streamId)\n } catch {\n // Best-effort cleanup\n }\n }\n\n // --- End IWebRtcProvider ---\n\n /** Exposed for testing */\n async waitForReady(url: string, timeoutMs: number): Promise<void> {\n const start = Date.now()\n while (Date.now() - start < timeoutMs) {\n try {\n const res = await fetch(url)\n if (res.ok) return\n } catch {\n // Not ready yet\n }\n await new Promise((r) => setTimeout(r, 500))\n }\n throw new Error(`go2rtc did not become ready within ${timeoutMs}ms`)\n }\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'info',\n title: 'go2rtc',\n fields: [\n {\n type: 'info',\n key: 'binary-info',\n label: 'Binary',\n content: 'go2rtc is automatically downloaded on first boot. The binary is stored in the data directory.',\n variant: 'info',\n },\n ],\n },\n {\n id: 'ports',\n title: 'Network Ports',\n columns: 3,\n fields: [\n {\n type: 'number',\n key: 'apiPort',\n label: 'API Port',\n description: 'HTTP API port for go2rtc management',\n min: 1024,\n max: 65535,\n step: 1,\n },\n {\n type: 'number',\n key: 'rtspPort',\n label: 'RTSP Port',\n description: 'Port for RTSP stream output',\n min: 1024,\n max: 65535,\n step: 1,\n },\n {\n type: 'number',\n key: 'webrtcPort',\n label: 'WebRTC Port',\n description: 'Port for WebRTC ICE/STUN negotiations',\n min: 1024,\n max: 65535,\n step: 1,\n },\n ],\n },\n ],\n }\n }\n\n getConfig(): Record<string, unknown> {\n return { ...this.currentConfig }\n }\n\n async onConfigChange(config: Record<string, unknown>): Promise<void> {\n this.currentConfig = {\n binaryPath: (config.binaryPath as string) ?? this.currentConfig.binaryPath,\n apiPort: (config.apiPort as number) ?? this.currentConfig.apiPort,\n rtspPort: (config.rtspPort as number) ?? this.currentConfig.rtspPort,\n webrtcPort: (config.webrtcPort as number) ?? this.currentConfig.webrtcPort,\n }\n }\n}\n","export interface Go2rtcGeneratedConfig {\n api: { listen: string }\n rtsp: { listen: string }\n webrtc: { listen: string }\n streams: Record<string, string[]>\n}\n\nexport function generateGo2rtcConfig(config: {\n apiPort: number\n rtspPort: number\n webrtcPort: number\n}): string {\n const yaml = `\napi:\n listen: \":${config.apiPort}\"\n\nrtsp:\n listen: \":${config.rtspPort}\"\n\nwebrtc:\n listen: \":${config.webrtcPort}\"\n ice_servers: []\n\nstreams: {}\n`\n return yaml.trim()\n}\n","/**\n * Auto-download go2rtc binary for the current platform.\n * Checks if binary exists at the target path. If not, downloads from GitHub releases.\n */\n\nimport { existsSync, mkdirSync, chmodSync, createWriteStream, unlinkSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { pipeline } from 'node:stream/promises'\nimport { Readable } from 'node:stream'\nimport { execFileSync } from 'node:child_process'\nimport type { IScopedLogger } from '@camstack/types'\n\nconst GO2RTC_VERSION = '1.9.14'\nconst BASE_URL = `https://github.com/AlexxIT/go2rtc/releases/download/v${GO2RTC_VERSION}`\n\nfunction getPlatformFilename(): { filename: string; isZip: boolean } {\n const os = process.platform\n const rawArch = process.arch\n\n let arch: string\n switch (rawArch) {\n case 'x64': arch = 'amd64'; break\n case 'arm64': arch = 'arm64'; break\n case 'arm': arch = 'arm'; break\n case 'ia32': arch = 'i386'; break\n default: throw new Error(`Unsupported architecture: ${rawArch}`)\n }\n\n switch (os) {\n case 'darwin':\n return { filename: `go2rtc_mac_${arch}.zip`, isZip: true }\n case 'linux':\n return { filename: `go2rtc_linux_${arch}`, isZip: false }\n case 'win32':\n return { filename: `go2rtc_win_${arch}.zip`, isZip: true }\n default:\n throw new Error(`Unsupported platform: ${os}`)\n }\n}\n\n/**\n * Ensure go2rtc binary is available at `targetPath`.\n * Downloads from GitHub releases if not present.\n * Returns the absolute path to the binary.\n */\nexport async function ensureGo2rtcBinary(targetDir: string, logger: IScopedLogger): Promise<string> {\n const binaryName = process.platform === 'win32' ? 'go2rtc.exe' : 'go2rtc'\n const targetPath = join(targetDir, binaryName)\n\n // Already downloaded?\n if (existsSync(targetPath)) {\n logger.debug(`go2rtc binary found at ${targetPath}`)\n return targetPath\n }\n\n // Check if it's in PATH\n try {\n execFileSync('go2rtc', ['--version'], { stdio: 'pipe', timeout: 5000 })\n logger.info('go2rtc found in system PATH')\n return 'go2rtc'\n } catch {\n // Not in PATH — download it\n }\n\n const { filename, isZip } = getPlatformFilename()\n const url = `${BASE_URL}/${filename}`\n\n logger.info(`Downloading go2rtc v${GO2RTC_VERSION} for ${process.platform}/${process.arch}...`)\n logger.info(` Source: ${url}`)\n\n mkdirSync(targetDir, { recursive: true })\n\n const response = await fetch(url, { redirect: 'follow' })\n if (!response.ok || !response.body) {\n throw new Error(`Failed to download go2rtc: ${response.status} ${response.statusText}`)\n }\n\n if (isZip) {\n // Download zip to temp, extract\n const tmpPath = join(targetDir, `go2rtc-download.zip`)\n const nodeStream = Readable.fromWeb(response.body as any)\n await pipeline(nodeStream, createWriteStream(tmpPath))\n\n // Use system unzip\n try {\n execFileSync('unzip', ['-o', '-q', tmpPath, '-d', targetDir], { stdio: 'pipe' })\n } catch {\n // Try tar on systems without unzip\n execFileSync('tar', ['-xf', tmpPath, '-C', targetDir], { stdio: 'pipe' })\n }\n unlinkSync(tmpPath)\n } else {\n // Direct binary download\n const nodeStream = Readable.fromWeb(response.body as any)\n await pipeline(nodeStream, createWriteStream(targetPath))\n }\n\n chmodSync(targetPath, 0o755)\n logger.info(`go2rtc v${GO2RTC_VERSION} downloaded to ${targetPath}`)\n\n return targetPath\n}\n"],"mappings":"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","names":["mkdirSync","join","join","mkdirSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/go2rtc-engine.ts","../src/go2rtc.addon.ts","../src/go2rtc-config-generator.ts","../src/go2rtc-downloader.ts"],"sourcesContent":["import type {\n IStreamingEngine,\n StreamFormat,\n StreamInfo,\n StreamingSource,\n StreamStatus,\n} from '@camstack/types'\n\nexport interface Go2rtcConfig {\n readonly apiPort: number\n readonly rtspPort: number\n readonly webrtcPort: number\n readonly binaryPath?: string\n}\n\nexport class Go2rtcEngine implements IStreamingEngine {\n private readonly baseUrl: string\n private readonly streams = new Map<string, StreamingSource>()\n\n constructor(private readonly config: Go2rtcConfig) {\n this.baseUrl = `http://127.0.0.1:${config.apiPort}`\n }\n\n async initialize(_config?: Record<string, unknown>): Promise<void> {\n try {\n const res = await fetch(`${this.baseUrl}/api/streams`)\n if (!res.ok) {\n throw new Error(`go2rtc API returned ${res.status}`)\n }\n } catch (err) {\n throw new Error(\n `Failed to reach go2rtc at ${this.baseUrl}: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n }\n\n async shutdown(): Promise<void> {\n this.streams.clear()\n }\n\n async registerStream(streamId: string, source: StreamingSource): Promise<void> {\n const url = `${this.baseUrl}/api/streams?src=${encodeURIComponent(streamId)}&url=${encodeURIComponent(source.url)}`\n const res = await fetch(url, { method: 'PUT' })\n if (!res.ok) {\n throw new Error(`go2rtc registerStream failed: ${res.status}`)\n }\n this.streams.set(streamId, source)\n }\n\n async unregisterStream(streamId: string): Promise<void> {\n const url = `${this.baseUrl}/api/streams?src=${encodeURIComponent(streamId)}`\n const res = await fetch(url, { method: 'DELETE' })\n if (!res.ok) {\n throw new Error(`go2rtc unregisterStream failed: ${res.status}`)\n }\n this.streams.delete(streamId)\n }\n\n getStreamUrl(streamId: string, format: StreamFormat): string | null {\n const encoded = encodeURIComponent(streamId)\n switch (format) {\n case 'webrtc':\n return `${this.baseUrl}/api/webrtc?src=${encoded}`\n case 'hls':\n return `${this.baseUrl}/api/stream.m3u8?src=${encoded}`\n case 'mjpeg':\n return `${this.baseUrl}/api/frame.jpeg?src=${encoded}`\n case 'rtsp':\n return `rtsp://127.0.0.1:${this.config.rtspPort}/${streamId}`\n default:\n return null\n }\n }\n\n async proxyWhepOffer(streamId: string, sdpOffer: string): Promise<string> {\n const url = `${this.baseUrl}/api/webrtc?src=${encodeURIComponent(streamId)}`\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/sdp' },\n body: sdpOffer,\n })\n if (!res.ok) {\n throw new Error(`go2rtc WHEP failed: ${res.status}`)\n }\n return res.text()\n }\n\n listStreams(): StreamInfo[] {\n return [...this.streams.entries()].map(([id, source]) => ({\n streamId: id,\n source,\n formats: ['webrtc', 'hls', 'mjpeg', 'rtsp'] as StreamFormat[],\n }))\n }\n\n getStreamStatus(streamId: string): StreamStatus | null {\n if (!this.streams.has(streamId)) return null\n return { active: true, viewers: 0 }\n }\n}\n","import { spawn, type ChildProcess } from 'node:child_process'\nimport { mkdirSync, writeFileSync } from 'node:fs'\nimport { join, dirname } from 'node:path'\nimport type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n IConfigurable,\n ConfigUISchema,\n CapabilityProviderMap,\n IRestreamer,\n RegisteredStream,\n RestreamerExposedResource as ExposedResource,\n EncodedPacket,\n} from '@camstack/types'\nimport type { IWebRtcProvider } from './webrtc-provider'\nimport { Go2rtcEngine } from './go2rtc-engine'\nimport type { Go2rtcConfig } from './go2rtc-engine'\nimport { generateGo2rtcConfig } from './go2rtc-config-generator'\nimport { ensureGo2rtcBinary } from './go2rtc-downloader'\n\nexport interface Go2rtcProcessConfig {\n readonly apiPort: number\n readonly rtspPort: number\n readonly webrtcPort: number\n readonly binaryPath: string\n}\n\ninterface DeviceRegistration {\n readonly deviceId: string\n readonly streams: readonly RegisteredStream[]\n /** The original source URL registered with go2rtc (for cleanup) */\n readonly streamIds: readonly string[]\n}\n\nexport class Go2rtcAddon implements ICamstackAddon, IConfigurable, IRestreamer, IWebRtcProvider {\n readonly manifest: AddonManifest = {\n id: 'go2rtc',\n name: 'go2rtc Streaming Engine',\n version: '1.0.0',\n capabilities: ['streaming-engine'],\n }\n\n /** IRestreamer identity */\n readonly id = 'go2rtc'\n readonly name = 'go2rtc Restreamer'\n\n private engine: Go2rtcEngine | null = null\n private process: ChildProcess | null = null\n private readonly registeredDevices = new Map<string, DeviceRegistration>()\n private currentConfig: Go2rtcProcessConfig = {\n apiPort: 1984,\n rtspPort: 8554,\n webrtcPort: 8555,\n binaryPath: 'go2rtc',\n }\n\n async initialize(context: AddonContext): Promise<void> {\n // Path comes from StorageLocationManager via context.locationPaths\n const dataPath = context.locationPaths.data\n const binDir = join(dataPath, 'bin')\n\n // Auto-download go2rtc binary if not found\n const resolvedBinaryPath = await ensureGo2rtcBinary(binDir, context.logger)\n\n const processConfig: Go2rtcProcessConfig = {\n apiPort: (context.addonConfig.apiPort as number) ?? this.currentConfig.apiPort,\n rtspPort: (context.addonConfig.rtspPort as number) ?? this.currentConfig.rtspPort,\n webrtcPort: (context.addonConfig.webrtcPort as number) ?? this.currentConfig.webrtcPort,\n binaryPath: resolvedBinaryPath,\n }\n this.currentConfig = processConfig\n\n // Generate config file\n const configYaml = generateGo2rtcConfig(processConfig)\n const configPath = join(dataPath, 'go2rtc.yaml')\n mkdirSync(dirname(configPath), { recursive: true })\n writeFileSync(configPath, configYaml)\n\n // Start go2rtc as a child process\n this.process = spawn(processConfig.binaryPath, ['-config', configPath], {\n stdio: ['pipe', 'pipe', 'pipe'],\n })\n\n this.process.on('exit', (code, signal) => {\n context.logger.warn(`go2rtc process exited (code: ${code}, signal: ${signal})`)\n })\n\n this.process.stderr?.on('data', (data: Buffer) => {\n const msg = data.toString().trim()\n if (msg) context.logger.debug(`go2rtc stderr: ${msg}`)\n })\n\n this.process.stdout?.on('data', (data: Buffer) => {\n const msg = data.toString().trim()\n if (msg) context.logger.debug(`go2rtc stdout: ${msg}`)\n })\n\n // Wait for API to be ready\n const apiUrl = `http://127.0.0.1:${processConfig.apiPort}/api/streams`\n await this.waitForReady(apiUrl, 10_000)\n\n // Create engine\n const engineConfig: Go2rtcConfig = {\n apiPort: processConfig.apiPort,\n rtspPort: processConfig.rtspPort,\n webrtcPort: processConfig.webrtcPort,\n binaryPath: processConfig.binaryPath,\n }\n this.engine = new Go2rtcEngine(engineConfig)\n await this.engine.initialize()\n\n context.logger.info(`go2rtc started (PID: ${this.process.pid}, API: ${processConfig.apiPort})`)\n }\n\n async shutdown(): Promise<void> {\n await this.engine?.shutdown()\n this.engine = null\n this.registeredDevices.clear()\n\n if (this.process) {\n this.process.kill('SIGTERM')\n this.process = null\n }\n }\n\n getEngine(): Go2rtcEngine {\n if (!this.engine) throw new Error('go2rtc not initialized')\n return this.engine\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === 'streaming-engine' && this.engine) {\n return this.engine as unknown as CapabilityProviderMap[K]\n }\n if (name === 'restreamer') {\n return this as unknown as CapabilityProviderMap[K]\n }\n if (name === 'webrtc') {\n return this as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n // --- IRestreamer implementation ---\n\n async registerDevice(deviceId: string, streams: readonly RegisteredStream[]): Promise<void> {\n if (!this.engine) {\n throw new Error('go2rtc not initialized — cannot register device')\n }\n\n const streamIds: string[] = []\n\n for (const stream of streams) {\n streamIds.push(stream.streamId)\n\n // Register the stream with go2rtc using the broker's local TCP URL.\n // The broker is the sole reader from the camera; go2rtc consumes the\n // broker's TCP pipe instead of connecting to the camera directly.\n if (stream.sourceUrl) {\n await this.engine.registerStream(stream.streamId, {\n url: `ffmpeg:${stream.sourceUrl}#video=${stream.codec}`,\n protocol: 'rtsp',\n deviceId,\n providerId: 'broker',\n })\n }\n }\n\n this.registeredDevices.set(deviceId, {\n deviceId,\n streams,\n streamIds,\n })\n }\n\n pushPacket(_streamId: string, _packet: EncodedPacket): void {\n // Packet push from the broker is a no-op — go2rtc reads directly from\n // the broker's local TCP pipe server instead.\n }\n\n async unregisterDevice(deviceId: string): Promise<void> {\n const registration = this.registeredDevices.get(deviceId)\n if (!registration) {\n return\n }\n\n if (this.engine) {\n for (const streamId of registration.streamIds) {\n try {\n await this.engine.unregisterStream(streamId)\n } catch {\n // Best-effort cleanup\n }\n }\n }\n\n this.registeredDevices.delete(deviceId)\n }\n\n getExposedResources(deviceId: string): readonly ExposedResource[] {\n const registration = this.registeredDevices.get(deviceId)\n if (!registration || !this.engine) {\n return []\n }\n\n const resources: ExposedResource[] = []\n\n for (const streamId of registration.streamIds) {\n const webrtcUrl = this.engine.getStreamUrl(streamId, 'webrtc')\n if (webrtcUrl) {\n resources.push({ type: 'url', format: 'webrtc', value: webrtcUrl })\n }\n\n const hlsUrl = this.engine.getStreamUrl(streamId, 'hls')\n if (hlsUrl) {\n resources.push({ type: 'url', format: 'hls', value: hlsUrl })\n }\n\n const mjpegUrl = this.engine.getStreamUrl(streamId, 'mjpeg')\n if (mjpegUrl) {\n resources.push({ type: 'url', format: 'mjpeg', value: mjpegUrl })\n }\n\n const rtspUrl = this.engine.getStreamUrl(streamId, 'rtsp')\n if (rtspUrl) {\n resources.push({ type: 'url', format: 'rtsp', value: rtspUrl })\n }\n }\n\n return resources\n }\n\n async proxyWhepOffer(streamId: string, sdpOffer: string): Promise<string> {\n if (!this.engine) {\n throw new Error('go2rtc not initialized — cannot proxy WHEP')\n }\n return this.engine.proxyWhepOffer(streamId, sdpOffer)\n }\n\n // --- End IRestreamer ---\n\n // --- IWebRtcProvider implementation ---\n\n async handleOffer(streamId: string, sdpOffer: string): Promise<string> {\n if (!this.engine) {\n throw new Error('go2rtc not initialized — cannot handle WebRTC offer')\n }\n return this.engine.proxyWhepOffer(streamId, sdpOffer)\n }\n\n supportsStream(streamId: string): boolean {\n const deviceId = streamId.split('/')[0]\n return deviceId !== undefined && this.registeredDevices.has(deviceId)\n }\n\n async registerStream(streamId: string, _codec: string): Promise<void> {\n // Stream registration is handled via registerDevice (IRestreamer).\n // This is a no-op since go2rtc manages streams through its own API.\n const deviceId = streamId.split('/')[0]\n if (deviceId && !this.registeredDevices.has(deviceId)) {\n this.registeredDevices.set(deviceId, {\n deviceId,\n streams: [{ streamId, label: streamId, codec: _codec, type: 'video' }],\n streamIds: [streamId],\n })\n }\n }\n\n async unregisterStream(streamId: string): Promise<void> {\n if (!this.engine) {\n return\n }\n try {\n await this.engine.unregisterStream(streamId)\n } catch {\n // Best-effort cleanup\n }\n }\n\n // --- End IWebRtcProvider ---\n\n /** Exposed for testing */\n async waitForReady(url: string, timeoutMs: number): Promise<void> {\n const start = Date.now()\n while (Date.now() - start < timeoutMs) {\n try {\n const res = await fetch(url)\n if (res.ok) return\n } catch {\n // Not ready yet\n }\n await new Promise((r) => setTimeout(r, 500))\n }\n throw new Error(`go2rtc did not become ready within ${timeoutMs}ms`)\n }\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'go2rtc-info',\n title: 'go2rtc',\n fields: [\n {\n type: 'info',\n key: 'binary-info',\n label: 'Binary',\n content: 'go2rtc is automatically downloaded on first boot. The binary is stored in the data directory.',\n variant: 'info',\n },\n ],\n },\n {\n id: 'go2rtc-ports',\n title: 'Network Ports',\n description: 'Port configuration for the go2rtc restreamer. Changes require a restart.',\n columns: 3,\n fields: [\n {\n type: 'info',\n key: 'restart-note',\n label: 'Restart required',\n content: 'Port changes require restarting go2rtc.',\n variant: 'warning',\n },\n {\n type: 'number',\n key: 'apiPort',\n label: 'API Port',\n description: 'go2rtc HTTP API',\n min: 1024,\n max: 65535,\n step: 1,\n },\n {\n type: 'number',\n key: 'rtspPort',\n label: 'RTSP Port',\n description: 'RTSP restream output',\n min: 1024,\n max: 65535,\n step: 1,\n },\n {\n type: 'number',\n key: 'webrtcPort',\n label: 'WebRTC Port',\n description: 'WebRTC UDP/TCP',\n min: 1024,\n max: 65535,\n step: 1,\n },\n ],\n },\n ],\n }\n }\n\n getConfig(): Record<string, unknown> {\n return { ...this.currentConfig }\n }\n\n async onConfigChange(config: Record<string, unknown>): Promise<void> {\n this.currentConfig = {\n binaryPath: (config.binaryPath as string) ?? this.currentConfig.binaryPath,\n apiPort: (config.apiPort as number) ?? this.currentConfig.apiPort,\n rtspPort: (config.rtspPort as number) ?? this.currentConfig.rtspPort,\n webrtcPort: (config.webrtcPort as number) ?? this.currentConfig.webrtcPort,\n }\n }\n}\n","export interface Go2rtcGeneratedConfig {\n api: { listen: string }\n rtsp: { listen: string }\n webrtc: { listen: string }\n streams: Record<string, string[]>\n}\n\nexport function generateGo2rtcConfig(config: {\n apiPort: number\n rtspPort: number\n webrtcPort: number\n}): string {\n const yaml = `\napi:\n listen: \":${config.apiPort}\"\n\nrtsp:\n listen: \":${config.rtspPort}\"\n\nwebrtc:\n listen: \":${config.webrtcPort}\"\n ice_servers: []\n\nstreams: {}\n`\n return yaml.trim()\n}\n","/**\n * Auto-download go2rtc binary for the current platform.\n * Refactored to use shared BinaryDownloader from @camstack/core.\n */\nimport { join } from 'node:path'\nimport type { IScopedLogger } from '@camstack/types'\nimport { ensureBinary } from '@camstack/core'\n\nconst GO2RTC_VERSION = '1.9.14'\nconst BASE_URL = `https://github.com/AlexxIT/go2rtc/releases/download/v${GO2RTC_VERSION}`\n\nfunction getGo2rtcDownloadUrl(): { url: string; isArchive: boolean; archiveFormat?: 'zip' } {\n const os = process.platform\n const rawArch = process.arch\n\n let arch: string\n switch (rawArch) {\n case 'x64': arch = 'amd64'; break\n case 'arm64': arch = 'arm64'; break\n case 'arm': arch = 'arm'; break\n case 'ia32': arch = 'i386'; break\n default: throw new Error(`Unsupported architecture: ${rawArch}`)\n }\n\n switch (os) {\n case 'darwin':\n return { url: `${BASE_URL}/go2rtc_mac_${arch}.zip`, isArchive: true, archiveFormat: 'zip' }\n case 'linux':\n return { url: `${BASE_URL}/go2rtc_linux_${arch}`, isArchive: false }\n case 'win32':\n return { url: `${BASE_URL}/go2rtc_win_${arch}.zip`, isArchive: true, archiveFormat: 'zip' }\n default:\n throw new Error(`Unsupported platform: ${os}`)\n }\n}\n\n/**\n * Ensure go2rtc binary is available.\n * Returns the absolute path to the binary.\n */\nexport async function ensureGo2rtcBinary(targetDir: string, logger: IScopedLogger): Promise<string> {\n const { url, isArchive, archiveFormat } = getGo2rtcDownloadUrl()\n\n return ensureBinary({\n name: 'go2rtc',\n targetDir,\n downloadUrl: url,\n logger,\n isArchive,\n archiveFormat,\n })\n}\n"],"mappings":";AAeO,IAAM,eAAN,MAA+C;AAAA,EAIpD,YAA6B,QAAsB;AAAtB;AAC3B,SAAK,UAAU,oBAAoB,OAAO,OAAO;AAAA,EACnD;AAAA,EALiB;AAAA,EACA,UAAU,oBAAI,IAA6B;AAAA,EAM5D,MAAM,WAAW,SAAkD;AACjE,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,cAAc;AACrD,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,IAAI,MAAM,uBAAuB,IAAI,MAAM,EAAE;AAAA,MACrD;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,6BAA6B,KAAK,OAAO,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MAChG;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,WAA0B;AAC9B,SAAK,QAAQ,MAAM;AAAA,EACrB;AAAA,EAEA,MAAM,eAAe,UAAkB,QAAwC;AAC7E,UAAM,MAAM,GAAG,KAAK,OAAO,oBAAoB,mBAAmB,QAAQ,CAAC,QAAQ,mBAAmB,OAAO,GAAG,CAAC;AACjH,UAAM,MAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,MAAM,CAAC;AAC9C,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,iCAAiC,IAAI,MAAM,EAAE;AAAA,IAC/D;AACA,SAAK,QAAQ,IAAI,UAAU,MAAM;AAAA,EACnC;AAAA,EAEA,MAAM,iBAAiB,UAAiC;AACtD,UAAM,MAAM,GAAG,KAAK,OAAO,oBAAoB,mBAAmB,QAAQ,CAAC;AAC3E,UAAM,MAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,SAAS,CAAC;AACjD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,mCAAmC,IAAI,MAAM,EAAE;AAAA,IACjE;AACA,SAAK,QAAQ,OAAO,QAAQ;AAAA,EAC9B;AAAA,EAEA,aAAa,UAAkB,QAAqC;AAClE,UAAM,UAAU,mBAAmB,QAAQ;AAC3C,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,eAAO,GAAG,KAAK,OAAO,mBAAmB,OAAO;AAAA,MAClD,KAAK;AACH,eAAO,GAAG,KAAK,OAAO,wBAAwB,OAAO;AAAA,MACvD,KAAK;AACH,eAAO,GAAG,KAAK,OAAO,uBAAuB,OAAO;AAAA,MACtD,KAAK;AACH,eAAO,oBAAoB,KAAK,OAAO,QAAQ,IAAI,QAAQ;AAAA,MAC7D;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAAA,EAEA,MAAM,eAAe,UAAkB,UAAmC;AACxE,UAAM,MAAM,GAAG,KAAK,OAAO,mBAAmB,mBAAmB,QAAQ,CAAC;AAC1E,UAAM,MAAM,MAAM,MAAM,KAAK;AAAA,MAC3B,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,kBAAkB;AAAA,MAC7C,MAAM;AAAA,IACR,CAAC;AACD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,uBAAuB,IAAI,MAAM,EAAE;AAAA,IACrD;AACA,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA,EAEA,cAA4B;AAC1B,WAAO,CAAC,GAAG,KAAK,QAAQ,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,MAAM,OAAO;AAAA,MACxD,UAAU;AAAA,MACV;AAAA,MACA,SAAS,CAAC,UAAU,OAAO,SAAS,MAAM;AAAA,IAC5C,EAAE;AAAA,EACJ;AAAA,EAEA,gBAAgB,UAAuC;AACrD,QAAI,CAAC,KAAK,QAAQ,IAAI,QAAQ,EAAG,QAAO;AACxC,WAAO,EAAE,QAAQ,MAAM,SAAS,EAAE;AAAA,EACpC;AACF;;;ACnGA,SAAS,aAAgC;AACzC,SAAS,WAAW,qBAAqB;AACzC,SAAS,MAAM,eAAe;;;ACKvB,SAAS,qBAAqB,QAI1B;AACT,QAAM,OAAO;AAAA;AAAA,cAED,OAAO,OAAO;AAAA;AAAA;AAAA,cAGd,OAAO,QAAQ;AAAA;AAAA;AAAA,cAGf,OAAO,UAAU;AAAA;AAAA;AAAA;AAAA;AAK7B,SAAO,KAAK,KAAK;AACnB;;;ACpBA,SAAS,oBAAoB;AAE7B,IAAM,iBAAiB;AACvB,IAAM,WAAW,wDAAwD,cAAc;AAEvF,SAAS,uBAAmF;AAC1F,QAAM,KAAK,QAAQ;AACnB,QAAM,UAAU,QAAQ;AAExB,MAAI;AACJ,UAAQ,SAAS;AAAA,IACf,KAAK;AAAO,aAAO;AAAS;AAAA,IAC5B,KAAK;AAAS,aAAO;AAAS;AAAA,IAC9B,KAAK;AAAO,aAAO;AAAO;AAAA,IAC1B,KAAK;AAAQ,aAAO;AAAQ;AAAA,IAC5B;AAAS,YAAM,IAAI,MAAM,6BAA6B,OAAO,EAAE;AAAA,EACjE;AAEA,UAAQ,IAAI;AAAA,IACV,KAAK;AACH,aAAO,EAAE,KAAK,GAAG,QAAQ,eAAe,IAAI,QAAQ,WAAW,MAAM,eAAe,MAAM;AAAA,IAC5F,KAAK;AACH,aAAO,EAAE,KAAK,GAAG,QAAQ,iBAAiB,IAAI,IAAI,WAAW,MAAM;AAAA,IACrE,KAAK;AACH,aAAO,EAAE,KAAK,GAAG,QAAQ,eAAe,IAAI,QAAQ,WAAW,MAAM,eAAe,MAAM;AAAA,IAC5F;AACE,YAAM,IAAI,MAAM,yBAAyB,EAAE,EAAE;AAAA,EACjD;AACF;AAMA,eAAsB,mBAAmB,WAAmB,QAAwC;AAClG,QAAM,EAAE,KAAK,WAAW,cAAc,IAAI,qBAAqB;AAE/D,SAAO,aAAa;AAAA,IAClB,MAAM;AAAA,IACN;AAAA,IACA,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACH;;;AFhBO,IAAM,cAAN,MAAyF;AAAA,EACrF,WAA0B;AAAA,IACjC,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,cAAc,CAAC,kBAAkB;AAAA,EACnC;AAAA;AAAA,EAGS,KAAK;AAAA,EACL,OAAO;AAAA,EAER,SAA8B;AAAA,EAC9B,UAA+B;AAAA,EACtB,oBAAoB,oBAAI,IAAgC;AAAA,EACjE,gBAAqC;AAAA,IAC3C,SAAS;AAAA,IACT,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,EACd;AAAA,EAEA,MAAM,WAAW,SAAsC;AAErD,UAAM,WAAW,QAAQ,cAAc;AACvC,UAAM,SAAS,KAAK,UAAU,KAAK;AAGnC,UAAM,qBAAqB,MAAM,mBAAmB,QAAQ,QAAQ,MAAM;AAE1E,UAAM,gBAAqC;AAAA,MACzC,SAAU,QAAQ,YAAY,WAAsB,KAAK,cAAc;AAAA,MACvE,UAAW,QAAQ,YAAY,YAAuB,KAAK,cAAc;AAAA,MACzE,YAAa,QAAQ,YAAY,cAAyB,KAAK,cAAc;AAAA,MAC7E,YAAY;AAAA,IACd;AACA,SAAK,gBAAgB;AAGrB,UAAM,aAAa,qBAAqB,aAAa;AACrD,UAAM,aAAa,KAAK,UAAU,aAAa;AAC/C,cAAU,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AAClD,kBAAc,YAAY,UAAU;AAGpC,SAAK,UAAU,MAAM,cAAc,YAAY,CAAC,WAAW,UAAU,GAAG;AAAA,MACtE,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC;AAED,SAAK,QAAQ,GAAG,QAAQ,CAAC,MAAM,WAAW;AACxC,cAAQ,OAAO,KAAK,gCAAgC,IAAI,aAAa,MAAM,GAAG;AAAA,IAChF,CAAC;AAED,SAAK,QAAQ,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AAChD,YAAM,MAAM,KAAK,SAAS,EAAE,KAAK;AACjC,UAAI,IAAK,SAAQ,OAAO,MAAM,kBAAkB,GAAG,EAAE;AAAA,IACvD,CAAC;AAED,SAAK,QAAQ,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AAChD,YAAM,MAAM,KAAK,SAAS,EAAE,KAAK;AACjC,UAAI,IAAK,SAAQ,OAAO,MAAM,kBAAkB,GAAG,EAAE;AAAA,IACvD,CAAC;AAGD,UAAM,SAAS,oBAAoB,cAAc,OAAO;AACxD,UAAM,KAAK,aAAa,QAAQ,GAAM;AAGtC,UAAM,eAA6B;AAAA,MACjC,SAAS,cAAc;AAAA,MACvB,UAAU,cAAc;AAAA,MACxB,YAAY,cAAc;AAAA,MAC1B,YAAY,cAAc;AAAA,IAC5B;AACA,SAAK,SAAS,IAAI,aAAa,YAAY;AAC3C,UAAM,KAAK,OAAO,WAAW;AAE7B,YAAQ,OAAO,KAAK,wBAAwB,KAAK,QAAQ,GAAG,UAAU,cAAc,OAAO,GAAG;AAAA,EAChG;AAAA,EAEA,MAAM,WAA0B;AAC9B,UAAM,KAAK,QAAQ,SAAS;AAC5B,SAAK,SAAS;AACd,SAAK,kBAAkB,MAAM;AAE7B,QAAI,KAAK,SAAS;AAChB,WAAK,QAAQ,KAAK,SAAS;AAC3B,WAAK,UAAU;AAAA,IACjB;AAAA,EACF;AAAA,EAEA,YAA0B;AACxB,QAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,MAAM,wBAAwB;AAC1D,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,sBACE,MACiC;AACjC,QAAI,SAAS,sBAAsB,KAAK,QAAQ;AAC9C,aAAO,KAAK;AAAA,IACd;AACA,QAAI,SAAS,cAAc;AACzB,aAAO;AAAA,IACT;AACA,QAAI,SAAS,UAAU;AACrB,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAIA,MAAM,eAAe,UAAkB,SAAqD;AAC1F,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI,MAAM,sDAAiD;AAAA,IACnE;AAEA,UAAM,YAAsB,CAAC;AAE7B,eAAW,UAAU,SAAS;AAC5B,gBAAU,KAAK,OAAO,QAAQ;AAK9B,UAAI,OAAO,WAAW;AACpB,cAAM,KAAK,OAAO,eAAe,OAAO,UAAU;AAAA,UAChD,KAAK,UAAU,OAAO,SAAS,UAAU,OAAO,KAAK;AAAA,UACrD,UAAU;AAAA,UACV;AAAA,UACA,YAAY;AAAA,QACd,CAAC;AAAA,MACH;AAAA,IACF;AAEA,SAAK,kBAAkB,IAAI,UAAU;AAAA,MACnC;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,WAAW,WAAmB,SAA8B;AAAA,EAG5D;AAAA,EAEA,MAAM,iBAAiB,UAAiC;AACtD,UAAM,eAAe,KAAK,kBAAkB,IAAI,QAAQ;AACxD,QAAI,CAAC,cAAc;AACjB;AAAA,IACF;AAEA,QAAI,KAAK,QAAQ;AACf,iBAAW,YAAY,aAAa,WAAW;AAC7C,YAAI;AACF,gBAAM,KAAK,OAAO,iBAAiB,QAAQ;AAAA,QAC7C,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAEA,SAAK,kBAAkB,OAAO,QAAQ;AAAA,EACxC;AAAA,EAEA,oBAAoB,UAA8C;AAChE,UAAM,eAAe,KAAK,kBAAkB,IAAI,QAAQ;AACxD,QAAI,CAAC,gBAAgB,CAAC,KAAK,QAAQ;AACjC,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,YAA+B,CAAC;AAEtC,eAAW,YAAY,aAAa,WAAW;AAC7C,YAAM,YAAY,KAAK,OAAO,aAAa,UAAU,QAAQ;AAC7D,UAAI,WAAW;AACb,kBAAU,KAAK,EAAE,MAAM,OAAO,QAAQ,UAAU,OAAO,UAAU,CAAC;AAAA,MACpE;AAEA,YAAM,SAAS,KAAK,OAAO,aAAa,UAAU,KAAK;AACvD,UAAI,QAAQ;AACV,kBAAU,KAAK,EAAE,MAAM,OAAO,QAAQ,OAAO,OAAO,OAAO,CAAC;AAAA,MAC9D;AAEA,YAAM,WAAW,KAAK,OAAO,aAAa,UAAU,OAAO;AAC3D,UAAI,UAAU;AACZ,kBAAU,KAAK,EAAE,MAAM,OAAO,QAAQ,SAAS,OAAO,SAAS,CAAC;AAAA,MAClE;AAEA,YAAM,UAAU,KAAK,OAAO,aAAa,UAAU,MAAM;AACzD,UAAI,SAAS;AACX,kBAAU,KAAK,EAAE,MAAM,OAAO,QAAQ,QAAQ,OAAO,QAAQ,CAAC;AAAA,MAChE;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,eAAe,UAAkB,UAAmC;AACxE,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI,MAAM,iDAA4C;AAAA,IAC9D;AACA,WAAO,KAAK,OAAO,eAAe,UAAU,QAAQ;AAAA,EACtD;AAAA;AAAA;AAAA,EAMA,MAAM,YAAY,UAAkB,UAAmC;AACrE,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI,MAAM,0DAAqD;AAAA,IACvE;AACA,WAAO,KAAK,OAAO,eAAe,UAAU,QAAQ;AAAA,EACtD;AAAA,EAEA,eAAe,UAA2B;AACxC,UAAM,WAAW,SAAS,MAAM,GAAG,EAAE,CAAC;AACtC,WAAO,aAAa,UAAa,KAAK,kBAAkB,IAAI,QAAQ;AAAA,EACtE;AAAA,EAEA,MAAM,eAAe,UAAkB,QAA+B;AAGpE,UAAM,WAAW,SAAS,MAAM,GAAG,EAAE,CAAC;AACtC,QAAI,YAAY,CAAC,KAAK,kBAAkB,IAAI,QAAQ,GAAG;AACrD,WAAK,kBAAkB,IAAI,UAAU;AAAA,QACnC;AAAA,QACA,SAAS,CAAC,EAAE,UAAU,OAAO,UAAU,OAAO,QAAQ,MAAM,QAAQ,CAAC;AAAA,QACrE,WAAW,CAAC,QAAQ;AAAA,MACtB,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAM,iBAAiB,UAAiC;AACtD,QAAI,CAAC,KAAK,QAAQ;AAChB;AAAA,IACF;AACA,QAAI;AACF,YAAM,KAAK,OAAO,iBAAiB,QAAQ;AAAA,IAC7C,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,KAAa,WAAkC;AAChE,UAAM,QAAQ,KAAK,IAAI;AACvB,WAAO,KAAK,IAAI,IAAI,QAAQ,WAAW;AACrC,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,GAAG;AAC3B,YAAI,IAAI,GAAI;AAAA,MACd,QAAQ;AAAA,MAER;AACA,YAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAAA,IAC7C;AACA,UAAM,IAAI,MAAM,sCAAsC,SAAS,IAAI;AAAA,EACrE;AAAA,EAEA,kBAAkC;AAChC,WAAO;AAAA,MACL,UAAU;AAAA,QACR;AAAA,UACE,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,QAAQ;AAAA,YACN;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,SAAS;AAAA,cACT,SAAS;AAAA,YACX;AAAA,UACF;AAAA,QACF;AAAA,QACA;AAAA,UACE,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,aAAa;AAAA,UACb,SAAS;AAAA,UACT,QAAQ;AAAA,YACN;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,SAAS;AAAA,cACT,SAAS;AAAA,YACX;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,YACR;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,YACR;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,YAAqC;AACnC,WAAO,EAAE,GAAG,KAAK,cAAc;AAAA,EACjC;AAAA,EAEA,MAAM,eAAe,QAAgD;AACnE,SAAK,gBAAgB;AAAA,MACnB,YAAa,OAAO,cAAyB,KAAK,cAAc;AAAA,MAChE,SAAU,OAAO,WAAsB,KAAK,cAAc;AAAA,MAC1D,UAAW,OAAO,YAAuB,KAAK,cAAc;AAAA,MAC5D,YAAa,OAAO,cAAyB,KAAK,cAAc;AAAA,IAClE;AAAA,EACF;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-go2rtc",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "go2rtc streaming engine addon for CamStack",
|
|
5
|
-
"keywords": [
|
|
5
|
+
"keywords": [
|
|
6
|
+
"camstack",
|
|
7
|
+
"addon",
|
|
8
|
+
"camstack-addon",
|
|
9
|
+
"go2rtc",
|
|
10
|
+
"streaming",
|
|
11
|
+
"webrtc",
|
|
12
|
+
"camera"
|
|
13
|
+
],
|
|
6
14
|
"license": "MIT",
|
|
7
15
|
"repository": {
|
|
8
16
|
"type": "git",
|
|
@@ -12,7 +20,12 @@
|
|
|
12
20
|
"module": "./dist/index.mjs",
|
|
13
21
|
"types": "./dist/index.d.ts",
|
|
14
22
|
"exports": {
|
|
15
|
-
".": {
|
|
23
|
+
".": {
|
|
24
|
+
"import": "./dist/index.mjs",
|
|
25
|
+
"require": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts"
|
|
27
|
+
},
|
|
28
|
+
"./package.json": "./package.json"
|
|
16
29
|
},
|
|
17
30
|
"camstack": {
|
|
18
31
|
"addons": [
|
|
@@ -21,23 +34,36 @@
|
|
|
21
34
|
"entry": "./dist/addon.js",
|
|
22
35
|
"slot": null,
|
|
23
36
|
"capabilities": [
|
|
24
|
-
{
|
|
25
|
-
|
|
26
|
-
|
|
37
|
+
{
|
|
38
|
+
"name": "streaming-engine",
|
|
39
|
+
"mode": "singleton"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "restreamer",
|
|
43
|
+
"mode": "collection"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "webrtc",
|
|
47
|
+
"mode": "collection"
|
|
48
|
+
}
|
|
27
49
|
]
|
|
28
50
|
}
|
|
29
51
|
]
|
|
30
52
|
},
|
|
31
|
-
"files": [
|
|
53
|
+
"files": [
|
|
54
|
+
"dist"
|
|
55
|
+
],
|
|
32
56
|
"scripts": {
|
|
33
57
|
"build": "tsup",
|
|
34
58
|
"dev": "tsup --watch",
|
|
35
59
|
"typecheck": "tsc --noEmit"
|
|
36
60
|
},
|
|
37
61
|
"peerDependencies": {
|
|
62
|
+
"@camstack/core": "^0.1.0",
|
|
38
63
|
"@camstack/types": "^0.1.0"
|
|
39
64
|
},
|
|
40
65
|
"devDependencies": {
|
|
66
|
+
"@camstack/core": "*",
|
|
41
67
|
"@camstack/types": "*",
|
|
42
68
|
"tsup": "^8.0.0",
|
|
43
69
|
"typescript": "~5.9.0"
|