@camstack/addon-go2rtc 0.1.0 → 0.1.2

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,9 @@ 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");
137
132
  var GO2RTC_VERSION = "1.9.14";
138
133
  var BASE_URL = `https://github.com/AlexxIT/go2rtc/releases/download/v${GO2RTC_VERSION}`;
139
- function getPlatformFilename() {
134
+ function getGo2rtcDownloadUrl() {
140
135
  const os = process.platform;
141
136
  const rawArch = process.arch;
142
137
  let arch;
@@ -158,54 +153,23 @@ function getPlatformFilename() {
158
153
  }
159
154
  switch (os) {
160
155
  case "darwin":
161
- return { filename: `go2rtc_mac_${arch}.zip`, isZip: true };
156
+ return { url: `${BASE_URL}/go2rtc_mac_${arch}.zip`, isArchive: true, archiveFormat: "zip" };
162
157
  case "linux":
163
- return { filename: `go2rtc_linux_${arch}`, isZip: false };
158
+ return { url: `${BASE_URL}/go2rtc_linux_${arch}`, isArchive: false };
164
159
  case "win32":
165
- return { filename: `go2rtc_win_${arch}.zip`, isZip: true };
160
+ return { url: `${BASE_URL}/go2rtc_win_${arch}.zip`, isArchive: true, archiveFormat: "zip" };
166
161
  default:
167
162
  throw new Error(`Unsupported platform: ${os}`);
168
163
  }
169
164
  }
170
- 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;
165
+ async function ensureGo2rtcBinary(deps) {
166
+ const { url, isArchive, archiveFormat } = getGo2rtcDownloadUrl();
167
+ return deps.ensureBinary({
168
+ name: "go2rtc",
169
+ downloadUrl: url,
170
+ isArchive,
171
+ archiveFormat
172
+ });
209
173
  }
210
174
 
211
175
  // src/go2rtc.addon.ts
@@ -230,8 +194,7 @@ var Go2rtcAddon = class {
230
194
  };
231
195
  async initialize(context) {
232
196
  const dataPath = context.locationPaths.data;
233
- const binDir = (0, import_node_path2.join)(dataPath, "bin");
234
- const resolvedBinaryPath = await ensureGo2rtcBinary(binDir, context.logger);
197
+ const resolvedBinaryPath = await ensureGo2rtcBinary(context.deps);
235
198
  const processConfig = {
236
199
  apiPort: context.addonConfig.apiPort ?? this.currentConfig.apiPort,
237
200
  rtspPort: context.addonConfig.rtspPort ?? this.currentConfig.rtspPort,
@@ -240,10 +203,10 @@ var Go2rtcAddon = class {
240
203
  };
241
204
  this.currentConfig = processConfig;
242
205
  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], {
206
+ const configPath = (0, import_node_path.join)(dataPath, "go2rtc.yaml");
207
+ (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(configPath), { recursive: true });
208
+ (0, import_node_fs.writeFileSync)(configPath, configYaml);
209
+ this.process = (0, import_node_child_process.spawn)(processConfig.binaryPath, ["-config", configPath], {
247
210
  stdio: ["pipe", "pipe", "pipe"]
248
211
  });
249
212
  this.process.on("exit", (code, signal) => {
@@ -415,7 +378,7 @@ var Go2rtcAddon = class {
415
378
  return {
416
379
  sections: [
417
380
  {
418
- id: "info",
381
+ id: "go2rtc-info",
419
382
  title: "go2rtc",
420
383
  fields: [
421
384
  {
@@ -428,15 +391,23 @@ var Go2rtcAddon = class {
428
391
  ]
429
392
  },
430
393
  {
431
- id: "ports",
394
+ id: "go2rtc-ports",
432
395
  title: "Network Ports",
396
+ description: "Port configuration for the go2rtc restreamer. Changes require a restart.",
433
397
  columns: 3,
434
398
  fields: [
399
+ {
400
+ type: "info",
401
+ key: "restart-note",
402
+ label: "Restart required",
403
+ content: "Port changes require restarting go2rtc.",
404
+ variant: "warning"
405
+ },
435
406
  {
436
407
  type: "number",
437
408
  key: "apiPort",
438
409
  label: "API Port",
439
- description: "HTTP API port for go2rtc management",
410
+ description: "go2rtc HTTP API",
440
411
  min: 1024,
441
412
  max: 65535,
442
413
  step: 1
@@ -445,7 +416,7 @@ var Go2rtcAddon = class {
445
416
  type: "number",
446
417
  key: "rtspPort",
447
418
  label: "RTSP Port",
448
- description: "Port for RTSP stream output",
419
+ description: "RTSP restream output",
449
420
  min: 1024,
450
421
  max: 65535,
451
422
  step: 1
@@ -454,7 +425,7 @@ var Go2rtcAddon = class {
454
425
  type: "number",
455
426
  key: "webrtcPort",
456
427
  label: "WebRTC Port",
457
- description: "Port for WebRTC ICE/STUN negotiations",
428
+ description: "WebRTC UDP/TCP",
458
429
  min: 1024,
459
430
  max: 65535,
460
431
  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\n // Auto-download go2rtc binary if not found\n const resolvedBinaryPath = await ensureGo2rtcBinary(context.deps!)\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 * Uses IAddonDepsManager (ctx.deps) for binary management.\n */\nimport type { IAddonDepsManager } 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 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(deps: IAddonDepsManager): Promise<string> {\n const { url, isArchive, archiveFormat } = getGo2rtcDownloadUrl()\n\n return deps.ensureBinary({\n name: 'go2rtc',\n downloadUrl: url,\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,9 @@ 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";
109
104
  var GO2RTC_VERSION = "1.9.14";
110
105
  var BASE_URL = `https://github.com/AlexxIT/go2rtc/releases/download/v${GO2RTC_VERSION}`;
111
- function getPlatformFilename() {
106
+ function getGo2rtcDownloadUrl() {
112
107
  const os = process.platform;
113
108
  const rawArch = process.arch;
114
109
  let arch;
@@ -130,54 +125,23 @@ function getPlatformFilename() {
130
125
  }
131
126
  switch (os) {
132
127
  case "darwin":
133
- return { filename: `go2rtc_mac_${arch}.zip`, isZip: true };
128
+ return { url: `${BASE_URL}/go2rtc_mac_${arch}.zip`, isArchive: true, archiveFormat: "zip" };
134
129
  case "linux":
135
- return { filename: `go2rtc_linux_${arch}`, isZip: false };
130
+ return { url: `${BASE_URL}/go2rtc_linux_${arch}`, isArchive: false };
136
131
  case "win32":
137
- return { filename: `go2rtc_win_${arch}.zip`, isZip: true };
132
+ return { url: `${BASE_URL}/go2rtc_win_${arch}.zip`, isArchive: true, archiveFormat: "zip" };
138
133
  default:
139
134
  throw new Error(`Unsupported platform: ${os}`);
140
135
  }
141
136
  }
142
- 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;
137
+ async function ensureGo2rtcBinary(deps) {
138
+ const { url, isArchive, archiveFormat } = getGo2rtcDownloadUrl();
139
+ return deps.ensureBinary({
140
+ name: "go2rtc",
141
+ downloadUrl: url,
142
+ isArchive,
143
+ archiveFormat
144
+ });
181
145
  }
182
146
 
183
147
  // src/go2rtc.addon.ts
@@ -202,8 +166,7 @@ var Go2rtcAddon = class {
202
166
  };
203
167
  async initialize(context) {
204
168
  const dataPath = context.locationPaths.data;
205
- const binDir = join2(dataPath, "bin");
206
- const resolvedBinaryPath = await ensureGo2rtcBinary(binDir, context.logger);
169
+ const resolvedBinaryPath = await ensureGo2rtcBinary(context.deps);
207
170
  const processConfig = {
208
171
  apiPort: context.addonConfig.apiPort ?? this.currentConfig.apiPort,
209
172
  rtspPort: context.addonConfig.rtspPort ?? this.currentConfig.rtspPort,
@@ -212,8 +175,8 @@ var Go2rtcAddon = class {
212
175
  };
213
176
  this.currentConfig = processConfig;
214
177
  const configYaml = generateGo2rtcConfig(processConfig);
215
- const configPath = join2(dataPath, "go2rtc.yaml");
216
- mkdirSync2(dirname(configPath), { recursive: true });
178
+ const configPath = join(dataPath, "go2rtc.yaml");
179
+ mkdirSync(dirname(configPath), { recursive: true });
217
180
  writeFileSync(configPath, configYaml);
218
181
  this.process = spawn(processConfig.binaryPath, ["-config", configPath], {
219
182
  stdio: ["pipe", "pipe", "pipe"]
@@ -387,7 +350,7 @@ var Go2rtcAddon = class {
387
350
  return {
388
351
  sections: [
389
352
  {
390
- id: "info",
353
+ id: "go2rtc-info",
391
354
  title: "go2rtc",
392
355
  fields: [
393
356
  {
@@ -400,15 +363,23 @@ var Go2rtcAddon = class {
400
363
  ]
401
364
  },
402
365
  {
403
- id: "ports",
366
+ id: "go2rtc-ports",
404
367
  title: "Network Ports",
368
+ description: "Port configuration for the go2rtc restreamer. Changes require a restart.",
405
369
  columns: 3,
406
370
  fields: [
371
+ {
372
+ type: "info",
373
+ key: "restart-note",
374
+ label: "Restart required",
375
+ content: "Port changes require restarting go2rtc.",
376
+ variant: "warning"
377
+ },
407
378
  {
408
379
  type: "number",
409
380
  key: "apiPort",
410
381
  label: "API Port",
411
- description: "HTTP API port for go2rtc management",
382
+ description: "go2rtc HTTP API",
412
383
  min: 1024,
413
384
  max: 65535,
414
385
  step: 1
@@ -417,7 +388,7 @@ var Go2rtcAddon = class {
417
388
  type: "number",
418
389
  key: "rtspPort",
419
390
  label: "RTSP Port",
420
- description: "Port for RTSP stream output",
391
+ description: "RTSP restream output",
421
392
  min: 1024,
422
393
  max: 65535,
423
394
  step: 1
@@ -426,7 +397,7 @@ var Go2rtcAddon = class {
426
397
  type: "number",
427
398
  key: "webrtcPort",
428
399
  label: "WebRTC Port",
429
- description: "Port for WebRTC ICE/STUN negotiations",
400
+ description: "WebRTC UDP/TCP",
430
401
  min: 1024,
431
402
  max: 65535,
432
403
  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\n // Auto-download go2rtc binary if not found\n const resolvedBinaryPath = await ensureGo2rtcBinary(context.deps!)\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 * Uses IAddonDepsManager (ctx.deps) for binary management.\n */\nimport type { IAddonDepsManager } 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 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(deps: IAddonDepsManager): Promise<string> {\n const { url, isArchive, archiveFormat } = getGo2rtcDownloadUrl()\n\n return deps.ensureBinary({\n name: 'go2rtc',\n downloadUrl: url,\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.2",
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,23 +20,40 @@
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": {
31
+ "displayName": "go2rtc Streaming",
18
32
  "addons": [
19
33
  {
20
34
  "id": "go2rtc",
21
35
  "entry": "./dist/addon.js",
22
36
  "slot": null,
23
37
  "capabilities": [
24
- { "name": "streaming-engine", "mode": "singleton" },
25
- { "name": "restreamer", "mode": "collection" },
26
- { "name": "webrtc", "mode": "collection" }
38
+ {
39
+ "name": "streaming-engine",
40
+ "mode": "singleton"
41
+ },
42
+ {
43
+ "name": "restreamer",
44
+ "mode": "collection"
45
+ },
46
+ {
47
+ "name": "webrtc",
48
+ "mode": "collection"
49
+ }
27
50
  ]
28
51
  }
29
52
  ]
30
53
  },
31
- "files": ["dist"],
54
+ "files": [
55
+ "dist"
56
+ ],
32
57
  "scripts": {
33
58
  "build": "tsup",
34
59
  "dev": "tsup --watch",