@eyepop.ai/eyepop 3.25.2 → 3.26.0-rc.0

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.
@@ -9214,7 +9214,7 @@ var PredictionVersion = /* @__PURE__ */ ((PredictionVersion3) => {
9214
9214
  PredictionVersion3[PredictionVersion3["V3"] = 3] = "V3";
9215
9215
  return PredictionVersion3;
9216
9216
  })(PredictionVersion || {});
9217
- var DEFAULT_PREDICTION_VERSION = 2 /* V2 */;
9217
+ var DEFAULT_PREDICTION_VERSION = 3 /* V3 */;
9218
9218
  var VideoMode = /* @__PURE__ */ ((VideoMode2) => {
9219
9219
  VideoMode2["STREAM"] = "stream";
9220
9220
  VideoMode2["BUFFER"] = "buffer";
@@ -10925,14 +10925,6 @@ var _SessionBackend = class _SessionBackend {
10925
10925
  }
10926
10926
  return this.ctx.options.pop || null;
10927
10927
  }
10928
- /**
10929
- * A select forward's results come only at V3. Only a Pop that selects asks
10930
- * for it, so a worker too old to know V3 - which also rejects that Pop - is
10931
- * never sent a version it would not accept.
10932
- */
10933
- predictionVersion() {
10934
- return popSelects(this.pop()) ? 3 /* V3 */ : DEFAULT_PREDICTION_VERSION;
10935
- }
10936
10928
  async changePop(pop) {
10937
10929
  if (this.ctx.options.popId == "transient" /* Transient */) {
10938
10930
  await this.changeTransientPop(pop);
@@ -11033,7 +11025,7 @@ var _SessionBackend = class _SessionBackend {
11033
11025
  return { stream: streamSource.stream, mimeType: streamSource.mimeType };
11034
11026
  })
11035
11027
  );
11036
- return this.start(new UploadGroupJob(sources, params, async () => this.session(), client, this.ctx.requestLogger, this.predictionVersion()));
11028
+ return this.start(new UploadGroupJob(sources, params, async () => this.session(), client, this.ctx.requestLogger, DEFAULT_PREDICTION_VERSION));
11037
11029
  } catch (e) {
11038
11030
  limit.release();
11039
11031
  throw e;
@@ -11052,7 +11044,7 @@ var _SessionBackend = class _SessionBackend {
11052
11044
  stream,
11053
11045
  mimeType: mimeTypes?.[i] ?? null
11054
11046
  }));
11055
- return this.start(new UploadGroupJob(sources, params, async () => this.session(), client, this.ctx.requestLogger, this.predictionVersion()));
11047
+ return this.start(new UploadGroupJob(sources, params, async () => this.session(), client, this.ctx.requestLogger, DEFAULT_PREDICTION_VERSION));
11056
11048
  } catch (e) {
11057
11049
  limit.release();
11058
11050
  throw e;
@@ -11067,7 +11059,7 @@ var _SessionBackend = class _SessionBackend {
11067
11059
  await limit.acquire();
11068
11060
  try {
11069
11061
  this.ctx.updateState();
11070
- return this.start(new LoadFromGroupJob(urls, params, async () => this.session(), client, this.ctx.requestLogger, this.predictionVersion()));
11062
+ return this.start(new LoadFromGroupJob(urls, params, async () => this.session(), client, this.ctx.requestLogger, DEFAULT_PREDICTION_VERSION));
11071
11063
  } catch (e) {
11072
11064
  limit.release();
11073
11065
  throw e;
@@ -11111,14 +11103,14 @@ var _SessionBackend = class _SessionBackend {
11111
11103
  client,
11112
11104
  this.ctx.requestLogger,
11113
11105
  void 0,
11114
- this.predictionVersion()
11106
+ DEFAULT_PREDICTION_VERSION
11115
11107
  ),
11116
11108
  true
11117
11109
  );
11118
11110
  }
11119
11111
  async uploadStream(source, params) {
11120
11112
  return await this.startConnected(
11121
- async (client) => new UploadJob(source.stream, source.mimeType, source.size, source.videoMode, params, async () => this.session(), client, this.ctx.requestLogger, void 0, this.predictionVersion()),
11113
+ async (client) => new UploadJob(source.stream, source.mimeType, source.size, source.videoMode, params, async () => this.session(), client, this.ctx.requestLogger, void 0, DEFAULT_PREDICTION_VERSION),
11122
11114
  true
11123
11115
  );
11124
11116
  }
@@ -11143,7 +11135,7 @@ var _SessionBackend = class _SessionBackend {
11143
11135
  client,
11144
11136
  this.ctx.requestLogger,
11145
11137
  uploadBodyFactory,
11146
- this.predictionVersion()
11138
+ DEFAULT_PREDICTION_VERSION
11147
11139
  );
11148
11140
  });
11149
11141
  }
@@ -11151,13 +11143,13 @@ var _SessionBackend = class _SessionBackend {
11151
11143
  if (source.url.startsWith("file://")) {
11152
11144
  return await this.uploadPath({ path: source.url.substring("file://".length) }, params);
11153
11145
  }
11154
- return await this.startConnected(async (client) => new LoadFromJob(source.url, params, async () => this.session(), client, this.ctx.requestLogger, this.predictionVersion()));
11146
+ return await this.startConnected(async (client) => new LoadFromJob(source.url, params, async () => this.session(), client, this.ctx.requestLogger, DEFAULT_PREDICTION_VERSION));
11155
11147
  }
11156
11148
  async loadFromAssetUuid(source, params) {
11157
- return await this.startConnected(async (client) => new LoadFromAssetUuidJob(source.assetUuid, params, async () => this.session(), client, this.ctx.requestLogger, this.predictionVersion()));
11149
+ return await this.startConnected(async (client) => new LoadFromAssetUuidJob(source.assetUuid, params, async () => this.session(), client, this.ctx.requestLogger, DEFAULT_PREDICTION_VERSION));
11158
11150
  }
11159
11151
  async loadMediaStream(source, params) {
11160
- return await this.startConnected(async (client) => new LoadMediaStreamJob(source.mediaStream, params, async () => this.session(), client, this.ctx.requestLogger, this.predictionVersion()));
11152
+ return await this.startConnected(async (client) => new LoadMediaStreamJob(source.mediaStream, params, async () => this.session(), client, this.ctx.requestLogger, DEFAULT_PREDICTION_VERSION));
11161
11153
  }
11162
11154
  async reconnect() {
11163
11155
  if (this.ctx.options.popId == "transient" /* Transient */) {
@@ -11505,6 +11497,8 @@ if (isBrowser) {
11505
11497
  var PIPELINE_PATH = "/api/v1/pipeline";
11506
11498
  var SUBPROTOCOL = "eyepop.v1";
11507
11499
  var MAX_OPEN_SOURCES = 256;
11500
+ var PIPELINE_SOCKETS = 4;
11501
+ var EXTRA_SOCKET_RETRY_MS = 10 * 1e3;
11508
11502
  var COMBINED_UPLOAD_MAX_BYTES = 8 * 1024 * 1024;
11509
11503
  var CHUNK = 1;
11510
11504
  var COMBINED = 2;
@@ -11607,6 +11601,8 @@ var OpenSource = class {
11607
11601
  this.id = id;
11608
11602
  this.inbox = new Inbox();
11609
11603
  this.uploads = /* @__PURE__ */ new Map();
11604
+ // The socket's ended count when the setSource went out.
11605
+ this.endedBefore = 0;
11610
11606
  }
11611
11607
  close(error) {
11612
11608
  this.uploads.forEach((upload) => upload.end());
@@ -11619,14 +11615,60 @@ var PipelineSocket = class {
11619
11615
  this.sources = /* @__PURE__ */ new Map();
11620
11616
  this.commands = /* @__PURE__ */ new Map();
11621
11617
  this._closed = false;
11618
+ // The ids of sources sent on this socket that have neither ended nor been refused, as the
11619
+ // pipeline API counts them, cancelled ones included.
11620
+ this.held = /* @__PURE__ */ new Set();
11621
+ // The held sources that are live. A live source never ends, so nothing queued behind it plays.
11622
+ this.lives = /* @__PURE__ */ new Set();
11623
+ // The held sources the pipeline API took: only these end, so only these make room.
11624
+ this.accepted = /* @__PURE__ */ new Set();
11625
+ // The ends on this socket so far.
11626
+ this.ended = 0;
11627
+ // How many sources the socket had taken when it last refused queue_full: while as many are held,
11628
+ // the next send waits. Null once none is held.
11629
+ this.cap = null;
11630
+ this._roomWaiters = [];
11631
+ this.heartbeat = null;
11622
11632
  this.closed = new Promise((resolve) => this._markClosed = resolve);
11623
11633
  }
11634
+ /** A source ended or the socket closed: wake the send waiting for room. */
11635
+ freed() {
11636
+ this.ended += 1;
11637
+ this.wake();
11638
+ }
11639
+ /** A held source ended or was refused: wake the send waiting for room. */
11640
+ wake() {
11641
+ if (this.held.size === 0) {
11642
+ this.cap = null;
11643
+ }
11644
+ const waiters = this._roomWaiters;
11645
+ this._roomWaiters = [];
11646
+ waiters.forEach((wake) => wake());
11647
+ }
11648
+ get hasRoom() {
11649
+ return this.cap === null || this.held.size < this.cap;
11650
+ }
11651
+ /**
11652
+ * Wait, while the socket is full, until a source ends, it closes or the job is cancelled. The
11653
+ * caller sends in order, so one send waits here and each end lets one go, instead of every
11654
+ * refused source sending its media again at once.
11655
+ */
11656
+ async room(job) {
11657
+ while (!this.hasRoom && this.open && !job.cancelled) {
11658
+ await Promise.race([new Promise((resolve) => this._roomWaiters.push(resolve)), job.whenCancelled]);
11659
+ }
11660
+ }
11624
11661
  get open() {
11625
11662
  return !this._closed && this.ws.readyState === 1;
11626
11663
  }
11627
11664
  markClosed() {
11665
+ if (this.heartbeat !== null) {
11666
+ clearInterval(this.heartbeat);
11667
+ this.heartbeat = null;
11668
+ }
11628
11669
  this._closed = true;
11629
11670
  this._markClosed();
11671
+ this.freed();
11630
11672
  }
11631
11673
  sendJson(message) {
11632
11674
  this.ws.send(JSON.stringify(message));
@@ -11645,6 +11687,8 @@ var PipelineJob = class {
11645
11687
  this._results = new Inbox();
11646
11688
  this._consumed = false;
11647
11689
  this._cancelled = false;
11690
+ // Settles when the job is cancelled, so that a send waiting for room ends at once.
11691
+ this.whenCancelled = new Promise((resolve) => this._markCancelled = resolve);
11648
11692
  this.openSource = null;
11649
11693
  // Stops the job's source on the pipeline, once it was sent.
11650
11694
  this.stopOnPipeline = null;
@@ -11684,6 +11728,7 @@ var PipelineJob = class {
11684
11728
  return;
11685
11729
  }
11686
11730
  this._cancelled = true;
11731
+ this._markCancelled();
11687
11732
  this._results.push(null);
11688
11733
  this.openSource?.inbox.push(CANCELLED);
11689
11734
  this.stopOnPipeline?.();
@@ -11709,14 +11754,14 @@ var PipelineBackend = class {
11709
11754
  constructor(ctx) {
11710
11755
  this.ctx = ctx;
11711
11756
  this._pipelineApiUrl = null;
11712
- this._socket = null;
11757
+ this._sockets = [];
11713
11758
  this._connecting = null;
11714
11759
  // Every job's setSource and every changePop goes out through this chain, in the order submitted.
11715
11760
  this._sendChain = Promise.resolve();
11716
11761
  this._disconnecting = false;
11717
11762
  this._nextId = 0;
11718
11763
  this._warned = /* @__PURE__ */ new Set();
11719
- this._heartbeat = null;
11764
+ this._noExtraSocketUntil = 0;
11720
11765
  // Reconnects at once since a source last played.
11721
11766
  this._reconnects = 0;
11722
11767
  this._currentPop = ctx.options.pop ?? null;
@@ -11730,13 +11775,9 @@ var PipelineBackend = class {
11730
11775
  async disconnect(closeEndpoint) {
11731
11776
  await closeEndpoint();
11732
11777
  this._disconnecting = true;
11733
- if (this._heartbeat !== null) {
11734
- clearInterval(this._heartbeat);
11735
- this._heartbeat = null;
11736
- }
11737
- const socket = this._socket;
11738
- this._socket = null;
11739
- if (socket !== null) {
11778
+ const sockets = this._sockets;
11779
+ this._sockets = [];
11780
+ for (const socket of sockets) {
11740
11781
  socket.ws.close(1e3);
11741
11782
  await socket.closed;
11742
11783
  }
@@ -11754,21 +11795,55 @@ var PipelineBackend = class {
11754
11795
  this._sendChain = result.catch(() => void 0);
11755
11796
  return result;
11756
11797
  }
11757
- /** The open socket, or a new one with the last pop set on it. */
11798
+ openSockets() {
11799
+ return this._sockets.filter((socket) => socket.open);
11800
+ }
11801
+ /** An open socket, or a new one with the last pop set on it. */
11758
11802
  async ensureSocket() {
11759
- if (this._socket !== null && this._socket.open) {
11760
- return this._socket;
11803
+ const open = this.openSockets()[0];
11804
+ if (open !== void 0) {
11805
+ return open;
11761
11806
  }
11762
11807
  if (this._connecting === null) {
11763
- this._connecting = this.openSocket().finally(() => this._connecting = null);
11808
+ this._connecting = this.openSocket(true).finally(() => this._connecting = null);
11764
11809
  }
11765
11810
  return await this._connecting;
11766
11811
  }
11767
- async openSocket() {
11812
+ /**
11813
+ * The socket for the next job; the caller runs it in submission order. A live job goes on a socket
11814
+ * of its own, an idle one or a new one, which `pipelineSockets` does not count. Any other job skips
11815
+ * the sockets with a live source: a new one while every other socket has a source and fewer than
11816
+ * `pipelineSockets` are open, else the one with room and the fewest sources.
11817
+ */
11818
+ async socketForJob(job) {
11819
+ if (job.live) {
11820
+ return this.openSockets().find((socket) => socket.held.size === 0) ?? await this.openSocket(true);
11821
+ }
11822
+ const sockets = this.openSockets().filter((socket) => socket.lives.size === 0);
11823
+ if (sockets.length === 0) {
11824
+ return await this.openSocket(true);
11825
+ }
11826
+ if (sockets.length < (this.ctx.options.pipelineSockets ?? PIPELINE_SOCKETS) && sockets.every((socket) => socket.held.size > 0) && Date.now() >= this._noExtraSocketUntil) {
11827
+ try {
11828
+ return await this.openSocket(false);
11829
+ } catch (error) {
11830
+ this._noExtraSocketUntil = Date.now() + EXTRA_SOCKET_RETRY_MS;
11831
+ this.ctx.requestLogger.info("could not open another pipeline API socket: %s", error);
11832
+ }
11833
+ }
11834
+ const rank = (socket) => (socket.hasRoom ? 0 : MAX_OPEN_SOURCES + 1) + socket.held.size;
11835
+ return sockets.reduce((best, socket) => rank(socket) < rank(best) ? socket : best);
11836
+ }
11837
+ /** A new socket with the last pop set on it; `patient` waits through 503 with Retry-After. */
11838
+ async openSocket(patient) {
11768
11839
  if (this.ctx.client() === null) {
11769
11840
  throw new PipelineSocketClosedError(void 0, "the endpoint is not connected");
11770
11841
  }
11771
- const ws = await this.upgrade(await this.socketUrl());
11842
+ const ws = await this.upgrade(await this.socketUrl(), patient);
11843
+ if (this._disconnecting) {
11844
+ ws.close(1e3);
11845
+ throw new PipelineSocketClosedError(void 0, "the endpoint disconnected");
11846
+ }
11772
11847
  const socket = new PipelineSocket(ws);
11773
11848
  ws.onmessage = (event) => {
11774
11849
  if (typeof event.data === "string") {
@@ -11782,31 +11857,29 @@ var PipelineBackend = class {
11782
11857
  ws.onerror = () => {
11783
11858
  };
11784
11859
  ws.onclose = (event) => this.closed(socket, event.code, event.reason);
11785
- this._socket = socket;
11786
- this.startHeartbeat(ws);
11860
+ this._sockets.push(socket);
11861
+ this.startHeartbeat(socket);
11787
11862
  if (this._currentPop !== null) {
11788
11863
  try {
11789
11864
  await this.setPopOn(socket, this._currentPop);
11790
11865
  } catch (error) {
11791
- this._socket = null;
11866
+ this._sockets = this._sockets.filter((other) => other !== socket);
11792
11867
  ws.close(1e3);
11793
11868
  throw error;
11794
11869
  }
11795
11870
  }
11796
11871
  return socket;
11797
11872
  }
11798
- startHeartbeat(ws) {
11799
- if (this._heartbeat !== null) {
11800
- clearInterval(this._heartbeat);
11801
- }
11873
+ startHeartbeat(socket) {
11874
+ const ws = socket.ws;
11802
11875
  const ping = ws.ping;
11803
11876
  if (typeof ping === "function") {
11804
- this._heartbeat = setInterval(() => {
11877
+ socket.heartbeat = setInterval(() => {
11805
11878
  if (ws.readyState === 1) {
11806
11879
  ping.call(ws);
11807
11880
  }
11808
11881
  }, HEARTBEAT_MS);
11809
- this._heartbeat.unref?.();
11882
+ socket.heartbeat.unref?.();
11810
11883
  }
11811
11884
  }
11812
11885
  async socketUrl() {
@@ -11844,7 +11917,7 @@ var PipelineBackend = class {
11844
11917
  return (await this.ctx.credentialHeader()).replace(/^Bearer /, "");
11845
11918
  }
11846
11919
  /** Open the socket, waiting through 503 with Retry-After and exchanging the API key again after a 401. */
11847
- async upgrade(url) {
11920
+ async upgrade(url, patient) {
11848
11921
  const deadline = Date.now() + MAX_RETRY_MS;
11849
11922
  let rejected = false;
11850
11923
  while (true) {
@@ -11862,7 +11935,7 @@ var PipelineBackend = class {
11862
11935
  continue;
11863
11936
  }
11864
11937
  const wait = error.status === 503 && error.retryAfter !== void 0 ? Math.max(error.retryAfter, 1) * 1e3 : void 0;
11865
- if (wait === void 0 || Date.now() + wait > deadline) {
11938
+ if (!patient || wait === void 0 || Date.now() + wait > deadline) {
11866
11939
  throw error;
11867
11940
  }
11868
11941
  this.ctx.requestLogger.info("the pipeline API answered 503, retrying in %d ms", wait);
@@ -11872,9 +11945,7 @@ var PipelineBackend = class {
11872
11945
  }
11873
11946
  closed(socket, code, reason) {
11874
11947
  socket.markClosed();
11875
- if (this._socket === socket) {
11876
- this._socket = null;
11877
- }
11948
+ this._sockets = this._sockets.filter((other) => other !== socket);
11878
11949
  const error = new PipelineSocketClosedError(code, reason);
11879
11950
  socket.sources.forEach((source) => source.close(error));
11880
11951
  socket.commands.forEach((command) => command.reject(error));
@@ -11898,6 +11969,18 @@ var PipelineBackend = class {
11898
11969
  const type = message["type"];
11899
11970
  const id = message["id"];
11900
11971
  const source = typeof id === "string" ? socket.sources.get(id) : void 0;
11972
+ if (type === "end") {
11973
+ socket.held.delete(id);
11974
+ socket.lives.delete(id);
11975
+ socket.accepted.delete(id);
11976
+ socket.freed();
11977
+ } else if (type === "result" && socket.held.has(id)) {
11978
+ socket.accepted.add(id);
11979
+ } else if (type === "error" && message["error"] !== "unknown_command") {
11980
+ socket.held.delete(id);
11981
+ socket.lives.delete(id);
11982
+ socket.wake();
11983
+ }
11901
11984
  if (type === "send" || type === "credit") {
11902
11985
  const upload = source?.uploads.get(message["member"] ?? null);
11903
11986
  upload?.grant(Number(type === "send" ? message["credit"] : message["bytes"]));
@@ -11945,15 +12028,28 @@ var PipelineBackend = class {
11945
12028
  pop() {
11946
12029
  return this._currentPop;
11947
12030
  }
11948
- /** Set the pop on the socket's pipeline; a pop it refuses rejects, and the last pop it took stays. */
12031
+ /**
12032
+ * Set the pop on every open socket's pipeline; a pop the first refuses rejects, and the last pop it
12033
+ * took stays. shortcut: a refusal from a later socket, which the first took, leaves the earlier ones
12034
+ * on the new pop; a refusal is the pop's own fault, so every socket answers it alike.
12035
+ */
11949
12036
  async changePop(pop) {
11950
12037
  if (this.ctx.client() === null) {
11951
12038
  this._currentPop = pop;
11952
12039
  return;
11953
12040
  }
11954
12041
  await this.inOrder(async () => {
11955
- if (this._socket !== null && this._socket.open) {
11956
- await this.setPopOn(this._socket, pop);
12042
+ const sockets = this.openSockets();
12043
+ if (sockets.length > 0) {
12044
+ for (const socket of sockets) {
12045
+ try {
12046
+ await this.setPopOn(socket, pop);
12047
+ } catch (error) {
12048
+ if (!(error instanceof PipelineSocketClosedError)) {
12049
+ throw error;
12050
+ }
12051
+ }
12052
+ }
11957
12053
  this._currentPop = pop;
11958
12054
  return;
11959
12055
  }
@@ -12056,7 +12152,12 @@ var PipelineBackend = class {
12056
12152
  }
12057
12153
  };
12058
12154
  }
12059
- /** Run the job's source to its end; a live source refused overloaded goes once more on a new socket. */
12155
+ /**
12156
+ * Run the job's source to its end. A live source refused overloaded goes once more on a new socket.
12157
+ * A source refused queue_full goes again once another source on the socket ended, so it may go after
12158
+ * jobs submitted later; with none of the endpoint's other sources open there, or with an upload from
12159
+ * the caller's own stream, which cannot be read twice, the refusal stands.
12160
+ */
12060
12161
  async run(job) {
12061
12162
  let retried = false;
12062
12163
  while (true) {
@@ -12067,9 +12168,9 @@ var PipelineBackend = class {
12067
12168
  const { socket, source } = sent;
12068
12169
  const uploading = source.uploads.size > 0 ? this.upload(socket, source, job) : Promise.resolve();
12069
12170
  let whip = null;
12070
- let overloaded;
12171
+ let refused;
12071
12172
  try {
12072
- overloaded = await this.follow(job, source, () => {
12173
+ refused = await this.follow(job, source, () => {
12073
12174
  if (job.mediaStream !== void 0) {
12074
12175
  const fail = (error) => {
12075
12176
  job.stopOnPipeline?.();
@@ -12087,9 +12188,19 @@ var PipelineBackend = class {
12087
12188
  await uploading;
12088
12189
  await whip?.close();
12089
12190
  }
12090
- if (!overloaded) {
12191
+ if (refused === null) {
12091
12192
  return;
12092
12193
  }
12194
+ if (refused === "queue_full") {
12195
+ if (socket.accepted.size === 0 && socket.ended === source.endedBefore) {
12196
+ throw new PipelineCommandError("queue_full", "the socket refused the source with none of its own open");
12197
+ }
12198
+ if (job.media.some((media) => media.once)) {
12199
+ throw new PipelineCommandError("queue_full", "the socket refused an upload from a stream, which cannot be sent again");
12200
+ }
12201
+ socket.cap = Math.min(socket.accepted.size, socket.cap ?? socket.accepted.size) || null;
12202
+ continue;
12203
+ }
12093
12204
  if (retried || !job.live) {
12094
12205
  throw new PipelineCommandError("overloaded", "the pipeline API has no capacity for another live source");
12095
12206
  }
@@ -12103,12 +12214,27 @@ var PipelineBackend = class {
12103
12214
  return null;
12104
12215
  }
12105
12216
  const combined = await combinedMedia(job);
12106
- const socket = await this.ensureSocket();
12217
+ let socket;
12218
+ while (true) {
12219
+ socket = await this.socketForJob(job);
12220
+ await socket.room(job);
12221
+ if (job.cancelled) {
12222
+ return null;
12223
+ }
12224
+ if (socket.open) {
12225
+ break;
12226
+ }
12227
+ }
12107
12228
  const source = new OpenSource(this.newId("source"));
12108
12229
  if (combined === null) {
12109
12230
  uploadKeys(job).forEach((key) => source.uploads.set(key, new Credit()));
12110
12231
  }
12111
12232
  socket.sources.set(source.id, source);
12233
+ socket.held.add(source.id);
12234
+ if (job.live) {
12235
+ socket.lives.add(source.id);
12236
+ }
12237
+ source.endedBefore = socket.ended;
12112
12238
  job.openSource = source;
12113
12239
  job.stopOnPipeline = () => {
12114
12240
  if (socket.open) {
@@ -12123,15 +12249,15 @@ var PipelineBackend = class {
12123
12249
  }
12124
12250
  return { socket, source };
12125
12251
  }
12126
- /** Hand the source's predictions to the job until it ends; true when the pipeline API was overloaded. */
12252
+ /** Hand the source's predictions to the job until it ends; the refusal to retry on, overloaded or queue_full. */
12127
12253
  async follow(job, source, onResult) {
12128
12254
  while (true) {
12129
12255
  if (job.cancelled) {
12130
- return false;
12256
+ return null;
12131
12257
  }
12132
12258
  const message = await source.inbox.next();
12133
12259
  if (message === CANCELLED) {
12134
- return false;
12260
+ return null;
12135
12261
  }
12136
12262
  if (message instanceof Error) {
12137
12263
  throw message;
@@ -12145,12 +12271,12 @@ var PipelineBackend = class {
12145
12271
  break;
12146
12272
  case "end":
12147
12273
  if (message["reason"] === "eos") {
12148
- return false;
12274
+ return null;
12149
12275
  }
12150
12276
  throw new PipelineSourceError(source.id, message["reason"] ?? "", message["message"] ?? "");
12151
12277
  case "error":
12152
- if (message["error"] === "overloaded") {
12153
- return true;
12278
+ if (message["error"] === "overloaded" || message["error"] === "queue_full") {
12279
+ return message["error"];
12154
12280
  }
12155
12281
  throw new PipelineCommandError(message["error"] ?? "", message["message"] ?? "");
12156
12282
  case "dropped":
@@ -12294,7 +12420,8 @@ function knownSize(body) {
12294
12420
  return void 0;
12295
12421
  }
12296
12422
  function bodyMedia(body, contentType, size) {
12297
- return { open: async () => body, contentType, size: size ?? knownSize(body) };
12423
+ const once = !(typeof Blob !== "undefined" && body instanceof Blob) && !(body instanceof ArrayBuffer) && !ArrayBuffer.isView(body);
12424
+ return { open: async () => body, contentType, size: size ?? knownSize(body), once };
12298
12425
  }
12299
12426
  function uploadGroup(media) {
12300
12427
  return { group: media.map((m) => ({ upload: { contentType: m.contentType } })) };
@@ -12382,6 +12509,9 @@ var WorkerEndpoint = class extends Endpoint {
12382
12509
  if (options.jobQueueLength > MAX_OPEN_SOURCES) {
12383
12510
  throw new PipelineConfigurationError("job_queue_length", `jobQueueLength ${options.jobQueueLength} exceeds ${MAX_OPEN_SOURCES}, the open sources a pipeline API socket takes`);
12384
12511
  }
12512
+ if (options.pipelineSockets !== void 0 && !(Number.isInteger(options.pipelineSockets) && options.pipelineSockets > 0)) {
12513
+ throw new PipelineConfigurationError("pipeline_sockets", `pipelineSockets ${options.pipelineSockets} is not a positive whole number`);
12514
+ }
12385
12515
  }
12386
12516
  super(options);
12387
12517
  const context = {
@@ -14356,6 +14486,7 @@ export {
14356
14486
  ModelType,
14357
14487
  MotionModel,
14358
14488
  PIPELINE_PATH,
14489
+ PIPELINE_SOCKETS,
14359
14490
  PipelineBackend,
14360
14491
  PipelineCommandError,
14361
14492
  PipelineConfigurationError,