@apocaliss92/nodelink-js 0.3.4 → 0.3.5
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 +48 -82
- package/dist/{chunk-YSEFEQYV.js → chunk-UDS2UR4S.js} +33 -2
- package/dist/chunk-UDS2UR4S.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +32 -1
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +1 -1
- package/dist/index.cjs +569 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +137 -1
- package/dist/index.d.ts +115 -0
- package/dist/index.js +535 -15
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
- package/dist/chunk-YSEFEQYV.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
normalizeUid,
|
|
43
43
|
parseSupportXml,
|
|
44
44
|
setGlobalLogger
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-UDS2UR4S.js";
|
|
46
46
|
import {
|
|
47
47
|
AesStreamDecryptor,
|
|
48
48
|
BC_AES_IV,
|
|
@@ -4585,8 +4585,527 @@ async function createReplayHttpServer(options) {
|
|
|
4585
4585
|
};
|
|
4586
4586
|
}
|
|
4587
4587
|
|
|
4588
|
-
// src/baichuan/stream/
|
|
4588
|
+
// src/baichuan/stream/Go2rtcTcpServer.ts
|
|
4589
4589
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
4590
|
+
import * as net from "net";
|
|
4591
|
+
var AsyncBoundedQueue = class {
|
|
4592
|
+
maxItems;
|
|
4593
|
+
queue = [];
|
|
4594
|
+
waiting;
|
|
4595
|
+
closed = false;
|
|
4596
|
+
constructor(maxItems) {
|
|
4597
|
+
this.maxItems = Math.max(1, maxItems | 0);
|
|
4598
|
+
}
|
|
4599
|
+
push(item) {
|
|
4600
|
+
if (this.closed) return;
|
|
4601
|
+
if (this.waiting) {
|
|
4602
|
+
const { resolve } = this.waiting;
|
|
4603
|
+
this.waiting = void 0;
|
|
4604
|
+
resolve({ value: item, done: false });
|
|
4605
|
+
return;
|
|
4606
|
+
}
|
|
4607
|
+
this.queue.push(item);
|
|
4608
|
+
if (this.queue.length > this.maxItems) {
|
|
4609
|
+
this.queue.splice(0, this.queue.length - this.maxItems);
|
|
4610
|
+
}
|
|
4611
|
+
}
|
|
4612
|
+
close() {
|
|
4613
|
+
if (this.closed) return;
|
|
4614
|
+
this.closed = true;
|
|
4615
|
+
if (this.waiting) {
|
|
4616
|
+
const { resolve } = this.waiting;
|
|
4617
|
+
this.waiting = void 0;
|
|
4618
|
+
resolve({ value: void 0, done: true });
|
|
4619
|
+
}
|
|
4620
|
+
}
|
|
4621
|
+
async next() {
|
|
4622
|
+
if (this.closed) return { value: void 0, done: true };
|
|
4623
|
+
const item = this.queue.shift();
|
|
4624
|
+
if (item !== void 0) return { value: item, done: false };
|
|
4625
|
+
return await new Promise((resolve) => {
|
|
4626
|
+
this.waiting = { resolve };
|
|
4627
|
+
});
|
|
4628
|
+
}
|
|
4629
|
+
};
|
|
4630
|
+
var NativeStreamFanout = class {
|
|
4631
|
+
opts;
|
|
4632
|
+
queues = /* @__PURE__ */ new Map();
|
|
4633
|
+
source = null;
|
|
4634
|
+
running = false;
|
|
4635
|
+
pumpPromise = null;
|
|
4636
|
+
constructor(opts) {
|
|
4637
|
+
this.opts = opts;
|
|
4638
|
+
}
|
|
4639
|
+
start() {
|
|
4640
|
+
if (this.running) return;
|
|
4641
|
+
this.running = true;
|
|
4642
|
+
this.source = this.opts.createSource();
|
|
4643
|
+
this.pumpPromise = (async () => {
|
|
4644
|
+
try {
|
|
4645
|
+
for await (const frame of this.source) {
|
|
4646
|
+
try {
|
|
4647
|
+
this.opts.onFrame?.(frame);
|
|
4648
|
+
} catch {
|
|
4649
|
+
}
|
|
4650
|
+
for (const q of this.queues.values()) {
|
|
4651
|
+
q.push(frame);
|
|
4652
|
+
}
|
|
4653
|
+
}
|
|
4654
|
+
} catch (e) {
|
|
4655
|
+
this.opts.onError?.(e);
|
|
4656
|
+
} finally {
|
|
4657
|
+
for (const q of this.queues.values()) q.close();
|
|
4658
|
+
this.queues.clear();
|
|
4659
|
+
this.running = false;
|
|
4660
|
+
this.opts.onEnd?.();
|
|
4661
|
+
}
|
|
4662
|
+
})();
|
|
4663
|
+
}
|
|
4664
|
+
subscribe(id) {
|
|
4665
|
+
const q = new AsyncBoundedQueue(this.opts.maxQueueItems);
|
|
4666
|
+
this.queues.set(id, q);
|
|
4667
|
+
const self = this;
|
|
4668
|
+
return (async function* () {
|
|
4669
|
+
try {
|
|
4670
|
+
while (true) {
|
|
4671
|
+
const r = await q.next();
|
|
4672
|
+
if (r.done) return;
|
|
4673
|
+
yield r.value;
|
|
4674
|
+
}
|
|
4675
|
+
} finally {
|
|
4676
|
+
q.close();
|
|
4677
|
+
self.queues.delete(id);
|
|
4678
|
+
}
|
|
4679
|
+
})();
|
|
4680
|
+
}
|
|
4681
|
+
async stop() {
|
|
4682
|
+
if (!this.running) return;
|
|
4683
|
+
this.running = false;
|
|
4684
|
+
const src = this.source;
|
|
4685
|
+
this.source = null;
|
|
4686
|
+
for (const q of this.queues.values()) q.close();
|
|
4687
|
+
this.queues.clear();
|
|
4688
|
+
try {
|
|
4689
|
+
await src?.return(void 0);
|
|
4690
|
+
} catch {
|
|
4691
|
+
}
|
|
4692
|
+
try {
|
|
4693
|
+
await this.pumpPromise;
|
|
4694
|
+
} catch {
|
|
4695
|
+
}
|
|
4696
|
+
this.pumpPromise = null;
|
|
4697
|
+
}
|
|
4698
|
+
};
|
|
4699
|
+
var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
|
|
4700
|
+
api;
|
|
4701
|
+
channel;
|
|
4702
|
+
profile;
|
|
4703
|
+
variant;
|
|
4704
|
+
listenHost;
|
|
4705
|
+
listenPort;
|
|
4706
|
+
logger;
|
|
4707
|
+
deviceId;
|
|
4708
|
+
gracePeriodMs;
|
|
4709
|
+
prebufferMaxMs;
|
|
4710
|
+
maxBufferBytes;
|
|
4711
|
+
prestartStream;
|
|
4712
|
+
active = false;
|
|
4713
|
+
server;
|
|
4714
|
+
resolvedPort;
|
|
4715
|
+
// Native stream
|
|
4716
|
+
nativeFanout = null;
|
|
4717
|
+
nativeStreamActive = false;
|
|
4718
|
+
dedicatedSessionRelease;
|
|
4719
|
+
detectedVideoType;
|
|
4720
|
+
// Client tracking
|
|
4721
|
+
connectedClients = /* @__PURE__ */ new Set();
|
|
4722
|
+
clientSockets = /* @__PURE__ */ new Map();
|
|
4723
|
+
stopGraceTimer;
|
|
4724
|
+
// Prebuffer
|
|
4725
|
+
prebuffer = [];
|
|
4726
|
+
constructor(options) {
|
|
4727
|
+
super();
|
|
4728
|
+
this.api = options.api;
|
|
4729
|
+
this.channel = options.channel;
|
|
4730
|
+
this.profile = options.profile;
|
|
4731
|
+
this.variant = options.variant ?? "default";
|
|
4732
|
+
this.listenHost = options.listenHost ?? "127.0.0.1";
|
|
4733
|
+
this.listenPort = options.listenPort ?? 0;
|
|
4734
|
+
this.logger = options.logger ?? console;
|
|
4735
|
+
this.deviceId = options.deviceId;
|
|
4736
|
+
this.gracePeriodMs = options.gracePeriodMs ?? 3e4;
|
|
4737
|
+
this.prebufferMaxMs = options.prebufferMs ?? 3e3;
|
|
4738
|
+
this.maxBufferBytes = options.maxBufferBytes ?? 1e8;
|
|
4739
|
+
this.prestartStream = options.prestartStream ?? true;
|
|
4740
|
+
}
|
|
4741
|
+
// -----------------------------------------------------------------------
|
|
4742
|
+
// Public API
|
|
4743
|
+
// -----------------------------------------------------------------------
|
|
4744
|
+
/** Start listening. Resolves once the TCP server is bound. */
|
|
4745
|
+
async start() {
|
|
4746
|
+
if (this.active) return;
|
|
4747
|
+
this.active = true;
|
|
4748
|
+
this.server = net.createServer((socket) => this.handleClient(socket));
|
|
4749
|
+
this.server.on("error", (err) => {
|
|
4750
|
+
this.logger.error?.(`[Go2rtcTcpServer] server error: ${err.message}`);
|
|
4751
|
+
this.emit("error", err);
|
|
4752
|
+
});
|
|
4753
|
+
await new Promise((resolve, reject) => {
|
|
4754
|
+
this.server.listen(this.listenPort, this.listenHost, () => {
|
|
4755
|
+
const addr = this.server.address();
|
|
4756
|
+
this.resolvedPort = addr.port;
|
|
4757
|
+
this.logger.info?.(
|
|
4758
|
+
`[Go2rtcTcpServer] listening on ${addr.address}:${addr.port} channel=${this.channel} profile=${this.profile}`
|
|
4759
|
+
);
|
|
4760
|
+
this.emit("listening", { host: addr.address, port: addr.port });
|
|
4761
|
+
resolve();
|
|
4762
|
+
});
|
|
4763
|
+
this.server.once("error", reject);
|
|
4764
|
+
});
|
|
4765
|
+
if (this.prestartStream) {
|
|
4766
|
+
this.logger.info?.(
|
|
4767
|
+
`[Go2rtcTcpServer] pre-starting native stream channel=${this.channel} profile=${this.profile}`
|
|
4768
|
+
);
|
|
4769
|
+
this.startNativeStream();
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4772
|
+
/** Stop the server and all active streams. */
|
|
4773
|
+
async stop() {
|
|
4774
|
+
if (!this.active) return;
|
|
4775
|
+
this.active = false;
|
|
4776
|
+
clearTimeout(this.stopGraceTimer);
|
|
4777
|
+
for (const [id, sock] of this.clientSockets) {
|
|
4778
|
+
sock.destroy();
|
|
4779
|
+
this.connectedClients.delete(id);
|
|
4780
|
+
}
|
|
4781
|
+
this.clientSockets.clear();
|
|
4782
|
+
await this.stopNativeStream();
|
|
4783
|
+
if (this.server) {
|
|
4784
|
+
await new Promise((resolve) => {
|
|
4785
|
+
this.server.close(() => resolve());
|
|
4786
|
+
});
|
|
4787
|
+
this.server = void 0;
|
|
4788
|
+
}
|
|
4789
|
+
this.prebuffer = [];
|
|
4790
|
+
this.resolvedPort = void 0;
|
|
4791
|
+
this.emit("close");
|
|
4792
|
+
}
|
|
4793
|
+
/** The actual port the server is listening on (available after start()). */
|
|
4794
|
+
get port() {
|
|
4795
|
+
return this.resolvedPort;
|
|
4796
|
+
}
|
|
4797
|
+
/** The go2rtc-compatible source URL. */
|
|
4798
|
+
get go2rtcSourceUrl() {
|
|
4799
|
+
if (this.resolvedPort == null) return void 0;
|
|
4800
|
+
return `tcp://127.0.0.1:${this.resolvedPort}`;
|
|
4801
|
+
}
|
|
4802
|
+
/** Number of currently connected clients. */
|
|
4803
|
+
get clientCount() {
|
|
4804
|
+
return this.connectedClients.size;
|
|
4805
|
+
}
|
|
4806
|
+
// -----------------------------------------------------------------------
|
|
4807
|
+
// Client handling
|
|
4808
|
+
// -----------------------------------------------------------------------
|
|
4809
|
+
handleClient(socket) {
|
|
4810
|
+
const clientId = `${socket.remoteAddress}:${socket.remotePort}`;
|
|
4811
|
+
socket.setNoDelay(true);
|
|
4812
|
+
this.connectedClients.add(clientId);
|
|
4813
|
+
this.clientSockets.set(clientId, socket);
|
|
4814
|
+
this.logger.info?.(
|
|
4815
|
+
`[Go2rtcTcpServer] client connected id=${clientId} total=${this.connectedClients.size}`
|
|
4816
|
+
);
|
|
4817
|
+
this.emit("client", clientId);
|
|
4818
|
+
if (this.stopGraceTimer) {
|
|
4819
|
+
clearTimeout(this.stopGraceTimer);
|
|
4820
|
+
this.stopGraceTimer = void 0;
|
|
4821
|
+
}
|
|
4822
|
+
if (!this.nativeStreamActive) {
|
|
4823
|
+
this.startNativeStream();
|
|
4824
|
+
}
|
|
4825
|
+
this.feedClient(clientId, socket).catch((err) => {
|
|
4826
|
+
this.logger.warn?.(
|
|
4827
|
+
`[Go2rtcTcpServer] feedClient error id=${clientId}: ${err}`
|
|
4828
|
+
);
|
|
4829
|
+
});
|
|
4830
|
+
const cleanup = () => {
|
|
4831
|
+
this.removeClient(clientId);
|
|
4832
|
+
socket.destroy();
|
|
4833
|
+
};
|
|
4834
|
+
socket.on("error", cleanup);
|
|
4835
|
+
socket.on("close", cleanup);
|
|
4836
|
+
}
|
|
4837
|
+
async feedClient(clientId, socket) {
|
|
4838
|
+
const fanoutDeadline = Date.now() + 3e4;
|
|
4839
|
+
while (this.active && !this.nativeFanout) {
|
|
4840
|
+
if (socket.destroyed) return;
|
|
4841
|
+
if (Date.now() > fanoutDeadline) {
|
|
4842
|
+
this.logger.warn?.(
|
|
4843
|
+
`[Go2rtcTcpServer] fanout not ready after 30s, dropping client ${clientId}`
|
|
4844
|
+
);
|
|
4845
|
+
return;
|
|
4846
|
+
}
|
|
4847
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
4848
|
+
}
|
|
4849
|
+
if (!this.active || !this.nativeFanout) return;
|
|
4850
|
+
const subscription = this.nativeFanout.subscribe(clientId);
|
|
4851
|
+
const prebufferSnap = this.prebuffer.slice();
|
|
4852
|
+
let lastIdrIdx = -1;
|
|
4853
|
+
for (let i = prebufferSnap.length - 1; i >= 0; i--) {
|
|
4854
|
+
if (prebufferSnap[i].isKeyframe) {
|
|
4855
|
+
lastIdrIdx = i;
|
|
4856
|
+
break;
|
|
4857
|
+
}
|
|
4858
|
+
}
|
|
4859
|
+
if (lastIdrIdx >= 0) {
|
|
4860
|
+
const replay = prebufferSnap.slice(lastIdrIdx);
|
|
4861
|
+
this.logger.info?.(
|
|
4862
|
+
`[Go2rtcTcpServer] prebuffer replay client=${clientId} frames=${replay.length}`
|
|
4863
|
+
);
|
|
4864
|
+
for (const entry of replay) {
|
|
4865
|
+
if (socket.destroyed) return;
|
|
4866
|
+
socket.write(entry.data);
|
|
4867
|
+
}
|
|
4868
|
+
}
|
|
4869
|
+
let seenKeyframe = lastIdrIdx >= 0;
|
|
4870
|
+
let liveFrameCount = 0;
|
|
4871
|
+
let liveVideoWritten = 0;
|
|
4872
|
+
let lastLogAt = Date.now();
|
|
4873
|
+
try {
|
|
4874
|
+
this.logger.info?.(
|
|
4875
|
+
`[Go2rtcTcpServer] entering live loop client=${clientId} seenKeyframe=${seenKeyframe}`
|
|
4876
|
+
);
|
|
4877
|
+
for await (const frame of subscription) {
|
|
4878
|
+
if (socket.destroyed || !this.active) {
|
|
4879
|
+
this.logger.info?.(
|
|
4880
|
+
`[Go2rtcTcpServer] live loop exit client=${clientId} destroyed=${socket.destroyed} active=${this.active}`
|
|
4881
|
+
);
|
|
4882
|
+
break;
|
|
4883
|
+
}
|
|
4884
|
+
liveFrameCount++;
|
|
4885
|
+
const annexB = this.convertFrame(frame);
|
|
4886
|
+
if (!annexB) continue;
|
|
4887
|
+
if (!seenKeyframe) {
|
|
4888
|
+
if (!this.isAnnexBKeyframe(annexB, frame.videoType)) continue;
|
|
4889
|
+
seenKeyframe = true;
|
|
4890
|
+
this.logger.info?.(
|
|
4891
|
+
`[Go2rtcTcpServer] first live keyframe client=${clientId} after ${liveFrameCount} frames`
|
|
4892
|
+
);
|
|
4893
|
+
}
|
|
4894
|
+
socket.write(annexB);
|
|
4895
|
+
liveVideoWritten++;
|
|
4896
|
+
if (Date.now() - lastLogAt > 1e4) {
|
|
4897
|
+
this.logger.info?.(
|
|
4898
|
+
`[Go2rtcTcpServer] live stats client=${clientId} received=${liveFrameCount} written=${liveVideoWritten} bufLen=${socket.writableLength}`
|
|
4899
|
+
);
|
|
4900
|
+
lastLogAt = Date.now();
|
|
4901
|
+
}
|
|
4902
|
+
if (socket.writableLength > this.maxBufferBytes) {
|
|
4903
|
+
this.logger.warn?.(
|
|
4904
|
+
`[Go2rtcTcpServer] buffer overflow (${socket.writableLength} bytes), dropping client ${clientId}`
|
|
4905
|
+
);
|
|
4906
|
+
socket.destroy();
|
|
4907
|
+
break;
|
|
4908
|
+
}
|
|
4909
|
+
}
|
|
4910
|
+
this.logger.info?.(
|
|
4911
|
+
`[Go2rtcTcpServer] live loop ended naturally client=${clientId} received=${liveFrameCount} written=${liveVideoWritten}`
|
|
4912
|
+
);
|
|
4913
|
+
} finally {
|
|
4914
|
+
await subscription.return(void 0).catch(() => {
|
|
4915
|
+
});
|
|
4916
|
+
}
|
|
4917
|
+
}
|
|
4918
|
+
// -----------------------------------------------------------------------
|
|
4919
|
+
// Frame conversion
|
|
4920
|
+
// -----------------------------------------------------------------------
|
|
4921
|
+
/**
|
|
4922
|
+
* Convert a native frame to wire-ready Annex-B.
|
|
4923
|
+
* Audio frames are skipped — raw TCP carries only video (Annex-B).
|
|
4924
|
+
* go2rtc auto-detects the codec from SPS/PPS/VPS NALUs.
|
|
4925
|
+
*/
|
|
4926
|
+
convertFrame(frame) {
|
|
4927
|
+
if (frame.audio) {
|
|
4928
|
+
return null;
|
|
4929
|
+
}
|
|
4930
|
+
if (frame.data.length === 0) return null;
|
|
4931
|
+
try {
|
|
4932
|
+
if (frame.videoType === "H264") {
|
|
4933
|
+
return convertToAnnexB(frame.data);
|
|
4934
|
+
}
|
|
4935
|
+
if (frame.videoType === "H265") {
|
|
4936
|
+
return convertToAnnexB2(frame.data);
|
|
4937
|
+
}
|
|
4938
|
+
} catch {
|
|
4939
|
+
}
|
|
4940
|
+
return frame.data;
|
|
4941
|
+
}
|
|
4942
|
+
/** Check if an Annex-B buffer contains a keyframe (IDR for H.264, IRAP for H.265). */
|
|
4943
|
+
isAnnexBKeyframe(annexB, videoType) {
|
|
4944
|
+
try {
|
|
4945
|
+
if (videoType === "H264") {
|
|
4946
|
+
const nals = _Go2rtcTcpServer.splitAnnexBNals(annexB);
|
|
4947
|
+
return nals.some((n) => n.length >= 1 && (n[0] & 31) === 5);
|
|
4948
|
+
}
|
|
4949
|
+
if (videoType === "H265") {
|
|
4950
|
+
const nals = splitAnnexBToNalPayloads2(annexB);
|
|
4951
|
+
return nals.some(
|
|
4952
|
+
(n) => n.length >= 2 && isH265Irap(n[0] >> 1 & 63)
|
|
4953
|
+
);
|
|
4954
|
+
}
|
|
4955
|
+
} catch {
|
|
4956
|
+
}
|
|
4957
|
+
return false;
|
|
4958
|
+
}
|
|
4959
|
+
/** Split Annex-B byte stream into individual NAL units. */
|
|
4960
|
+
static splitAnnexBNals(buf) {
|
|
4961
|
+
const nals = [];
|
|
4962
|
+
let i = 0;
|
|
4963
|
+
while (i < buf.length) {
|
|
4964
|
+
if (i + 2 < buf.length && buf[i] === 0 && buf[i + 1] === 0) {
|
|
4965
|
+
let scLen;
|
|
4966
|
+
if (buf[i + 2] === 1) {
|
|
4967
|
+
scLen = 3;
|
|
4968
|
+
} else if (i + 3 < buf.length && buf[i + 2] === 0 && buf[i + 3] === 1) {
|
|
4969
|
+
scLen = 4;
|
|
4970
|
+
} else {
|
|
4971
|
+
i++;
|
|
4972
|
+
continue;
|
|
4973
|
+
}
|
|
4974
|
+
const nalStart = i + scLen;
|
|
4975
|
+
let nalEnd = buf.length;
|
|
4976
|
+
for (let j = nalStart; j < buf.length - 2; j++) {
|
|
4977
|
+
if (buf[j] === 0 && buf[j + 1] === 0 && (buf[j + 2] === 1 || j + 3 < buf.length && buf[j + 2] === 0 && buf[j + 3] === 1)) {
|
|
4978
|
+
nalEnd = j;
|
|
4979
|
+
break;
|
|
4980
|
+
}
|
|
4981
|
+
}
|
|
4982
|
+
if (nalEnd > nalStart) {
|
|
4983
|
+
nals.push(buf.subarray(nalStart, nalEnd));
|
|
4984
|
+
}
|
|
4985
|
+
i = nalEnd;
|
|
4986
|
+
} else {
|
|
4987
|
+
i++;
|
|
4988
|
+
}
|
|
4989
|
+
}
|
|
4990
|
+
return nals;
|
|
4991
|
+
}
|
|
4992
|
+
// -----------------------------------------------------------------------
|
|
4993
|
+
// Native stream management
|
|
4994
|
+
// -----------------------------------------------------------------------
|
|
4995
|
+
async startNativeStream() {
|
|
4996
|
+
if (this.nativeStreamActive) return;
|
|
4997
|
+
this.nativeStreamActive = true;
|
|
4998
|
+
let dedicatedClient;
|
|
4999
|
+
if (this.deviceId) {
|
|
5000
|
+
try {
|
|
5001
|
+
const session = await this.api.createDedicatedSession(
|
|
5002
|
+
`live:${this.deviceId}:ch${this.channel}:${this.profile}`
|
|
5003
|
+
);
|
|
5004
|
+
dedicatedClient = session.client;
|
|
5005
|
+
this.dedicatedSessionRelease = session.release;
|
|
5006
|
+
} catch (e) {
|
|
5007
|
+
this.logger.warn?.(
|
|
5008
|
+
`[Go2rtcTcpServer] failed to acquire dedicated session, using shared socket: ${e}`
|
|
5009
|
+
);
|
|
5010
|
+
}
|
|
5011
|
+
}
|
|
5012
|
+
this.logger.info?.(
|
|
5013
|
+
`[Go2rtcTcpServer] native stream starting channel=${this.channel} profile=${this.profile} dedicated=${!!dedicatedClient}`
|
|
5014
|
+
);
|
|
5015
|
+
this.nativeFanout = new NativeStreamFanout({
|
|
5016
|
+
maxQueueItems: 200,
|
|
5017
|
+
createSource: () => createNativeStream(this.api, this.channel, this.profile, {
|
|
5018
|
+
variant: this.variant,
|
|
5019
|
+
...dedicatedClient ? { client: dedicatedClient } : {}
|
|
5020
|
+
}),
|
|
5021
|
+
onFrame: (frame) => {
|
|
5022
|
+
if (!frame.audio && (frame.videoType === "H264" || frame.videoType === "H265")) {
|
|
5023
|
+
this.detectedVideoType = frame.videoType;
|
|
5024
|
+
}
|
|
5025
|
+
const wireData = this.convertFrame(frame);
|
|
5026
|
+
if (!wireData || wireData.length === 0) return;
|
|
5027
|
+
const isKeyframe = !frame.audio && this.isAnnexBKeyframe(wireData, frame.videoType);
|
|
5028
|
+
this.prebuffer.push({
|
|
5029
|
+
data: Buffer.from(wireData),
|
|
5030
|
+
time: Date.now(),
|
|
5031
|
+
isKeyframe,
|
|
5032
|
+
audio: frame.audio
|
|
5033
|
+
});
|
|
5034
|
+
const cutoff = Date.now() - this.prebufferMaxMs;
|
|
5035
|
+
let trimIdx = 0;
|
|
5036
|
+
while (trimIdx < this.prebuffer.length && this.prebuffer[trimIdx].time < cutoff) {
|
|
5037
|
+
trimIdx++;
|
|
5038
|
+
}
|
|
5039
|
+
if (trimIdx > 0) this.prebuffer.splice(0, trimIdx);
|
|
5040
|
+
},
|
|
5041
|
+
onError: (error) => {
|
|
5042
|
+
this.logger.warn?.(`[Go2rtcTcpServer] native stream error: ${error}`);
|
|
5043
|
+
},
|
|
5044
|
+
onEnd: () => {
|
|
5045
|
+
if (!this.nativeStreamActive) return;
|
|
5046
|
+
this.nativeStreamActive = false;
|
|
5047
|
+
this.nativeFanout = null;
|
|
5048
|
+
if (this.dedicatedSessionRelease) {
|
|
5049
|
+
this.dedicatedSessionRelease().catch(() => {
|
|
5050
|
+
});
|
|
5051
|
+
this.dedicatedSessionRelease = void 0;
|
|
5052
|
+
}
|
|
5053
|
+
if (this.active && (this.connectedClients.size > 0 || this.prestartStream)) {
|
|
5054
|
+
this.logger.info?.(
|
|
5055
|
+
`[Go2rtcTcpServer] native stream ended, restarting (clients=${this.connectedClients.size}, prestart=${this.prestartStream})`
|
|
5056
|
+
);
|
|
5057
|
+
this.startNativeStream();
|
|
5058
|
+
}
|
|
5059
|
+
}
|
|
5060
|
+
});
|
|
5061
|
+
this.nativeFanout.start();
|
|
5062
|
+
}
|
|
5063
|
+
async stopNativeStream() {
|
|
5064
|
+
this.nativeStreamActive = false;
|
|
5065
|
+
const fanout = this.nativeFanout;
|
|
5066
|
+
this.nativeFanout = null;
|
|
5067
|
+
if (fanout) {
|
|
5068
|
+
await fanout.stop();
|
|
5069
|
+
}
|
|
5070
|
+
this.prebuffer = [];
|
|
5071
|
+
if (this.dedicatedSessionRelease) {
|
|
5072
|
+
await this.dedicatedSessionRelease().catch(() => {
|
|
5073
|
+
});
|
|
5074
|
+
this.dedicatedSessionRelease = void 0;
|
|
5075
|
+
}
|
|
5076
|
+
}
|
|
5077
|
+
// -----------------------------------------------------------------------
|
|
5078
|
+
// Client lifecycle
|
|
5079
|
+
// -----------------------------------------------------------------------
|
|
5080
|
+
removeClient(clientId) {
|
|
5081
|
+
if (!this.connectedClients.has(clientId)) return;
|
|
5082
|
+
this.connectedClients.delete(clientId);
|
|
5083
|
+
this.clientSockets.delete(clientId);
|
|
5084
|
+
this.logger.info?.(
|
|
5085
|
+
`[Go2rtcTcpServer] client disconnected id=${clientId} remaining=${this.connectedClients.size}`
|
|
5086
|
+
);
|
|
5087
|
+
this.emit("clientDisconnected", clientId);
|
|
5088
|
+
if (this.connectedClients.size === 0 && !this.prestartStream) {
|
|
5089
|
+
this.scheduleStop();
|
|
5090
|
+
}
|
|
5091
|
+
}
|
|
5092
|
+
scheduleStop() {
|
|
5093
|
+
if (this.stopGraceTimer) return;
|
|
5094
|
+
this.logger.info?.(
|
|
5095
|
+
`[Go2rtcTcpServer] no clients, scheduling stream stop in ${this.gracePeriodMs}ms`
|
|
5096
|
+
);
|
|
5097
|
+
this.stopGraceTimer = setTimeout(async () => {
|
|
5098
|
+
this.stopGraceTimer = void 0;
|
|
5099
|
+
if (this.connectedClients.size === 0 && this.nativeStreamActive) {
|
|
5100
|
+
this.logger.info?.("[Go2rtcTcpServer] grace period expired, stopping native stream");
|
|
5101
|
+
await this.stopNativeStream();
|
|
5102
|
+
}
|
|
5103
|
+
}, this.gracePeriodMs);
|
|
5104
|
+
}
|
|
5105
|
+
};
|
|
5106
|
+
|
|
5107
|
+
// src/baichuan/stream/BaichuanHttpStreamServer.ts
|
|
5108
|
+
import { EventEmitter as EventEmitter3 } from "events";
|
|
4590
5109
|
import { spawn as spawn5 } from "child_process";
|
|
4591
5110
|
import * as http4 from "http";
|
|
4592
5111
|
var NAL_START_CODE_4B = Buffer.from([0, 0, 0, 1]);
|
|
@@ -4633,7 +5152,7 @@ function isH264KeyframeFromAnnexB(annexB) {
|
|
|
4633
5152
|
}
|
|
4634
5153
|
return false;
|
|
4635
5154
|
}
|
|
4636
|
-
var BaichuanHttpStreamServer = class extends
|
|
5155
|
+
var BaichuanHttpStreamServer = class extends EventEmitter3 {
|
|
4637
5156
|
videoStream;
|
|
4638
5157
|
listenPort;
|
|
4639
5158
|
path;
|
|
@@ -4900,15 +5419,15 @@ var BaichuanHttpStreamServer = class extends EventEmitter2 {
|
|
|
4900
5419
|
};
|
|
4901
5420
|
|
|
4902
5421
|
// src/baichuan/stream/BaichuanMjpegServer.ts
|
|
4903
|
-
import { EventEmitter as
|
|
5422
|
+
import { EventEmitter as EventEmitter5 } from "events";
|
|
4904
5423
|
import * as http5 from "http";
|
|
4905
5424
|
|
|
4906
5425
|
// src/baichuan/stream/MjpegTransformer.ts
|
|
4907
|
-
import { EventEmitter as
|
|
5426
|
+
import { EventEmitter as EventEmitter4 } from "events";
|
|
4908
5427
|
import { spawn as spawn6 } from "child_process";
|
|
4909
5428
|
var JPEG_SOI = Buffer.from([255, 216]);
|
|
4910
5429
|
var JPEG_EOI = Buffer.from([255, 217]);
|
|
4911
|
-
var MjpegTransformer = class extends
|
|
5430
|
+
var MjpegTransformer = class extends EventEmitter4 {
|
|
4912
5431
|
options;
|
|
4913
5432
|
ffmpeg = null;
|
|
4914
5433
|
started = false;
|
|
@@ -5105,7 +5624,7 @@ Content-Length: ${frame.length}\r
|
|
|
5105
5624
|
}
|
|
5106
5625
|
|
|
5107
5626
|
// src/baichuan/stream/BaichuanMjpegServer.ts
|
|
5108
|
-
var BaichuanMjpegServer = class extends
|
|
5627
|
+
var BaichuanMjpegServer = class extends EventEmitter5 {
|
|
5109
5628
|
options;
|
|
5110
5629
|
clients = /* @__PURE__ */ new Map();
|
|
5111
5630
|
httpServer = null;
|
|
@@ -5386,7 +5905,7 @@ var BaichuanMjpegServer = class extends EventEmitter4 {
|
|
|
5386
5905
|
};
|
|
5387
5906
|
|
|
5388
5907
|
// src/baichuan/stream/BaichuanWebRTCServer.ts
|
|
5389
|
-
import { EventEmitter as
|
|
5908
|
+
import { EventEmitter as EventEmitter6 } from "events";
|
|
5390
5909
|
function parseAnnexBNalUnits(annexB) {
|
|
5391
5910
|
const nalUnits = [];
|
|
5392
5911
|
let offset = 0;
|
|
@@ -5421,7 +5940,7 @@ function getH264NalType(nalUnit) {
|
|
|
5421
5940
|
function getH265NalType2(nalUnit) {
|
|
5422
5941
|
return nalUnit[0] >> 1 & 63;
|
|
5423
5942
|
}
|
|
5424
|
-
var BaichuanWebRTCServer = class extends
|
|
5943
|
+
var BaichuanWebRTCServer = class extends EventEmitter6 {
|
|
5425
5944
|
options;
|
|
5426
5945
|
sessions = /* @__PURE__ */ new Map();
|
|
5427
5946
|
sessionIdCounter = 0;
|
|
@@ -6323,7 +6842,7 @@ Error: ${err}`
|
|
|
6323
6842
|
};
|
|
6324
6843
|
|
|
6325
6844
|
// src/baichuan/stream/BaichuanHlsServer.ts
|
|
6326
|
-
import { EventEmitter as
|
|
6845
|
+
import { EventEmitter as EventEmitter7 } from "events";
|
|
6327
6846
|
import fs from "fs";
|
|
6328
6847
|
import fsp from "fs/promises";
|
|
6329
6848
|
import os from "os";
|
|
@@ -6400,7 +6919,7 @@ function getNalTypes(codec, annexB) {
|
|
|
6400
6919
|
}
|
|
6401
6920
|
});
|
|
6402
6921
|
}
|
|
6403
|
-
var BaichuanHlsServer = class extends
|
|
6922
|
+
var BaichuanHlsServer = class extends EventEmitter7 {
|
|
6404
6923
|
api;
|
|
6405
6924
|
channel;
|
|
6406
6925
|
profile;
|
|
@@ -6818,10 +7337,10 @@ var BaichuanHlsServer = class extends EventEmitter6 {
|
|
|
6818
7337
|
};
|
|
6819
7338
|
|
|
6820
7339
|
// src/multifocal/compositeRtspServer.ts
|
|
6821
|
-
import { EventEmitter as
|
|
7340
|
+
import { EventEmitter as EventEmitter8 } from "events";
|
|
6822
7341
|
import { spawn as spawn8 } from "child_process";
|
|
6823
|
-
import * as
|
|
6824
|
-
var CompositeRtspServer = class extends
|
|
7342
|
+
import * as net2 from "net";
|
|
7343
|
+
var CompositeRtspServer = class extends EventEmitter8 {
|
|
6825
7344
|
options;
|
|
6826
7345
|
compositeStream = null;
|
|
6827
7346
|
rtspServer = null;
|
|
@@ -6887,7 +7406,7 @@ var CompositeRtspServer = class extends EventEmitter7 {
|
|
|
6887
7406
|
const width = widerStreamInfo?.width ?? 1920;
|
|
6888
7407
|
const height = widerStreamInfo?.height ?? 1080;
|
|
6889
7408
|
const fps = widerStreamInfo?.frameRate ?? 25;
|
|
6890
|
-
this.rtspServer =
|
|
7409
|
+
this.rtspServer = net2.createServer((socket) => {
|
|
6891
7410
|
this.handleRtspConnection(socket);
|
|
6892
7411
|
});
|
|
6893
7412
|
await new Promise((resolve, reject) => {
|
|
@@ -7162,6 +7681,7 @@ export {
|
|
|
7162
7681
|
DUAL_LENS_DUAL_MOTION_MODELS,
|
|
7163
7682
|
DUAL_LENS_MODELS,
|
|
7164
7683
|
DUAL_LENS_SINGLE_MOTION_MODELS,
|
|
7684
|
+
Go2rtcTcpServer,
|
|
7165
7685
|
H264RtpDepacketizer,
|
|
7166
7686
|
H265RtpDepacketizer,
|
|
7167
7687
|
HlsSessionManager,
|