@apocaliss92/nodelink-js 0.6.6 → 0.6.7
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/{chunk-JQ5NSEVD.js → chunk-T22QCNBR.js} +54 -16
- package/dist/chunk-T22QCNBR.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +53 -15
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +1 -1
- package/dist/index.cjs +75 -842
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -192
- package/dist/index.d.ts +4 -171
- package/dist/index.js +21 -823
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-JQ5NSEVD.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -76,7 +76,7 @@ import {
|
|
|
76
76
|
setGlobalLogger,
|
|
77
77
|
tcpReachabilityProbe,
|
|
78
78
|
xmlIndicatesFloodlight
|
|
79
|
-
} from "./chunk-
|
|
79
|
+
} from "./chunk-T22QCNBR.js";
|
|
80
80
|
import {
|
|
81
81
|
ReolinkCgiApi,
|
|
82
82
|
ReolinkHttpClient,
|
|
@@ -4763,809 +4763,8 @@ async function createReplayHttpServer(options) {
|
|
|
4763
4763
|
};
|
|
4764
4764
|
}
|
|
4765
4765
|
|
|
4766
|
-
// src/baichuan/stream/Go2rtcTcpServer.ts
|
|
4767
|
-
import { EventEmitter as EventEmitter2 } from "events";
|
|
4768
|
-
import * as net from "net";
|
|
4769
|
-
var AsyncBoundedQueue = class {
|
|
4770
|
-
maxItems;
|
|
4771
|
-
queue = [];
|
|
4772
|
-
waiting;
|
|
4773
|
-
closed = false;
|
|
4774
|
-
constructor(maxItems) {
|
|
4775
|
-
this.maxItems = Math.max(1, maxItems | 0);
|
|
4776
|
-
}
|
|
4777
|
-
push(item) {
|
|
4778
|
-
if (this.closed) return;
|
|
4779
|
-
if (this.waiting) {
|
|
4780
|
-
const { resolve } = this.waiting;
|
|
4781
|
-
this.waiting = void 0;
|
|
4782
|
-
resolve({ value: item, done: false });
|
|
4783
|
-
return;
|
|
4784
|
-
}
|
|
4785
|
-
this.queue.push(item);
|
|
4786
|
-
if (this.queue.length > this.maxItems) {
|
|
4787
|
-
this.queue.splice(0, this.queue.length - this.maxItems);
|
|
4788
|
-
}
|
|
4789
|
-
}
|
|
4790
|
-
close() {
|
|
4791
|
-
if (this.closed) return;
|
|
4792
|
-
this.closed = true;
|
|
4793
|
-
if (this.waiting) {
|
|
4794
|
-
const { resolve } = this.waiting;
|
|
4795
|
-
this.waiting = void 0;
|
|
4796
|
-
resolve({ value: void 0, done: true });
|
|
4797
|
-
}
|
|
4798
|
-
}
|
|
4799
|
-
async next() {
|
|
4800
|
-
if (this.closed) return { value: void 0, done: true };
|
|
4801
|
-
const item = this.queue.shift();
|
|
4802
|
-
if (item !== void 0) return { value: item, done: false };
|
|
4803
|
-
return await new Promise((resolve) => {
|
|
4804
|
-
this.waiting = { resolve };
|
|
4805
|
-
});
|
|
4806
|
-
}
|
|
4807
|
-
};
|
|
4808
|
-
var NativeStreamFanout = class {
|
|
4809
|
-
opts;
|
|
4810
|
-
queues = /* @__PURE__ */ new Map();
|
|
4811
|
-
source = null;
|
|
4812
|
-
running = false;
|
|
4813
|
-
pumpPromise = null;
|
|
4814
|
-
constructor(opts) {
|
|
4815
|
-
this.opts = opts;
|
|
4816
|
-
}
|
|
4817
|
-
start() {
|
|
4818
|
-
if (this.running) return;
|
|
4819
|
-
this.running = true;
|
|
4820
|
-
this.source = this.opts.createSource();
|
|
4821
|
-
this.pumpPromise = (async () => {
|
|
4822
|
-
try {
|
|
4823
|
-
for await (const frame of this.source) {
|
|
4824
|
-
try {
|
|
4825
|
-
this.opts.onFrame?.(frame);
|
|
4826
|
-
} catch {
|
|
4827
|
-
}
|
|
4828
|
-
for (const q of this.queues.values()) {
|
|
4829
|
-
q.push(frame);
|
|
4830
|
-
}
|
|
4831
|
-
}
|
|
4832
|
-
} catch (e) {
|
|
4833
|
-
this.opts.onError?.(e);
|
|
4834
|
-
} finally {
|
|
4835
|
-
for (const q of this.queues.values()) q.close();
|
|
4836
|
-
this.queues.clear();
|
|
4837
|
-
this.running = false;
|
|
4838
|
-
this.opts.onEnd?.();
|
|
4839
|
-
}
|
|
4840
|
-
})();
|
|
4841
|
-
}
|
|
4842
|
-
subscribe(id) {
|
|
4843
|
-
const q = new AsyncBoundedQueue(this.opts.maxQueueItems);
|
|
4844
|
-
this.queues.set(id, q);
|
|
4845
|
-
const self = this;
|
|
4846
|
-
return (async function* () {
|
|
4847
|
-
try {
|
|
4848
|
-
while (true) {
|
|
4849
|
-
const r = await q.next();
|
|
4850
|
-
if (r.done) return;
|
|
4851
|
-
yield r.value;
|
|
4852
|
-
}
|
|
4853
|
-
} finally {
|
|
4854
|
-
q.close();
|
|
4855
|
-
self.queues.delete(id);
|
|
4856
|
-
}
|
|
4857
|
-
})();
|
|
4858
|
-
}
|
|
4859
|
-
async stop() {
|
|
4860
|
-
if (!this.running) return;
|
|
4861
|
-
this.running = false;
|
|
4862
|
-
const src = this.source;
|
|
4863
|
-
this.source = null;
|
|
4864
|
-
for (const q of this.queues.values()) q.close();
|
|
4865
|
-
this.queues.clear();
|
|
4866
|
-
try {
|
|
4867
|
-
await src?.return(void 0);
|
|
4868
|
-
} catch {
|
|
4869
|
-
}
|
|
4870
|
-
try {
|
|
4871
|
-
await this.pumpPromise;
|
|
4872
|
-
} catch {
|
|
4873
|
-
}
|
|
4874
|
-
this.pumpPromise = null;
|
|
4875
|
-
}
|
|
4876
|
-
};
|
|
4877
|
-
var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
|
|
4878
|
-
api;
|
|
4879
|
-
channel;
|
|
4880
|
-
profile;
|
|
4881
|
-
variant;
|
|
4882
|
-
listenHost;
|
|
4883
|
-
listenPort;
|
|
4884
|
-
logger;
|
|
4885
|
-
deviceId;
|
|
4886
|
-
gracePeriodMs;
|
|
4887
|
-
prebufferMaxMs;
|
|
4888
|
-
maxBufferBytes;
|
|
4889
|
-
streamTimeoutMs;
|
|
4890
|
-
prestartStream;
|
|
4891
|
-
active = false;
|
|
4892
|
-
server;
|
|
4893
|
-
resolvedPort;
|
|
4894
|
-
// Native stream
|
|
4895
|
-
nativeFanout = null;
|
|
4896
|
-
nativeStreamActive = false;
|
|
4897
|
-
// Set only by stopNativeStream() (explicit teardown) so the fanout's onEnd
|
|
4898
|
-
// callback can short-circuit cleanup/restart logic. NOT set by the inactivity-
|
|
4899
|
-
// timeout force-restart path — that flow wants onEnd to run and decide
|
|
4900
|
-
// whether to restart based on prestartStream / connected clients.
|
|
4901
|
-
nativeStreamStopping = false;
|
|
4902
|
-
// Pending retry timer for the unbounded auto-restart loop. When a stream
|
|
4903
|
-
// start fails transiently (camera in maintenance reboot, idle-disconnect
|
|
4904
|
-
// race, etc.) we keep trying with exponential backoff until either the
|
|
4905
|
-
// server is stopped or a frame finally arrives.
|
|
4906
|
-
nativeStreamRetryTimer;
|
|
4907
|
-
nativeStreamRetryDelayMs = 0;
|
|
4908
|
-
dedicatedSessionRelease;
|
|
4909
|
-
detectedVideoType;
|
|
4910
|
-
// Client tracking
|
|
4911
|
-
connectedClients = /* @__PURE__ */ new Set();
|
|
4912
|
-
clientSockets = /* @__PURE__ */ new Map();
|
|
4913
|
-
stopGraceTimer;
|
|
4914
|
-
// Stream health monitoring
|
|
4915
|
-
lastFrameAt = 0;
|
|
4916
|
-
streamHealthTimer;
|
|
4917
|
-
totalFramesReceived = 0;
|
|
4918
|
-
totalVideoFramesWritten = 0;
|
|
4919
|
-
// Prebuffer
|
|
4920
|
-
prebuffer = [];
|
|
4921
|
-
// Audio metadata — populated on first valid ADTS AAC frame.
|
|
4922
|
-
// Exposed via getAudioInfo() for the stream-diagnostics feature.
|
|
4923
|
-
audioInfo = null;
|
|
4924
|
-
constructor(options) {
|
|
4925
|
-
super();
|
|
4926
|
-
this.api = options.api;
|
|
4927
|
-
this.channel = options.channel;
|
|
4928
|
-
this.profile = options.profile;
|
|
4929
|
-
this.variant = options.variant ?? "default";
|
|
4930
|
-
this.listenHost = options.listenHost ?? "127.0.0.1";
|
|
4931
|
-
this.listenPort = options.listenPort ?? 0;
|
|
4932
|
-
this.logger = options.logger ?? console;
|
|
4933
|
-
this.deviceId = options.deviceId;
|
|
4934
|
-
this.gracePeriodMs = options.gracePeriodMs ?? 3e4;
|
|
4935
|
-
this.prebufferMaxMs = options.prebufferMs ?? 3e3;
|
|
4936
|
-
this.maxBufferBytes = options.maxBufferBytes ?? 1e8;
|
|
4937
|
-
this.streamTimeoutMs = options.streamTimeoutMs ?? 15e3;
|
|
4938
|
-
this.prestartStream = options.prestartStream ?? true;
|
|
4939
|
-
}
|
|
4940
|
-
// -----------------------------------------------------------------------
|
|
4941
|
-
// Public API
|
|
4942
|
-
// -----------------------------------------------------------------------
|
|
4943
|
-
/** Start listening. Resolves once the TCP server is bound. */
|
|
4944
|
-
async start() {
|
|
4945
|
-
if (this.active) return;
|
|
4946
|
-
this.active = true;
|
|
4947
|
-
this.server = net.createServer((socket) => this.handleClient(socket));
|
|
4948
|
-
this.server.on("error", (err) => {
|
|
4949
|
-
this.logger.error?.(`[Go2rtcTcpServer] server error: ${err.message}`);
|
|
4950
|
-
this.emit("error", err);
|
|
4951
|
-
});
|
|
4952
|
-
await new Promise((resolve, reject) => {
|
|
4953
|
-
this.server.listen(this.listenPort, this.listenHost, () => {
|
|
4954
|
-
const addr = this.server.address();
|
|
4955
|
-
this.resolvedPort = addr.port;
|
|
4956
|
-
this.logger.info?.(
|
|
4957
|
-
`[Go2rtcTcpServer] listening on ${addr.address}:${addr.port} channel=${this.channel} profile=${this.profile}`
|
|
4958
|
-
);
|
|
4959
|
-
this.emit("listening", { host: addr.address, port: addr.port });
|
|
4960
|
-
resolve();
|
|
4961
|
-
});
|
|
4962
|
-
this.server.once("error", reject);
|
|
4963
|
-
});
|
|
4964
|
-
if (this.prestartStream) {
|
|
4965
|
-
this.logger.info?.(
|
|
4966
|
-
`[Go2rtcTcpServer] pre-starting native stream channel=${this.channel} profile=${this.profile}`
|
|
4967
|
-
);
|
|
4968
|
-
this.startNativeStream();
|
|
4969
|
-
}
|
|
4970
|
-
}
|
|
4971
|
-
/** Stop the server and all active streams. */
|
|
4972
|
-
async stop() {
|
|
4973
|
-
if (!this.active) return;
|
|
4974
|
-
this.active = false;
|
|
4975
|
-
clearTimeout(this.stopGraceTimer);
|
|
4976
|
-
this.clearNativeStreamRetry();
|
|
4977
|
-
this.stopStreamHealthMonitor();
|
|
4978
|
-
for (const [id, sock] of this.clientSockets) {
|
|
4979
|
-
sock.destroy();
|
|
4980
|
-
this.connectedClients.delete(id);
|
|
4981
|
-
}
|
|
4982
|
-
this.clientSockets.clear();
|
|
4983
|
-
await this.stopNativeStream();
|
|
4984
|
-
if (this.server) {
|
|
4985
|
-
await new Promise((resolve) => {
|
|
4986
|
-
this.server.close(() => resolve());
|
|
4987
|
-
});
|
|
4988
|
-
this.server = void 0;
|
|
4989
|
-
}
|
|
4990
|
-
this.prebuffer = [];
|
|
4991
|
-
this.resolvedPort = void 0;
|
|
4992
|
-
this.emit("close");
|
|
4993
|
-
}
|
|
4994
|
-
/** The actual port the server is listening on (available after start()). */
|
|
4995
|
-
get port() {
|
|
4996
|
-
return this.resolvedPort;
|
|
4997
|
-
}
|
|
4998
|
-
/** The go2rtc-compatible source URL. */
|
|
4999
|
-
get go2rtcSourceUrl() {
|
|
5000
|
-
if (this.resolvedPort == null) return void 0;
|
|
5001
|
-
return `tcp://127.0.0.1:${this.resolvedPort}`;
|
|
5002
|
-
}
|
|
5003
|
-
/** Number of currently connected clients. */
|
|
5004
|
-
get clientCount() {
|
|
5005
|
-
return this.connectedClients.size;
|
|
5006
|
-
}
|
|
5007
|
-
// -----------------------------------------------------------------------
|
|
5008
|
-
// Diagnostic subscription API (implements DiagnosticStreamServer)
|
|
5009
|
-
//
|
|
5010
|
-
// Matches the shape of BaichuanRtspServer's diagnostic API so the
|
|
5011
|
-
// stream-diagnostic feature in the Manager app can drive either backend
|
|
5012
|
-
// with identical code.
|
|
5013
|
-
// -----------------------------------------------------------------------
|
|
5014
|
-
/**
|
|
5015
|
-
* Subscribe to the raw native stream for diagnostic purposes.
|
|
5016
|
-
* The subscriber receives the same frames the MPEG-TS muxer consumes
|
|
5017
|
-
* (pre-muxing). Counts as a "consumer" so the native stream is kept alive
|
|
5018
|
-
* for the lifetime of the subscription. If the stream is not already
|
|
5019
|
-
* running (battery camera, prestart=false), this starts it.
|
|
5020
|
-
*/
|
|
5021
|
-
async subscribeDiagnostic(id) {
|
|
5022
|
-
this.connectedClients.add(`diag:${id}`);
|
|
5023
|
-
if (!this.nativeStreamActive) {
|
|
5024
|
-
await this.startNativeStream();
|
|
5025
|
-
}
|
|
5026
|
-
if (!this.nativeFanout) {
|
|
5027
|
-
this.connectedClients.delete(`diag:${id}`);
|
|
5028
|
-
throw new Error(
|
|
5029
|
-
"Go2rtcTcpServer: native stream failed to start \u2014 cannot subscribe diagnostic"
|
|
5030
|
-
);
|
|
5031
|
-
}
|
|
5032
|
-
return this.nativeFanout.subscribe(`diag:${id}`);
|
|
5033
|
-
}
|
|
5034
|
-
/** Unsubscribe a diagnostic session and release its consumer slot. */
|
|
5035
|
-
unsubscribeDiagnostic(id) {
|
|
5036
|
-
this.removeClient(`diag:${id}`, "diagnostic unsubscribe");
|
|
5037
|
-
}
|
|
5038
|
-
/**
|
|
5039
|
-
* Returns ADTS AAC audio metadata detected from the native stream, or
|
|
5040
|
-
* null if no audio frame has been observed yet (e.g. video-only cameras
|
|
5041
|
-
* or before the first audio packet arrives).
|
|
5042
|
-
*/
|
|
5043
|
-
getAudioInfo() {
|
|
5044
|
-
return this.audioInfo;
|
|
5045
|
-
}
|
|
5046
|
-
// -----------------------------------------------------------------------
|
|
5047
|
-
// Client handling
|
|
5048
|
-
// -----------------------------------------------------------------------
|
|
5049
|
-
handleClient(socket) {
|
|
5050
|
-
const clientId = `${socket.remoteAddress}:${socket.remotePort}`;
|
|
5051
|
-
socket.setNoDelay(true);
|
|
5052
|
-
this.connectedClients.add(clientId);
|
|
5053
|
-
this.clientSockets.set(clientId, socket);
|
|
5054
|
-
this.logger.info?.(
|
|
5055
|
-
`[Go2rtcTcpServer] client connected id=${clientId} total=${this.connectedClients.size}`
|
|
5056
|
-
);
|
|
5057
|
-
this.emit("client", clientId);
|
|
5058
|
-
if (this.stopGraceTimer) {
|
|
5059
|
-
clearTimeout(this.stopGraceTimer);
|
|
5060
|
-
this.stopGraceTimer = void 0;
|
|
5061
|
-
}
|
|
5062
|
-
if (!this.nativeStreamActive) {
|
|
5063
|
-
this.startNativeStream();
|
|
5064
|
-
}
|
|
5065
|
-
this.feedClient(clientId, socket).catch((err) => {
|
|
5066
|
-
this.logger.warn?.(
|
|
5067
|
-
`[Go2rtcTcpServer] feedClient error id=${clientId}: ${err}`
|
|
5068
|
-
);
|
|
5069
|
-
});
|
|
5070
|
-
const cleanup = (reason) => {
|
|
5071
|
-
this.removeClient(clientId, reason);
|
|
5072
|
-
socket.destroy();
|
|
5073
|
-
};
|
|
5074
|
-
socket.on("error", (err) => cleanup(`error: ${err.message}`));
|
|
5075
|
-
socket.on("close", (hadError) => cleanup(hadError ? "close (with error)" : "close (clean)"));
|
|
5076
|
-
}
|
|
5077
|
-
async feedClient(clientId, socket) {
|
|
5078
|
-
const fanoutDeadline = Date.now() + 3e4;
|
|
5079
|
-
while (this.active && !this.nativeFanout) {
|
|
5080
|
-
if (socket.destroyed) return;
|
|
5081
|
-
if (Date.now() > fanoutDeadline) {
|
|
5082
|
-
this.logger.warn?.(
|
|
5083
|
-
`[Go2rtcTcpServer] fanout not ready after 30s, dropping client ${clientId}`
|
|
5084
|
-
);
|
|
5085
|
-
return;
|
|
5086
|
-
}
|
|
5087
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
5088
|
-
}
|
|
5089
|
-
if (!this.active || !this.nativeFanout) return;
|
|
5090
|
-
const subscription = this.nativeFanout.subscribe(clientId);
|
|
5091
|
-
let muxer = null;
|
|
5092
|
-
const prebufferSnap = this.prebuffer.slice();
|
|
5093
|
-
let lastIdrIdx = -1;
|
|
5094
|
-
for (let i = prebufferSnap.length - 1; i >= 0; i--) {
|
|
5095
|
-
if (prebufferSnap[i].isKeyframe) {
|
|
5096
|
-
lastIdrIdx = i;
|
|
5097
|
-
break;
|
|
5098
|
-
}
|
|
5099
|
-
}
|
|
5100
|
-
if (lastIdrIdx >= 0) {
|
|
5101
|
-
const replay = prebufferSnap.slice(lastIdrIdx);
|
|
5102
|
-
this.logger.info?.(
|
|
5103
|
-
`[Go2rtcTcpServer] prebuffer replay client=${clientId} frames=${replay.length}`
|
|
5104
|
-
);
|
|
5105
|
-
if (!muxer) {
|
|
5106
|
-
muxer = new MpegTsMuxer({
|
|
5107
|
-
videoType: this.detectedVideoType ?? "H264",
|
|
5108
|
-
includeAudio: true
|
|
5109
|
-
});
|
|
5110
|
-
}
|
|
5111
|
-
for (const entry of replay) {
|
|
5112
|
-
if (socket.destroyed) return;
|
|
5113
|
-
let ts;
|
|
5114
|
-
if (!entry.audio) {
|
|
5115
|
-
ts = muxer.muxVideo(entry.data, entry.pts, entry.isKeyframe);
|
|
5116
|
-
} else {
|
|
5117
|
-
ts = muxer.muxAudio(entry.data, entry.pts);
|
|
5118
|
-
}
|
|
5119
|
-
if (ts.length > 0) socket.write(ts);
|
|
5120
|
-
}
|
|
5121
|
-
}
|
|
5122
|
-
let seenKeyframe = lastIdrIdx >= 0;
|
|
5123
|
-
let liveFrameCount = 0;
|
|
5124
|
-
let liveVideoWritten = 0;
|
|
5125
|
-
let lastLogAt = Date.now();
|
|
5126
|
-
try {
|
|
5127
|
-
this.logger.info?.(
|
|
5128
|
-
`[Go2rtcTcpServer] entering live loop client=${clientId} seenKeyframe=${seenKeyframe}`
|
|
5129
|
-
);
|
|
5130
|
-
for await (const frame of subscription) {
|
|
5131
|
-
if (socket.destroyed || !this.active) {
|
|
5132
|
-
this.logger.info?.(
|
|
5133
|
-
`[Go2rtcTcpServer] live loop exit client=${clientId} destroyed=${socket.destroyed} active=${this.active}`
|
|
5134
|
-
);
|
|
5135
|
-
break;
|
|
5136
|
-
}
|
|
5137
|
-
liveFrameCount++;
|
|
5138
|
-
if (frame.audio) {
|
|
5139
|
-
if (muxer) {
|
|
5140
|
-
const pts2 = frame.microseconds ?? Date.now() * 1e3;
|
|
5141
|
-
const ts2 = muxer.muxAudio(frame.data, pts2);
|
|
5142
|
-
if (ts2.length > 0) socket.write(ts2);
|
|
5143
|
-
}
|
|
5144
|
-
continue;
|
|
5145
|
-
}
|
|
5146
|
-
const annexB = this.convertVideoFrame(frame);
|
|
5147
|
-
if (!annexB) continue;
|
|
5148
|
-
const isKf = this.isAnnexBKeyframe(annexB, frame.videoType);
|
|
5149
|
-
if (!seenKeyframe) {
|
|
5150
|
-
if (!isKf) continue;
|
|
5151
|
-
seenKeyframe = true;
|
|
5152
|
-
this.logger.info?.(
|
|
5153
|
-
`[Go2rtcTcpServer] first live keyframe client=${clientId} after ${liveFrameCount} frames`
|
|
5154
|
-
);
|
|
5155
|
-
if (!muxer) {
|
|
5156
|
-
muxer = new MpegTsMuxer({
|
|
5157
|
-
videoType: frame.videoType ?? this.detectedVideoType ?? "H264",
|
|
5158
|
-
includeAudio: true
|
|
5159
|
-
});
|
|
5160
|
-
}
|
|
5161
|
-
}
|
|
5162
|
-
const pts = frame.microseconds ?? Date.now() * 1e3;
|
|
5163
|
-
const ts = muxer.muxVideo(annexB, pts, isKf);
|
|
5164
|
-
socket.write(ts);
|
|
5165
|
-
liveVideoWritten++;
|
|
5166
|
-
this.totalVideoFramesWritten++;
|
|
5167
|
-
if (Date.now() - lastLogAt > 1e4) {
|
|
5168
|
-
this.logger.info?.(
|
|
5169
|
-
`[Go2rtcTcpServer] live stats client=${clientId} received=${liveFrameCount} written=${liveVideoWritten} bufLen=${socket.writableLength}`
|
|
5170
|
-
);
|
|
5171
|
-
lastLogAt = Date.now();
|
|
5172
|
-
}
|
|
5173
|
-
if (socket.writableLength > this.maxBufferBytes) {
|
|
5174
|
-
this.logger.warn?.(
|
|
5175
|
-
`[Go2rtcTcpServer] buffer overflow (${socket.writableLength} bytes), dropping client ${clientId}`
|
|
5176
|
-
);
|
|
5177
|
-
socket.destroy();
|
|
5178
|
-
break;
|
|
5179
|
-
}
|
|
5180
|
-
}
|
|
5181
|
-
this.logger.info?.(
|
|
5182
|
-
`[Go2rtcTcpServer] live loop ended naturally client=${clientId} received=${liveFrameCount} written=${liveVideoWritten}`
|
|
5183
|
-
);
|
|
5184
|
-
} finally {
|
|
5185
|
-
await subscription.return(void 0).catch(() => {
|
|
5186
|
-
});
|
|
5187
|
-
}
|
|
5188
|
-
}
|
|
5189
|
-
// -----------------------------------------------------------------------
|
|
5190
|
-
// Frame conversion
|
|
5191
|
-
// -----------------------------------------------------------------------
|
|
5192
|
-
/**
|
|
5193
|
-
* Convert a native video frame to Annex-B.
|
|
5194
|
-
* Returns null for audio frames (handled separately by muxAudio).
|
|
5195
|
-
*/
|
|
5196
|
-
convertVideoFrame(frame) {
|
|
5197
|
-
if (frame.audio) return null;
|
|
5198
|
-
if (frame.data.length === 0) return null;
|
|
5199
|
-
try {
|
|
5200
|
-
if (frame.videoType === "H264") {
|
|
5201
|
-
return convertToAnnexB(frame.data);
|
|
5202
|
-
}
|
|
5203
|
-
if (frame.videoType === "H265") {
|
|
5204
|
-
return convertToAnnexB2(frame.data);
|
|
5205
|
-
}
|
|
5206
|
-
} catch {
|
|
5207
|
-
}
|
|
5208
|
-
return frame.data;
|
|
5209
|
-
}
|
|
5210
|
-
/** Check if an Annex-B buffer contains a keyframe (IDR for H.264, IRAP for H.265). */
|
|
5211
|
-
isAnnexBKeyframe(annexB, videoType) {
|
|
5212
|
-
try {
|
|
5213
|
-
if (videoType === "H264") {
|
|
5214
|
-
const nals = _Go2rtcTcpServer.splitAnnexBNals(annexB);
|
|
5215
|
-
return nals.some((n) => n.length >= 1 && (n[0] & 31) === 5);
|
|
5216
|
-
}
|
|
5217
|
-
if (videoType === "H265") {
|
|
5218
|
-
const nals = splitAnnexBToNalPayloads2(annexB);
|
|
5219
|
-
return nals.some(
|
|
5220
|
-
(n) => n.length >= 2 && isH265Irap(n[0] >> 1 & 63)
|
|
5221
|
-
);
|
|
5222
|
-
}
|
|
5223
|
-
} catch {
|
|
5224
|
-
}
|
|
5225
|
-
return false;
|
|
5226
|
-
}
|
|
5227
|
-
/** Split Annex-B byte stream into individual NAL units. */
|
|
5228
|
-
static splitAnnexBNals(buf) {
|
|
5229
|
-
const nals = [];
|
|
5230
|
-
let i = 0;
|
|
5231
|
-
while (i < buf.length) {
|
|
5232
|
-
if (i + 2 < buf.length && buf[i] === 0 && buf[i + 1] === 0) {
|
|
5233
|
-
let scLen;
|
|
5234
|
-
if (buf[i + 2] === 1) {
|
|
5235
|
-
scLen = 3;
|
|
5236
|
-
} else if (i + 3 < buf.length && buf[i + 2] === 0 && buf[i + 3] === 1) {
|
|
5237
|
-
scLen = 4;
|
|
5238
|
-
} else {
|
|
5239
|
-
i++;
|
|
5240
|
-
continue;
|
|
5241
|
-
}
|
|
5242
|
-
const nalStart = i + scLen;
|
|
5243
|
-
let nalEnd = buf.length;
|
|
5244
|
-
for (let j = nalStart; j < buf.length - 2; j++) {
|
|
5245
|
-
if (buf[j] === 0 && buf[j + 1] === 0 && (buf[j + 2] === 1 || j + 3 < buf.length && buf[j + 2] === 0 && buf[j + 3] === 1)) {
|
|
5246
|
-
nalEnd = j;
|
|
5247
|
-
break;
|
|
5248
|
-
}
|
|
5249
|
-
}
|
|
5250
|
-
if (nalEnd > nalStart) {
|
|
5251
|
-
nals.push(buf.subarray(nalStart, nalEnd));
|
|
5252
|
-
}
|
|
5253
|
-
i = nalEnd;
|
|
5254
|
-
} else {
|
|
5255
|
-
i++;
|
|
5256
|
-
}
|
|
5257
|
-
}
|
|
5258
|
-
return nals;
|
|
5259
|
-
}
|
|
5260
|
-
// -----------------------------------------------------------------------
|
|
5261
|
-
// ADTS AAC parsing (used for audio metadata exposed via getAudioInfo)
|
|
5262
|
-
// -----------------------------------------------------------------------
|
|
5263
|
-
/** True if `b` starts with an ADTS AAC syncword (0xFFF). */
|
|
5264
|
-
static isAdtsAacFrame(b) {
|
|
5265
|
-
return b.length >= 2 && b[0] === 255 && (b[1] & 240) === 240;
|
|
5266
|
-
}
|
|
5267
|
-
/**
|
|
5268
|
-
* Parse an ADTS header into {sampleRate, channels, AudioSpecificConfig hex}.
|
|
5269
|
-
* Returns null when the buffer is not a valid ADTS frame.
|
|
5270
|
-
*/
|
|
5271
|
-
static parseAdtsSamplingInfo(b) {
|
|
5272
|
-
if (b.length < 7) return null;
|
|
5273
|
-
if (!_Go2rtcTcpServer.isAdtsAacFrame(b)) return null;
|
|
5274
|
-
const samplingIndex = b[2] >> 2 & 15;
|
|
5275
|
-
const sampleRates = [
|
|
5276
|
-
96e3,
|
|
5277
|
-
88200,
|
|
5278
|
-
64e3,
|
|
5279
|
-
48e3,
|
|
5280
|
-
44100,
|
|
5281
|
-
32e3,
|
|
5282
|
-
24e3,
|
|
5283
|
-
22050,
|
|
5284
|
-
16e3,
|
|
5285
|
-
12e3,
|
|
5286
|
-
11025,
|
|
5287
|
-
8e3,
|
|
5288
|
-
7350
|
|
5289
|
-
];
|
|
5290
|
-
const sampleRate = sampleRates[samplingIndex] ?? null;
|
|
5291
|
-
if (!sampleRate) return null;
|
|
5292
|
-
const channelConfig = (b[2] & 1) << 2 | b[3] >> 6 & 3;
|
|
5293
|
-
const channels = channelConfig === 0 ? 1 : channelConfig;
|
|
5294
|
-
const profile = b[2] >> 6 & 3;
|
|
5295
|
-
const audioObjectType = profile + 1;
|
|
5296
|
-
const asc = audioObjectType << 11 | samplingIndex << 7 | channelConfig << 3;
|
|
5297
|
-
const configHex = Buffer.from([asc >> 8 & 255, asc & 255]).toString(
|
|
5298
|
-
"hex"
|
|
5299
|
-
);
|
|
5300
|
-
return { sampleRate, channels, configHex };
|
|
5301
|
-
}
|
|
5302
|
-
// -----------------------------------------------------------------------
|
|
5303
|
-
// Native stream management
|
|
5304
|
-
// -----------------------------------------------------------------------
|
|
5305
|
-
/**
|
|
5306
|
-
* Schedule another startNativeStream() attempt after the given delay.
|
|
5307
|
-
* Idempotent: a no-op if a retry is already scheduled, the server is no
|
|
5308
|
-
* longer active, or an explicit stop is in progress. Implements unbounded
|
|
5309
|
-
* exponential backoff (5s → 60s) so a camera that stays unreachable for
|
|
5310
|
-
* minutes (e.g. nightly maintenance reboot) eventually recovers without
|
|
5311
|
-
* manual intervention — see issue #16.
|
|
5312
|
-
*/
|
|
5313
|
-
scheduleNativeStreamRetry(reason) {
|
|
5314
|
-
if (!this.active) return;
|
|
5315
|
-
if (this.nativeStreamStopping) return;
|
|
5316
|
-
if (this.nativeStreamRetryTimer) return;
|
|
5317
|
-
const delay = this.nativeStreamRetryDelayMs > 0 ? this.nativeStreamRetryDelayMs : 5e3;
|
|
5318
|
-
this.logger.info?.(
|
|
5319
|
-
`[Go2rtcTcpServer] scheduling native stream retry in ${(delay / 1e3).toFixed(0)}s (reason=${reason})`
|
|
5320
|
-
);
|
|
5321
|
-
this.nativeStreamRetryTimer = setTimeout(() => {
|
|
5322
|
-
this.nativeStreamRetryTimer = void 0;
|
|
5323
|
-
if (!this.active) return;
|
|
5324
|
-
if (this.nativeStreamStopping) return;
|
|
5325
|
-
this.startNativeStream().catch((err) => {
|
|
5326
|
-
this.logger.warn?.(
|
|
5327
|
-
`[Go2rtcTcpServer] retry of startNativeStream threw: ${err instanceof Error ? err.message : err}`
|
|
5328
|
-
);
|
|
5329
|
-
});
|
|
5330
|
-
}, delay);
|
|
5331
|
-
this.nativeStreamRetryDelayMs = Math.min(delay * 2, 6e4);
|
|
5332
|
-
}
|
|
5333
|
-
/**
|
|
5334
|
-
* Cancel any pending retry timer and reset the backoff. Called on explicit
|
|
5335
|
-
* stop and on first-frame-received so the next failure starts the backoff
|
|
5336
|
-
* window from scratch.
|
|
5337
|
-
*/
|
|
5338
|
-
clearNativeStreamRetry() {
|
|
5339
|
-
if (this.nativeStreamRetryTimer) {
|
|
5340
|
-
clearTimeout(this.nativeStreamRetryTimer);
|
|
5341
|
-
this.nativeStreamRetryTimer = void 0;
|
|
5342
|
-
}
|
|
5343
|
-
this.nativeStreamRetryDelayMs = 0;
|
|
5344
|
-
}
|
|
5345
|
-
async startNativeStream() {
|
|
5346
|
-
if (this.nativeStreamActive) return;
|
|
5347
|
-
if (!this.api.isReady) {
|
|
5348
|
-
if (this.api.isClosed) {
|
|
5349
|
-
this.logger.warn?.(
|
|
5350
|
-
`[Go2rtcTcpServer] API has been explicitly closed \u2014 stream cannot start`
|
|
5351
|
-
);
|
|
5352
|
-
return;
|
|
5353
|
-
}
|
|
5354
|
-
try {
|
|
5355
|
-
this.logger.info?.(
|
|
5356
|
-
`[Go2rtcTcpServer] API not ready (idle disconnect?), calling ensureConnected`
|
|
5357
|
-
);
|
|
5358
|
-
await this.api.ensureConnected();
|
|
5359
|
-
} catch (e) {
|
|
5360
|
-
this.logger.warn?.(
|
|
5361
|
-
`[Go2rtcTcpServer] ensureConnected failed: ${e}`
|
|
5362
|
-
);
|
|
5363
|
-
this.scheduleNativeStreamRetry("ensureConnected failed");
|
|
5364
|
-
return;
|
|
5365
|
-
}
|
|
5366
|
-
}
|
|
5367
|
-
this.nativeStreamActive = true;
|
|
5368
|
-
let dedicatedClient;
|
|
5369
|
-
if (this.deviceId) {
|
|
5370
|
-
try {
|
|
5371
|
-
const session = await this.api.createDedicatedSession(
|
|
5372
|
-
`live:${this.deviceId}:ch${this.channel}:${this.profile}`
|
|
5373
|
-
);
|
|
5374
|
-
dedicatedClient = session.client;
|
|
5375
|
-
this.dedicatedSessionRelease = session.release;
|
|
5376
|
-
} catch (e) {
|
|
5377
|
-
this.logger.warn?.(
|
|
5378
|
-
`[Go2rtcTcpServer] failed to acquire dedicated session, using shared socket: ${e}`
|
|
5379
|
-
);
|
|
5380
|
-
}
|
|
5381
|
-
}
|
|
5382
|
-
this.logger.info?.(
|
|
5383
|
-
`[Go2rtcTcpServer] native stream starting channel=${this.channel} profile=${this.profile} dedicated=${!!dedicatedClient}`
|
|
5384
|
-
);
|
|
5385
|
-
let hadFrames = false;
|
|
5386
|
-
this.nativeFanout = new NativeStreamFanout({
|
|
5387
|
-
maxQueueItems: 200,
|
|
5388
|
-
createSource: () => createNativeStream(this.api, this.channel, this.profile, {
|
|
5389
|
-
variant: this.variant,
|
|
5390
|
-
...dedicatedClient ? { client: dedicatedClient } : {}
|
|
5391
|
-
}),
|
|
5392
|
-
onFrame: (frame) => {
|
|
5393
|
-
if (!hadFrames) {
|
|
5394
|
-
this.clearNativeStreamRetry();
|
|
5395
|
-
}
|
|
5396
|
-
hadFrames = true;
|
|
5397
|
-
this.lastFrameAt = Date.now();
|
|
5398
|
-
this.totalFramesReceived++;
|
|
5399
|
-
if (!frame.audio && (frame.videoType === "H264" || frame.videoType === "H265")) {
|
|
5400
|
-
this.detectedVideoType = frame.videoType;
|
|
5401
|
-
}
|
|
5402
|
-
let prebufData;
|
|
5403
|
-
let isKeyframe;
|
|
5404
|
-
if (frame.audio) {
|
|
5405
|
-
if (frame.data.length === 0) return;
|
|
5406
|
-
if (!this.audioInfo) {
|
|
5407
|
-
const parsed = _Go2rtcTcpServer.parseAdtsSamplingInfo(frame.data);
|
|
5408
|
-
if (parsed) {
|
|
5409
|
-
this.audioInfo = { codec: "aac-adts", ...parsed };
|
|
5410
|
-
}
|
|
5411
|
-
}
|
|
5412
|
-
prebufData = frame.data;
|
|
5413
|
-
isKeyframe = false;
|
|
5414
|
-
} else {
|
|
5415
|
-
const annexB = this.convertVideoFrame(frame);
|
|
5416
|
-
if (!annexB || annexB.length === 0) return;
|
|
5417
|
-
prebufData = annexB;
|
|
5418
|
-
isKeyframe = this.isAnnexBKeyframe(annexB, frame.videoType);
|
|
5419
|
-
}
|
|
5420
|
-
const pts = frame.microseconds ?? Date.now() * 1e3;
|
|
5421
|
-
this.prebuffer.push({
|
|
5422
|
-
data: Buffer.from(prebufData),
|
|
5423
|
-
time: Date.now(),
|
|
5424
|
-
isKeyframe,
|
|
5425
|
-
audio: frame.audio,
|
|
5426
|
-
pts
|
|
5427
|
-
});
|
|
5428
|
-
const cutoff = Date.now() - this.prebufferMaxMs;
|
|
5429
|
-
let trimIdx = 0;
|
|
5430
|
-
while (trimIdx < this.prebuffer.length && this.prebuffer[trimIdx].time < cutoff) {
|
|
5431
|
-
trimIdx++;
|
|
5432
|
-
}
|
|
5433
|
-
if (trimIdx > 0) this.prebuffer.splice(0, trimIdx);
|
|
5434
|
-
},
|
|
5435
|
-
onError: (error) => {
|
|
5436
|
-
this.logger.warn?.(`[Go2rtcTcpServer] native stream error: ${error}`);
|
|
5437
|
-
},
|
|
5438
|
-
onEnd: () => {
|
|
5439
|
-
if (this.nativeStreamStopping) return;
|
|
5440
|
-
this.nativeStreamActive = false;
|
|
5441
|
-
this.nativeFanout = null;
|
|
5442
|
-
this.stopStreamHealthMonitor();
|
|
5443
|
-
const silenceMs = this.lastFrameAt > 0 ? Date.now() - this.lastFrameAt : -1;
|
|
5444
|
-
const diagnosis = silenceMs > this.streamTimeoutMs ? "camera stopped sending frames" : silenceMs >= 0 ? "stream source closed" : "no frames were ever received";
|
|
5445
|
-
this.logger.warn?.(
|
|
5446
|
-
`[Go2rtcTcpServer] native stream ended diagnosis="${diagnosis}" lastFrame=${silenceMs >= 0 ? `${(silenceMs / 1e3).toFixed(1)}s ago` : "never"} totalRx=${this.totalFramesReceived} clients=${this.connectedClients.size}`
|
|
5447
|
-
);
|
|
5448
|
-
if (this.dedicatedSessionRelease) {
|
|
5449
|
-
this.dedicatedSessionRelease().catch(() => {
|
|
5450
|
-
});
|
|
5451
|
-
this.dedicatedSessionRelease = void 0;
|
|
5452
|
-
}
|
|
5453
|
-
if (!this.prestartStream) {
|
|
5454
|
-
this.logger.info?.(
|
|
5455
|
-
`[Go2rtcTcpServer] battery native stream ended hadFrames=${hadFrames} channel=${this.channel} profile=${this.profile} \u2014 dropping ${this.connectedClients.size} client(s) to prevent wake loop`
|
|
5456
|
-
);
|
|
5457
|
-
for (const [, sock] of this.clientSockets) {
|
|
5458
|
-
sock.destroy();
|
|
5459
|
-
}
|
|
5460
|
-
} else if (this.active) {
|
|
5461
|
-
if (typeof this.api.isStreamProfileRejected === "function" && this.api.isStreamProfileRejected(this.channel, this.profile)) {
|
|
5462
|
-
this.logger.warn?.(
|
|
5463
|
-
`[Go2rtcTcpServer] profile rejected by device channel=${this.channel} profile=${this.profile} \u2014 not restarting`
|
|
5464
|
-
);
|
|
5465
|
-
for (const [, sock] of this.clientSockets) {
|
|
5466
|
-
sock.destroy();
|
|
5467
|
-
}
|
|
5468
|
-
return;
|
|
5469
|
-
}
|
|
5470
|
-
this.logger.info?.(
|
|
5471
|
-
`[Go2rtcTcpServer] restarting native stream (clients=${this.connectedClients.size}, prestart=${this.prestartStream})`
|
|
5472
|
-
);
|
|
5473
|
-
this.startNativeStream();
|
|
5474
|
-
}
|
|
5475
|
-
}
|
|
5476
|
-
});
|
|
5477
|
-
this.nativeFanout.start();
|
|
5478
|
-
this.startStreamHealthMonitor();
|
|
5479
|
-
}
|
|
5480
|
-
async stopNativeStream() {
|
|
5481
|
-
this.nativeStreamStopping = true;
|
|
5482
|
-
this.nativeStreamActive = false;
|
|
5483
|
-
this.clearNativeStreamRetry();
|
|
5484
|
-
this.stopStreamHealthMonitor();
|
|
5485
|
-
const fanout = this.nativeFanout;
|
|
5486
|
-
this.nativeFanout = null;
|
|
5487
|
-
try {
|
|
5488
|
-
if (fanout) {
|
|
5489
|
-
await fanout.stop();
|
|
5490
|
-
}
|
|
5491
|
-
this.prebuffer = [];
|
|
5492
|
-
if (this.dedicatedSessionRelease) {
|
|
5493
|
-
await this.dedicatedSessionRelease().catch(() => {
|
|
5494
|
-
});
|
|
5495
|
-
this.dedicatedSessionRelease = void 0;
|
|
5496
|
-
}
|
|
5497
|
-
} finally {
|
|
5498
|
-
this.nativeStreamStopping = false;
|
|
5499
|
-
}
|
|
5500
|
-
}
|
|
5501
|
-
// -----------------------------------------------------------------------
|
|
5502
|
-
// Stream health monitoring
|
|
5503
|
-
// -----------------------------------------------------------------------
|
|
5504
|
-
startStreamHealthMonitor() {
|
|
5505
|
-
this.stopStreamHealthMonitor();
|
|
5506
|
-
if (this.streamTimeoutMs <= 0) return;
|
|
5507
|
-
this.lastFrameAt = Date.now();
|
|
5508
|
-
this.streamHealthTimer = setInterval(() => {
|
|
5509
|
-
if (!this.nativeStreamActive || !this.active) {
|
|
5510
|
-
this.stopStreamHealthMonitor();
|
|
5511
|
-
return;
|
|
5512
|
-
}
|
|
5513
|
-
const silenceMs = Date.now() - this.lastFrameAt;
|
|
5514
|
-
if (silenceMs > this.streamTimeoutMs) {
|
|
5515
|
-
this.logger.warn?.(
|
|
5516
|
-
`[Go2rtcTcpServer] stream inactivity timeout: no frames for ${(silenceMs / 1e3).toFixed(1)}s (threshold=${this.streamTimeoutMs}ms), totalReceived=${this.totalFramesReceived} clients=${this.connectedClients.size} \u2014 forcing stream restart`
|
|
5517
|
-
);
|
|
5518
|
-
this.stopStreamHealthMonitor();
|
|
5519
|
-
const fanout = this.nativeFanout;
|
|
5520
|
-
if (fanout) {
|
|
5521
|
-
this.nativeStreamActive = false;
|
|
5522
|
-
this.nativeFanout = null;
|
|
5523
|
-
fanout.stop().catch(() => {
|
|
5524
|
-
});
|
|
5525
|
-
}
|
|
5526
|
-
}
|
|
5527
|
-
}, Math.min(this.streamTimeoutMs / 2, 5e3));
|
|
5528
|
-
}
|
|
5529
|
-
stopStreamHealthMonitor() {
|
|
5530
|
-
if (this.streamHealthTimer) {
|
|
5531
|
-
clearInterval(this.streamHealthTimer);
|
|
5532
|
-
this.streamHealthTimer = void 0;
|
|
5533
|
-
}
|
|
5534
|
-
}
|
|
5535
|
-
// -----------------------------------------------------------------------
|
|
5536
|
-
// Client lifecycle
|
|
5537
|
-
// -----------------------------------------------------------------------
|
|
5538
|
-
removeClient(clientId, reason) {
|
|
5539
|
-
if (!this.connectedClients.has(clientId)) return;
|
|
5540
|
-
this.connectedClients.delete(clientId);
|
|
5541
|
-
this.clientSockets.delete(clientId);
|
|
5542
|
-
const silenceMs = this.lastFrameAt > 0 ? Date.now() - this.lastFrameAt : -1;
|
|
5543
|
-
const silenceInfo = silenceMs >= 0 ? ` lastFrame=${(silenceMs / 1e3).toFixed(1)}s ago` : "";
|
|
5544
|
-
this.logger.info?.(
|
|
5545
|
-
`[Go2rtcTcpServer] client disconnected id=${clientId} reason=${reason ?? "unknown"} remaining=${this.connectedClients.size} totalRx=${this.totalFramesReceived} totalTx=${this.totalVideoFramesWritten}${silenceInfo}`
|
|
5546
|
-
);
|
|
5547
|
-
this.emit("clientDisconnected", clientId);
|
|
5548
|
-
if (this.connectedClients.size === 0 && !this.prestartStream) {
|
|
5549
|
-
this.scheduleStop();
|
|
5550
|
-
}
|
|
5551
|
-
}
|
|
5552
|
-
scheduleStop() {
|
|
5553
|
-
if (this.stopGraceTimer) return;
|
|
5554
|
-
this.logger.info?.(
|
|
5555
|
-
`[Go2rtcTcpServer] no clients, scheduling stream stop in ${this.gracePeriodMs}ms`
|
|
5556
|
-
);
|
|
5557
|
-
this.stopGraceTimer = setTimeout(async () => {
|
|
5558
|
-
this.stopGraceTimer = void 0;
|
|
5559
|
-
if (this.connectedClients.size === 0 && this.nativeStreamActive) {
|
|
5560
|
-
this.logger.info?.("[Go2rtcTcpServer] grace period expired, stopping native stream");
|
|
5561
|
-
await this.stopNativeStream();
|
|
5562
|
-
}
|
|
5563
|
-
}, this.gracePeriodMs);
|
|
5564
|
-
}
|
|
5565
|
-
};
|
|
5566
|
-
|
|
5567
4766
|
// src/baichuan/stream/BaichuanHttpStreamServer.ts
|
|
5568
|
-
import { EventEmitter as
|
|
4767
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
5569
4768
|
import { spawn as spawn5 } from "child_process";
|
|
5570
4769
|
import * as http4 from "http";
|
|
5571
4770
|
var NAL_START_CODE_4B = Buffer.from([0, 0, 0, 1]);
|
|
@@ -5612,7 +4811,7 @@ function isH264KeyframeFromAnnexB(annexB) {
|
|
|
5612
4811
|
}
|
|
5613
4812
|
return false;
|
|
5614
4813
|
}
|
|
5615
|
-
var BaichuanHttpStreamServer = class extends
|
|
4814
|
+
var BaichuanHttpStreamServer = class extends EventEmitter2 {
|
|
5616
4815
|
videoStream;
|
|
5617
4816
|
listenPort;
|
|
5618
4817
|
path;
|
|
@@ -5884,15 +5083,15 @@ var BaichuanHttpStreamServer = class extends EventEmitter3 {
|
|
|
5884
5083
|
};
|
|
5885
5084
|
|
|
5886
5085
|
// src/baichuan/stream/BaichuanMjpegServer.ts
|
|
5887
|
-
import { EventEmitter as
|
|
5086
|
+
import { EventEmitter as EventEmitter4 } from "events";
|
|
5888
5087
|
import * as http5 from "http";
|
|
5889
5088
|
|
|
5890
5089
|
// src/baichuan/stream/MjpegTransformer.ts
|
|
5891
|
-
import { EventEmitter as
|
|
5090
|
+
import { EventEmitter as EventEmitter3 } from "events";
|
|
5892
5091
|
import { spawn as spawn6 } from "child_process";
|
|
5893
5092
|
var JPEG_SOI = Buffer.from([255, 216]);
|
|
5894
5093
|
var JPEG_EOI = Buffer.from([255, 217]);
|
|
5895
|
-
var MjpegTransformer = class extends
|
|
5094
|
+
var MjpegTransformer = class extends EventEmitter3 {
|
|
5896
5095
|
options;
|
|
5897
5096
|
ffmpeg = null;
|
|
5898
5097
|
started = false;
|
|
@@ -6089,7 +5288,7 @@ Content-Length: ${frame.length}\r
|
|
|
6089
5288
|
}
|
|
6090
5289
|
|
|
6091
5290
|
// src/baichuan/stream/BaichuanMjpegServer.ts
|
|
6092
|
-
var BaichuanMjpegServer = class extends
|
|
5291
|
+
var BaichuanMjpegServer = class extends EventEmitter4 {
|
|
6093
5292
|
options;
|
|
6094
5293
|
clients = /* @__PURE__ */ new Map();
|
|
6095
5294
|
httpServer = null;
|
|
@@ -6370,13 +5569,13 @@ var BaichuanMjpegServer = class extends EventEmitter5 {
|
|
|
6370
5569
|
};
|
|
6371
5570
|
|
|
6372
5571
|
// src/baichuan/stream/BaichuanWebRTCServer.ts
|
|
6373
|
-
import { EventEmitter as
|
|
5572
|
+
import { EventEmitter as EventEmitter6 } from "events";
|
|
6374
5573
|
|
|
6375
5574
|
// src/baichuan/stream/AacToOpusTranscoder.ts
|
|
6376
5575
|
import { spawn as spawn7 } from "child_process";
|
|
6377
5576
|
import { createSocket } from "dgram";
|
|
6378
|
-
import { EventEmitter as
|
|
6379
|
-
var AacToOpusTranscoder = class extends
|
|
5577
|
+
import { EventEmitter as EventEmitter5 } from "events";
|
|
5578
|
+
var AacToOpusTranscoder = class extends EventEmitter5 {
|
|
6380
5579
|
opts;
|
|
6381
5580
|
socket = null;
|
|
6382
5581
|
ffmpeg = null;
|
|
@@ -6592,7 +5791,7 @@ function getH264NalType(nalUnit) {
|
|
|
6592
5791
|
function getH265NalType2(nalUnit) {
|
|
6593
5792
|
return nalUnit[0] >> 1 & 63;
|
|
6594
5793
|
}
|
|
6595
|
-
var BaichuanWebRTCServer = class extends
|
|
5794
|
+
var BaichuanWebRTCServer = class extends EventEmitter6 {
|
|
6596
5795
|
options;
|
|
6597
5796
|
sessions = /* @__PURE__ */ new Map();
|
|
6598
5797
|
sessionIdCounter = 0;
|
|
@@ -7584,7 +6783,7 @@ Error: ${err}`
|
|
|
7584
6783
|
};
|
|
7585
6784
|
|
|
7586
6785
|
// src/baichuan/stream/BaichuanHlsServer.ts
|
|
7587
|
-
import { EventEmitter as
|
|
6786
|
+
import { EventEmitter as EventEmitter7 } from "events";
|
|
7588
6787
|
import fs from "fs";
|
|
7589
6788
|
import fsp from "fs/promises";
|
|
7590
6789
|
import os from "os";
|
|
@@ -7661,7 +6860,7 @@ function getNalTypes(codec, annexB) {
|
|
|
7661
6860
|
}
|
|
7662
6861
|
});
|
|
7663
6862
|
}
|
|
7664
|
-
var BaichuanHlsServer = class extends
|
|
6863
|
+
var BaichuanHlsServer = class extends EventEmitter7 {
|
|
7665
6864
|
api;
|
|
7666
6865
|
channel;
|
|
7667
6866
|
profile;
|
|
@@ -8086,10 +7285,10 @@ var BaichuanHlsServer = class extends EventEmitter8 {
|
|
|
8086
7285
|
};
|
|
8087
7286
|
|
|
8088
7287
|
// src/multifocal/compositeRtspServer.ts
|
|
8089
|
-
import { EventEmitter as
|
|
7288
|
+
import { EventEmitter as EventEmitter8 } from "events";
|
|
8090
7289
|
import { spawn as spawn9 } from "child_process";
|
|
8091
|
-
import * as
|
|
8092
|
-
var CompositeRtspServer = class extends
|
|
7290
|
+
import * as net from "net";
|
|
7291
|
+
var CompositeRtspServer = class extends EventEmitter8 {
|
|
8093
7292
|
options;
|
|
8094
7293
|
compositeStream = null;
|
|
8095
7294
|
rtspServer = null;
|
|
@@ -8155,7 +7354,7 @@ var CompositeRtspServer = class extends EventEmitter9 {
|
|
|
8155
7354
|
const width = widerStreamInfo?.width ?? 1920;
|
|
8156
7355
|
const height = widerStreamInfo?.height ?? 1080;
|
|
8157
7356
|
const fps = widerStreamInfo?.frameRate ?? 25;
|
|
8158
|
-
this.rtspServer =
|
|
7357
|
+
this.rtspServer = net.createServer((socket) => {
|
|
8159
7358
|
this.handleRtspConnection(socket);
|
|
8160
7359
|
});
|
|
8161
7360
|
await new Promise((resolve, reject) => {
|
|
@@ -8865,8 +8064,8 @@ var RtspBackchannel = class _RtspBackchannel {
|
|
|
8865
8064
|
};
|
|
8866
8065
|
|
|
8867
8066
|
// src/baichuan/stream/BaichuanRtspBackchannelServer.ts
|
|
8868
|
-
import { EventEmitter as
|
|
8869
|
-
import * as
|
|
8067
|
+
import { EventEmitter as EventEmitter9 } from "events";
|
|
8068
|
+
import * as net2 from "net";
|
|
8870
8069
|
import * as crypto2 from "crypto";
|
|
8871
8070
|
var md5Hex = (s) => crypto2.createHash("md5").update(s).digest("hex");
|
|
8872
8071
|
var RTCP_KEEPALIVE_INTERVAL_MS = 1e4;
|
|
@@ -8922,7 +8121,7 @@ function extractPublicEndpoint(url, requestText) {
|
|
|
8922
8121
|
if (hostHeader) return hostHeader;
|
|
8923
8122
|
return null;
|
|
8924
8123
|
}
|
|
8925
|
-
var BaichuanRtspBackchannelServer = class _BaichuanRtspBackchannelServer extends
|
|
8124
|
+
var BaichuanRtspBackchannelServer = class _BaichuanRtspBackchannelServer extends EventEmitter9 {
|
|
8926
8125
|
listenHost;
|
|
8927
8126
|
listenPort;
|
|
8928
8127
|
logger;
|
|
@@ -9039,7 +8238,7 @@ var BaichuanRtspBackchannelServer = class _BaichuanRtspBackchannelServer extends
|
|
|
9039
8238
|
async start() {
|
|
9040
8239
|
if (this.server) return;
|
|
9041
8240
|
await new Promise((resolve, reject) => {
|
|
9042
|
-
const server =
|
|
8241
|
+
const server = net2.createServer((socket) => this.handleConnection(socket));
|
|
9043
8242
|
const onError = (err) => {
|
|
9044
8243
|
server.removeListener("error", onError);
|
|
9045
8244
|
reject(err);
|
|
@@ -10066,7 +9265,6 @@ export {
|
|
|
10066
9265
|
DUAL_LENS_DUAL_MOTION_MODELS,
|
|
10067
9266
|
DUAL_LENS_MODELS,
|
|
10068
9267
|
DUAL_LENS_SINGLE_MOTION_MODELS,
|
|
10069
|
-
Go2rtcTcpServer,
|
|
10070
9268
|
H264RtpDepacketizer,
|
|
10071
9269
|
H265RtpDepacketizer,
|
|
10072
9270
|
HlsSessionManager,
|