@apocaliss92/nodelink-js 0.4.11 → 0.4.13
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/README.md +24 -0
- package/dist/BaichuanVideoStream-HGPU2MZ3.js +7 -0
- package/dist/{DiagnosticsTools-RNIDFEJK.js → DiagnosticsTools-BQOWJ23L.js} +3 -2
- package/dist/DiagnosticsTools-BQOWJ23L.js.map +1 -0
- package/dist/{chunk-EDLMKBG2.js → chunk-2JNXKT3C.js} +151 -2733
- package/dist/chunk-2JNXKT3C.js.map +1 -0
- package/dist/chunk-C57QV7IL.js +2723 -0
- package/dist/chunk-C57QV7IL.js.map +1 -0
- package/dist/{chunk-HGQ53FB3.js → chunk-R5AJV73A.js} +1348 -47
- package/dist/chunk-R5AJV73A.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +1422 -12
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +3 -2
- package/dist/cli/rtsp-server.js.map +1 -1
- package/dist/index.cjs +1929 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1051 -172
- package/dist/index.d.ts +965 -46
- package/dist/index.js +547 -85
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/dist/chunk-EDLMKBG2.js.map +0 -1
- package/dist/chunk-HGQ53FB3.js.map +0 -1
- /package/dist/{DiagnosticsTools-RNIDFEJK.js.map → BaichuanVideoStream-HGPU2MZ3.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -45,7 +45,54 @@ import {
|
|
|
45
45
|
parseSupportXml,
|
|
46
46
|
setGlobalLogger,
|
|
47
47
|
xmlIndicatesFloodlight
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-R5AJV73A.js";
|
|
49
|
+
import {
|
|
50
|
+
ReolinkCgiApi,
|
|
51
|
+
ReolinkHttpClient,
|
|
52
|
+
applyStreamPatch,
|
|
53
|
+
applyXmlTagPatch,
|
|
54
|
+
buildAbilityInfoExtensionXml,
|
|
55
|
+
buildBinaryExtensionXml,
|
|
56
|
+
buildChannelExtensionXml,
|
|
57
|
+
buildFloodlightManualXml,
|
|
58
|
+
buildLoginXml,
|
|
59
|
+
buildLogoutXml,
|
|
60
|
+
buildPreviewStopXml,
|
|
61
|
+
buildPreviewStopXmlV11,
|
|
62
|
+
buildPreviewXml,
|
|
63
|
+
buildPreviewXmlV11,
|
|
64
|
+
buildPtzControlXml,
|
|
65
|
+
buildPtzPresetXml,
|
|
66
|
+
buildPtzPresetXmlV2,
|
|
67
|
+
buildRtspPath,
|
|
68
|
+
buildRtspUrl,
|
|
69
|
+
buildSirenManualXml,
|
|
70
|
+
buildSirenTimesXml,
|
|
71
|
+
buildStartZoomFocusXml,
|
|
72
|
+
buildWhiteLedStateXml,
|
|
73
|
+
captureModelFixtures,
|
|
74
|
+
collectCgiDiagnostics,
|
|
75
|
+
collectMultifocalDiagnostics,
|
|
76
|
+
collectNativeDiagnostics,
|
|
77
|
+
collectNvrDiagnostics,
|
|
78
|
+
computeExpectedStreamCompatibility,
|
|
79
|
+
createDiagnosticsBundle,
|
|
80
|
+
ensureXmlHeader,
|
|
81
|
+
getXmlText,
|
|
82
|
+
normalizeDayNightMode,
|
|
83
|
+
normalizeOpenClose,
|
|
84
|
+
parseRecordingFileName,
|
|
85
|
+
patchNestedTag,
|
|
86
|
+
printNvrDiagnostics,
|
|
87
|
+
runAllDiagnosticsConsecutively,
|
|
88
|
+
runMultifocalDiagnosticsConsecutively,
|
|
89
|
+
sampleStreams,
|
|
90
|
+
sanitizeFixtureData,
|
|
91
|
+
testChannelStreams,
|
|
92
|
+
upsertXmlTag,
|
|
93
|
+
xmlEscape,
|
|
94
|
+
zipDirectory
|
|
95
|
+
} from "./chunk-2JNXKT3C.js";
|
|
49
96
|
import {
|
|
50
97
|
AesStreamDecryptor,
|
|
51
98
|
BC_AES_IV,
|
|
@@ -91,6 +138,7 @@ import {
|
|
|
91
138
|
BC_CMD_ID_GET_AUDIO_CFG,
|
|
92
139
|
BC_CMD_ID_GET_AUDIO_TASK,
|
|
93
140
|
BC_CMD_ID_GET_AUTO_FOCUS,
|
|
141
|
+
BC_CMD_ID_GET_AUTO_REBOOT,
|
|
94
142
|
BC_CMD_ID_GET_BATTERY_INFO,
|
|
95
143
|
BC_CMD_ID_GET_BATTERY_INFO_LIST,
|
|
96
144
|
BC_CMD_ID_GET_DAY_NIGHT_THRESHOLD,
|
|
@@ -98,6 +146,8 @@ import {
|
|
|
98
146
|
BC_CMD_ID_GET_DING_DONG_CFG,
|
|
99
147
|
BC_CMD_ID_GET_DING_DONG_LIST,
|
|
100
148
|
BC_CMD_ID_GET_DING_DONG_SILENT,
|
|
149
|
+
BC_CMD_ID_GET_DST,
|
|
150
|
+
BC_CMD_ID_GET_EMAIL,
|
|
101
151
|
BC_CMD_ID_GET_EMAIL_TASK,
|
|
102
152
|
BC_CMD_ID_GET_ENC,
|
|
103
153
|
BC_CMD_ID_GET_FTP_TASK,
|
|
@@ -105,6 +155,7 @@ import {
|
|
|
105
155
|
BC_CMD_ID_GET_KIT_AP_CFG,
|
|
106
156
|
BC_CMD_ID_GET_LED_STATE,
|
|
107
157
|
BC_CMD_ID_GET_MOTION_ALARM,
|
|
158
|
+
BC_CMD_ID_GET_NTP,
|
|
108
159
|
BC_CMD_ID_GET_ONLINE_USER_LIST,
|
|
109
160
|
BC_CMD_ID_GET_OSD_DATETIME,
|
|
110
161
|
BC_CMD_ID_GET_PIR_INFO,
|
|
@@ -121,6 +172,7 @@ import {
|
|
|
121
172
|
BC_CMD_ID_GET_SUPPORT,
|
|
122
173
|
BC_CMD_ID_GET_SYSTEM_GENERAL,
|
|
123
174
|
BC_CMD_ID_GET_TIMELAPSE_CFG,
|
|
175
|
+
BC_CMD_ID_GET_VERSION_INFO,
|
|
124
176
|
BC_CMD_ID_GET_VIDEO_INPUT,
|
|
125
177
|
BC_CMD_ID_GET_WHITE_LED,
|
|
126
178
|
BC_CMD_ID_GET_WIFI,
|
|
@@ -144,18 +196,24 @@ import {
|
|
|
144
196
|
BC_CMD_ID_SET_AUDIO_CFG,
|
|
145
197
|
BC_CMD_ID_SET_AUDIO_TASK,
|
|
146
198
|
BC_CMD_ID_SET_AUTO_FOCUS,
|
|
199
|
+
BC_CMD_ID_SET_AUTO_REBOOT,
|
|
147
200
|
BC_CMD_ID_SET_DAY_NIGHT_THRESHOLD,
|
|
148
201
|
BC_CMD_ID_SET_DING_DONG_CFG,
|
|
149
202
|
BC_CMD_ID_SET_DING_DONG_SILENT,
|
|
203
|
+
BC_CMD_ID_SET_DST,
|
|
204
|
+
BC_CMD_ID_SET_EMAIL,
|
|
150
205
|
BC_CMD_ID_SET_EMAIL_TASK,
|
|
151
206
|
BC_CMD_ID_SET_ENC,
|
|
152
207
|
BC_CMD_ID_SET_LED_STATE,
|
|
153
208
|
BC_CMD_ID_SET_MOTION_ALARM,
|
|
209
|
+
BC_CMD_ID_SET_NTP,
|
|
210
|
+
BC_CMD_ID_SET_OSD_DATETIME,
|
|
154
211
|
BC_CMD_ID_SET_PIR_INFO,
|
|
155
212
|
BC_CMD_ID_SET_PRIVACY_MASK,
|
|
156
213
|
BC_CMD_ID_SET_PUSH_TASK,
|
|
157
214
|
BC_CMD_ID_SET_RECORD,
|
|
158
215
|
BC_CMD_ID_SET_RECORD_CFG,
|
|
216
|
+
BC_CMD_ID_SET_SYSTEM_GENERAL,
|
|
159
217
|
BC_CMD_ID_SET_VIDEO_INPUT,
|
|
160
218
|
BC_CMD_ID_SET_WHITE_LED_STATE,
|
|
161
219
|
BC_CMD_ID_SET_WHITE_LED_TASK,
|
|
@@ -165,6 +223,7 @@ import {
|
|
|
165
223
|
BC_CMD_ID_TALK_ABILITY,
|
|
166
224
|
BC_CMD_ID_TALK_CONFIG,
|
|
167
225
|
BC_CMD_ID_TALK_RESET,
|
|
226
|
+
BC_CMD_ID_TEST_EMAIL,
|
|
168
227
|
BC_CMD_ID_UDP_KEEP_ALIVE,
|
|
169
228
|
BC_CMD_ID_VIDEO,
|
|
170
229
|
BC_CMD_ID_VIDEO_STOP,
|
|
@@ -177,52 +236,20 @@ import {
|
|
|
177
236
|
BcMediaCodec,
|
|
178
237
|
H264RtpDepacketizer,
|
|
179
238
|
H265RtpDepacketizer,
|
|
180
|
-
ReolinkCgiApi,
|
|
181
|
-
ReolinkHttpClient,
|
|
182
239
|
aesDecrypt,
|
|
183
240
|
aesEncrypt,
|
|
184
|
-
applyStreamPatch,
|
|
185
|
-
applyXmlTagPatch,
|
|
186
241
|
bcDecrypt,
|
|
187
242
|
bcEncrypt,
|
|
188
243
|
bcHeaderHasPayloadOffset,
|
|
189
|
-
buildAbilityInfoExtensionXml,
|
|
190
|
-
buildBinaryExtensionXml,
|
|
191
|
-
buildChannelExtensionXml,
|
|
192
|
-
buildFloodlightManualXml,
|
|
193
|
-
buildLoginXml,
|
|
194
|
-
buildLogoutXml,
|
|
195
|
-
buildPreviewStopXml,
|
|
196
|
-
buildPreviewStopXmlV11,
|
|
197
|
-
buildPreviewXml,
|
|
198
|
-
buildPreviewXmlV11,
|
|
199
|
-
buildPtzControlXml,
|
|
200
|
-
buildPtzPresetXml,
|
|
201
|
-
buildPtzPresetXmlV2,
|
|
202
|
-
buildRtspPath,
|
|
203
|
-
buildRtspUrl,
|
|
204
|
-
buildSirenManualXml,
|
|
205
|
-
buildSirenTimesXml,
|
|
206
|
-
buildStartZoomFocusXml,
|
|
207
|
-
buildWhiteLedStateXml,
|
|
208
|
-
captureModelFixtures,
|
|
209
|
-
collectCgiDiagnostics,
|
|
210
|
-
collectMultifocalDiagnostics,
|
|
211
|
-
collectNativeDiagnostics,
|
|
212
|
-
collectNvrDiagnostics,
|
|
213
|
-
computeExpectedStreamCompatibility,
|
|
214
244
|
convertToAnnexB,
|
|
215
245
|
convertToAnnexB2,
|
|
216
246
|
convertToLengthPrefixed,
|
|
217
|
-
createDiagnosticsBundle,
|
|
218
247
|
deriveAesKey,
|
|
219
248
|
detectVideoCodecFromNal,
|
|
220
|
-
ensureXmlHeader,
|
|
221
249
|
extractPpsFromAnnexB,
|
|
222
250
|
extractSpsFromAnnexB,
|
|
223
251
|
extractVpsFromAnnexB,
|
|
224
252
|
getH265NalType,
|
|
225
|
-
getXmlText,
|
|
226
253
|
hasStartCodes,
|
|
227
254
|
hasStartCodes2,
|
|
228
255
|
isH264KeyframeAnnexB,
|
|
@@ -232,22 +259,10 @@ import {
|
|
|
232
259
|
isValidH265AnnexBAccessUnit,
|
|
233
260
|
md5HexUpper,
|
|
234
261
|
md5StrModern,
|
|
235
|
-
normalizeDayNightMode,
|
|
236
|
-
normalizeOpenClose,
|
|
237
262
|
parseBcMedia,
|
|
238
|
-
parseRecordingFileName,
|
|
239
|
-
patchNestedTag,
|
|
240
|
-
printNvrDiagnostics,
|
|
241
|
-
runAllDiagnosticsConsecutively,
|
|
242
|
-
runMultifocalDiagnosticsConsecutively,
|
|
243
|
-
sampleStreams,
|
|
244
|
-
sanitizeFixtureData,
|
|
245
263
|
splitAnnexBToNalPayloads,
|
|
246
|
-
splitAnnexBToNalPayloads2
|
|
247
|
-
|
|
248
|
-
xmlEscape,
|
|
249
|
-
zipDirectory
|
|
250
|
-
} from "./chunk-EDLMKBG2.js";
|
|
264
|
+
splitAnnexBToNalPayloads2
|
|
265
|
+
} from "./chunk-C57QV7IL.js";
|
|
251
266
|
|
|
252
267
|
// src/reolink/baichuan/HlsSessionManager.ts
|
|
253
268
|
var withTimeout = async (p, ms, label) => {
|
|
@@ -4757,6 +4772,17 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
|
|
|
4757
4772
|
// Native stream
|
|
4758
4773
|
nativeFanout = null;
|
|
4759
4774
|
nativeStreamActive = false;
|
|
4775
|
+
// Set only by stopNativeStream() (explicit teardown) so the fanout's onEnd
|
|
4776
|
+
// callback can short-circuit cleanup/restart logic. NOT set by the inactivity-
|
|
4777
|
+
// timeout force-restart path — that flow wants onEnd to run and decide
|
|
4778
|
+
// whether to restart based on prestartStream / connected clients.
|
|
4779
|
+
nativeStreamStopping = false;
|
|
4780
|
+
// Pending retry timer for the unbounded auto-restart loop. When a stream
|
|
4781
|
+
// start fails transiently (camera in maintenance reboot, idle-disconnect
|
|
4782
|
+
// race, etc.) we keep trying with exponential backoff until either the
|
|
4783
|
+
// server is stopped or a frame finally arrives.
|
|
4784
|
+
nativeStreamRetryTimer;
|
|
4785
|
+
nativeStreamRetryDelayMs = 0;
|
|
4760
4786
|
dedicatedSessionRelease;
|
|
4761
4787
|
detectedVideoType;
|
|
4762
4788
|
// Client tracking
|
|
@@ -4825,6 +4851,7 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
|
|
|
4825
4851
|
if (!this.active) return;
|
|
4826
4852
|
this.active = false;
|
|
4827
4853
|
clearTimeout(this.stopGraceTimer);
|
|
4854
|
+
this.clearNativeStreamRetry();
|
|
4828
4855
|
this.stopStreamHealthMonitor();
|
|
4829
4856
|
for (const [id, sock] of this.clientSockets) {
|
|
4830
4857
|
sock.destroy();
|
|
@@ -5153,6 +5180,46 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
|
|
|
5153
5180
|
// -----------------------------------------------------------------------
|
|
5154
5181
|
// Native stream management
|
|
5155
5182
|
// -----------------------------------------------------------------------
|
|
5183
|
+
/**
|
|
5184
|
+
* Schedule another startNativeStream() attempt after the given delay.
|
|
5185
|
+
* Idempotent: a no-op if a retry is already scheduled, the server is no
|
|
5186
|
+
* longer active, or an explicit stop is in progress. Implements unbounded
|
|
5187
|
+
* exponential backoff (5s → 60s) so a camera that stays unreachable for
|
|
5188
|
+
* minutes (e.g. nightly maintenance reboot) eventually recovers without
|
|
5189
|
+
* manual intervention — see issue #16.
|
|
5190
|
+
*/
|
|
5191
|
+
scheduleNativeStreamRetry(reason) {
|
|
5192
|
+
if (!this.active) return;
|
|
5193
|
+
if (this.nativeStreamStopping) return;
|
|
5194
|
+
if (this.nativeStreamRetryTimer) return;
|
|
5195
|
+
const delay = this.nativeStreamRetryDelayMs > 0 ? this.nativeStreamRetryDelayMs : 5e3;
|
|
5196
|
+
this.logger.info?.(
|
|
5197
|
+
`[Go2rtcTcpServer] scheduling native stream retry in ${(delay / 1e3).toFixed(0)}s (reason=${reason})`
|
|
5198
|
+
);
|
|
5199
|
+
this.nativeStreamRetryTimer = setTimeout(() => {
|
|
5200
|
+
this.nativeStreamRetryTimer = void 0;
|
|
5201
|
+
if (!this.active) return;
|
|
5202
|
+
if (this.nativeStreamStopping) return;
|
|
5203
|
+
this.startNativeStream().catch((err) => {
|
|
5204
|
+
this.logger.warn?.(
|
|
5205
|
+
`[Go2rtcTcpServer] retry of startNativeStream threw: ${err instanceof Error ? err.message : err}`
|
|
5206
|
+
);
|
|
5207
|
+
});
|
|
5208
|
+
}, delay);
|
|
5209
|
+
this.nativeStreamRetryDelayMs = Math.min(delay * 2, 6e4);
|
|
5210
|
+
}
|
|
5211
|
+
/**
|
|
5212
|
+
* Cancel any pending retry timer and reset the backoff. Called on explicit
|
|
5213
|
+
* stop and on first-frame-received so the next failure starts the backoff
|
|
5214
|
+
* window from scratch.
|
|
5215
|
+
*/
|
|
5216
|
+
clearNativeStreamRetry() {
|
|
5217
|
+
if (this.nativeStreamRetryTimer) {
|
|
5218
|
+
clearTimeout(this.nativeStreamRetryTimer);
|
|
5219
|
+
this.nativeStreamRetryTimer = void 0;
|
|
5220
|
+
}
|
|
5221
|
+
this.nativeStreamRetryDelayMs = 0;
|
|
5222
|
+
}
|
|
5156
5223
|
async startNativeStream() {
|
|
5157
5224
|
if (this.nativeStreamActive) return;
|
|
5158
5225
|
if (!this.api.isReady) {
|
|
@@ -5169,8 +5236,9 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
|
|
|
5169
5236
|
await this.api.ensureConnected();
|
|
5170
5237
|
} catch (e) {
|
|
5171
5238
|
this.logger.warn?.(
|
|
5172
|
-
`[Go2rtcTcpServer] ensureConnected failed
|
|
5239
|
+
`[Go2rtcTcpServer] ensureConnected failed: ${e}`
|
|
5173
5240
|
);
|
|
5241
|
+
this.scheduleNativeStreamRetry("ensureConnected failed");
|
|
5174
5242
|
return;
|
|
5175
5243
|
}
|
|
5176
5244
|
}
|
|
@@ -5200,6 +5268,9 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
|
|
|
5200
5268
|
...dedicatedClient ? { client: dedicatedClient } : {}
|
|
5201
5269
|
}),
|
|
5202
5270
|
onFrame: (frame) => {
|
|
5271
|
+
if (!hadFrames) {
|
|
5272
|
+
this.clearNativeStreamRetry();
|
|
5273
|
+
}
|
|
5203
5274
|
hadFrames = true;
|
|
5204
5275
|
this.lastFrameAt = Date.now();
|
|
5205
5276
|
this.totalFramesReceived++;
|
|
@@ -5243,7 +5314,7 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
|
|
|
5243
5314
|
this.logger.warn?.(`[Go2rtcTcpServer] native stream error: ${error}`);
|
|
5244
5315
|
},
|
|
5245
5316
|
onEnd: () => {
|
|
5246
|
-
if (
|
|
5317
|
+
if (this.nativeStreamStopping) return;
|
|
5247
5318
|
this.nativeStreamActive = false;
|
|
5248
5319
|
this.nativeFanout = null;
|
|
5249
5320
|
this.stopStreamHealthMonitor();
|
|
@@ -5285,18 +5356,24 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
|
|
|
5285
5356
|
this.startStreamHealthMonitor();
|
|
5286
5357
|
}
|
|
5287
5358
|
async stopNativeStream() {
|
|
5359
|
+
this.nativeStreamStopping = true;
|
|
5288
5360
|
this.nativeStreamActive = false;
|
|
5361
|
+
this.clearNativeStreamRetry();
|
|
5289
5362
|
this.stopStreamHealthMonitor();
|
|
5290
5363
|
const fanout = this.nativeFanout;
|
|
5291
5364
|
this.nativeFanout = null;
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5365
|
+
try {
|
|
5366
|
+
if (fanout) {
|
|
5367
|
+
await fanout.stop();
|
|
5368
|
+
}
|
|
5369
|
+
this.prebuffer = [];
|
|
5370
|
+
if (this.dedicatedSessionRelease) {
|
|
5371
|
+
await this.dedicatedSessionRelease().catch(() => {
|
|
5372
|
+
});
|
|
5373
|
+
this.dedicatedSessionRelease = void 0;
|
|
5374
|
+
}
|
|
5375
|
+
} finally {
|
|
5376
|
+
this.nativeStreamStopping = false;
|
|
5300
5377
|
}
|
|
5301
5378
|
}
|
|
5302
5379
|
// -----------------------------------------------------------------------
|
|
@@ -5486,10 +5563,15 @@ var BaichuanHttpStreamServer = class extends EventEmitter3 {
|
|
|
5486
5563
|
// Force a known frame rate on raw H.264 input so the muxer gets valid PTS/DTS.
|
|
5487
5564
|
"-r",
|
|
5488
5565
|
String(this.inputFps),
|
|
5566
|
+
// `+genpts` generates uniform PTS from `-r` for raw Annex-B input. We
|
|
5567
|
+
// deliberately do NOT pass `-use_wallclock_as_timestamps 1`: that
|
|
5568
|
+
// overrides the generated PTS with the host wallclock at frame ARRIVAL
|
|
5569
|
+
// time, and the network stream is bursty so the resulting PTS is
|
|
5570
|
+
// uneven. With `-r` forcing a target rate downstream, ffmpeg then
|
|
5571
|
+
// drops/duplicates frames to match — visible as the periodic stutter
|
|
5572
|
+
// reported on local-restreamer recordings (issue #11).
|
|
5489
5573
|
"-fflags",
|
|
5490
5574
|
"+genpts",
|
|
5491
|
-
"-use_wallclock_as_timestamps",
|
|
5492
|
-
"1",
|
|
5493
5575
|
"-f",
|
|
5494
5576
|
"h264",
|
|
5495
5577
|
// Input format (H.264 Annex-B)
|
|
@@ -6166,7 +6248,194 @@ var BaichuanMjpegServer = class extends EventEmitter5 {
|
|
|
6166
6248
|
};
|
|
6167
6249
|
|
|
6168
6250
|
// src/baichuan/stream/BaichuanWebRTCServer.ts
|
|
6251
|
+
import { EventEmitter as EventEmitter7 } from "events";
|
|
6252
|
+
|
|
6253
|
+
// src/baichuan/stream/AacToOpusTranscoder.ts
|
|
6254
|
+
import { spawn as spawn7 } from "child_process";
|
|
6255
|
+
import { createSocket } from "dgram";
|
|
6169
6256
|
import { EventEmitter as EventEmitter6 } from "events";
|
|
6257
|
+
var AacToOpusTranscoder = class extends EventEmitter6 {
|
|
6258
|
+
opts;
|
|
6259
|
+
socket = null;
|
|
6260
|
+
ffmpeg = null;
|
|
6261
|
+
port = 0;
|
|
6262
|
+
starting = null;
|
|
6263
|
+
stopped = false;
|
|
6264
|
+
constructor(options = {}) {
|
|
6265
|
+
super();
|
|
6266
|
+
this.opts = {
|
|
6267
|
+
ffmpegPath: options.ffmpegPath ?? "ffmpeg",
|
|
6268
|
+
opusSampleRate: options.opusSampleRate ?? 48e3,
|
|
6269
|
+
opusChannels: options.opusChannels ?? 2,
|
|
6270
|
+
opusFrameMs: options.opusFrameMs ?? 20,
|
|
6271
|
+
opusBitrate: options.opusBitrate ?? 64e3,
|
|
6272
|
+
...options.logger !== void 0 ? { logger: options.logger } : {}
|
|
6273
|
+
};
|
|
6274
|
+
}
|
|
6275
|
+
log(level, message) {
|
|
6276
|
+
this.opts.logger?.(level, `[AacToOpusTranscoder] ${message}`);
|
|
6277
|
+
}
|
|
6278
|
+
/**
|
|
6279
|
+
* Allocate the UDP loopback socket and spawn ffmpeg. Must be awaited before
|
|
6280
|
+
* the first call to `feedAac`.
|
|
6281
|
+
*/
|
|
6282
|
+
async start() {
|
|
6283
|
+
if (this.starting) return this.starting;
|
|
6284
|
+
this.starting = this._start();
|
|
6285
|
+
return this.starting;
|
|
6286
|
+
}
|
|
6287
|
+
async _start() {
|
|
6288
|
+
if (this.stopped) throw new Error("transcoder stopped");
|
|
6289
|
+
this.socket = createSocket("udp4");
|
|
6290
|
+
await new Promise((resolve, reject) => {
|
|
6291
|
+
this.socket.once("error", reject);
|
|
6292
|
+
this.socket.bind({ address: "127.0.0.1", port: 0 }, () => {
|
|
6293
|
+
this.socket.removeListener("error", reject);
|
|
6294
|
+
this.port = this.socket.address().port;
|
|
6295
|
+
resolve();
|
|
6296
|
+
});
|
|
6297
|
+
});
|
|
6298
|
+
this.log("info", `UDP loopback bound on 127.0.0.1:${this.port}`);
|
|
6299
|
+
this.socket.on("message", (rtpPacket) => this.handleRtp(rtpPacket));
|
|
6300
|
+
this.socket.on("error", (err) => {
|
|
6301
|
+
this.log("error", `socket error: ${err.message}`);
|
|
6302
|
+
this.emit("error", err);
|
|
6303
|
+
});
|
|
6304
|
+
const args = [
|
|
6305
|
+
"-loglevel",
|
|
6306
|
+
"warning",
|
|
6307
|
+
"-fflags",
|
|
6308
|
+
"nobuffer",
|
|
6309
|
+
"-f",
|
|
6310
|
+
"aac",
|
|
6311
|
+
"-i",
|
|
6312
|
+
"pipe:0",
|
|
6313
|
+
"-c:a",
|
|
6314
|
+
"libopus",
|
|
6315
|
+
"-ar",
|
|
6316
|
+
String(this.opts.opusSampleRate),
|
|
6317
|
+
"-ac",
|
|
6318
|
+
String(this.opts.opusChannels),
|
|
6319
|
+
"-application",
|
|
6320
|
+
"audio",
|
|
6321
|
+
"-frame_duration",
|
|
6322
|
+
String(this.opts.opusFrameMs),
|
|
6323
|
+
"-b:a",
|
|
6324
|
+
String(this.opts.opusBitrate),
|
|
6325
|
+
// Important: disable VBR so the output rate matches the configured
|
|
6326
|
+
// bitrate consistently — easier on the browser jitter buffer.
|
|
6327
|
+
"-vbr",
|
|
6328
|
+
"off",
|
|
6329
|
+
"-f",
|
|
6330
|
+
"rtp",
|
|
6331
|
+
`rtp://127.0.0.1:${this.port}`
|
|
6332
|
+
];
|
|
6333
|
+
this.log("info", `spawning ffmpeg with: ${this.opts.ffmpegPath} ${args.join(" ")}`);
|
|
6334
|
+
this.ffmpeg = spawn7(this.opts.ffmpegPath, args, {
|
|
6335
|
+
stdio: ["pipe", "ignore", "pipe"]
|
|
6336
|
+
});
|
|
6337
|
+
this.ffmpeg.on("error", (err) => {
|
|
6338
|
+
this.log("error", `ffmpeg spawn error: ${err.message}`);
|
|
6339
|
+
this.emit("error", err);
|
|
6340
|
+
});
|
|
6341
|
+
this.ffmpeg.stderr?.on("data", (chunk) => {
|
|
6342
|
+
this.log("debug", `ffmpeg stderr: ${chunk.toString().trim()}`);
|
|
6343
|
+
});
|
|
6344
|
+
this.ffmpeg.on("exit", (code) => {
|
|
6345
|
+
this.log(code === 0 ? "info" : "warn", `ffmpeg exited code=${code}`);
|
|
6346
|
+
this.emit("exit", code);
|
|
6347
|
+
});
|
|
6348
|
+
}
|
|
6349
|
+
/**
|
|
6350
|
+
* Feed one or more concatenated ADTS AAC frames to ffmpeg. Returns the
|
|
6351
|
+
* number of bytes written. Safe to call before a frame is fully buffered.
|
|
6352
|
+
*/
|
|
6353
|
+
feedAac(buf) {
|
|
6354
|
+
if (this.stopped) return false;
|
|
6355
|
+
if (!this.ffmpeg?.stdin || !this.ffmpeg.stdin.writable) {
|
|
6356
|
+
this.log("debug", "drop AAC frame \u2014 ffmpeg stdin not writable");
|
|
6357
|
+
return false;
|
|
6358
|
+
}
|
|
6359
|
+
return this.ffmpeg.stdin.write(buf);
|
|
6360
|
+
}
|
|
6361
|
+
/**
|
|
6362
|
+
* Close stdin (ffmpeg exits cleanly on EOF) and tear down the UDP socket.
|
|
6363
|
+
*/
|
|
6364
|
+
async stop() {
|
|
6365
|
+
if (this.stopped) return;
|
|
6366
|
+
this.stopped = true;
|
|
6367
|
+
try {
|
|
6368
|
+
this.ffmpeg?.stdin?.end();
|
|
6369
|
+
} catch {
|
|
6370
|
+
}
|
|
6371
|
+
if (this.ffmpeg && this.ffmpeg.exitCode === null) {
|
|
6372
|
+
await new Promise((resolve) => {
|
|
6373
|
+
const t = setTimeout(() => {
|
|
6374
|
+
this.ffmpeg?.kill("SIGKILL");
|
|
6375
|
+
resolve();
|
|
6376
|
+
}, 500);
|
|
6377
|
+
this.ffmpeg?.once("exit", () => {
|
|
6378
|
+
clearTimeout(t);
|
|
6379
|
+
resolve();
|
|
6380
|
+
});
|
|
6381
|
+
});
|
|
6382
|
+
}
|
|
6383
|
+
this.ffmpeg = null;
|
|
6384
|
+
if (this.socket) {
|
|
6385
|
+
try {
|
|
6386
|
+
this.socket.close();
|
|
6387
|
+
} catch {
|
|
6388
|
+
}
|
|
6389
|
+
this.socket = null;
|
|
6390
|
+
}
|
|
6391
|
+
this.log("info", "stopped");
|
|
6392
|
+
}
|
|
6393
|
+
/**
|
|
6394
|
+
* Parse an RTP packet ffmpeg emitted on the loopback socket and surface
|
|
6395
|
+
* its Opus payload. RTP header layout (RFC 3550):
|
|
6396
|
+
*
|
|
6397
|
+
* 0 1 2 3
|
|
6398
|
+
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
6399
|
+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
6400
|
+
* |V=2|P|X| CC |M| PT | sequence number |
|
|
6401
|
+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
6402
|
+
* | timestamp |
|
|
6403
|
+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
6404
|
+
* | synchronization source (SSRC) identifier |
|
|
6405
|
+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
6406
|
+
*
|
|
6407
|
+
* We honor the CC, X (extension) and P (padding) bits to compute the
|
|
6408
|
+
* payload offset / length correctly even if ffmpeg ever emits those.
|
|
6409
|
+
*/
|
|
6410
|
+
handleRtp(packet) {
|
|
6411
|
+
if (packet.length < 12) return;
|
|
6412
|
+
const b0 = packet[0];
|
|
6413
|
+
const b1 = packet[1];
|
|
6414
|
+
const version = b0 >> 6;
|
|
6415
|
+
if (version !== 2) return;
|
|
6416
|
+
const padding = (b0 & 32) !== 0;
|
|
6417
|
+
const extension = (b0 & 16) !== 0;
|
|
6418
|
+
const csrcCount = b0 & 15;
|
|
6419
|
+
const marker = (b1 & 128) !== 0;
|
|
6420
|
+
let offset = 12 + csrcCount * 4;
|
|
6421
|
+
if (extension) {
|
|
6422
|
+
if (packet.length < offset + 4) return;
|
|
6423
|
+
const extLen = packet.readUInt16BE(offset + 2);
|
|
6424
|
+
offset += 4 + extLen * 4;
|
|
6425
|
+
}
|
|
6426
|
+
let end = packet.length;
|
|
6427
|
+
if (padding) {
|
|
6428
|
+
const pad = packet[packet.length - 1];
|
|
6429
|
+
end -= pad;
|
|
6430
|
+
}
|
|
6431
|
+
if (offset >= end) return;
|
|
6432
|
+
const timestamp = packet.readUInt32BE(4);
|
|
6433
|
+
const payload = Buffer.from(packet.subarray(offset, end));
|
|
6434
|
+
this.emit("packet", { payload, timestamp, marker });
|
|
6435
|
+
}
|
|
6436
|
+
};
|
|
6437
|
+
|
|
6438
|
+
// src/baichuan/stream/BaichuanWebRTCServer.ts
|
|
6170
6439
|
function parseAnnexBNalUnits(annexB) {
|
|
6171
6440
|
const nalUnits = [];
|
|
6172
6441
|
let offset = 0;
|
|
@@ -6201,7 +6470,7 @@ function getH264NalType(nalUnit) {
|
|
|
6201
6470
|
function getH265NalType2(nalUnit) {
|
|
6202
6471
|
return nalUnit[0] >> 1 & 63;
|
|
6203
6472
|
}
|
|
6204
|
-
var BaichuanWebRTCServer = class extends
|
|
6473
|
+
var BaichuanWebRTCServer = class extends EventEmitter7 {
|
|
6205
6474
|
options;
|
|
6206
6475
|
sessions = /* @__PURE__ */ new Map();
|
|
6207
6476
|
sessionIdCounter = 0;
|
|
@@ -6439,6 +6708,14 @@ Error: ${err}`
|
|
|
6439
6708
|
}
|
|
6440
6709
|
session.dataChannel = null;
|
|
6441
6710
|
}
|
|
6711
|
+
if (session.audioTranscoder) {
|
|
6712
|
+
try {
|
|
6713
|
+
await session.audioTranscoder.stop();
|
|
6714
|
+
} catch (err) {
|
|
6715
|
+
this.log("debug", `Error stopping audio transcoder: ${err}`);
|
|
6716
|
+
}
|
|
6717
|
+
session.audioTranscoder = null;
|
|
6718
|
+
}
|
|
6442
6719
|
if (session.cleanup) {
|
|
6443
6720
|
session.cleanup();
|
|
6444
6721
|
}
|
|
@@ -6565,6 +6842,17 @@ Error: ${err}`
|
|
|
6565
6842
|
}
|
|
6566
6843
|
if (frame.audio) {
|
|
6567
6844
|
session.stats.audioFrames++;
|
|
6845
|
+
if (session.stats.audioFrames === 1) {
|
|
6846
|
+
const head = frame.data && frame.data.length > 0 ? frame.data.subarray(0, Math.min(8, frame.data.length)).toString("hex") : "(empty)";
|
|
6847
|
+
this.log(
|
|
6848
|
+
"info",
|
|
6849
|
+
`First audio frame for ${session.id}: codec=${frame.codec ?? "?"} bytes=${frame.data?.length ?? 0} head=${head}`
|
|
6850
|
+
);
|
|
6851
|
+
}
|
|
6852
|
+
if (this.options.ffmpegPath !== "" && frame.data && frame.data.length > 0) {
|
|
6853
|
+
await this.ensureAudioTranscoder(session, werift);
|
|
6854
|
+
session.audioTranscoder?.feedAac(frame.data);
|
|
6855
|
+
}
|
|
6568
6856
|
} else {
|
|
6569
6857
|
if (frame.data) {
|
|
6570
6858
|
if (!session.videoCodec && frame.videoType) {
|
|
@@ -6635,7 +6923,7 @@ Error: ${err}`
|
|
|
6635
6923
|
if (now - lastLogTime >= 5e3) {
|
|
6636
6924
|
this.log(
|
|
6637
6925
|
"debug",
|
|
6638
|
-
`WebRTC session ${session.id} [${session.videoCodec}]: sent ${session.stats.videoFrames} frames, ${packetsSentSinceLastLog} packets, ${Math.round(session.stats.bytesSent / 1024)} KB`
|
|
6926
|
+
`WebRTC session ${session.id} [${session.videoCodec}]: sent ${session.stats.videoFrames} video frames, ${packetsSentSinceLastLog} packets, ${Math.round(session.stats.bytesSent / 1024)} KB | audio frames=${session.stats.audioFrames}`
|
|
6639
6927
|
);
|
|
6640
6928
|
lastLogTime = now;
|
|
6641
6929
|
packetsSentSinceLastLog = 0;
|
|
@@ -6651,6 +6939,77 @@ Error: ${err}`
|
|
|
6651
6939
|
}
|
|
6652
6940
|
this.log("info", `Native stream ended for session ${session.id}`);
|
|
6653
6941
|
}
|
|
6942
|
+
/**
|
|
6943
|
+
* Lazily start the AAC → Opus transcoder for `session` and wire it to the
|
|
6944
|
+
* audio RTP track. ffmpeg writes RTP-formatted Opus packets back to a UDP
|
|
6945
|
+
* loopback the transcoder owns; we strip the RTP header and rewrap the
|
|
6946
|
+
* Opus payload with our audioTrack's SSRC so the browser receives a
|
|
6947
|
+
* coherent stream.
|
|
6948
|
+
*/
|
|
6949
|
+
async ensureAudioTranscoder(session, werift) {
|
|
6950
|
+
if (session.audioTranscoder !== void 0) return;
|
|
6951
|
+
const { RtpPacket, RtpHeader } = werift;
|
|
6952
|
+
const ssrc = session.audioTrack?.ssrc ?? Math.floor(Math.random() * 4294967295);
|
|
6953
|
+
const transcoder = new AacToOpusTranscoder({
|
|
6954
|
+
...this.options.ffmpegPath ? { ffmpegPath: this.options.ffmpegPath } : {},
|
|
6955
|
+
logger: (level, msg) => this.log(level, msg)
|
|
6956
|
+
});
|
|
6957
|
+
session.audioRtpSequence = Math.floor(Math.random() * 65535);
|
|
6958
|
+
session.audioRtpTimestampBase = 0;
|
|
6959
|
+
transcoder.on("packet", ({ payload, timestamp, marker }) => {
|
|
6960
|
+
try {
|
|
6961
|
+
if (session.audioRtpTimestampBase === void 0 || session.audioRtpTimestampBase === 0) {
|
|
6962
|
+
session.audioRtpTimestampBase = timestamp;
|
|
6963
|
+
}
|
|
6964
|
+
const localTs = timestamp - session.audioRtpTimestampBase >>> 0;
|
|
6965
|
+
const seq = session.audioRtpSequence;
|
|
6966
|
+
session.audioRtpSequence = seq + 1 & 65535;
|
|
6967
|
+
const header = new RtpHeader({
|
|
6968
|
+
version: 2,
|
|
6969
|
+
padding: false,
|
|
6970
|
+
extension: false,
|
|
6971
|
+
marker,
|
|
6972
|
+
// Werift assigns 111 to Opus by default in the receiver SDP, but it
|
|
6973
|
+
// also accepts other PTs. Use 111 for compatibility with the offer
|
|
6974
|
+
// we generated earlier.
|
|
6975
|
+
payloadType: 111,
|
|
6976
|
+
sequenceNumber: seq,
|
|
6977
|
+
timestamp: localTs,
|
|
6978
|
+
ssrc
|
|
6979
|
+
});
|
|
6980
|
+
const rtp = new RtpPacket(header, payload);
|
|
6981
|
+
session.audioTrack?.writeRtp(rtp);
|
|
6982
|
+
if (!session.audioStartedLogged) {
|
|
6983
|
+
session.audioStartedLogged = true;
|
|
6984
|
+
this.log(
|
|
6985
|
+
"info",
|
|
6986
|
+
`Audio RTP started for ${session.id} (PT=111, ssrc=${ssrc})`
|
|
6987
|
+
);
|
|
6988
|
+
}
|
|
6989
|
+
} catch (err) {
|
|
6990
|
+
this.log(
|
|
6991
|
+
"warn",
|
|
6992
|
+
`audio writeRtp failed for ${session.id}: ${err.message}`
|
|
6993
|
+
);
|
|
6994
|
+
}
|
|
6995
|
+
});
|
|
6996
|
+
transcoder.on("error", (err) => {
|
|
6997
|
+
this.log("error", `audio transcoder error for ${session.id}: ${err.message}`);
|
|
6998
|
+
});
|
|
6999
|
+
transcoder.on("exit", (code) => {
|
|
7000
|
+
this.log("info", `audio transcoder exited (${code}) for ${session.id}`);
|
|
7001
|
+
});
|
|
7002
|
+
try {
|
|
7003
|
+
await transcoder.start();
|
|
7004
|
+
session.audioTranscoder = transcoder;
|
|
7005
|
+
} catch (err) {
|
|
7006
|
+
this.log(
|
|
7007
|
+
"error",
|
|
7008
|
+
`failed to start audio transcoder for ${session.id}: ${err.message}`
|
|
7009
|
+
);
|
|
7010
|
+
session.audioTranscoder = null;
|
|
7011
|
+
}
|
|
7012
|
+
}
|
|
6654
7013
|
/**
|
|
6655
7014
|
* Send H.264 frame via RTP media track
|
|
6656
7015
|
* Returns the number of RTP packets sent
|
|
@@ -6938,21 +7297,21 @@ Error: ${err}`
|
|
|
6938
7297
|
`Sending ${codec} frame ${frameNumber}: ${packet.length} bytes, keyframe=${isKeyframe}`
|
|
6939
7298
|
);
|
|
6940
7299
|
}
|
|
6941
|
-
const
|
|
7300
|
+
const CHUNK_HEADER_LEN = 4;
|
|
7301
|
+
const MAX_PAYLOAD_PER_CHUNK = 16e3 - CHUNK_HEADER_LEN;
|
|
6942
7302
|
try {
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
}
|
|
7303
|
+
const totalChunks = Math.max(
|
|
7304
|
+
1,
|
|
7305
|
+
Math.ceil(packet.length / MAX_PAYLOAD_PER_CHUNK)
|
|
7306
|
+
);
|
|
7307
|
+
for (let i = 0; i < totalChunks; i++) {
|
|
7308
|
+
const start = i * MAX_PAYLOAD_PER_CHUNK;
|
|
7309
|
+
const end = Math.min(start + MAX_PAYLOAD_PER_CHUNK, packet.length);
|
|
7310
|
+
const chunk = packet.subarray(start, end);
|
|
7311
|
+
const chunkHeader = Buffer.alloc(CHUNK_HEADER_LEN);
|
|
7312
|
+
chunkHeader.writeUInt16BE(i, 0);
|
|
7313
|
+
chunkHeader.writeUInt16BE(totalChunks, 2);
|
|
7314
|
+
session.videoDataChannel.send(Buffer.concat([chunkHeader, chunk]));
|
|
6956
7315
|
}
|
|
6957
7316
|
return true;
|
|
6958
7317
|
} catch (err) {
|
|
@@ -7103,12 +7462,12 @@ Error: ${err}`
|
|
|
7103
7462
|
};
|
|
7104
7463
|
|
|
7105
7464
|
// src/baichuan/stream/BaichuanHlsServer.ts
|
|
7106
|
-
import { EventEmitter as
|
|
7465
|
+
import { EventEmitter as EventEmitter8 } from "events";
|
|
7107
7466
|
import fs from "fs";
|
|
7108
7467
|
import fsp from "fs/promises";
|
|
7109
7468
|
import os from "os";
|
|
7110
7469
|
import path from "path";
|
|
7111
|
-
import { spawn as
|
|
7470
|
+
import { spawn as spawn8 } from "child_process";
|
|
7112
7471
|
function parseAnnexBNalUnits2(data) {
|
|
7113
7472
|
const units = [];
|
|
7114
7473
|
const len = data.length;
|
|
@@ -7180,7 +7539,7 @@ function getNalTypes(codec, annexB) {
|
|
|
7180
7539
|
}
|
|
7181
7540
|
});
|
|
7182
7541
|
}
|
|
7183
|
-
var BaichuanHlsServer = class extends
|
|
7542
|
+
var BaichuanHlsServer = class extends EventEmitter8 {
|
|
7184
7543
|
api;
|
|
7185
7544
|
channel;
|
|
7186
7545
|
profile;
|
|
@@ -7537,10 +7896,17 @@ var BaichuanHlsServer = class extends EventEmitter7 {
|
|
|
7537
7896
|
"-hide_banner",
|
|
7538
7897
|
"-loglevel",
|
|
7539
7898
|
"warning",
|
|
7899
|
+
// `+genpts` makes ffmpeg generate uniform PTS from the declared `-r`
|
|
7900
|
+
// when the raw H.264/H.265 input has none. We deliberately do NOT use
|
|
7901
|
+
// `-use_wallclock_as_timestamps 1` here: it replaces the generated
|
|
7902
|
+
// PTS with the host wallclock at FRAME ARRIVAL time, and because the
|
|
7903
|
+
// camera ships frames in bursty network reads, the resulting PTS
|
|
7904
|
+
// sequence is uneven. With `-r 25` (or anything else) forcing a
|
|
7905
|
+
// target rate downstream, ffmpeg then drops/duplicates frames to
|
|
7906
|
+
// match — visible as the periodic stutter / pulsing reported on
|
|
7907
|
+
// local-restreamer recordings (issue #11).
|
|
7540
7908
|
"-fflags",
|
|
7541
7909
|
"+genpts",
|
|
7542
|
-
"-use_wallclock_as_timestamps",
|
|
7543
|
-
"1",
|
|
7544
7910
|
"-r",
|
|
7545
7911
|
"25",
|
|
7546
7912
|
"-f",
|
|
@@ -7575,7 +7941,7 @@ var BaichuanHlsServer = class extends EventEmitter7 {
|
|
|
7575
7941
|
this.segmentPattern,
|
|
7576
7942
|
this.playlistPath
|
|
7577
7943
|
);
|
|
7578
|
-
const p =
|
|
7944
|
+
const p = spawn8(this.ffmpegPath, args, {
|
|
7579
7945
|
stdio: ["pipe", "ignore", "pipe"]
|
|
7580
7946
|
});
|
|
7581
7947
|
p.on("error", (err) => {
|
|
@@ -7598,10 +7964,10 @@ var BaichuanHlsServer = class extends EventEmitter7 {
|
|
|
7598
7964
|
};
|
|
7599
7965
|
|
|
7600
7966
|
// src/multifocal/compositeRtspServer.ts
|
|
7601
|
-
import { EventEmitter as
|
|
7602
|
-
import { spawn as
|
|
7967
|
+
import { EventEmitter as EventEmitter9 } from "events";
|
|
7968
|
+
import { spawn as spawn9 } from "child_process";
|
|
7603
7969
|
import * as net2 from "net";
|
|
7604
|
-
var CompositeRtspServer = class extends
|
|
7970
|
+
var CompositeRtspServer = class extends EventEmitter9 {
|
|
7605
7971
|
options;
|
|
7606
7972
|
compositeStream = null;
|
|
7607
7973
|
rtspServer = null;
|
|
@@ -7706,7 +8072,7 @@ var CompositeRtspServer = class extends EventEmitter8 {
|
|
|
7706
8072
|
this.logger.log?.(
|
|
7707
8073
|
`[CompositeRtspServer] Starting ffmpeg RTSP server: ${ffmpegArgs.join(" ")}`
|
|
7708
8074
|
);
|
|
7709
|
-
this.ffmpegProcess =
|
|
8075
|
+
this.ffmpegProcess = spawn9("ffmpeg", ffmpegArgs, {
|
|
7710
8076
|
stdio: ["pipe", "pipe", "pipe"]
|
|
7711
8077
|
});
|
|
7712
8078
|
this.ffmpegProcess.on("error", (error) => {
|
|
@@ -7814,6 +8180,86 @@ var CompositeRtspServer = class extends EventEmitter8 {
|
|
|
7814
8180
|
return this.connectedClients.size;
|
|
7815
8181
|
}
|
|
7816
8182
|
};
|
|
8183
|
+
|
|
8184
|
+
// src/reolink/baichuan/utils/motionZone.ts
|
|
8185
|
+
function decodeMotionScopeBitmap(valueTable, columns, rows, width = columns, height = rows) {
|
|
8186
|
+
const trimmed = valueTable.trim().replace(/[^A-Za-z0-9+/=]/g, "");
|
|
8187
|
+
const bytes = base64DecodeToBytes(trimmed);
|
|
8188
|
+
const totalBits = columns * rows;
|
|
8189
|
+
if (bytes.length * 8 < totalBits) {
|
|
8190
|
+
throw new Error(
|
|
8191
|
+
`valueTable too short: have ${bytes.length * 8} bits, need ${totalBits}`
|
|
8192
|
+
);
|
|
8193
|
+
}
|
|
8194
|
+
const w = Math.min(width, columns);
|
|
8195
|
+
const h = Math.min(height, rows);
|
|
8196
|
+
const cells = new Array(w * h);
|
|
8197
|
+
for (let r = 0; r < h; r++) {
|
|
8198
|
+
for (let c = 0; c < w; c++) {
|
|
8199
|
+
const bitIndex = r * columns + c;
|
|
8200
|
+
const byteIdx = bitIndex >> 3;
|
|
8201
|
+
const bitIdx = 7 - (bitIndex & 7);
|
|
8202
|
+
cells[r * w + c] = (bytes[byteIdx] ?? 0) >> bitIdx & 1 ? true : false;
|
|
8203
|
+
}
|
|
8204
|
+
}
|
|
8205
|
+
return { width: w, height: h, columns, rows, cells };
|
|
8206
|
+
}
|
|
8207
|
+
function encodeMotionScopeBitmap(scope) {
|
|
8208
|
+
const bytes = new Uint8Array(Math.ceil(scope.columns * scope.rows / 8));
|
|
8209
|
+
for (let r = 0; r < scope.height; r++) {
|
|
8210
|
+
for (let c = 0; c < scope.width; c++) {
|
|
8211
|
+
const on = scope.cells[r * scope.width + c];
|
|
8212
|
+
if (!on) continue;
|
|
8213
|
+
const bitIndex = r * scope.columns + c;
|
|
8214
|
+
const byteIdx = bitIndex >> 3;
|
|
8215
|
+
const bitIdx = 7 - (bitIndex & 7);
|
|
8216
|
+
bytes[byteIdx] = (bytes[byteIdx] ?? 0) | 1 << bitIdx;
|
|
8217
|
+
}
|
|
8218
|
+
}
|
|
8219
|
+
return base64EncodeBytes(bytes);
|
|
8220
|
+
}
|
|
8221
|
+
function fullCoverageScope(columns, rows, width = columns, height = rows) {
|
|
8222
|
+
return {
|
|
8223
|
+
width,
|
|
8224
|
+
height,
|
|
8225
|
+
columns,
|
|
8226
|
+
rows,
|
|
8227
|
+
cells: new Array(width * height).fill(true)
|
|
8228
|
+
};
|
|
8229
|
+
}
|
|
8230
|
+
var B64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
8231
|
+
function base64EncodeBytes(bytes) {
|
|
8232
|
+
let out = "";
|
|
8233
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
8234
|
+
const b0 = bytes[i] ?? 0;
|
|
8235
|
+
const b1 = bytes[i + 1] ?? 0;
|
|
8236
|
+
const b2 = bytes[i + 2] ?? 0;
|
|
8237
|
+
out += B64_ALPHABET[b0 >> 2];
|
|
8238
|
+
out += B64_ALPHABET[(b0 & 3) << 4 | b1 >> 4];
|
|
8239
|
+
out += i + 1 < bytes.length ? B64_ALPHABET[(b1 & 15) << 2 | b2 >> 6] : "=";
|
|
8240
|
+
out += i + 2 < bytes.length ? B64_ALPHABET[b2 & 63] : "=";
|
|
8241
|
+
}
|
|
8242
|
+
return out;
|
|
8243
|
+
}
|
|
8244
|
+
function base64DecodeToBytes(b64) {
|
|
8245
|
+
const padded = b64.padEnd(Math.ceil(b64.length / 4) * 4, "=");
|
|
8246
|
+
const stripped = padded.replace(/=+$/, "");
|
|
8247
|
+
const out = new Uint8Array(Math.floor(stripped.length * 6 / 8));
|
|
8248
|
+
let bits = 0;
|
|
8249
|
+
let value = 0;
|
|
8250
|
+
let outIdx = 0;
|
|
8251
|
+
for (const ch of stripped) {
|
|
8252
|
+
const idx = B64_ALPHABET.indexOf(ch);
|
|
8253
|
+
if (idx < 0) continue;
|
|
8254
|
+
value = value << 6 | idx;
|
|
8255
|
+
bits += 6;
|
|
8256
|
+
if (bits >= 8) {
|
|
8257
|
+
bits -= 8;
|
|
8258
|
+
out[outIdx++] = value >> bits & 255;
|
|
8259
|
+
}
|
|
8260
|
+
}
|
|
8261
|
+
return out.subarray(0, outIdx);
|
|
8262
|
+
}
|
|
7817
8263
|
export {
|
|
7818
8264
|
AesStreamDecryptor,
|
|
7819
8265
|
AutodiscoveryClient,
|
|
@@ -7860,6 +8306,7 @@ export {
|
|
|
7860
8306
|
BC_CMD_ID_GET_AUDIO_CFG,
|
|
7861
8307
|
BC_CMD_ID_GET_AUDIO_TASK,
|
|
7862
8308
|
BC_CMD_ID_GET_AUTO_FOCUS,
|
|
8309
|
+
BC_CMD_ID_GET_AUTO_REBOOT,
|
|
7863
8310
|
BC_CMD_ID_GET_BATTERY_INFO,
|
|
7864
8311
|
BC_CMD_ID_GET_BATTERY_INFO_LIST,
|
|
7865
8312
|
BC_CMD_ID_GET_DAY_NIGHT_THRESHOLD,
|
|
@@ -7867,6 +8314,8 @@ export {
|
|
|
7867
8314
|
BC_CMD_ID_GET_DING_DONG_CFG,
|
|
7868
8315
|
BC_CMD_ID_GET_DING_DONG_LIST,
|
|
7869
8316
|
BC_CMD_ID_GET_DING_DONG_SILENT,
|
|
8317
|
+
BC_CMD_ID_GET_DST,
|
|
8318
|
+
BC_CMD_ID_GET_EMAIL,
|
|
7870
8319
|
BC_CMD_ID_GET_EMAIL_TASK,
|
|
7871
8320
|
BC_CMD_ID_GET_ENC,
|
|
7872
8321
|
BC_CMD_ID_GET_FTP_TASK,
|
|
@@ -7874,6 +8323,7 @@ export {
|
|
|
7874
8323
|
BC_CMD_ID_GET_KIT_AP_CFG,
|
|
7875
8324
|
BC_CMD_ID_GET_LED_STATE,
|
|
7876
8325
|
BC_CMD_ID_GET_MOTION_ALARM,
|
|
8326
|
+
BC_CMD_ID_GET_NTP,
|
|
7877
8327
|
BC_CMD_ID_GET_ONLINE_USER_LIST,
|
|
7878
8328
|
BC_CMD_ID_GET_OSD_DATETIME,
|
|
7879
8329
|
BC_CMD_ID_GET_PIR_INFO,
|
|
@@ -7890,6 +8340,7 @@ export {
|
|
|
7890
8340
|
BC_CMD_ID_GET_SUPPORT,
|
|
7891
8341
|
BC_CMD_ID_GET_SYSTEM_GENERAL,
|
|
7892
8342
|
BC_CMD_ID_GET_TIMELAPSE_CFG,
|
|
8343
|
+
BC_CMD_ID_GET_VERSION_INFO,
|
|
7893
8344
|
BC_CMD_ID_GET_VIDEO_INPUT,
|
|
7894
8345
|
BC_CMD_ID_GET_WHITE_LED,
|
|
7895
8346
|
BC_CMD_ID_GET_WIFI,
|
|
@@ -7913,18 +8364,24 @@ export {
|
|
|
7913
8364
|
BC_CMD_ID_SET_AUDIO_CFG,
|
|
7914
8365
|
BC_CMD_ID_SET_AUDIO_TASK,
|
|
7915
8366
|
BC_CMD_ID_SET_AUTO_FOCUS,
|
|
8367
|
+
BC_CMD_ID_SET_AUTO_REBOOT,
|
|
7916
8368
|
BC_CMD_ID_SET_DAY_NIGHT_THRESHOLD,
|
|
7917
8369
|
BC_CMD_ID_SET_DING_DONG_CFG,
|
|
7918
8370
|
BC_CMD_ID_SET_DING_DONG_SILENT,
|
|
8371
|
+
BC_CMD_ID_SET_DST,
|
|
8372
|
+
BC_CMD_ID_SET_EMAIL,
|
|
7919
8373
|
BC_CMD_ID_SET_EMAIL_TASK,
|
|
7920
8374
|
BC_CMD_ID_SET_ENC,
|
|
7921
8375
|
BC_CMD_ID_SET_LED_STATE,
|
|
7922
8376
|
BC_CMD_ID_SET_MOTION_ALARM,
|
|
8377
|
+
BC_CMD_ID_SET_NTP,
|
|
8378
|
+
BC_CMD_ID_SET_OSD_DATETIME,
|
|
7923
8379
|
BC_CMD_ID_SET_PIR_INFO,
|
|
7924
8380
|
BC_CMD_ID_SET_PRIVACY_MASK,
|
|
7925
8381
|
BC_CMD_ID_SET_PUSH_TASK,
|
|
7926
8382
|
BC_CMD_ID_SET_RECORD,
|
|
7927
8383
|
BC_CMD_ID_SET_RECORD_CFG,
|
|
8384
|
+
BC_CMD_ID_SET_SYSTEM_GENERAL,
|
|
7928
8385
|
BC_CMD_ID_SET_VIDEO_INPUT,
|
|
7929
8386
|
BC_CMD_ID_SET_WHITE_LED_STATE,
|
|
7930
8387
|
BC_CMD_ID_SET_WHITE_LED_TASK,
|
|
@@ -7934,6 +8391,7 @@ export {
|
|
|
7934
8391
|
BC_CMD_ID_TALK_ABILITY,
|
|
7935
8392
|
BC_CMD_ID_TALK_CONFIG,
|
|
7936
8393
|
BC_CMD_ID_TALK_RESET,
|
|
8394
|
+
BC_CMD_ID_TEST_EMAIL,
|
|
7937
8395
|
BC_CMD_ID_UDP_KEEP_ALIVE,
|
|
7938
8396
|
BC_CMD_ID_VIDEO,
|
|
7939
8397
|
BC_CMD_ID_VIDEO_STOP,
|
|
@@ -8028,6 +8486,7 @@ export {
|
|
|
8028
8486
|
decideSleepInferenceTransition,
|
|
8029
8487
|
decideVideoclipTranscodeMode,
|
|
8030
8488
|
decodeHeader,
|
|
8489
|
+
decodeMotionScopeBitmap,
|
|
8031
8490
|
deriveAesKey,
|
|
8032
8491
|
detectIosClient,
|
|
8033
8492
|
detectVideoCodecFromNal,
|
|
@@ -8040,6 +8499,7 @@ export {
|
|
|
8040
8499
|
discoverViaUdpBroadcast,
|
|
8041
8500
|
discoverViaUdpDirect,
|
|
8042
8501
|
encodeHeader,
|
|
8502
|
+
encodeMotionScopeBitmap,
|
|
8043
8503
|
ensureXmlHeader,
|
|
8044
8504
|
extractH264ParamSetsFromAccessUnit,
|
|
8045
8505
|
extractH265ParamSetsFromAccessUnit,
|
|
@@ -8048,6 +8508,7 @@ export {
|
|
|
8048
8508
|
extractVpsFromAnnexB,
|
|
8049
8509
|
flattenAbilitiesForChannel,
|
|
8050
8510
|
formatMjpegFrame,
|
|
8511
|
+
fullCoverageScope,
|
|
8051
8512
|
getConstructedVideoStreamOptions,
|
|
8052
8513
|
getGlobalLogger,
|
|
8053
8514
|
getH265NalType,
|
|
@@ -8091,6 +8552,7 @@ export {
|
|
|
8091
8552
|
splitAnnexBToNals,
|
|
8092
8553
|
splitAnnexBToNalPayloads2 as splitH265AnnexBToNalPayloads,
|
|
8093
8554
|
testChannelStreams,
|
|
8555
|
+
upsertXmlTag,
|
|
8094
8556
|
xmlEscape,
|
|
8095
8557
|
xmlIndicatesFloodlight,
|
|
8096
8558
|
zipDirectory
|