@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 CHANGED
@@ -106,9 +106,9 @@ var Go2rtcEngine = class {
106
106
  };
107
107
 
108
108
  // src/go2rtc.addon.ts
109
- var import_node_child_process2 = require("child_process");
110
- var import_node_fs2 = require("fs");
111
- var import_node_path2 = require("path");
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 import_node_fs = require("fs");
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 getPlatformFilename() {
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 { filename: `go2rtc_mac_${arch}.zip`, isZip: true };
157
+ return { url: `${BASE_URL}/go2rtc_mac_${arch}.zip`, isArchive: true, archiveFormat: "zip" };
162
158
  case "linux":
163
- return { filename: `go2rtc_linux_${arch}`, isZip: false };
159
+ return { url: `${BASE_URL}/go2rtc_linux_${arch}`, isArchive: false };
164
160
  case "win32":
165
- return { filename: `go2rtc_win_${arch}.zip`, isZip: true };
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 binaryName = process.platform === "win32" ? "go2rtc.exe" : "go2rtc";
172
- const targetPath = (0, import_node_path.join)(targetDir, binaryName);
173
- if ((0, import_node_fs.existsSync)(targetPath)) {
174
- logger.debug(`go2rtc binary found at ${targetPath}`);
175
- return targetPath;
176
- }
177
- try {
178
- (0, import_node_child_process.execFileSync)("go2rtc", ["--version"], { stdio: "pipe", timeout: 5e3 });
179
- logger.info("go2rtc found in system PATH");
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, import_node_path2.join)(dataPath, "bin");
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, import_node_path2.join)(dataPath, "go2rtc.yaml");
244
- (0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(configPath), { recursive: true });
245
- (0, import_node_fs2.writeFileSync)(configPath, configYaml);
246
- this.process = (0, import_node_child_process2.spawn)(processConfig.binaryPath, ["-config", configPath], {
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 port for go2rtc management",
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: "Port for RTSP stream output",
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: "Port for WebRTC ICE/STUN negotiations",
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":"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","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 as mkdirSync2, writeFileSync } from "fs";
83
- import { join as join2, dirname } from "path";
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 { existsSync, mkdirSync, chmodSync, createWriteStream, unlinkSync } from "fs";
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 getPlatformFilename() {
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 { filename: `go2rtc_mac_${arch}.zip`, isZip: true };
129
+ return { url: `${BASE_URL}/go2rtc_mac_${arch}.zip`, isArchive: true, archiveFormat: "zip" };
134
130
  case "linux":
135
- return { filename: `go2rtc_linux_${arch}`, isZip: false };
131
+ return { url: `${BASE_URL}/go2rtc_linux_${arch}`, isArchive: false };
136
132
  case "win32":
137
- return { filename: `go2rtc_win_${arch}.zip`, isZip: true };
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 binaryName = process.platform === "win32" ? "go2rtc.exe" : "go2rtc";
144
- const targetPath = join(targetDir, binaryName);
145
- if (existsSync(targetPath)) {
146
- logger.debug(`go2rtc binary found at ${targetPath}`);
147
- return targetPath;
148
- }
149
- try {
150
- execFileSync("go2rtc", ["--version"], { stdio: "pipe", timeout: 5e3 });
151
- logger.info("go2rtc found in system PATH");
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 = join2(dataPath, "bin");
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 = join2(dataPath, "go2rtc.yaml");
216
- mkdirSync2(dirname(configPath), { recursive: true });
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 port for go2rtc management",
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: "Port for RTSP stream output",
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: "Port for WebRTC ICE/STUN negotiations",
404
+ description: "WebRTC UDP/TCP",
430
405
  min: 1024,
431
406
  max: 65535,
432
407
  step: 1
@@ -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":"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","names":[]}
package/package.json CHANGED
@@ -1,8 +1,16 @@
1
1
  {
2
2
  "name": "@camstack/addon-go2rtc",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "go2rtc streaming engine addon for CamStack",
5
- "keywords": ["camstack", "addon", "camstack-addon", "go2rtc", "streaming", "webrtc", "camera"],
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
- ".": { "import": "./dist/index.mjs", "require": "./dist/index.js", "types": "./dist/index.d.ts" }
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
- { "name": "streaming-engine", "mode": "singleton" },
25
- { "name": "restreamer", "mode": "collection" },
26
- { "name": "webrtc", "mode": "collection" }
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": ["dist"],
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"