@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.
- package/dist/eyepop.index.d.mts +43 -15
- package/dist/eyepop.index.d.ts +43 -15
- package/dist/eyepop.index.js +194 -62
- package/dist/eyepop.index.js.map +1 -1
- package/dist/eyepop.index.mjs +193 -62
- package/dist/eyepop.index.mjs.map +1 -1
- package/dist/eyepop.min.js +1 -1
- package/package.json +1 -1
package/dist/eyepop.index.mjs
CHANGED
|
@@ -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 =
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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.
|
|
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.
|
|
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
|
-
|
|
11734
|
-
|
|
11735
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11760
|
-
|
|
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
|
-
|
|
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.
|
|
11786
|
-
this.startHeartbeat(
|
|
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.
|
|
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(
|
|
11799
|
-
|
|
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
|
-
|
|
11877
|
+
socket.heartbeat = setInterval(() => {
|
|
11805
11878
|
if (ws.readyState === 1) {
|
|
11806
11879
|
ping.call(ws);
|
|
11807
11880
|
}
|
|
11808
11881
|
}, HEARTBEAT_MS);
|
|
11809
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
11956
|
-
|
|
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
|
-
/**
|
|
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
|
|
12171
|
+
let refused;
|
|
12071
12172
|
try {
|
|
12072
|
-
|
|
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 (
|
|
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
|
-
|
|
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;
|
|
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
|
|
12256
|
+
return null;
|
|
12131
12257
|
}
|
|
12132
12258
|
const message = await source.inbox.next();
|
|
12133
12259
|
if (message === CANCELLED) {
|
|
12134
|
-
return
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|