@eyepop.ai/eyepop 1.17.5 → 1.18.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.
@@ -8674,6 +8674,7 @@ __export(index_exports, {
8674
8674
  LoadFromAssetUuidJob: () => LoadFromAssetUuidJob,
8675
8675
  LoadFromJob: () => LoadFromJob,
8676
8676
  LoadLiveIngressJob: () => LoadLiveIngressJob,
8677
+ LoadMediaStreamJob: () => LoadMediaStreamJob,
8677
8678
  ModelExportStatus: () => ModelExportStatus,
8678
8679
  ModelFormat: () => ModelFormat,
8679
8680
  ModelPrecisionType: () => ModelPrecisionType,
@@ -8953,748 +8954,724 @@ var ContourType = /* @__PURE__ */ ((ContourType2) => {
8953
8954
  return ContourType2;
8954
8955
  })(ContourType || {});
8955
8956
 
8956
- // worker/jobs.ts
8957
- var AbstractJob = class {
8958
- constructor(params, getSession, client, requestLogger) {
8959
- this._responseStream = null;
8960
- this._controller = new AbortController();
8957
+ // worker/webrtc_whip.ts
8958
+ var import_uuid = require("uuid");
8959
+
8960
+ // worker/webrtc_base.ts
8961
+ var import_path = __toESM(require("path"));
8962
+ var WebrtcBase = class _WebrtcBase {
8963
+ constructor(getSession, client, ingressId, urlBasePath, is_pipeline_direct, requestLogger) {
8961
8964
  this._getSession = getSession;
8962
- this._params = params ?? null;
8963
8965
  this._client = client;
8964
8966
  this._requestLogger = requestLogger;
8967
+ this._ingressId = ingressId;
8968
+ this._urlPath = `${urlBasePath}/${this._ingressId}`;
8969
+ this._is_pipeline_direct = is_pipeline_direct;
8970
+ this._pc = null;
8971
+ this._eTag = "";
8972
+ this._location = null;
8973
+ this._queuedCandidates = [];
8974
+ this._offerData = null;
8965
8975
  }
8966
- [Symbol.asyncIterator]() {
8967
- if (!this._responseStream) {
8968
- throw Error("logical bug");
8976
+ async close() {
8977
+ const pc = this._pc;
8978
+ this._pc = null;
8979
+ if (pc) {
8980
+ return pc.close();
8969
8981
  }
8970
- return Stream.iterFromReadableStream(this._responseStream, this._controller, async (event) => {
8971
- return await this.onEvent(event);
8972
- });
8973
8982
  }
8974
- async startJob() {
8975
- return Promise.reject("abstract class");
8983
+ ingressId() {
8984
+ return this._ingressId;
8976
8985
  }
8977
- async onEvent(event) {
8978
- if (event.type == "error") {
8979
- return Promise.reject(`Error event for source ${event.source_id}: ${event.message}`);
8986
+ gresUrl(session, location = null) {
8987
+ let urlPath;
8988
+ if (this._is_pipeline_direct) {
8989
+ if (!session.pipelineId) {
8990
+ throw new Error("cannot connect with is_pipeline_direct=true but !session.pipelineId");
8991
+ }
8992
+ urlPath = import_path.default.join("pipelines", session.pipelineId, this._urlPath);
8993
+ } else {
8994
+ urlPath = this._urlPath;
8980
8995
  }
8981
- return Promise.resolve();
8996
+ if (location) {
8997
+ urlPath = import_path.default.join(urlPath, location);
8998
+ }
8999
+ let baseUrl;
9000
+ if (session.baseUrl?.endsWith("/")) {
9001
+ baseUrl = session.baseUrl;
9002
+ } else {
9003
+ baseUrl = session.baseUrl + "/";
9004
+ }
9005
+ return new URL(urlPath, baseUrl);
8982
9006
  }
8983
- start(done, status) {
8984
- this._responseStream = new Promise(async (resolve, reject) => {
8985
- try {
8986
- let retries = 1;
8987
- while (true) {
8988
- try {
8989
- const response = await this.startJob();
8990
- if (response.status == 401) {
8991
- status(response.status);
8992
- if (retries--) {
8993
- this._requestLogger.info("received 401 response, attempt to reauthorize once");
8994
- } else {
8995
- reject(`response ${response.status}: ${response.statusText}`);
8996
- break;
8997
- }
8998
- } else if (response.status == 404) {
8999
- status(response.status);
9000
- if (retries--) {
9001
- this._requestLogger.info("received 404 response, attempt to restart pop");
9002
- } else {
9003
- reject(`response ${response.status}: ${response.statusText}`);
9004
- break;
9005
- }
9006
- } else if (response.status != 200) {
9007
- reject(`upload error ${response.status}: ${await response.text()}`);
9008
- break;
9009
- } else if (response.body) {
9010
- resolve(response.body);
9011
- break;
9012
- } else {
9013
- const text = await response.text();
9014
- if (text) {
9015
- resolve(readableStreamFromString(text));
9016
- break;
9017
- } else {
9018
- reject("response body is empty");
9019
- break;
9020
- }
9021
- }
9022
- } catch (error) {
9023
- if (retries--) {
9024
- this._requestLogger.info("unknown, attempt to restart pop (as if we received 404)", error);
9025
- status(404);
9026
- } else {
9027
- status(500);
9028
- reject(error);
9029
- break;
9030
- }
9031
- }
9032
- }
9033
- } catch (reason) {
9034
- reject(reason);
9035
- } finally {
9036
- done();
9037
- }
9007
+ async onLocalOffer(offer) {
9008
+ if (!this._pc) {
9009
+ throw new Error("onLocalOffer no peer connection");
9010
+ }
9011
+ if (!offer.sdp) {
9012
+ throw new Error("onLocalOffer no sdp");
9013
+ }
9014
+ this._offerData = _WebrtcBase.parseOffer(offer.sdp);
9015
+ await this._pc.setLocalDescription(offer);
9016
+ const session = await this._getSession();
9017
+ const ingressUrl = this.gresUrl(session);
9018
+ const headers = {
9019
+ ...session.authenticationHeaders(),
9020
+ "Content-Type": "application/sdp"
9021
+ };
9022
+ const response = await this._client.fetch(ingressUrl, {
9023
+ headers,
9024
+ method: "POST",
9025
+ body: offer.sdp
9038
9026
  });
9039
- return this;
9040
- }
9041
- cancel() {
9042
- this._controller.abort("cancelled");
9027
+ if (response.status >= 300) {
9028
+ return Promise.reject(`unknown status code for POST '${ingressUrl}': ${response.status} (${response.statusText})`);
9029
+ }
9030
+ this._eTag = response.headers.get("ETag") ?? "";
9031
+ this._location = response.headers.get("Location");
9032
+ return await this.onRemoteAnswer(new RTCSessionDescription({
9033
+ type: "answer",
9034
+ sdp: await response.text()
9035
+ }));
9043
9036
  }
9044
- };
9045
- AbstractJob.n = 0;
9046
- var UploadJob = class extends AbstractJob {
9047
- get [Symbol.toStringTag]() {
9048
- return "uploadJob";
9037
+ async sendLocalCandidates(candidates) {
9038
+ if (!this._offerData) {
9039
+ throw new Error("sendLocalCandidates no offerData");
9040
+ }
9041
+ const session = await this._getSession();
9042
+ const ingressUrl = this.gresUrl(session, this._location);
9043
+ const headers = {
9044
+ ...session.authenticationHeaders(),
9045
+ "Content-Type": "application/trickle-ice-sdpfrag",
9046
+ "If-Match": this._eTag
9047
+ };
9048
+ const response = await this._client.fetch(ingressUrl, {
9049
+ headers,
9050
+ method: "PATCH",
9051
+ body: _WebrtcBase.generateSdpFragment(this._offerData, candidates)
9052
+ });
9053
+ if (response.status >= 300) {
9054
+ return Promise.reject(`unknown status code for PATCH '${ingressUrl}': ${response.status} (${response.statusText})`);
9055
+ }
9056
+ return this;
9049
9057
  }
9050
- constructor(stream, mimeType, size, videoMode, params, getSession, client, requestLogger) {
9051
- super(params, getSession, client, requestLogger);
9052
- this._uploadStream = stream;
9053
- this._mimeType = mimeType;
9054
- this._size = size;
9055
- this._videoMode = videoMode;
9056
- this._needsFullDuplex = !client.isFullDuplex() && mimeType.startsWith("video/") && videoMode == "stream" /* STREAM */;
9058
+ async onLocalCandidate(evt) {
9059
+ if (evt.candidate !== null) {
9060
+ if (this._eTag === "") {
9061
+ this._queuedCandidates.push(evt.candidate);
9062
+ } else {
9063
+ await this.sendLocalCandidates([evt.candidate]);
9064
+ }
9065
+ }
9066
+ return this;
9057
9067
  }
9058
- async onEvent(event) {
9059
- if (event.type == "prepared") {
9060
- if (!event.source_id) {
9061
- return Promise.reject("did not get a prepared sourceId to simulate full duplex");
9068
+ static linkToIceServers(links) {
9069
+ const servers = links !== null ? links.split(", ").map((link) => {
9070
+ let m = link.match(/^<(.+?)>; rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i);
9071
+ if (!m) {
9072
+ m = link.match(/^(.+?); rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i);
9062
9073
  }
9063
- const session = await this._getSession();
9064
- if (!session.baseUrl) {
9065
- return Promise.reject("session.baseUrl must not ne null");
9074
+ if (!m) {
9075
+ return null;
9066
9076
  }
9067
- let request;
9068
- const videoModeQuery = this._videoMode ? `&videoMode=${this._videoMode}` : "";
9069
- const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=async&sourceId=${event.source_id}${videoModeQuery}`;
9070
- if (this._params) {
9071
- const params = JSON.stringify(this._params);
9072
- const paramBlob = new Blob([params], { type: "application/json" });
9073
- const formData = new FormData();
9074
- const fileBlob = await new Response(this._uploadStream).blob();
9075
- formData.append("params", paramBlob);
9076
- formData.append("file", fileBlob);
9077
- const headers = {
9078
- ...session.authenticationHeaders(),
9079
- Accept: "application/jsonl"
9080
- };
9081
- request = this._client.fetch(postUrl, {
9082
- headers,
9083
- method: "POST",
9084
- body: formData,
9085
- signal: this._controller.signal,
9086
- // @ts-ignore
9087
- duplex: "half",
9088
- eyepop: { responseStreaming: true }
9089
- });
9077
+ const iceServer = {
9078
+ urls: [m[1]],
9079
+ username: WebrtcWhip.unquoteCredential(m[3]),
9080
+ credential: WebrtcWhip.unquoteCredential(m[4])
9081
+ // credentialType: m[3]? "password" : undefined
9082
+ };
9083
+ return iceServer;
9084
+ }) : [];
9085
+ return servers.filter(function(el) {
9086
+ return el != null;
9087
+ });
9088
+ }
9089
+ static editAnswer(answer, videoCodec, audioCodec, videoBitrate, audioBitrate, audioVoice) {
9090
+ const sections = answer.split("m=");
9091
+ for (let i = 0; i < sections.length; i++) {
9092
+ const section = sections[i];
9093
+ if (section.startsWith("video")) {
9094
+ sections[i] = WebrtcWhip.setVideoBitrate(WebrtcWhip.setCodec(section, videoCodec), videoBitrate);
9095
+ } else if (section.startsWith("audio")) {
9096
+ sections[i] = WebrtcWhip.setAudioBitrate(WebrtcWhip.setCodec(section, audioCodec), audioBitrate, audioVoice);
9097
+ }
9098
+ }
9099
+ return sections.join("m=");
9100
+ }
9101
+ static setCodec(section, codec) {
9102
+ const lines = section.split("\r\n");
9103
+ const lines2 = [];
9104
+ const payloadFormats = [];
9105
+ for (const line of lines) {
9106
+ if (!line.startsWith("a=rtpmap:")) {
9107
+ lines2.push(line);
9090
9108
  } else {
9091
- const headers = {
9092
- ...session.authenticationHeaders(),
9093
- Accept: "application/jsonl",
9094
- "Content-Type": this._mimeType
9095
- };
9096
- if (this._size) {
9097
- headers["Content-Length"] = `${this._size}`;
9109
+ if (codec && line.toLowerCase().includes(codec)) {
9110
+ payloadFormats.push(line.slice("a=rtpmap:".length).split(" ")[0]);
9111
+ lines2.push(line);
9098
9112
  }
9099
- request = this._client.fetch(postUrl, {
9100
- headers,
9101
- method: "POST",
9102
- body: this._uploadStream,
9103
- signal: this._controller.signal,
9104
- // @ts-ignore
9105
- duplex: "half",
9106
- eyepop: { responseStreaming: true }
9107
- });
9108
9113
  }
9109
- request.then(async (value) => {
9110
- if (value.status != 204) {
9111
- this._requestLogger.debug(`unexpected status ${value.status} (${await value.text()}) for POST ${postUrl}`);
9114
+ }
9115
+ const lines3 = [];
9116
+ for (const line of lines2) {
9117
+ if (line.startsWith("a=fmtp:")) {
9118
+ if (payloadFormats.includes(line.slice("a=fmtp:".length).split(" ")[0])) {
9119
+ lines3.push(line);
9112
9120
  }
9113
- }).catch((reason) => {
9114
- this._requestLogger.debug(`error ${reason} for POST ${postUrl}`);
9115
- });
9121
+ } else if (line.startsWith("a=rtcp-fb:")) {
9122
+ if (payloadFormats.includes(line.slice("a=rtcp-fb:".length).split(" ")[0])) {
9123
+ lines3.push(line);
9124
+ }
9125
+ } else {
9126
+ lines3.push(line);
9127
+ }
9116
9128
  }
9117
- return Promise.resolve();
9129
+ return lines3.join("\r\n");
9118
9130
  }
9119
- async startJob() {
9120
- const session = await this._getSession();
9121
- if (!session.baseUrl) {
9122
- return Promise.reject("session.baseUrl must not be null");
9131
+ static setVideoBitrate(section, bitrate) {
9132
+ let lines = section.split("\r\n");
9133
+ for (let i = 0; i < lines.length; i++) {
9134
+ if (lines[i].startsWith("c=")) {
9135
+ lines = [...lines.slice(0, i + 1), "b=TIAS:" + (bitrate * 1024).toString(), ...lines.slice(i + 1)];
9136
+ break;
9137
+ }
9123
9138
  }
9124
- let response;
9125
- if (this._needsFullDuplex) {
9126
- const prepareUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/prepareSource?timeout=10s`;
9127
- const headers = {
9128
- ...session.authenticationHeaders(),
9129
- Accept: "application/jsonl"
9130
- };
9131
- response = await this._client.fetch(prepareUrl, {
9132
- headers,
9133
- method: "POST",
9134
- signal: this._controller.signal,
9135
- // @ts-ignore
9136
- duplex: "half",
9137
- eyepop: { responseStreaming: true }
9138
- });
9139
- } else {
9140
- const videoModeQuery = this._videoMode ? `&videoMode=${this._videoMode}` : "";
9141
- const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=sync${videoModeQuery}`;
9142
- if (this._params) {
9143
- const params = JSON.stringify(this._params);
9144
- const paramBlob = new Blob([params], { type: "application/json" });
9145
- const formData = new FormData();
9146
- const fileBlob = await new Response(this._uploadStream).blob();
9147
- formData.append("params", paramBlob);
9148
- formData.append("file", fileBlob);
9149
- const headers = {
9150
- ...session.authenticationHeaders(),
9151
- Accept: "application/jsonl"
9152
- };
9153
- response = await this._client.fetch(postUrl, {
9154
- headers,
9155
- method: "POST",
9156
- body: formData,
9157
- signal: this._controller.signal,
9158
- // @ts-ignore
9159
- duplex: "half",
9160
- eyepop: { responseStreaming: true }
9161
- });
9162
- } else {
9163
- const headers = {
9164
- ...session.authenticationHeaders(),
9165
- Accept: "application/jsonl",
9166
- "Content-Type": this._mimeType
9167
- };
9168
- if (this._size) {
9169
- headers["Content-Length"] = `${this._size}`;
9139
+ return lines.join("\r\n");
9140
+ }
9141
+ static setAudioBitrate(section, bitrate, voice) {
9142
+ let opusPayloadFormat = "";
9143
+ let lines = section.split("\r\n");
9144
+ for (let i = 0; i < lines.length; i++) {
9145
+ if (lines[i].startsWith("a=rtpmap:") && lines[i].toLowerCase().includes("opus/")) {
9146
+ opusPayloadFormat = lines[i].slice("a=rtpmap:".length).split(" ")[0];
9147
+ break;
9148
+ }
9149
+ }
9150
+ if (opusPayloadFormat === "") {
9151
+ return section;
9152
+ }
9153
+ for (let i = 0; i < lines.length; i++) {
9154
+ if (lines[i].startsWith("a=fmtp:" + opusPayloadFormat + " ")) {
9155
+ if (voice) {
9156
+ lines[i] = "a=fmtp:" + opusPayloadFormat + " minptime=10;useinbandfec=1;maxaveragebitrate=" + ((bitrate ?? 0) * 1024).toString();
9157
+ } else {
9158
+ lines[i] = "a=fmtp:" + opusPayloadFormat + " maxplaybackrate=48000;stereo=1;sprop-stereo=1;maxaveragebitrate" + ((bitrate ?? 0) * 1024).toString();
9170
9159
  }
9171
- response = await this._client.fetch(postUrl, {
9172
- headers,
9173
- method: "POST",
9174
- body: this._uploadStream,
9175
- signal: this._controller.signal,
9176
- // @ts-ignore
9177
- duplex: "half"
9178
- });
9179
9160
  }
9180
9161
  }
9181
- return response;
9182
- }
9183
- };
9184
- var LoadFromJob = class extends AbstractJob {
9185
- constructor(location, params, getSession, client, requestLogger) {
9186
- super(params, getSession, client, requestLogger);
9187
- this._location = location;
9188
- }
9189
- get [Symbol.toStringTag]() {
9190
- return "loadFromJob";
9162
+ return lines.join("\r\n");
9191
9163
  }
9192
- async startJob() {
9193
- const session = await this._getSession();
9194
- if (!session.baseUrl) {
9195
- return Promise.reject("session.baseUrl must not ne null");
9164
+ static parseOffer(offer) {
9165
+ let medias = [];
9166
+ let iceUfrag = "";
9167
+ let icePwd = "";
9168
+ for (const line of offer.split("\r\n")) {
9169
+ if (line.startsWith("m=")) {
9170
+ medias.push(line.slice("m=".length));
9171
+ } else if (iceUfrag === "" && line.startsWith("a=ice-ufrag:")) {
9172
+ iceUfrag = line.slice("a=ice-ufrag:".length);
9173
+ } else if (icePwd === "" && line.startsWith("a=ice-pwd:")) {
9174
+ icePwd = line.slice("a=ice-pwd:".length);
9175
+ }
9196
9176
  }
9197
- const body = {
9198
- sourceType: "URL",
9199
- url: this._location,
9200
- params: this._params
9201
- };
9202
- const headers = {
9203
- ...session.authenticationHeaders(),
9204
- Accept: "application/jsonl",
9205
- "Content-Type": "application/json"
9177
+ return {
9178
+ iceUfrag,
9179
+ icePwd,
9180
+ medias
9206
9181
  };
9207
- const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=sync`;
9208
- return await this._client.fetch(patchUrl, {
9209
- headers,
9210
- method: "PATCH",
9211
- body: JSON.stringify(body),
9212
- signal: this._controller.signal,
9213
- // @ts-ignore
9214
- eyepop: { responseStreaming: true }
9215
- });
9216
- }
9217
- };
9218
- var LoadFromAssetUuidJob = class extends AbstractJob {
9219
- constructor(assetUuid, params, getSession, client, requestLogger) {
9220
- super(params, getSession, client, requestLogger);
9221
- this._assetUuid = assetUuid;
9222
9182
  }
9223
- get [Symbol.toStringTag]() {
9224
- return "loadFromAssetUuidJob";
9183
+ static generateSdpFragment(offerData, candidates) {
9184
+ const candidatesByMedia = /* @__PURE__ */ new Map();
9185
+ for (const candidate of candidates) {
9186
+ if (candidate.sdpMLineIndex !== null) {
9187
+ const mid2 = candidate.sdpMLineIndex;
9188
+ let matched = candidatesByMedia.get(mid2);
9189
+ if (matched === void 0) {
9190
+ matched = [];
9191
+ candidatesByMedia.set(mid2, matched);
9192
+ }
9193
+ matched.push(candidate);
9194
+ }
9195
+ }
9196
+ let frag = "a=ice-ufrag:" + offerData.iceUfrag + "\r\na=ice-pwd:" + offerData.icePwd + "\r\n";
9197
+ let mid = 0;
9198
+ for (const media of offerData.medias) {
9199
+ let matched = candidatesByMedia.get(mid);
9200
+ if (matched !== void 0) {
9201
+ frag += "m=" + media + "\r\na=mid:" + mid + "\r\n";
9202
+ for (const candidate of matched) {
9203
+ frag += "a=" + candidate.candidate + "\r\n";
9204
+ }
9205
+ }
9206
+ mid++;
9207
+ }
9208
+ return frag;
9225
9209
  }
9226
- async startJob() {
9227
- const session = await this._getSession();
9228
- if (!session.baseUrl) {
9229
- return Promise.reject("session.baseUrl must not ne null");
9210
+ static unquoteCredential(v) {
9211
+ if (!v) {
9212
+ return "";
9230
9213
  }
9231
- const body = {
9232
- sourceType: "ASSET_UUID",
9233
- assetUuid: this._assetUuid,
9234
- params: this._params
9235
- };
9236
- const headers = {
9237
- ...session.authenticationHeaders(),
9238
- Accept: "application/jsonl",
9239
- "Content-Type": "application/json"
9240
- };
9241
- const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=sync`;
9242
- return await this._client.fetch(patchUrl, {
9243
- headers,
9244
- method: "PATCH",
9245
- body: JSON.stringify(body),
9246
- signal: this._controller.signal,
9247
- // @ts-ignore
9248
- eyepop: { responseStreaming: true }
9249
- });
9214
+ return JSON.parse(`"${v}"`);
9250
9215
  }
9251
9216
  };
9252
- var LoadLiveIngressJob = class extends AbstractJob {
9253
- constructor(ingressId, params, getSession, client, requestLogger) {
9254
- super(params, getSession, client, requestLogger);
9255
- this._ingressId = ingressId;
9256
- }
9257
- get [Symbol.toStringTag]() {
9258
- return "loadLiveIngressJob";
9217
+
9218
+ // worker/webrtc_whip.ts
9219
+ var WebrtcWhip = class extends WebrtcBase {
9220
+ constructor(stream, getSession, client, is_pipeline_direct, requestLogger) {
9221
+ super(getSession, client, (0, import_uuid.v4)().toString(), "liveIngress/whip", is_pipeline_direct, requestLogger);
9222
+ this._stream = stream;
9259
9223
  }
9260
- async startJob() {
9224
+ async start() {
9261
9225
  const session = await this._getSession();
9262
- if (!session.baseUrl) {
9263
- return Promise.reject("session.baseUrl must not ne null");
9226
+ const ingressUrl = this.gresUrl(session);
9227
+ const response = await this._client.fetch(ingressUrl, {
9228
+ headers: session.authenticationHeaders(),
9229
+ method: "GET"
9230
+ });
9231
+ if (response.status >= 300) {
9232
+ return Promise.reject(`unknown status code for GET '${ingressUrl}': ${response.status} (${response.statusText})`);
9264
9233
  }
9265
- const body = {
9266
- sourceType: "LIVE_INGRESS",
9267
- liveIngressId: this._ingressId,
9268
- params: this._params
9269
- };
9270
- const headers = {
9271
- ...session.authenticationHeaders(),
9272
- Accept: "application/jsonl",
9273
- "Content-Type": "application/json"
9274
- };
9275
- const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=preempt&processing=sync`;
9276
- return await this._client.fetch(patchUrl, {
9277
- headers,
9278
- method: "PATCH",
9279
- body: JSON.stringify(body),
9280
- signal: this._controller.signal,
9281
- // @ts-ignore
9282
- eyepop: { responseStreaming: true }
9283
- });
9284
- }
9285
- };
9286
-
9287
- // worker/webrtc_whip.ts
9288
- var import_uuid = require("uuid");
9289
-
9290
- // worker/webrtc_base.ts
9291
- var import_path = __toESM(require("path"));
9292
- var WebrtcBase = class _WebrtcBase {
9293
- constructor(getSession, client, ingressId, urlBasePath, requestLogger) {
9294
- this._getSession = getSession;
9295
- this._client = client;
9296
- this._requestLogger = requestLogger;
9297
- this._ingressId = ingressId;
9298
- this._urlPath = `${urlBasePath}/${this._ingressId}`;
9299
- this._pc = null;
9300
- this._eTag = "";
9301
- this._location = null;
9302
- this._queuedCandidates = [];
9303
- this._offerData = null;
9304
- }
9305
- async close() {
9306
- const pc = this._pc;
9307
- this._pc = null;
9308
- if (pc) {
9309
- return pc.close();
9310
- }
9311
- }
9312
- ingressId() {
9313
- return this._ingressId;
9234
+ return await this.onIceServers(response);
9314
9235
  }
9315
- gresUrl(session, location = null) {
9316
- let urlPath;
9317
- if (location) {
9318
- urlPath = import_path.default.join(this._urlPath, location);
9319
- } else {
9320
- urlPath = this._urlPath;
9321
- }
9322
- let baseUrl;
9323
- if (session.baseUrl?.endsWith("/")) {
9324
- baseUrl = session.baseUrl;
9236
+ async stream() {
9237
+ if (this._stream) {
9238
+ return Promise.resolve(this._stream);
9325
9239
  } else {
9326
- baseUrl = session.baseUrl + "/";
9240
+ return Promise.reject("fetching stream failed");
9327
9241
  }
9328
- return new URL(urlPath, baseUrl);
9329
9242
  }
9330
- async onLocalOffer(offer) {
9243
+ async onRemoteAnswer(answer) {
9331
9244
  if (!this._pc) {
9332
9245
  throw new Error("onLocalOffer no peer connection");
9333
9246
  }
9334
- if (!offer.sdp) {
9335
- throw new Error("onLocalOffer no sdp");
9336
- }
9337
- this._offerData = _WebrtcBase.parseOffer(offer.sdp);
9338
- await this._pc.setLocalDescription(offer);
9339
- const session = await this._getSession();
9340
- const ingressUrl = this.gresUrl(session);
9341
- const headers = {
9342
- ...session.authenticationHeaders(),
9343
- "Content-Type": "application/sdp"
9344
- };
9345
- const response = await this._client.fetch(ingressUrl, {
9346
- headers,
9347
- method: "POST",
9348
- body: offer.sdp
9349
- });
9350
- if (response.status != 201) {
9351
- return Promise.reject(`unknown status code for POST '${ingressUrl}': ${response.status} (${response.statusText})`);
9247
+ await this._pc.setRemoteDescription(new RTCSessionDescription(answer));
9248
+ if (this._queuedCandidates.length !== 0) {
9249
+ await this.sendLocalCandidates(this._queuedCandidates);
9250
+ this._queuedCandidates = [];
9352
9251
  }
9353
- this._eTag = response.headers.get("ETag") ?? "";
9354
- this._location = response.headers.get("Location");
9355
- return await this.onRemoteAnswer(new RTCSessionDescription({
9356
- type: "answer",
9357
- sdp: await response.text()
9358
- }));
9252
+ return this;
9359
9253
  }
9360
- async sendLocalCandidates(candidates) {
9361
- if (!this._offerData) {
9362
- throw new Error("sendLocalCandidates no offerData");
9254
+ async onIceServers(response) {
9255
+ if (!this._stream) {
9256
+ throw new Error("onIceServers no stream");
9363
9257
  }
9364
- const session = await this._getSession();
9365
- const ingressUrl = this.gresUrl(session, this._location);
9366
- const headers = {
9367
- ...session.authenticationHeaders(),
9368
- "Content-Type": "application/trickle-ice-sdpfrag",
9369
- "If-Match": this._eTag
9370
- };
9371
- const response = await this._client.fetch(ingressUrl, {
9372
- headers,
9373
- method: "PATCH",
9374
- body: _WebrtcBase.generateSdpFragment(this._offerData, candidates)
9258
+ const stream = this._stream;
9259
+ const pc = new RTCPeerConnection({
9260
+ iceServers: WebrtcBase.linkToIceServers(response.headers.get("Link"))
9261
+ });
9262
+ this._pc = pc;
9263
+ pc.onicecandidate = (evt) => this.onLocalCandidate(evt);
9264
+ return new Promise(async (resolve, reject) => {
9265
+ pc.oniceconnectionstatechange = () => {
9266
+ switch (pc.iceConnectionState) {
9267
+ case "failed":
9268
+ case "disconnected":
9269
+ reject(`peer connection: ${pc.iceConnectionState}`);
9270
+ break;
9271
+ case "connected":
9272
+ resolve(this);
9273
+ break;
9274
+ }
9275
+ };
9276
+ stream.getTracks().forEach((track) => {
9277
+ this._requestLogger.debug("onIceServers our stream has track: %s", track);
9278
+ pc.addTrack(track, stream);
9279
+ });
9280
+ stream.addEventListener("addtrack", (trackEvent) => {
9281
+ this._requestLogger.debug("onIceServers got lazy track: ", trackEvent.track);
9282
+ pc.addTrack(trackEvent.track, stream);
9283
+ });
9284
+ await this.onLocalOffer(await pc.createOffer());
9375
9285
  });
9376
- if (response.status != 204) {
9377
- return Promise.reject(`unknown status code for PATCH '${ingressUrl}': ${response.status} (${response.statusText})`);
9378
- }
9379
- return this;
9380
9286
  }
9381
- async onLocalCandidate(evt) {
9382
- if (evt.candidate !== null) {
9383
- if (this._eTag === "") {
9384
- this._queuedCandidates.push(evt.candidate);
9385
- } else {
9386
- await this.sendLocalCandidates([evt.candidate]);
9387
- }
9287
+ async close() {
9288
+ const stream = this._stream;
9289
+ this._stream = null;
9290
+ if (stream) {
9291
+ const tracks = stream.getTracks();
9292
+ tracks.forEach((track) => {
9293
+ track.stop();
9294
+ });
9388
9295
  }
9389
- return this;
9296
+ return super.close();
9390
9297
  }
9391
- static linkToIceServers(links) {
9392
- const servers = links !== null ? links.split(", ").map((link) => {
9393
- const m = link.match(/^<(.+?)>; rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i);
9394
- if (!m) {
9395
- return null;
9396
- }
9397
- const iceServer = {
9398
- urls: [m[1]],
9399
- username: WebrtcWhip.unquoteCredential(m[3]),
9400
- credential: WebrtcWhip.unquoteCredential(m[4])
9401
- // credentialType: m[3]? "password" : undefined
9402
- };
9403
- return iceServer;
9404
- }) : [];
9405
- return servers.filter(function(el) {
9406
- return el != null;
9407
- });
9298
+ };
9299
+
9300
+ // worker/jobs.ts
9301
+ var AbstractJob = class {
9302
+ constructor(params, getSession, client, requestLogger) {
9303
+ this._responseStream = null;
9304
+ this._controller = new AbortController();
9305
+ this._getSession = getSession;
9306
+ this._params = params ?? null;
9307
+ this._client = client;
9308
+ this._requestLogger = requestLogger;
9408
9309
  }
9409
- static editAnswer(answer, videoCodec, audioCodec, videoBitrate, audioBitrate, audioVoice) {
9410
- const sections = answer.split("m=");
9411
- for (let i = 0; i < sections.length; i++) {
9412
- const section = sections[i];
9413
- if (section.startsWith("video")) {
9414
- sections[i] = WebrtcWhip.setVideoBitrate(WebrtcWhip.setCodec(section, videoCodec), videoBitrate);
9415
- } else if (section.startsWith("audio")) {
9416
- sections[i] = WebrtcWhip.setAudioBitrate(WebrtcWhip.setCodec(section, audioCodec), audioBitrate, audioVoice);
9417
- }
9310
+ [Symbol.asyncIterator]() {
9311
+ if (!this._responseStream) {
9312
+ throw Error("logical bug");
9418
9313
  }
9419
- return sections.join("m=");
9314
+ return Stream.iterFromReadableStream(this._responseStream, this._controller, async (event) => {
9315
+ return await this.onEvent(event);
9316
+ });
9420
9317
  }
9421
- static setCodec(section, codec) {
9422
- const lines = section.split("\r\n");
9423
- const lines2 = [];
9424
- const payloadFormats = [];
9425
- for (const line of lines) {
9426
- if (!line.startsWith("a=rtpmap:")) {
9427
- lines2.push(line);
9428
- } else {
9429
- if (codec && line.toLowerCase().includes(codec)) {
9430
- payloadFormats.push(line.slice("a=rtpmap:".length).split(" ")[0]);
9431
- lines2.push(line);
9432
- }
9433
- }
9318
+ async startJob() {
9319
+ return Promise.reject("abstract class");
9320
+ }
9321
+ async onEvent(event) {
9322
+ if (event.type == "error") {
9323
+ return Promise.reject(`Error event for source ${event.source_id}: ${event.message}`);
9434
9324
  }
9435
- const lines3 = [];
9436
- for (const line of lines2) {
9437
- if (line.startsWith("a=fmtp:")) {
9438
- if (payloadFormats.includes(line.slice("a=fmtp:".length).split(" ")[0])) {
9439
- lines3.push(line);
9440
- }
9441
- } else if (line.startsWith("a=rtcp-fb:")) {
9442
- if (payloadFormats.includes(line.slice("a=rtcp-fb:".length).split(" ")[0])) {
9443
- lines3.push(line);
9325
+ return Promise.resolve();
9326
+ }
9327
+ start(done, status) {
9328
+ this._responseStream = new Promise(async (resolve, reject) => {
9329
+ try {
9330
+ let retries = 1;
9331
+ while (true) {
9332
+ try {
9333
+ const response = await this.startJob();
9334
+ if (response.status == 401) {
9335
+ status(response.status);
9336
+ if (retries--) {
9337
+ this._requestLogger.info("received 401 response, attempt to reauthorize once");
9338
+ } else {
9339
+ reject(`response ${response.status}: ${response.statusText}`);
9340
+ break;
9341
+ }
9342
+ } else if (response.status == 404) {
9343
+ status(response.status);
9344
+ if (retries--) {
9345
+ this._requestLogger.info("received 404 response, attempt to restart pop");
9346
+ } else {
9347
+ reject(`response ${response.status}: ${response.statusText}`);
9348
+ break;
9349
+ }
9350
+ } else if (response.status != 200) {
9351
+ reject(`upload error ${response.status}: ${await response.text()}`);
9352
+ break;
9353
+ } else if (response.body) {
9354
+ resolve(response.body);
9355
+ break;
9356
+ } else {
9357
+ const text = await response.text();
9358
+ if (text) {
9359
+ resolve(readableStreamFromString(text));
9360
+ break;
9361
+ } else {
9362
+ reject("response body is empty");
9363
+ break;
9364
+ }
9365
+ }
9366
+ } catch (error) {
9367
+ if (retries--) {
9368
+ this._requestLogger.info("unknown, attempt to restart pop (as if we received 404)", error);
9369
+ status(404);
9370
+ } else {
9371
+ status(500);
9372
+ reject(error);
9373
+ break;
9374
+ }
9375
+ }
9444
9376
  }
9445
- } else {
9446
- lines3.push(line);
9377
+ } catch (reason) {
9378
+ reject(reason);
9379
+ } finally {
9380
+ done();
9447
9381
  }
9448
- }
9449
- return lines3.join("\r\n");
9382
+ });
9383
+ return this;
9450
9384
  }
9451
- static setVideoBitrate(section, bitrate) {
9452
- let lines = section.split("\r\n");
9453
- for (let i = 0; i < lines.length; i++) {
9454
- if (lines[i].startsWith("c=")) {
9455
- lines = [...lines.slice(0, i + 1), "b=TIAS:" + (bitrate * 1024).toString(), ...lines.slice(i + 1)];
9456
- break;
9457
- }
9458
- }
9459
- return lines.join("\r\n");
9385
+ cancel() {
9386
+ this._controller.abort("cancelled");
9460
9387
  }
9461
- static setAudioBitrate(section, bitrate, voice) {
9462
- let opusPayloadFormat = "";
9463
- let lines = section.split("\r\n");
9464
- for (let i = 0; i < lines.length; i++) {
9465
- if (lines[i].startsWith("a=rtpmap:") && lines[i].toLowerCase().includes("opus/")) {
9466
- opusPayloadFormat = lines[i].slice("a=rtpmap:".length).split(" ")[0];
9467
- break;
9388
+ };
9389
+ AbstractJob.n = 0;
9390
+ var UploadJob = class extends AbstractJob {
9391
+ get [Symbol.toStringTag]() {
9392
+ return "uploadJob";
9393
+ }
9394
+ constructor(stream, mimeType, size, videoMode, params, getSession, client, requestLogger) {
9395
+ super(params, getSession, client, requestLogger);
9396
+ this._uploadStream = stream;
9397
+ this._mimeType = mimeType;
9398
+ this._size = size;
9399
+ this._videoMode = videoMode;
9400
+ this._needsFullDuplex = !client.isFullDuplex() && mimeType.startsWith("video/") && videoMode == "stream" /* STREAM */;
9401
+ }
9402
+ async onEvent(event) {
9403
+ if (event.type == "prepared") {
9404
+ if (!event.source_id) {
9405
+ return Promise.reject("did not get a prepared sourceId to simulate full duplex");
9468
9406
  }
9469
- }
9470
- if (opusPayloadFormat === "") {
9471
- return section;
9472
- }
9473
- for (let i = 0; i < lines.length; i++) {
9474
- if (lines[i].startsWith("a=fmtp:" + opusPayloadFormat + " ")) {
9475
- if (voice) {
9476
- lines[i] = "a=fmtp:" + opusPayloadFormat + " minptime=10;useinbandfec=1;maxaveragebitrate=" + ((bitrate ?? 0) * 1024).toString();
9477
- } else {
9478
- lines[i] = "a=fmtp:" + opusPayloadFormat + " maxplaybackrate=48000;stereo=1;sprop-stereo=1;maxaveragebitrate" + ((bitrate ?? 0) * 1024).toString();
9407
+ const session = await this._getSession();
9408
+ if (!session.baseUrl) {
9409
+ return Promise.reject("session.baseUrl must not ne null");
9410
+ }
9411
+ let request;
9412
+ const videoModeQuery = this._videoMode ? `&videoMode=${this._videoMode}` : "";
9413
+ const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=async&sourceId=${event.source_id}${videoModeQuery}`;
9414
+ if (this._params) {
9415
+ const params = JSON.stringify(this._params);
9416
+ const paramBlob = new Blob([params], { type: "application/json" });
9417
+ const formData = new FormData();
9418
+ const fileBlob = await new Response(this._uploadStream).blob();
9419
+ formData.append("params", paramBlob);
9420
+ formData.append("file", fileBlob);
9421
+ const headers = {
9422
+ ...session.authenticationHeaders(),
9423
+ Accept: "application/jsonl"
9424
+ };
9425
+ request = this._client.fetch(postUrl, {
9426
+ headers,
9427
+ method: "POST",
9428
+ body: formData,
9429
+ signal: this._controller.signal,
9430
+ // @ts-ignore
9431
+ duplex: "half",
9432
+ eyepop: { responseStreaming: true }
9433
+ });
9434
+ } else {
9435
+ const headers = {
9436
+ ...session.authenticationHeaders(),
9437
+ Accept: "application/jsonl",
9438
+ "Content-Type": this._mimeType
9439
+ };
9440
+ if (this._size) {
9441
+ headers["Content-Length"] = `${this._size}`;
9479
9442
  }
9443
+ request = this._client.fetch(postUrl, {
9444
+ headers,
9445
+ method: "POST",
9446
+ body: this._uploadStream,
9447
+ signal: this._controller.signal,
9448
+ // @ts-ignore
9449
+ duplex: "half",
9450
+ eyepop: { responseStreaming: true }
9451
+ });
9480
9452
  }
9453
+ request.then(async (value) => {
9454
+ if (value.status != 204) {
9455
+ this._requestLogger.debug(`unexpected status ${value.status} (${await value.text()}) for POST ${postUrl}`);
9456
+ }
9457
+ }).catch((reason) => {
9458
+ this._requestLogger.debug(`error ${reason} for POST ${postUrl}`);
9459
+ });
9481
9460
  }
9482
- return lines.join("\r\n");
9483
- }
9484
- static parseOffer(offer) {
9485
- let medias = [];
9486
- let iceUfrag = "";
9487
- let icePwd = "";
9488
- for (const line of offer.split("\r\n")) {
9489
- if (line.startsWith("m=")) {
9490
- medias.push(line.slice("m=".length));
9491
- } else if (iceUfrag === "" && line.startsWith("a=ice-ufrag:")) {
9492
- iceUfrag = line.slice("a=ice-ufrag:".length);
9493
- } else if (icePwd === "" && line.startsWith("a=ice-pwd:")) {
9494
- icePwd = line.slice("a=ice-pwd:".length);
9495
- }
9496
- }
9497
- return {
9498
- iceUfrag,
9499
- icePwd,
9500
- medias
9501
- };
9461
+ return Promise.resolve();
9502
9462
  }
9503
- static generateSdpFragment(offerData, candidates) {
9504
- const candidatesByMedia = /* @__PURE__ */ new Map();
9505
- for (const candidate of candidates) {
9506
- if (candidate.sdpMLineIndex !== null) {
9507
- const mid2 = candidate.sdpMLineIndex;
9508
- let matched = candidatesByMedia.get(mid2);
9509
- if (matched === void 0) {
9510
- matched = [];
9511
- candidatesByMedia.set(mid2, matched);
9512
- }
9513
- matched.push(candidate);
9514
- }
9463
+ async startJob() {
9464
+ const session = await this._getSession();
9465
+ if (!session.baseUrl) {
9466
+ return Promise.reject("session.baseUrl must not be null");
9515
9467
  }
9516
- let frag = "a=ice-ufrag:" + offerData.iceUfrag + "\r\na=ice-pwd:" + offerData.icePwd + "\r\n";
9517
- let mid = 0;
9518
- for (const media of offerData.medias) {
9519
- let matched = candidatesByMedia.get(mid);
9520
- if (matched !== void 0) {
9521
- frag += "m=" + media + "\r\na=mid:" + mid + "\r\n";
9522
- for (const candidate of matched) {
9523
- frag += "a=" + candidate.candidate + "\r\n";
9468
+ let response;
9469
+ if (this._needsFullDuplex) {
9470
+ const prepareUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/prepareSource?timeout=10s`;
9471
+ const headers = {
9472
+ ...session.authenticationHeaders(),
9473
+ Accept: "application/jsonl"
9474
+ };
9475
+ response = await this._client.fetch(prepareUrl, {
9476
+ headers,
9477
+ method: "POST",
9478
+ signal: this._controller.signal,
9479
+ // @ts-ignore
9480
+ duplex: "half",
9481
+ eyepop: { responseStreaming: true }
9482
+ });
9483
+ } else {
9484
+ const videoModeQuery = this._videoMode ? `&videoMode=${this._videoMode}` : "";
9485
+ const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=sync${videoModeQuery}`;
9486
+ if (this._params) {
9487
+ const params = JSON.stringify(this._params);
9488
+ const paramBlob = new Blob([params], { type: "application/json" });
9489
+ const formData = new FormData();
9490
+ const fileBlob = await new Response(this._uploadStream).blob();
9491
+ formData.append("params", paramBlob);
9492
+ formData.append("file", fileBlob);
9493
+ const headers = {
9494
+ ...session.authenticationHeaders(),
9495
+ Accept: "application/jsonl"
9496
+ };
9497
+ response = await this._client.fetch(postUrl, {
9498
+ headers,
9499
+ method: "POST",
9500
+ body: formData,
9501
+ signal: this._controller.signal,
9502
+ // @ts-ignore
9503
+ duplex: "half",
9504
+ eyepop: { responseStreaming: true }
9505
+ });
9506
+ } else {
9507
+ const headers = {
9508
+ ...session.authenticationHeaders(),
9509
+ Accept: "application/jsonl",
9510
+ "Content-Type": this._mimeType
9511
+ };
9512
+ if (this._size) {
9513
+ headers["Content-Length"] = `${this._size}`;
9524
9514
  }
9515
+ response = await this._client.fetch(postUrl, {
9516
+ headers,
9517
+ method: "POST",
9518
+ body: this._uploadStream,
9519
+ signal: this._controller.signal,
9520
+ // @ts-ignore
9521
+ duplex: "half"
9522
+ });
9525
9523
  }
9526
- mid++;
9527
- }
9528
- return frag;
9529
- }
9530
- static unquoteCredential(v) {
9531
- if (!v) {
9532
- return "";
9533
9524
  }
9534
- return JSON.parse(`"${v}"`);
9525
+ return response;
9535
9526
  }
9536
9527
  };
9537
-
9538
- // worker/webrtc_whip.ts
9539
- var WebrtcWhip = class extends WebrtcBase {
9540
- constructor(stream, getSession, client, requestLogger) {
9541
- super(getSession, client, (0, import_uuid.v4)().toString(), "liveIngress/whip", requestLogger);
9542
- this._stream = stream;
9528
+ var LoadFromJob = class extends AbstractJob {
9529
+ constructor(location, params, getSession, client, requestLogger) {
9530
+ super(params, getSession, client, requestLogger);
9531
+ this._location = location;
9543
9532
  }
9544
- async start() {
9545
- const session = await this._getSession();
9546
- const ingressUrl = this.gresUrl(session);
9547
- const response = await this._client.fetch(ingressUrl, {
9548
- headers: session.authenticationHeaders(),
9549
- method: "GET"
9550
- });
9551
- if (response.status >= 300) {
9552
- return Promise.reject(`unknown status code for GET '${ingressUrl}': ${response.status} (${response.statusText})`);
9553
- }
9554
- return await this.onIceServers(response);
9533
+ get [Symbol.toStringTag]() {
9534
+ return "loadFromJob";
9555
9535
  }
9556
- async stream() {
9557
- if (this._stream) {
9558
- return Promise.resolve(this._stream);
9559
- } else {
9560
- return Promise.reject("fetching stream failed");
9536
+ async startJob() {
9537
+ const session = await this._getSession();
9538
+ if (!session.baseUrl) {
9539
+ return Promise.reject("session.baseUrl must not ne null");
9561
9540
  }
9541
+ const body = {
9542
+ sourceType: "URL",
9543
+ url: this._location,
9544
+ params: this._params
9545
+ };
9546
+ const headers = {
9547
+ ...session.authenticationHeaders(),
9548
+ Accept: "application/jsonl",
9549
+ "Content-Type": "application/json"
9550
+ };
9551
+ const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=sync`;
9552
+ return await this._client.fetch(patchUrl, {
9553
+ headers,
9554
+ method: "PATCH",
9555
+ body: JSON.stringify(body),
9556
+ signal: this._controller.signal,
9557
+ // @ts-ignore
9558
+ eyepop: { responseStreaming: true }
9559
+ });
9562
9560
  }
9563
- async onRemoteAnswer(answer) {
9564
- if (!this._pc) {
9565
- throw new Error("onLocalOffer no peer connection");
9566
- }
9567
- await this._pc.setRemoteDescription(new RTCSessionDescription(answer));
9568
- if (this._queuedCandidates.length !== 0) {
9569
- await this.sendLocalCandidates(this._queuedCandidates);
9570
- this._queuedCandidates = [];
9571
- }
9572
- return this;
9561
+ };
9562
+ var LoadFromAssetUuidJob = class extends AbstractJob {
9563
+ constructor(assetUuid, params, getSession, client, requestLogger) {
9564
+ super(params, getSession, client, requestLogger);
9565
+ this._assetUuid = assetUuid;
9573
9566
  }
9574
- async onIceServers(response) {
9575
- if (!this._stream) {
9576
- throw new Error("onIceServers no stream");
9577
- }
9578
- const stream = this._stream;
9579
- const pc = new RTCPeerConnection({
9580
- iceServers: WebrtcBase.linkToIceServers(response.headers.get("Link"))
9581
- });
9582
- this._pc = pc;
9583
- pc.onicecandidate = (evt) => this.onLocalCandidate(evt);
9584
- return new Promise(async (resolve, reject) => {
9585
- pc.oniceconnectionstatechange = () => {
9586
- switch (pc.iceConnectionState) {
9587
- case "failed":
9588
- case "disconnected":
9589
- reject(`peer connection: ${pc.iceConnectionState}`);
9590
- break;
9591
- case "connected":
9592
- resolve(this);
9593
- break;
9594
- }
9595
- };
9596
- stream.getTracks().forEach((track) => {
9597
- this._requestLogger.debug("onIceServers our stream has track: %s", track);
9598
- pc.addTrack(track, stream);
9599
- });
9600
- stream.addEventListener("addtrack", (trackEvent) => {
9601
- this._requestLogger.debug("onIceServers got lazy track: ", trackEvent.track);
9602
- pc.addTrack(trackEvent.track, stream);
9603
- });
9604
- await this.onLocalOffer(await pc.createOffer());
9605
- });
9567
+ get [Symbol.toStringTag]() {
9568
+ return "loadFromAssetUuidJob";
9606
9569
  }
9607
- async close() {
9608
- const stream = this._stream;
9609
- this._stream = null;
9610
- if (stream) {
9611
- const tracks = stream.getTracks();
9612
- tracks.forEach((track) => {
9613
- track.stop();
9614
- });
9570
+ async startJob() {
9571
+ const session = await this._getSession();
9572
+ if (!session.baseUrl) {
9573
+ return Promise.reject("session.baseUrl must not ne null");
9615
9574
  }
9616
- return super.close();
9575
+ const body = {
9576
+ sourceType: "ASSET_UUID",
9577
+ assetUuid: this._assetUuid,
9578
+ params: this._params
9579
+ };
9580
+ const headers = {
9581
+ ...session.authenticationHeaders(),
9582
+ Accept: "application/jsonl",
9583
+ "Content-Type": "application/json"
9584
+ };
9585
+ const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=sync`;
9586
+ return await this._client.fetch(patchUrl, {
9587
+ headers,
9588
+ method: "PATCH",
9589
+ body: JSON.stringify(body),
9590
+ signal: this._controller.signal,
9591
+ // @ts-ignore
9592
+ eyepop: { responseStreaming: true }
9593
+ });
9617
9594
  }
9618
9595
  };
9619
-
9620
- // worker/webrtc_whep.ts
9621
- var WebrtcWhep = class extends WebrtcBase {
9622
- constructor(ingressId, getSession, client, requestLogger) {
9623
- super(getSession, client, ingressId, "liveIngress/whep", requestLogger);
9624
- this._stream = null;
9625
- }
9626
- async close() {
9627
- const stream = this._stream;
9628
- this._stream = null;
9629
- if (stream) {
9630
- const tracks = stream.getTracks();
9631
- tracks.forEach((track) => {
9632
- track.stop();
9633
- });
9634
- }
9635
- return super.close();
9596
+ var LoadLiveIngressJob = class extends AbstractJob {
9597
+ constructor(ingressId, params, getSession, client, requestLogger) {
9598
+ super(params, getSession, client, requestLogger);
9599
+ this._ingressId = ingressId;
9636
9600
  }
9637
- async stream() {
9638
- if (this._stream) {
9639
- return Promise.resolve(this._stream);
9640
- } else {
9641
- return Promise.reject("fetching stream failed");
9642
- }
9601
+ get [Symbol.toStringTag]() {
9602
+ return "loadLiveIngressJob";
9643
9603
  }
9644
- async start() {
9604
+ async startJob() {
9645
9605
  const session = await this._getSession();
9646
- const egressUrl = this.gresUrl(session);
9647
- const response = await this._client.fetch(egressUrl, {
9648
- headers: session.authenticationHeaders(),
9649
- method: "GET"
9650
- });
9651
- if (response.status >= 300) {
9652
- return Promise.reject(`unknown status code for GET '${egressUrl}': ${response.status} (${response.statusText})`);
9606
+ if (!session.baseUrl) {
9607
+ return Promise.reject("session.baseUrl must not ne null");
9653
9608
  }
9654
- return this.onIceServers(response);
9609
+ const body = {
9610
+ sourceType: "LIVE_INGRESS",
9611
+ liveIngressId: this._ingressId,
9612
+ params: this._params
9613
+ };
9614
+ const headers = {
9615
+ ...session.authenticationHeaders(),
9616
+ Accept: "application/jsonl",
9617
+ "Content-Type": "application/json"
9618
+ };
9619
+ const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=preempt&processing=sync`;
9620
+ return await this._client.fetch(patchUrl, {
9621
+ headers,
9622
+ method: "PATCH",
9623
+ body: JSON.stringify(body),
9624
+ signal: this._controller.signal,
9625
+ // @ts-ignore
9626
+ eyepop: { responseStreaming: true }
9627
+ });
9655
9628
  }
9656
- async onRemoteAnswer(answer) {
9657
- if (!this._pc) {
9658
- throw new Error("onLocalOffer no peer connection");
9659
- }
9660
- await this._pc.setRemoteDescription(new RTCSessionDescription(answer));
9661
- if (this._queuedCandidates.length !== 0) {
9662
- await this.sendLocalCandidates(this._queuedCandidates);
9663
- this._queuedCandidates = [];
9664
- }
9665
- return this;
9629
+ };
9630
+ var LoadMediaStreamJob = class extends AbstractJob {
9631
+ constructor(mediaStream, params, getSession, client, requestLogger) {
9632
+ super(params, getSession, client, requestLogger);
9633
+ this._mediaStream = mediaStream;
9666
9634
  }
9667
- async onIceServers(response) {
9668
- if (this._stream) {
9669
- throw new Error("onIceServers has stream");
9635
+ get [Symbol.toStringTag]() {
9636
+ return "loadLiveIngressJob";
9637
+ }
9638
+ async startJob() {
9639
+ const session = await this._getSession();
9640
+ if (!session.baseUrl) {
9641
+ return Promise.reject("session.baseUrl must not ne null");
9670
9642
  }
9671
- const pc = new RTCPeerConnection({
9672
- iceServers: WebrtcBase.linkToIceServers(response.headers.get("Link"))
9643
+ const whip = new WebrtcWhip(
9644
+ this._mediaStream,
9645
+ async () => {
9646
+ return session;
9647
+ },
9648
+ this._client,
9649
+ true,
9650
+ this._requestLogger
9651
+ );
9652
+ whip.start().then((value) => {
9653
+ this._requestLogger.debug("direct whip for ingressId=%s successful", whip.ingressId());
9654
+ }).catch((reason) => {
9655
+ this._controller.abort(reason);
9673
9656
  });
9674
- const direction = "sendrecv";
9675
- pc.addTransceiver("video", { direction });
9676
- pc.onicecandidate = (evt) => this.onLocalCandidate(evt);
9677
- this._pc = pc;
9678
- this._requestLogger.info(`onIceServers ${pc.iceConnectionState}`);
9679
- return new Promise(async (resolve, reject) => {
9680
- pc.ontrack = (evt) => {
9681
- this._requestLogger.debug(`new track: ${evt.track.kind}`);
9682
- this._stream = evt.streams[0];
9683
- resolve(this);
9684
- };
9685
- pc.oniceconnectionstatechange = () => {
9686
- switch (pc.iceConnectionState) {
9687
- case "failed":
9688
- case "disconnected":
9689
- this._requestLogger.debug("oniceconnectionstatechange disconnected");
9690
- reject(`peer connection: ${pc.iceConnectionState}`);
9691
- break;
9692
- case "connected":
9693
- this._requestLogger.debug("oniceconnectionstatechange connected");
9694
- break;
9695
- }
9696
- };
9697
- await this.onLocalOffer(await pc.createOffer());
9657
+ const body = {
9658
+ sourceType: "WHIP",
9659
+ whipIngressId: whip.ingressId(),
9660
+ params: this._params
9661
+ };
9662
+ const headers = {
9663
+ ...session.authenticationHeaders(),
9664
+ Accept: "application/jsonl",
9665
+ "Content-Type": "application/json"
9666
+ };
9667
+ const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=preempt&processing=sync`;
9668
+ return await this._client.fetch(patchUrl, {
9669
+ headers,
9670
+ method: "PATCH",
9671
+ body: JSON.stringify(body),
9672
+ signal: this._controller.signal,
9673
+ // @ts-ignore
9674
+ eyepop: { responseStreaming: true }
9698
9675
  });
9699
9676
  }
9700
9677
  };
@@ -10208,8 +10185,6 @@ var WorkerEndpoint = class extends Endpoint {
10208
10185
  this._pipelineId = null;
10209
10186
  this._popName = null;
10210
10187
  this._pipeline = null;
10211
- this._ingressEventHandler = null;
10212
- this._ingressEventWs = null;
10213
10188
  this._sandboxId = null;
10214
10189
  const sessionAuth = options.auth;
10215
10190
  if (sessionAuth !== void 0 && sessionAuth.session !== void 0) {
@@ -10235,7 +10210,7 @@ var WorkerEndpoint = class extends Endpoint {
10235
10210
  });
10236
10211
  if (response?.status != 204) {
10237
10212
  const message = await response?.text();
10238
- this._logger.info(`stoppping pipeline failed, status ${response?.status}: ${message}`);
10213
+ this._logger.info(`stopping pipeline failed, status ${response?.status}: ${message}`);
10239
10214
  }
10240
10215
  }
10241
10216
  if (this._sandboxId && this._baseUrl) {
@@ -10409,42 +10384,6 @@ var WorkerEndpoint = class extends Endpoint {
10409
10384
  popId() {
10410
10385
  return this.options().popId || null;
10411
10386
  }
10412
- onIngressEvent(handler) {
10413
- this._ingressEventHandler = handler;
10414
- this.startIngressWs().then(() => {
10415
- }).catch((reason) => {
10416
- this._logger.warn("unexpected error starting the ingress event handler: %s", reason);
10417
- });
10418
- return this;
10419
- }
10420
- async liveIngress(stream) {
10421
- if (!this._baseUrl || !this._client) {
10422
- return Promise.reject("endpoint not connected, use connect()");
10423
- }
10424
- const whip = new WebrtcWhip(
10425
- stream,
10426
- async () => {
10427
- return this.session();
10428
- },
10429
- this._client,
10430
- this._requestLogger
10431
- );
10432
- return whip.start();
10433
- }
10434
- async liveEgress(ingressId) {
10435
- if (!this._baseUrl || !this._client) {
10436
- return Promise.reject("endpoint not connected, use connect()");
10437
- }
10438
- const whep = new WebrtcWhep(
10439
- ingressId,
10440
- async () => {
10441
- return this.session();
10442
- },
10443
- this._client,
10444
- this._requestLogger
10445
- );
10446
- return whep.start();
10447
- }
10448
10387
  async process(source, params) {
10449
10388
  if (source.file !== void 0) {
10450
10389
  return this.uploadFile(source, params);
@@ -10454,45 +10393,14 @@ var WorkerEndpoint = class extends Endpoint {
10454
10393
  return this.uploadPath(source, params);
10455
10394
  } else if (source.url !== void 0) {
10456
10395
  return this.loadFrom(source, params);
10457
- } else if (source.ingressId !== void 0) {
10458
- return this.loadLiveIngress(source, params);
10459
10396
  } else if (source.assetUuid !== void 0) {
10460
10397
  return this.loadFromAssetUuid(source, params);
10398
+ } else if (source.mediaStream !== void 0) {
10399
+ return this.loadMediaStream(source, params);
10461
10400
  } else {
10462
10401
  return Promise.reject("unknown source type");
10463
10402
  }
10464
10403
  }
10465
- async startIngressWs() {
10466
- const client = this._client;
10467
- if (this._ingressEventWs || !this._baseUrl || !client) {
10468
- return;
10469
- }
10470
- try {
10471
- const session = await this.session();
10472
- let response = await this.fetchWithRetry(async () => {
10473
- const headers = {
10474
- ...this._authenticationHeaders(session)
10475
- };
10476
- const getTokenUrl = `${session.baseUrl}/liveIngress/events/token`;
10477
- return client.fetch(getTokenUrl, { headers });
10478
- });
10479
- if (response.status != 200) {
10480
- const message = await response.text();
10481
- return Promise.reject(`Unexpected status ${response.status}: ${message}`);
10482
- }
10483
- const wsAuthToken = await response.json();
10484
- const wsUrl = this._baseUrl.startsWith("https://") ? `${this._baseUrl.replace("https://", "wss://")}/liveIngress/events/${wsAuthToken.token}` : `${this._baseUrl.replace("http://", "ws://")}/liveIngress/events/${wsAuthToken.token}`;
10485
- this._ingressEventWs = new WebSocket(wsUrl);
10486
- this._ingressEventWs.addEventListener("message", (event) => {
10487
- const ingressEvent = JSON.parse(event.data);
10488
- if (this._ingressEventHandler) {
10489
- this._ingressEventHandler(ingressEvent);
10490
- }
10491
- });
10492
- } finally {
10493
- this.updateState();
10494
- }
10495
- }
10496
10404
  async uploadFile(source, params) {
10497
10405
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10498
10406
  return Promise.reject("endpoint not connected, use connect()");
@@ -10658,15 +10566,15 @@ var WorkerEndpoint = class extends Endpoint {
10658
10566
  throw e;
10659
10567
  }
10660
10568
  }
10661
- async loadLiveIngress(source, params) {
10569
+ async loadMediaStream(source, params) {
10662
10570
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10663
10571
  throw new Error("endpoint not connected, use connect()");
10664
10572
  }
10665
10573
  await this._limit.acquire();
10666
10574
  try {
10667
10575
  this.updateState();
10668
- const job = new LoadLiveIngressJob(
10669
- source.ingressId,
10576
+ const job = new LoadMediaStreamJob(
10577
+ source.mediaStream,
10670
10578
  params,
10671
10579
  async () => {
10672
10580
  return this.session();
@@ -10814,9 +10722,6 @@ var WorkerEndpoint = class extends Endpoint {
10814
10722
  }
10815
10723
  }
10816
10724
  }
10817
- if (!this._ingressEventWs && this._ingressEventHandler) {
10818
- await this.startIngressWs();
10819
- }
10820
10725
  this.updateState();
10821
10726
  return Promise.resolve(this);
10822
10727
  }
@@ -11725,6 +11630,88 @@ var DataEndpoint = class extends Endpoint {
11725
11630
  }
11726
11631
  };
11727
11632
 
11633
+ // worker/webrtc_whep.ts
11634
+ var WebrtcWhep = class extends WebrtcBase {
11635
+ constructor(ingressId, getSession, client, requestLogger) {
11636
+ super(getSession, client, ingressId, "liveIngress/whep", false, requestLogger);
11637
+ this._stream = null;
11638
+ }
11639
+ async close() {
11640
+ const stream = this._stream;
11641
+ this._stream = null;
11642
+ if (stream) {
11643
+ const tracks = stream.getTracks();
11644
+ tracks.forEach((track) => {
11645
+ track.stop();
11646
+ });
11647
+ }
11648
+ return super.close();
11649
+ }
11650
+ async stream() {
11651
+ if (this._stream) {
11652
+ return Promise.resolve(this._stream);
11653
+ } else {
11654
+ return Promise.reject("fetching stream failed");
11655
+ }
11656
+ }
11657
+ async start() {
11658
+ const session = await this._getSession();
11659
+ const egressUrl = this.gresUrl(session);
11660
+ const response = await this._client.fetch(egressUrl, {
11661
+ headers: session.authenticationHeaders(),
11662
+ method: "GET"
11663
+ });
11664
+ if (response.status >= 300) {
11665
+ return Promise.reject(`unknown status code for GET '${egressUrl}': ${response.status} (${response.statusText})`);
11666
+ }
11667
+ return this.onIceServers(response);
11668
+ }
11669
+ async onRemoteAnswer(answer) {
11670
+ if (!this._pc) {
11671
+ throw new Error("onLocalOffer no peer connection");
11672
+ }
11673
+ await this._pc.setRemoteDescription(new RTCSessionDescription(answer));
11674
+ if (this._queuedCandidates.length !== 0) {
11675
+ await this.sendLocalCandidates(this._queuedCandidates);
11676
+ this._queuedCandidates = [];
11677
+ }
11678
+ return this;
11679
+ }
11680
+ async onIceServers(response) {
11681
+ if (this._stream) {
11682
+ throw new Error("onIceServers has stream");
11683
+ }
11684
+ const pc = new RTCPeerConnection({
11685
+ iceServers: WebrtcBase.linkToIceServers(response.headers.get("Link"))
11686
+ });
11687
+ const direction = "sendrecv";
11688
+ pc.addTransceiver("video", { direction });
11689
+ pc.onicecandidate = (evt) => this.onLocalCandidate(evt);
11690
+ this._pc = pc;
11691
+ this._requestLogger.info(`onIceServers ${pc.iceConnectionState}`);
11692
+ return new Promise(async (resolve, reject) => {
11693
+ pc.ontrack = (evt) => {
11694
+ this._requestLogger.debug(`new track: ${evt.track.kind}`);
11695
+ this._stream = evt.streams[0];
11696
+ resolve(this);
11697
+ };
11698
+ pc.oniceconnectionstatechange = () => {
11699
+ switch (pc.iceConnectionState) {
11700
+ case "failed":
11701
+ case "disconnected":
11702
+ this._requestLogger.debug("oniceconnectionstatechange disconnected");
11703
+ reject(`peer connection: ${pc.iceConnectionState}`);
11704
+ break;
11705
+ case "connected":
11706
+ this._requestLogger.debug("oniceconnectionstatechange connected");
11707
+ break;
11708
+ }
11709
+ };
11710
+ await this.onLocalOffer(await pc.createOffer());
11711
+ });
11712
+ }
11713
+ };
11714
+
11728
11715
  // index.ts
11729
11716
  var readEnv = (env) => {
11730
11717
  if (typeof process !== "undefined") {
@@ -11860,6 +11847,7 @@ var index_default = EyePop;
11860
11847
  LoadFromAssetUuidJob,
11861
11848
  LoadFromJob,
11862
11849
  LoadLiveIngressJob,
11850
+ LoadMediaStreamJob,
11863
11851
  ModelExportStatus,
11864
11852
  ModelFormat,
11865
11853
  ModelPrecisionType,