@camstack/addon-pipeline 0.1.9 → 0.1.11

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.
Files changed (26) hide show
  1. package/dist/stream-broker/@mf-types.zip +0 -0
  2. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-ChoHjdk6.mjs +17 -0
  3. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-DbMNirr7.mjs +20 -0
  4. package/dist/stream-broker/_stub.js +1 -1
  5. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-ZvGYClo0.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BuV9ar3i.mjs} +6 -6
  6. package/dist/stream-broker/{hostInit-wF9ApmDF.mjs → hostInit-CjVI5LuK.mjs} +6 -6
  7. package/dist/stream-broker/{index-BhXZh4lQ.mjs → index-BF5Qr03x.mjs} +49 -34
  8. package/dist/stream-broker/{index-BYclbfM0.mjs → index-DUJwOcGq.mjs} +6369 -5559
  9. package/dist/stream-broker/{index-D2-K2YJ7.mjs → index-DuBCn5us.mjs} +4552 -4330
  10. package/dist/stream-broker/index.js +425 -14
  11. package/dist/stream-broker/index.js.map +1 -1
  12. package/dist/stream-broker/index.mjs +425 -14
  13. package/dist/stream-broker/index.mjs.map +1 -1
  14. package/dist/stream-broker/remoteEntry.js +1 -1
  15. package/package.json +1 -1
  16. package/python/postprocessors/__pycache__/__init__.cpython-312.pyc +0 -0
  17. package/python/postprocessors/__pycache__/arcface.cpython-312.pyc +0 -0
  18. package/python/postprocessors/__pycache__/ctc.cpython-312.pyc +0 -0
  19. package/python/postprocessors/__pycache__/saliency.cpython-312.pyc +0 -0
  20. package/python/postprocessors/__pycache__/scrfd.cpython-312.pyc +0 -0
  21. package/python/postprocessors/__pycache__/softmax.cpython-312.pyc +0 -0
  22. package/python/postprocessors/__pycache__/yamnet.cpython-312.pyc +0 -0
  23. package/python/postprocessors/__pycache__/yolo.cpython-312.pyc +0 -0
  24. package/python/postprocessors/__pycache__/yolo_seg.cpython-312.pyc +0 -0
  25. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-C-URP6DW.mjs +0 -17
  26. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-69eEmXwl.mjs +0 -20
@@ -27,8 +27,8 @@ const net = require("node:net");
27
27
  const crypto = require("node:crypto");
28
28
  const net$1 = require("net");
29
29
  const events = require("events");
30
- const promises = require("node:dns/promises");
31
30
  const node_child_process = require("node:child_process");
31
+ const promises = require("node:dns/promises");
32
32
  const fs = require("node:fs");
33
33
  const path = require("node:path");
34
34
  const os = require("node:os");
@@ -5816,6 +5816,305 @@ class RtspRestreamProviderImpl {
5816
5816
  }
5817
5817
  }
5818
5818
  }
5819
+ function normaliseCodec(codec) {
5820
+ if (!codec) return null;
5821
+ const c = codec.toLowerCase();
5822
+ if (c === "h264" || c === "avc" || c === "avc1") return "H264";
5823
+ if (c === "h265" || c === "hevc" || c === "hvc1" || c === "hev1") return "H265";
5824
+ return null;
5825
+ }
5826
+ function pipelineKeyFor(deviceId, videoCodec, audioCodec, width, height) {
5827
+ return `${deviceId}|v=${videoCodec}|a=${audioCodec}|${width}x${height}`;
5828
+ }
5829
+ function pickVideoEncoder(target, encoders) {
5830
+ if (!encoders) return target === "H264" ? "libx264" : "libx265";
5831
+ return target === "H264" ? encoders.defaultH264 : encoders.defaultH265;
5832
+ }
5833
+ function pickAudioEncoderArgs(audio) {
5834
+ switch (audio) {
5835
+ case "AAC":
5836
+ return ["-c:a", "aac", "-b:a", "128k", "-ar", "48000", "-ac", "2"];
5837
+ case "Opus":
5838
+ return ["-c:a", "libopus", "-b:a", "64k", "-ar", "48000", "-ac", "2"];
5839
+ case "PCMU":
5840
+ return ["-c:a", "pcm_mulaw", "-ar", "8000", "-ac", "1"];
5841
+ case "none":
5842
+ return ["-an"];
5843
+ default:
5844
+ return ["-c:a", "aac", "-b:a", "128k"];
5845
+ }
5846
+ }
5847
+ class TranscodePipelineManager {
5848
+ entries = /* @__PURE__ */ new Map();
5849
+ logger;
5850
+ api;
5851
+ cameraStreamLookup;
5852
+ rtspEntryLookup;
5853
+ localRtspPort;
5854
+ releaseGraceMs;
5855
+ ffmpegPath;
5856
+ constructor(opts) {
5857
+ this.logger = opts.logger;
5858
+ this.api = opts.api;
5859
+ this.cameraStreamLookup = opts.cameraStreamLookup;
5860
+ this.rtspEntryLookup = opts.rtspEntryLookup;
5861
+ this.localRtspPort = opts.localRtspPort;
5862
+ this.releaseGraceMs = opts.releaseGraceMs ?? 5e3;
5863
+ this.ffmpegPath = opts.ffmpegPath ?? "ffmpeg";
5864
+ }
5865
+ async acquire(input) {
5866
+ const wantedVideo = input.videoCodec;
5867
+ const wantedAudio = input.audioCodec ?? "AAC";
5868
+ const direct = this.findDirectMatch(input.deviceId, wantedVideo, input.maxResolution);
5869
+ if (direct) {
5870
+ const key2 = pipelineKeyFor(
5871
+ input.deviceId,
5872
+ direct.videoCodec,
5873
+ wantedAudio,
5874
+ direct.resolution.width,
5875
+ direct.resolution.height
5876
+ );
5877
+ return this.shareOrCreate(key2, () => ({
5878
+ url: direct.url,
5879
+ videoCodec: direct.videoCodec,
5880
+ audioCodec: "passthrough",
5881
+ resolution: direct.resolution,
5882
+ transcoded: false,
5883
+ encoder: "copy",
5884
+ pipelineKey: key2
5885
+ }), null);
5886
+ }
5887
+ const sourceStream = this.pickTranscodeSource(input.deviceId, input.maxResolution);
5888
+ if (!sourceStream || !sourceStream.url) {
5889
+ throw new Error(
5890
+ `getStreamWithCodec: no source stream available for device ${input.deviceId}`
5891
+ );
5892
+ }
5893
+ const targetCodec = wantedVideo === "auto" ? normaliseCodec(sourceStream.codec) ?? "H264" : wantedVideo;
5894
+ const targetResolution = input.maxResolution ?? sourceStream.resolution ?? { width: 1280, height: 720 };
5895
+ const key = pipelineKeyFor(
5896
+ input.deviceId,
5897
+ targetCodec,
5898
+ wantedAudio,
5899
+ targetResolution.width,
5900
+ targetResolution.height
5901
+ );
5902
+ const existing = this.entries.get(key);
5903
+ if (existing) {
5904
+ return this.shareEntry(existing).source;
5905
+ }
5906
+ const encoders = await this.loadHardwareEncoders();
5907
+ const encoder = pickVideoEncoder(targetCodec, encoders);
5908
+ const dialableUrl = this.allocateLoopbackUrl(input.deviceId, key);
5909
+ const child = this.spawnFfmpeg({
5910
+ sourceUrl: sourceStream.url,
5911
+ outputUrl: dialableUrl,
5912
+ encoder,
5913
+ targetCodec,
5914
+ width: targetResolution.width,
5915
+ height: targetResolution.height,
5916
+ audio: wantedAudio,
5917
+ tag: input.tag ?? `stream-with-codec:${input.deviceId}`
5918
+ });
5919
+ const source = {
5920
+ url: dialableUrl,
5921
+ videoCodec: targetCodec,
5922
+ audioCodec: wantedAudio,
5923
+ resolution: targetResolution,
5924
+ transcoded: true,
5925
+ encoder,
5926
+ pipelineKey: key
5927
+ };
5928
+ const entry = { key, source, child, refcount: 1, releaseTimer: null };
5929
+ this.entries.set(key, entry);
5930
+ this.logger.info("Transcode pipeline started", {
5931
+ meta: { key, encoder, sourceUrl: sourceStream.url, outputUrl: dialableUrl }
5932
+ });
5933
+ if (child) {
5934
+ child.once("exit", (code, signal) => {
5935
+ this.logger.info("Transcode pipeline exited", {
5936
+ meta: { key, code, signal }
5937
+ });
5938
+ const cur = this.entries.get(key);
5939
+ if (cur && cur.child === child) {
5940
+ this.entries.delete(key);
5941
+ }
5942
+ });
5943
+ }
5944
+ return source;
5945
+ }
5946
+ release(pipelineKey) {
5947
+ const entry = this.entries.get(pipelineKey);
5948
+ if (!entry) return { released: false, refcount: 0 };
5949
+ entry.refcount = Math.max(0, entry.refcount - 1);
5950
+ if (entry.refcount > 0) {
5951
+ return { released: true, refcount: entry.refcount };
5952
+ }
5953
+ if (entry.releaseTimer) {
5954
+ clearTimeout(entry.releaseTimer);
5955
+ }
5956
+ entry.releaseTimer = setTimeout(() => {
5957
+ const cur = this.entries.get(pipelineKey);
5958
+ if (!cur || cur.refcount > 0) return;
5959
+ this.shutdownEntry(cur);
5960
+ }, this.releaseGraceMs);
5961
+ return { released: true, refcount: 0 };
5962
+ }
5963
+ shutdownAll() {
5964
+ for (const entry of this.entries.values()) {
5965
+ this.shutdownEntry(entry);
5966
+ }
5967
+ this.entries.clear();
5968
+ }
5969
+ findDirectMatch(deviceId, wanted, maxRes) {
5970
+ if (wanted === "auto") return null;
5971
+ const streams = this.cameraStreamLookup(deviceId);
5972
+ for (const s of streams) {
5973
+ if (!s.url) continue;
5974
+ const codec = normaliseCodec(s.codec);
5975
+ if (codec !== wanted) continue;
5976
+ if (maxRes && s.resolution) {
5977
+ if (s.resolution.width > maxRes.width || s.resolution.height > maxRes.height) continue;
5978
+ }
5979
+ const resolution = s.resolution ?? { width: 1280, height: 720 };
5980
+ return { url: s.url, videoCodec: codec, resolution };
5981
+ }
5982
+ return null;
5983
+ }
5984
+ pickTranscodeSource(deviceId, maxRes) {
5985
+ const streams = this.cameraStreamLookup(deviceId);
5986
+ if (streams.length === 0) return null;
5987
+ if (!maxRes) return streams[0] ?? null;
5988
+ let best = null;
5989
+ let bestArea = -1;
5990
+ for (const s of streams) {
5991
+ if (!s.url) continue;
5992
+ const w = s.resolution?.width ?? 1280;
5993
+ const h = s.resolution?.height ?? 720;
5994
+ if (w > maxRes.width || h > maxRes.height) continue;
5995
+ const area = w * h;
5996
+ if (area > bestArea) {
5997
+ bestArea = area;
5998
+ best = s;
5999
+ }
6000
+ }
6001
+ if (best) return best;
6002
+ let smallest = null;
6003
+ let smallestArea = Number.MAX_SAFE_INTEGER;
6004
+ for (const s of streams) {
6005
+ if (!s.url) continue;
6006
+ const w = s.resolution?.width ?? 1280;
6007
+ const h = s.resolution?.height ?? 720;
6008
+ const area = w * h;
6009
+ if (area < smallestArea) {
6010
+ smallestArea = area;
6011
+ smallest = s;
6012
+ }
6013
+ }
6014
+ return smallest;
6015
+ }
6016
+ allocateLoopbackUrl(deviceId, pipelineKey) {
6017
+ const port = this.localRtspPort();
6018
+ const token = `t-${deviceId}-${crypto.randomUUID().slice(0, 8)}`;
6019
+ if (!port) {
6020
+ return `rtsp://0.0.0.0:0/${token}`;
6021
+ }
6022
+ return `rtsp://127.0.0.1:${port}/${token}`;
6023
+ }
6024
+ async loadHardwareEncoders() {
6025
+ if (!this.api) return null;
6026
+ try {
6027
+ const api = this.api;
6028
+ const probe = api["platformProbe"];
6029
+ if (!probe || typeof probe !== "object") return null;
6030
+ const fn = probe["getHardwareEncoders"];
6031
+ if (!fn || typeof fn !== "object") return null;
6032
+ const query = fn["query"];
6033
+ if (typeof query !== "function") return null;
6034
+ const callable = query;
6035
+ return await callable();
6036
+ } catch (err) {
6037
+ this.logger.warn("platformProbe.getHardwareEncoders failed — using software fallback", {
6038
+ meta: { error: types.errMsg(err) }
6039
+ });
6040
+ return null;
6041
+ }
6042
+ }
6043
+ spawnFfmpeg(opts) {
6044
+ const args = [
6045
+ "-hide_banner",
6046
+ "-loglevel",
6047
+ "warning",
6048
+ "-rtsp_transport",
6049
+ "tcp",
6050
+ "-i",
6051
+ opts.sourceUrl,
6052
+ "-vf",
6053
+ `scale=${opts.width}:${opts.height}`,
6054
+ "-c:v",
6055
+ opts.encoder,
6056
+ ...pickAudioEncoderArgs(opts.audio),
6057
+ "-f",
6058
+ "rtsp",
6059
+ "-rtsp_transport",
6060
+ "tcp",
6061
+ opts.outputUrl
6062
+ ];
6063
+ try {
6064
+ const child = node_child_process.spawn(this.ffmpegPath, args, { stdio: ["ignore", "ignore", "pipe"] });
6065
+ child.stderr?.setEncoding("utf8");
6066
+ child.stderr?.on("data", (chunk) => {
6067
+ this.logger.debug("ffmpeg", { meta: { tag: opts.tag, line: chunk.trim() } });
6068
+ });
6069
+ child.once("error", (err) => {
6070
+ this.logger.error("ffmpeg spawn error", {
6071
+ meta: { tag: opts.tag, error: types.errMsg(err) }
6072
+ });
6073
+ });
6074
+ return child;
6075
+ } catch (err) {
6076
+ this.logger.error("Failed to spawn ffmpeg", {
6077
+ meta: { tag: opts.tag, error: types.errMsg(err) }
6078
+ });
6079
+ return null;
6080
+ }
6081
+ }
6082
+ shareEntry(entry) {
6083
+ entry.refcount += 1;
6084
+ if (entry.releaseTimer) {
6085
+ clearTimeout(entry.releaseTimer);
6086
+ entry.releaseTimer = null;
6087
+ }
6088
+ return entry;
6089
+ }
6090
+ shareOrCreate(key, factory, child) {
6091
+ const existing = this.entries.get(key);
6092
+ if (existing) {
6093
+ this.shareEntry(existing);
6094
+ return existing.source;
6095
+ }
6096
+ const source = factory();
6097
+ const entry = { key, source, child, refcount: 1, releaseTimer: null };
6098
+ this.entries.set(key, entry);
6099
+ return source;
6100
+ }
6101
+ shutdownEntry(entry) {
6102
+ if (entry.releaseTimer) {
6103
+ clearTimeout(entry.releaseTimer);
6104
+ entry.releaseTimer = null;
6105
+ }
6106
+ if (entry.child && !entry.child.killed) {
6107
+ try {
6108
+ entry.child.kill("SIGTERM");
6109
+ } catch (err) {
6110
+ this.logger.warn("ffmpeg kill error", {
6111
+ meta: { key: entry.key, error: types.errMsg(err) }
6112
+ });
6113
+ }
6114
+ }
6115
+ this.entries.delete(entry.key);
6116
+ }
6117
+ }
5819
6118
  const PUSH_KINDS = /* @__PURE__ */ new Set(["push-annexb"]);
5820
6119
  function findProfileForStream(map, camStreamId) {
5821
6120
  for (const profile of types.CAM_PROFILE_ORDER) {
@@ -5882,11 +6181,42 @@ class StreamBrokerManager {
5882
6181
  api = null;
5883
6182
  defaultPreBufferSec = 10;
5884
6183
  webrtcServer = null;
6184
+ /**
6185
+ * Transcode-pipeline manager — backs `getStreamWithCodec`. Lazily
6186
+ * created on first acquire, lives until `destroyAll`.
6187
+ */
6188
+ transcodeManager = null;
5885
6189
  constructor(staticDecoders, logger) {
5886
6190
  this.staticDecoders = staticDecoders ?? [];
5887
6191
  this.logger = logger;
5888
6192
  this.rtspProvider = new RtspRestreamProviderImpl(this.rtspServer, logger.child("rtsp-restream"));
5889
6193
  }
6194
+ /**
6195
+ * Lazily create the transcode manager wired with closures rather than
6196
+ * direct refs so it only sees the surface it needs.
6197
+ */
6198
+ getTranscodeManager() {
6199
+ if (!this.transcodeManager) {
6200
+ this.transcodeManager = new TranscodePipelineManager({
6201
+ logger: this.logger.child("transcode"),
6202
+ api: this.api,
6203
+ cameraStreamLookup: (deviceId) => this.getCameraStreamsForDevice(deviceId),
6204
+ rtspEntryLookup: (brokerId) => {
6205
+ const entry = this.rtspProvider.getEntry(brokerId);
6206
+ return entry ? { url: entry.url } : null;
6207
+ },
6208
+ localRtspPort: () => this.rtspProvider.getRtspPort()
6209
+ });
6210
+ }
6211
+ return this.transcodeManager;
6212
+ }
6213
+ // ── Cap methods: getStreamWithCodec / releaseStreamWithCodec ─────────
6214
+ async getStreamWithCodec(input) {
6215
+ return this.getTranscodeManager().acquire(input);
6216
+ }
6217
+ async releaseStreamWithCodec(input) {
6218
+ return this.getTranscodeManager().release(input.pipelineKey);
6219
+ }
5890
6220
  // ── Decoder resolution ───────────────────────────────────────────────
5891
6221
  //
5892
6222
  // Routes every decoder call through `ctx.api.decoder.*` (tRPC). The
@@ -6336,6 +6666,10 @@ class StreamBrokerManager {
6336
6666
  clearInterval(this.streamHealthTimer);
6337
6667
  this.streamHealthTimer = void 0;
6338
6668
  }
6669
+ if (this.transcodeManager) {
6670
+ this.transcodeManager.shutdownAll();
6671
+ this.transcodeManager = null;
6672
+ }
6339
6673
  const stopPromises = [...this.brokers.values()].map((broker) => broker.stop());
6340
6674
  await Promise.all(stopPromises);
6341
6675
  this.brokers.clear();
@@ -9157,6 +9491,73 @@ class BrokerWebrtcServer {
9157
9491
  }
9158
9492
  // ── Session lifecycle ───────────────────────────────────────────────
9159
9493
  async createSession(streamId, hints = {}, opts = {}) {
9494
+ const setup = await this.setupSessionForBroker(streamId, hints, opts);
9495
+ try {
9496
+ const offer = await setup.session.createOffer();
9497
+ setup.sessionLogger.info("WebRTC session created", {
9498
+ meta: {
9499
+ sessionId: setup.sessionId,
9500
+ brokerId: setup.brokerId,
9501
+ iceServers: setup.iceServerCount,
9502
+ codec: setup.sessionCodec,
9503
+ useH265Repacketizer: setup.useH265Repacketizer
9504
+ }
9505
+ });
9506
+ return { sessionId: setup.sessionId, sdpOffer: offer.sdp };
9507
+ } catch (err) {
9508
+ this.cleanupSession(setup.sessionId);
9509
+ await setup.session.close().catch(() => {
9510
+ });
9511
+ throw err;
9512
+ }
9513
+ }
9514
+ /**
9515
+ * WHEP-style client-offer signaling: caller has already constructed
9516
+ * an SDP offer (Alexa / WHEP / mobile clients that do
9517
+ * `createOffer()` locally) and posts it here. We build the same
9518
+ * broker subscription + AdaptiveSession plumbing as `createSession`,
9519
+ * then call `session.handleOffer(clientOffer)` to produce the
9520
+ * server's SDP answer.
9521
+ *
9522
+ * The session lifecycle is identical to `createSession` — broker
9523
+ * unsubscribe and `closeSession(sessionId)` still apply — so the
9524
+ * RTC controller / Alexa handler closes the session the same way.
9525
+ */
9526
+ async handleOffer(streamId, clientOfferSdp, hints = {}, opts = {}) {
9527
+ const setup = await this.setupSessionForBroker(streamId, hints, opts);
9528
+ try {
9529
+ const answer = await setup.session.handleOffer({ sdp: clientOfferSdp, type: "offer" });
9530
+ setup.sessionLogger.info("WebRTC session created (client-offer)", {
9531
+ meta: {
9532
+ sessionId: setup.sessionId,
9533
+ brokerId: setup.brokerId,
9534
+ iceServers: setup.iceServerCount,
9535
+ codec: setup.sessionCodec,
9536
+ useH265Repacketizer: setup.useH265Repacketizer
9537
+ }
9538
+ });
9539
+ return { sessionId: setup.sessionId, sdpAnswer: answer.sdp };
9540
+ } catch (err) {
9541
+ this.cleanupSession(setup.sessionId);
9542
+ await setup.session.close().catch(() => {
9543
+ });
9544
+ throw err;
9545
+ }
9546
+ }
9547
+ /**
9548
+ * Shared session bring-up: resolve broker → subscribe to encoded
9549
+ * data + RTP → flush pre-buffer → seed H.265 codec info → register
9550
+ * the SessionEntry. Returns the `AdaptiveSession` ready for SDP
9551
+ * negotiation (either `createOffer()` for the server-offer flow or
9552
+ * `handleOffer(clientOffer)` for the WHEP / Alexa flow).
9553
+ *
9554
+ * The returned session has its `onClosed` hook already wired to
9555
+ * cleanup, and the SessionEntry is already in `this.sessions` keyed
9556
+ * by `sessionId` — callers MUST cleanup + close on negotiation
9557
+ * failure (both `createSession` and `handleOffer` do this in their
9558
+ * catch blocks).
9559
+ */
9560
+ async setupSessionForBroker(streamId, hints, opts) {
9160
9561
  if (this.stopped) throw new Error("Server stopped");
9161
9562
  const brokerId = this.resolveBrokerId(streamId, hints);
9162
9563
  const broker = this.brokers.get(brokerId);
@@ -9268,7 +9669,11 @@ class BrokerWebrtcServer {
9268
9669
  });
9269
9670
  }
9270
9671
  }
9271
- const sessionId = crypto.randomUUID();
9672
+ const requestedSessionId = opts.sessionId;
9673
+ if (requestedSessionId !== void 0 && this.sessions.has(requestedSessionId)) {
9674
+ throw new Error(`Session id already in use: ${requestedSessionId}`);
9675
+ }
9676
+ const sessionId = requestedSessionId ?? crypto.randomUUID();
9272
9677
  const iceServers = await this.resolveIceServers();
9273
9678
  const session = new AdaptiveSession({
9274
9679
  sessionId,
@@ -9328,18 +9733,15 @@ class BrokerWebrtcServer {
9328
9733
  const entry = { session, brokerId, unsubBroker, unsubRtp, unsubSdpParams, closeSource };
9329
9734
  this.sessions.set(sessionId, entry);
9330
9735
  session.onClosed = () => this.cleanupSession(sessionId);
9331
- try {
9332
- const offer = await session.createOffer();
9333
- sessionLogger.info("WebRTC session created", {
9334
- meta: { sessionId, brokerId, iceServers: iceServers.length, codec: sessionCodec, useH265Repacketizer }
9335
- });
9336
- return { sessionId, sdpOffer: offer.sdp };
9337
- } catch (err) {
9338
- this.cleanupSession(sessionId);
9339
- await session.close().catch(() => {
9340
- });
9341
- throw err;
9342
- }
9736
+ return {
9737
+ sessionId,
9738
+ session,
9739
+ sessionLogger,
9740
+ brokerId,
9741
+ sessionCodec,
9742
+ useH265Repacketizer,
9743
+ iceServerCount: iceServers.length
9744
+ };
9343
9745
  }
9344
9746
  async handleAnswer(sessionId, sdpAnswer) {
9345
9747
  const entry = this.sessions.get(sessionId);
@@ -9620,6 +10022,15 @@ class WebrtcSessionProvider {
9620
10022
  const streamingDebug = typeof this.manager.isStreamingDebug === "function" ? this.manager.isStreamingDebug(input.deviceId) : false;
9621
10023
  return this.webrtcServer.createSession(streamId, input.hints, { streamingDebug });
9622
10024
  }
10025
+ async handleOffer(input) {
10026
+ const target = input.target ?? { kind: "adaptive" };
10027
+ const streamId = this.resolveTargetToStreamId(input.deviceId, target);
10028
+ const streamingDebug = typeof this.manager.isStreamingDebug === "function" ? this.manager.isStreamingDebug(input.deviceId) : false;
10029
+ return this.webrtcServer.handleOffer(streamId, input.sdpOffer, void 0, {
10030
+ streamingDebug,
10031
+ sessionId: input.sessionId
10032
+ });
10033
+ }
9623
10034
  async handleAnswer(input) {
9624
10035
  await this.webrtcServer.handleAnswer(input.sessionId, input.sdpAnswer);
9625
10036
  }