@eyepop.ai/eyepop 1.17.5 → 1.18.1

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.
@@ -8886,748 +8886,724 @@ var ContourType = /* @__PURE__ */ ((ContourType2) => {
8886
8886
  return ContourType2;
8887
8887
  })(ContourType || {});
8888
8888
 
8889
- // worker/jobs.ts
8890
- var AbstractJob = class {
8891
- constructor(params, getSession, client, requestLogger) {
8892
- this._responseStream = null;
8893
- this._controller = new AbortController();
8889
+ // worker/webrtc_whip.ts
8890
+ import { v4 as uuidv4 } from "uuid";
8891
+
8892
+ // worker/webrtc_base.ts
8893
+ import path from "path";
8894
+ var WebrtcBase = class _WebrtcBase {
8895
+ constructor(getSession, client, ingressId, urlBasePath, is_pipeline_direct, requestLogger) {
8894
8896
  this._getSession = getSession;
8895
- this._params = params ?? null;
8896
8897
  this._client = client;
8897
8898
  this._requestLogger = requestLogger;
8899
+ this._ingressId = ingressId;
8900
+ this._urlPath = `${urlBasePath}/${this._ingressId}`;
8901
+ this._is_pipeline_direct = is_pipeline_direct;
8902
+ this._pc = null;
8903
+ this._eTag = "";
8904
+ this._location = null;
8905
+ this._queuedCandidates = [];
8906
+ this._offerData = null;
8898
8907
  }
8899
- [Symbol.asyncIterator]() {
8900
- if (!this._responseStream) {
8901
- throw Error("logical bug");
8908
+ async close() {
8909
+ const pc = this._pc;
8910
+ this._pc = null;
8911
+ if (pc) {
8912
+ return pc.close();
8902
8913
  }
8903
- return Stream.iterFromReadableStream(this._responseStream, this._controller, async (event) => {
8904
- return await this.onEvent(event);
8905
- });
8906
8914
  }
8907
- async startJob() {
8908
- return Promise.reject("abstract class");
8915
+ ingressId() {
8916
+ return this._ingressId;
8909
8917
  }
8910
- async onEvent(event) {
8911
- if (event.type == "error") {
8912
- return Promise.reject(`Error event for source ${event.source_id}: ${event.message}`);
8918
+ gresUrl(session, location = null) {
8919
+ let urlPath;
8920
+ if (this._is_pipeline_direct) {
8921
+ if (!session.pipelineId) {
8922
+ throw new Error("cannot connect with is_pipeline_direct=true but !session.pipelineId");
8923
+ }
8924
+ urlPath = path.join("pipelines", session.pipelineId, this._urlPath);
8925
+ } else {
8926
+ urlPath = this._urlPath;
8913
8927
  }
8914
- return Promise.resolve();
8928
+ if (location) {
8929
+ urlPath = path.join(urlPath, location);
8930
+ }
8931
+ let baseUrl;
8932
+ if (session.baseUrl?.endsWith("/")) {
8933
+ baseUrl = session.baseUrl;
8934
+ } else {
8935
+ baseUrl = session.baseUrl + "/";
8936
+ }
8937
+ return new URL(urlPath, baseUrl);
8915
8938
  }
8916
- start(done, status) {
8917
- this._responseStream = new Promise(async (resolve, reject) => {
8918
- try {
8919
- let retries = 1;
8920
- while (true) {
8921
- try {
8922
- const response = await this.startJob();
8923
- if (response.status == 401) {
8924
- status(response.status);
8925
- if (retries--) {
8926
- this._requestLogger.info("received 401 response, attempt to reauthorize once");
8927
- } else {
8928
- reject(`response ${response.status}: ${response.statusText}`);
8929
- break;
8930
- }
8931
- } else if (response.status == 404) {
8932
- status(response.status);
8933
- if (retries--) {
8934
- this._requestLogger.info("received 404 response, attempt to restart pop");
8935
- } else {
8936
- reject(`response ${response.status}: ${response.statusText}`);
8937
- break;
8938
- }
8939
- } else if (response.status != 200) {
8940
- reject(`upload error ${response.status}: ${await response.text()}`);
8941
- break;
8942
- } else if (response.body) {
8943
- resolve(response.body);
8944
- break;
8945
- } else {
8946
- const text = await response.text();
8947
- if (text) {
8948
- resolve(readableStreamFromString(text));
8949
- break;
8950
- } else {
8951
- reject("response body is empty");
8952
- break;
8953
- }
8954
- }
8955
- } catch (error) {
8956
- if (retries--) {
8957
- this._requestLogger.info("unknown, attempt to restart pop (as if we received 404)", error);
8958
- status(404);
8959
- } else {
8960
- status(500);
8961
- reject(error);
8962
- break;
8963
- }
8964
- }
8965
- }
8966
- } catch (reason) {
8967
- reject(reason);
8968
- } finally {
8969
- done();
8970
- }
8939
+ async onLocalOffer(offer) {
8940
+ if (!this._pc) {
8941
+ throw new Error("onLocalOffer no peer connection");
8942
+ }
8943
+ if (!offer.sdp) {
8944
+ throw new Error("onLocalOffer no sdp");
8945
+ }
8946
+ this._offerData = _WebrtcBase.parseOffer(offer.sdp);
8947
+ await this._pc.setLocalDescription(offer);
8948
+ const session = await this._getSession();
8949
+ const ingressUrl = this.gresUrl(session);
8950
+ const headers = {
8951
+ ...session.authenticationHeaders(),
8952
+ "Content-Type": "application/sdp"
8953
+ };
8954
+ const response = await this._client.fetch(ingressUrl, {
8955
+ headers,
8956
+ method: "POST",
8957
+ body: offer.sdp
8971
8958
  });
8972
- return this;
8973
- }
8974
- cancel() {
8975
- this._controller.abort("cancelled");
8959
+ if (response.status >= 300) {
8960
+ return Promise.reject(`unknown status code for POST '${ingressUrl}': ${response.status} (${response.statusText})`);
8961
+ }
8962
+ this._eTag = response.headers.get("ETag") ?? "";
8963
+ this._location = response.headers.get("Location");
8964
+ return await this.onRemoteAnswer(new RTCSessionDescription({
8965
+ type: "answer",
8966
+ sdp: await response.text()
8967
+ }));
8976
8968
  }
8977
- };
8978
- AbstractJob.n = 0;
8979
- var UploadJob = class extends AbstractJob {
8980
- get [Symbol.toStringTag]() {
8981
- return "uploadJob";
8969
+ async sendLocalCandidates(candidates) {
8970
+ if (!this._offerData) {
8971
+ throw new Error("sendLocalCandidates no offerData");
8972
+ }
8973
+ const session = await this._getSession();
8974
+ const ingressUrl = this.gresUrl(session, this._location);
8975
+ const headers = {
8976
+ ...session.authenticationHeaders(),
8977
+ "Content-Type": "application/trickle-ice-sdpfrag",
8978
+ "If-Match": this._eTag
8979
+ };
8980
+ const response = await this._client.fetch(ingressUrl, {
8981
+ headers,
8982
+ method: "PATCH",
8983
+ body: _WebrtcBase.generateSdpFragment(this._offerData, candidates)
8984
+ });
8985
+ if (response.status >= 300) {
8986
+ return Promise.reject(`unknown status code for PATCH '${ingressUrl}': ${response.status} (${response.statusText})`);
8987
+ }
8988
+ return this;
8982
8989
  }
8983
- constructor(stream, mimeType, size, videoMode, params, getSession, client, requestLogger) {
8984
- super(params, getSession, client, requestLogger);
8985
- this._uploadStream = stream;
8986
- this._mimeType = mimeType;
8987
- this._size = size;
8988
- this._videoMode = videoMode;
8989
- this._needsFullDuplex = !client.isFullDuplex() && mimeType.startsWith("video/") && videoMode == "stream" /* STREAM */;
8990
+ async onLocalCandidate(evt) {
8991
+ if (evt.candidate !== null) {
8992
+ if (this._eTag === "") {
8993
+ this._queuedCandidates.push(evt.candidate);
8994
+ } else {
8995
+ await this.sendLocalCandidates([evt.candidate]);
8996
+ }
8997
+ }
8998
+ return this;
8990
8999
  }
8991
- async onEvent(event) {
8992
- if (event.type == "prepared") {
8993
- if (!event.source_id) {
8994
- return Promise.reject("did not get a prepared sourceId to simulate full duplex");
9000
+ static linkToIceServers(links) {
9001
+ const servers = links !== null ? links.split(", ").map((link) => {
9002
+ let m = link.match(/^<(.+?)>; rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i);
9003
+ if (!m) {
9004
+ m = link.match(/^(.+?); rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i);
8995
9005
  }
8996
- const session = await this._getSession();
8997
- if (!session.baseUrl) {
8998
- return Promise.reject("session.baseUrl must not ne null");
9006
+ if (!m) {
9007
+ return null;
8999
9008
  }
9000
- let request;
9001
- const videoModeQuery = this._videoMode ? `&videoMode=${this._videoMode}` : "";
9002
- const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=async&sourceId=${event.source_id}${videoModeQuery}`;
9003
- if (this._params) {
9004
- const params = JSON.stringify(this._params);
9005
- const paramBlob = new Blob([params], { type: "application/json" });
9006
- const formData = new FormData();
9007
- const fileBlob = await new Response(this._uploadStream).blob();
9008
- formData.append("params", paramBlob);
9009
- formData.append("file", fileBlob);
9010
- const headers = {
9011
- ...session.authenticationHeaders(),
9012
- Accept: "application/jsonl"
9013
- };
9014
- request = this._client.fetch(postUrl, {
9015
- headers,
9016
- method: "POST",
9017
- body: formData,
9018
- signal: this._controller.signal,
9019
- // @ts-ignore
9020
- duplex: "half",
9021
- eyepop: { responseStreaming: true }
9022
- });
9009
+ const iceServer = {
9010
+ urls: [m[1]],
9011
+ username: WebrtcWhip.unquoteCredential(m[3]),
9012
+ credential: WebrtcWhip.unquoteCredential(m[4])
9013
+ // credentialType: m[3]? "password" : undefined
9014
+ };
9015
+ return iceServer;
9016
+ }) : [];
9017
+ return servers.filter(function(el) {
9018
+ return el != null;
9019
+ });
9020
+ }
9021
+ static editAnswer(answer, videoCodec, audioCodec, videoBitrate, audioBitrate, audioVoice) {
9022
+ const sections = answer.split("m=");
9023
+ for (let i = 0; i < sections.length; i++) {
9024
+ const section = sections[i];
9025
+ if (section.startsWith("video")) {
9026
+ sections[i] = WebrtcWhip.setVideoBitrate(WebrtcWhip.setCodec(section, videoCodec), videoBitrate);
9027
+ } else if (section.startsWith("audio")) {
9028
+ sections[i] = WebrtcWhip.setAudioBitrate(WebrtcWhip.setCodec(section, audioCodec), audioBitrate, audioVoice);
9029
+ }
9030
+ }
9031
+ return sections.join("m=");
9032
+ }
9033
+ static setCodec(section, codec) {
9034
+ const lines = section.split("\r\n");
9035
+ const lines2 = [];
9036
+ const payloadFormats = [];
9037
+ for (const line of lines) {
9038
+ if (!line.startsWith("a=rtpmap:")) {
9039
+ lines2.push(line);
9023
9040
  } else {
9024
- const headers = {
9025
- ...session.authenticationHeaders(),
9026
- Accept: "application/jsonl",
9027
- "Content-Type": this._mimeType
9028
- };
9029
- if (this._size) {
9030
- headers["Content-Length"] = `${this._size}`;
9041
+ if (codec && line.toLowerCase().includes(codec)) {
9042
+ payloadFormats.push(line.slice("a=rtpmap:".length).split(" ")[0]);
9043
+ lines2.push(line);
9031
9044
  }
9032
- request = this._client.fetch(postUrl, {
9033
- headers,
9034
- method: "POST",
9035
- body: this._uploadStream,
9036
- signal: this._controller.signal,
9037
- // @ts-ignore
9038
- duplex: "half",
9039
- eyepop: { responseStreaming: true }
9040
- });
9041
9045
  }
9042
- request.then(async (value) => {
9043
- if (value.status != 204) {
9044
- this._requestLogger.debug(`unexpected status ${value.status} (${await value.text()}) for POST ${postUrl}`);
9046
+ }
9047
+ const lines3 = [];
9048
+ for (const line of lines2) {
9049
+ if (line.startsWith("a=fmtp:")) {
9050
+ if (payloadFormats.includes(line.slice("a=fmtp:".length).split(" ")[0])) {
9051
+ lines3.push(line);
9045
9052
  }
9046
- }).catch((reason) => {
9047
- this._requestLogger.debug(`error ${reason} for POST ${postUrl}`);
9048
- });
9053
+ } else if (line.startsWith("a=rtcp-fb:")) {
9054
+ if (payloadFormats.includes(line.slice("a=rtcp-fb:".length).split(" ")[0])) {
9055
+ lines3.push(line);
9056
+ }
9057
+ } else {
9058
+ lines3.push(line);
9059
+ }
9049
9060
  }
9050
- return Promise.resolve();
9061
+ return lines3.join("\r\n");
9051
9062
  }
9052
- async startJob() {
9053
- const session = await this._getSession();
9054
- if (!session.baseUrl) {
9055
- return Promise.reject("session.baseUrl must not be null");
9063
+ static setVideoBitrate(section, bitrate) {
9064
+ let lines = section.split("\r\n");
9065
+ for (let i = 0; i < lines.length; i++) {
9066
+ if (lines[i].startsWith("c=")) {
9067
+ lines = [...lines.slice(0, i + 1), "b=TIAS:" + (bitrate * 1024).toString(), ...lines.slice(i + 1)];
9068
+ break;
9069
+ }
9056
9070
  }
9057
- let response;
9058
- if (this._needsFullDuplex) {
9059
- const prepareUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/prepareSource?timeout=10s`;
9060
- const headers = {
9061
- ...session.authenticationHeaders(),
9062
- Accept: "application/jsonl"
9063
- };
9064
- response = await this._client.fetch(prepareUrl, {
9065
- headers,
9066
- method: "POST",
9067
- signal: this._controller.signal,
9068
- // @ts-ignore
9069
- duplex: "half",
9070
- eyepop: { responseStreaming: true }
9071
- });
9072
- } else {
9073
- const videoModeQuery = this._videoMode ? `&videoMode=${this._videoMode}` : "";
9074
- const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=sync${videoModeQuery}`;
9075
- if (this._params) {
9076
- const params = JSON.stringify(this._params);
9077
- const paramBlob = new Blob([params], { type: "application/json" });
9078
- const formData = new FormData();
9079
- const fileBlob = await new Response(this._uploadStream).blob();
9080
- formData.append("params", paramBlob);
9081
- formData.append("file", fileBlob);
9082
- const headers = {
9083
- ...session.authenticationHeaders(),
9084
- Accept: "application/jsonl"
9085
- };
9086
- response = await this._client.fetch(postUrl, {
9087
- headers,
9088
- method: "POST",
9089
- body: formData,
9090
- signal: this._controller.signal,
9091
- // @ts-ignore
9092
- duplex: "half",
9093
- eyepop: { responseStreaming: true }
9094
- });
9095
- } else {
9096
- const headers = {
9097
- ...session.authenticationHeaders(),
9098
- Accept: "application/jsonl",
9099
- "Content-Type": this._mimeType
9100
- };
9101
- if (this._size) {
9102
- headers["Content-Length"] = `${this._size}`;
9071
+ return lines.join("\r\n");
9072
+ }
9073
+ static setAudioBitrate(section, bitrate, voice) {
9074
+ let opusPayloadFormat = "";
9075
+ let lines = section.split("\r\n");
9076
+ for (let i = 0; i < lines.length; i++) {
9077
+ if (lines[i].startsWith("a=rtpmap:") && lines[i].toLowerCase().includes("opus/")) {
9078
+ opusPayloadFormat = lines[i].slice("a=rtpmap:".length).split(" ")[0];
9079
+ break;
9080
+ }
9081
+ }
9082
+ if (opusPayloadFormat === "") {
9083
+ return section;
9084
+ }
9085
+ for (let i = 0; i < lines.length; i++) {
9086
+ if (lines[i].startsWith("a=fmtp:" + opusPayloadFormat + " ")) {
9087
+ if (voice) {
9088
+ lines[i] = "a=fmtp:" + opusPayloadFormat + " minptime=10;useinbandfec=1;maxaveragebitrate=" + ((bitrate ?? 0) * 1024).toString();
9089
+ } else {
9090
+ lines[i] = "a=fmtp:" + opusPayloadFormat + " maxplaybackrate=48000;stereo=1;sprop-stereo=1;maxaveragebitrate" + ((bitrate ?? 0) * 1024).toString();
9103
9091
  }
9104
- response = await this._client.fetch(postUrl, {
9105
- headers,
9106
- method: "POST",
9107
- body: this._uploadStream,
9108
- signal: this._controller.signal,
9109
- // @ts-ignore
9110
- duplex: "half"
9111
- });
9112
9092
  }
9113
9093
  }
9114
- return response;
9115
- }
9116
- };
9117
- var LoadFromJob = class extends AbstractJob {
9118
- constructor(location, params, getSession, client, requestLogger) {
9119
- super(params, getSession, client, requestLogger);
9120
- this._location = location;
9121
- }
9122
- get [Symbol.toStringTag]() {
9123
- return "loadFromJob";
9094
+ return lines.join("\r\n");
9124
9095
  }
9125
- async startJob() {
9126
- const session = await this._getSession();
9127
- if (!session.baseUrl) {
9128
- return Promise.reject("session.baseUrl must not ne null");
9096
+ static parseOffer(offer) {
9097
+ let medias = [];
9098
+ let iceUfrag = "";
9099
+ let icePwd = "";
9100
+ for (const line of offer.split("\r\n")) {
9101
+ if (line.startsWith("m=")) {
9102
+ medias.push(line.slice("m=".length));
9103
+ } else if (iceUfrag === "" && line.startsWith("a=ice-ufrag:")) {
9104
+ iceUfrag = line.slice("a=ice-ufrag:".length);
9105
+ } else if (icePwd === "" && line.startsWith("a=ice-pwd:")) {
9106
+ icePwd = line.slice("a=ice-pwd:".length);
9107
+ }
9129
9108
  }
9130
- const body = {
9131
- sourceType: "URL",
9132
- url: this._location,
9133
- params: this._params
9134
- };
9135
- const headers = {
9136
- ...session.authenticationHeaders(),
9137
- Accept: "application/jsonl",
9138
- "Content-Type": "application/json"
9109
+ return {
9110
+ iceUfrag,
9111
+ icePwd,
9112
+ medias
9139
9113
  };
9140
- const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=sync`;
9141
- return await this._client.fetch(patchUrl, {
9142
- headers,
9143
- method: "PATCH",
9144
- body: JSON.stringify(body),
9145
- signal: this._controller.signal,
9146
- // @ts-ignore
9147
- eyepop: { responseStreaming: true }
9148
- });
9149
- }
9150
- };
9151
- var LoadFromAssetUuidJob = class extends AbstractJob {
9152
- constructor(assetUuid, params, getSession, client, requestLogger) {
9153
- super(params, getSession, client, requestLogger);
9154
- this._assetUuid = assetUuid;
9155
9114
  }
9156
- get [Symbol.toStringTag]() {
9157
- return "loadFromAssetUuidJob";
9115
+ static generateSdpFragment(offerData, candidates) {
9116
+ const candidatesByMedia = /* @__PURE__ */ new Map();
9117
+ for (const candidate of candidates) {
9118
+ if (candidate.sdpMLineIndex !== null) {
9119
+ const mid2 = candidate.sdpMLineIndex;
9120
+ let matched = candidatesByMedia.get(mid2);
9121
+ if (matched === void 0) {
9122
+ matched = [];
9123
+ candidatesByMedia.set(mid2, matched);
9124
+ }
9125
+ matched.push(candidate);
9126
+ }
9127
+ }
9128
+ let frag = "a=ice-ufrag:" + offerData.iceUfrag + "\r\na=ice-pwd:" + offerData.icePwd + "\r\n";
9129
+ let mid = 0;
9130
+ for (const media of offerData.medias) {
9131
+ let matched = candidatesByMedia.get(mid);
9132
+ if (matched !== void 0) {
9133
+ frag += "m=" + media + "\r\na=mid:" + mid + "\r\n";
9134
+ for (const candidate of matched) {
9135
+ frag += "a=" + candidate.candidate + "\r\n";
9136
+ }
9137
+ }
9138
+ mid++;
9139
+ }
9140
+ return frag;
9158
9141
  }
9159
- async startJob() {
9160
- const session = await this._getSession();
9161
- if (!session.baseUrl) {
9162
- return Promise.reject("session.baseUrl must not ne null");
9142
+ static unquoteCredential(v) {
9143
+ if (!v) {
9144
+ return "";
9163
9145
  }
9164
- const body = {
9165
- sourceType: "ASSET_UUID",
9166
- assetUuid: this._assetUuid,
9167
- params: this._params
9168
- };
9169
- const headers = {
9170
- ...session.authenticationHeaders(),
9171
- Accept: "application/jsonl",
9172
- "Content-Type": "application/json"
9173
- };
9174
- const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=sync`;
9175
- return await this._client.fetch(patchUrl, {
9176
- headers,
9177
- method: "PATCH",
9178
- body: JSON.stringify(body),
9179
- signal: this._controller.signal,
9180
- // @ts-ignore
9181
- eyepop: { responseStreaming: true }
9182
- });
9146
+ return JSON.parse(`"${v}"`);
9183
9147
  }
9184
9148
  };
9185
- var LoadLiveIngressJob = class extends AbstractJob {
9186
- constructor(ingressId, params, getSession, client, requestLogger) {
9187
- super(params, getSession, client, requestLogger);
9188
- this._ingressId = ingressId;
9189
- }
9190
- get [Symbol.toStringTag]() {
9191
- return "loadLiveIngressJob";
9149
+
9150
+ // worker/webrtc_whip.ts
9151
+ var WebrtcWhip = class extends WebrtcBase {
9152
+ constructor(stream, getSession, client, is_pipeline_direct, requestLogger) {
9153
+ super(getSession, client, uuidv4().toString(), "liveIngress/whip", is_pipeline_direct, requestLogger);
9154
+ this._stream = stream;
9192
9155
  }
9193
- async startJob() {
9156
+ async start() {
9194
9157
  const session = await this._getSession();
9195
- if (!session.baseUrl) {
9196
- return Promise.reject("session.baseUrl must not ne null");
9158
+ const ingressUrl = this.gresUrl(session);
9159
+ const response = await this._client.fetch(ingressUrl, {
9160
+ headers: session.authenticationHeaders(),
9161
+ method: "GET"
9162
+ });
9163
+ if (response.status >= 300) {
9164
+ return Promise.reject(`unknown status code for GET '${ingressUrl}': ${response.status} (${response.statusText})`);
9197
9165
  }
9198
- const body = {
9199
- sourceType: "LIVE_INGRESS",
9200
- liveIngressId: this._ingressId,
9201
- params: this._params
9202
- };
9203
- const headers = {
9204
- ...session.authenticationHeaders(),
9205
- Accept: "application/jsonl",
9206
- "Content-Type": "application/json"
9207
- };
9208
- const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=preempt&processing=sync`;
9209
- return await this._client.fetch(patchUrl, {
9210
- headers,
9211
- method: "PATCH",
9212
- body: JSON.stringify(body),
9213
- signal: this._controller.signal,
9214
- // @ts-ignore
9215
- eyepop: { responseStreaming: true }
9216
- });
9217
- }
9218
- };
9219
-
9220
- // worker/webrtc_whip.ts
9221
- import { v4 as uuidv4 } from "uuid";
9222
-
9223
- // worker/webrtc_base.ts
9224
- import path from "path";
9225
- var WebrtcBase = class _WebrtcBase {
9226
- constructor(getSession, client, ingressId, urlBasePath, requestLogger) {
9227
- this._getSession = getSession;
9228
- this._client = client;
9229
- this._requestLogger = requestLogger;
9230
- this._ingressId = ingressId;
9231
- this._urlPath = `${urlBasePath}/${this._ingressId}`;
9232
- this._pc = null;
9233
- this._eTag = "";
9234
- this._location = null;
9235
- this._queuedCandidates = [];
9236
- this._offerData = null;
9237
- }
9238
- async close() {
9239
- const pc = this._pc;
9240
- this._pc = null;
9241
- if (pc) {
9242
- return pc.close();
9243
- }
9244
- }
9245
- ingressId() {
9246
- return this._ingressId;
9166
+ return await this.onIceServers(response);
9247
9167
  }
9248
- gresUrl(session, location = null) {
9249
- let urlPath;
9250
- if (location) {
9251
- urlPath = path.join(this._urlPath, location);
9252
- } else {
9253
- urlPath = this._urlPath;
9254
- }
9255
- let baseUrl;
9256
- if (session.baseUrl?.endsWith("/")) {
9257
- baseUrl = session.baseUrl;
9168
+ async stream() {
9169
+ if (this._stream) {
9170
+ return Promise.resolve(this._stream);
9258
9171
  } else {
9259
- baseUrl = session.baseUrl + "/";
9172
+ return Promise.reject("fetching stream failed");
9260
9173
  }
9261
- return new URL(urlPath, baseUrl);
9262
9174
  }
9263
- async onLocalOffer(offer) {
9175
+ async onRemoteAnswer(answer) {
9264
9176
  if (!this._pc) {
9265
9177
  throw new Error("onLocalOffer no peer connection");
9266
9178
  }
9267
- if (!offer.sdp) {
9268
- throw new Error("onLocalOffer no sdp");
9269
- }
9270
- this._offerData = _WebrtcBase.parseOffer(offer.sdp);
9271
- await this._pc.setLocalDescription(offer);
9272
- const session = await this._getSession();
9273
- const ingressUrl = this.gresUrl(session);
9274
- const headers = {
9275
- ...session.authenticationHeaders(),
9276
- "Content-Type": "application/sdp"
9277
- };
9278
- const response = await this._client.fetch(ingressUrl, {
9279
- headers,
9280
- method: "POST",
9281
- body: offer.sdp
9282
- });
9283
- if (response.status != 201) {
9284
- return Promise.reject(`unknown status code for POST '${ingressUrl}': ${response.status} (${response.statusText})`);
9179
+ await this._pc.setRemoteDescription(new RTCSessionDescription(answer));
9180
+ if (this._queuedCandidates.length !== 0) {
9181
+ await this.sendLocalCandidates(this._queuedCandidates);
9182
+ this._queuedCandidates = [];
9285
9183
  }
9286
- this._eTag = response.headers.get("ETag") ?? "";
9287
- this._location = response.headers.get("Location");
9288
- return await this.onRemoteAnswer(new RTCSessionDescription({
9289
- type: "answer",
9290
- sdp: await response.text()
9291
- }));
9184
+ return this;
9292
9185
  }
9293
- async sendLocalCandidates(candidates) {
9294
- if (!this._offerData) {
9295
- throw new Error("sendLocalCandidates no offerData");
9186
+ async onIceServers(response) {
9187
+ if (!this._stream) {
9188
+ throw new Error("onIceServers no stream");
9296
9189
  }
9297
- const session = await this._getSession();
9298
- const ingressUrl = this.gresUrl(session, this._location);
9299
- const headers = {
9300
- ...session.authenticationHeaders(),
9301
- "Content-Type": "application/trickle-ice-sdpfrag",
9302
- "If-Match": this._eTag
9303
- };
9304
- const response = await this._client.fetch(ingressUrl, {
9305
- headers,
9306
- method: "PATCH",
9307
- body: _WebrtcBase.generateSdpFragment(this._offerData, candidates)
9190
+ const stream = this._stream;
9191
+ const pc = new RTCPeerConnection({
9192
+ iceServers: WebrtcBase.linkToIceServers(response.headers.get("Link"))
9193
+ });
9194
+ this._pc = pc;
9195
+ pc.onicecandidate = (evt) => this.onLocalCandidate(evt);
9196
+ return new Promise(async (resolve, reject) => {
9197
+ pc.oniceconnectionstatechange = () => {
9198
+ switch (pc.iceConnectionState) {
9199
+ case "failed":
9200
+ case "disconnected":
9201
+ reject(`peer connection: ${pc.iceConnectionState}`);
9202
+ break;
9203
+ case "connected":
9204
+ resolve(this);
9205
+ break;
9206
+ }
9207
+ };
9208
+ stream.getTracks().forEach((track) => {
9209
+ this._requestLogger.debug("onIceServers our stream has track: %s", track);
9210
+ pc.addTrack(track, stream);
9211
+ });
9212
+ stream.addEventListener("addtrack", (trackEvent) => {
9213
+ this._requestLogger.debug("onIceServers got lazy track: ", trackEvent.track);
9214
+ pc.addTrack(trackEvent.track, stream);
9215
+ });
9216
+ await this.onLocalOffer(await pc.createOffer());
9308
9217
  });
9309
- if (response.status != 204) {
9310
- return Promise.reject(`unknown status code for PATCH '${ingressUrl}': ${response.status} (${response.statusText})`);
9311
- }
9312
- return this;
9313
9218
  }
9314
- async onLocalCandidate(evt) {
9315
- if (evt.candidate !== null) {
9316
- if (this._eTag === "") {
9317
- this._queuedCandidates.push(evt.candidate);
9318
- } else {
9319
- await this.sendLocalCandidates([evt.candidate]);
9320
- }
9219
+ async close() {
9220
+ const stream = this._stream;
9221
+ this._stream = null;
9222
+ if (stream) {
9223
+ const tracks = stream.getTracks();
9224
+ tracks.forEach((track) => {
9225
+ track.stop();
9226
+ });
9321
9227
  }
9322
- return this;
9228
+ return super.close();
9323
9229
  }
9324
- static linkToIceServers(links) {
9325
- const servers = links !== null ? links.split(", ").map((link) => {
9326
- const m = link.match(/^<(.+?)>; rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i);
9327
- if (!m) {
9328
- return null;
9329
- }
9330
- const iceServer = {
9331
- urls: [m[1]],
9332
- username: WebrtcWhip.unquoteCredential(m[3]),
9333
- credential: WebrtcWhip.unquoteCredential(m[4])
9334
- // credentialType: m[3]? "password" : undefined
9335
- };
9336
- return iceServer;
9337
- }) : [];
9338
- return servers.filter(function(el) {
9339
- return el != null;
9340
- });
9230
+ };
9231
+
9232
+ // worker/jobs.ts
9233
+ var AbstractJob = class {
9234
+ constructor(params, getSession, client, requestLogger) {
9235
+ this._responseStream = null;
9236
+ this._controller = new AbortController();
9237
+ this._getSession = getSession;
9238
+ this._params = params ?? null;
9239
+ this._client = client;
9240
+ this._requestLogger = requestLogger;
9341
9241
  }
9342
- static editAnswer(answer, videoCodec, audioCodec, videoBitrate, audioBitrate, audioVoice) {
9343
- const sections = answer.split("m=");
9344
- for (let i = 0; i < sections.length; i++) {
9345
- const section = sections[i];
9346
- if (section.startsWith("video")) {
9347
- sections[i] = WebrtcWhip.setVideoBitrate(WebrtcWhip.setCodec(section, videoCodec), videoBitrate);
9348
- } else if (section.startsWith("audio")) {
9349
- sections[i] = WebrtcWhip.setAudioBitrate(WebrtcWhip.setCodec(section, audioCodec), audioBitrate, audioVoice);
9350
- }
9242
+ [Symbol.asyncIterator]() {
9243
+ if (!this._responseStream) {
9244
+ throw Error("logical bug");
9351
9245
  }
9352
- return sections.join("m=");
9246
+ return Stream.iterFromReadableStream(this._responseStream, this._controller, async (event) => {
9247
+ return await this.onEvent(event);
9248
+ });
9353
9249
  }
9354
- static setCodec(section, codec) {
9355
- const lines = section.split("\r\n");
9356
- const lines2 = [];
9357
- const payloadFormats = [];
9358
- for (const line of lines) {
9359
- if (!line.startsWith("a=rtpmap:")) {
9360
- lines2.push(line);
9361
- } else {
9362
- if (codec && line.toLowerCase().includes(codec)) {
9363
- payloadFormats.push(line.slice("a=rtpmap:".length).split(" ")[0]);
9364
- lines2.push(line);
9365
- }
9366
- }
9250
+ async startJob() {
9251
+ return Promise.reject("abstract class");
9252
+ }
9253
+ async onEvent(event) {
9254
+ if (event.type == "error") {
9255
+ return Promise.reject(`Error event for source ${event.source_id}: ${event.message}`);
9367
9256
  }
9368
- const lines3 = [];
9369
- for (const line of lines2) {
9370
- if (line.startsWith("a=fmtp:")) {
9371
- if (payloadFormats.includes(line.slice("a=fmtp:".length).split(" ")[0])) {
9372
- lines3.push(line);
9373
- }
9374
- } else if (line.startsWith("a=rtcp-fb:")) {
9375
- if (payloadFormats.includes(line.slice("a=rtcp-fb:".length).split(" ")[0])) {
9376
- lines3.push(line);
9257
+ return Promise.resolve();
9258
+ }
9259
+ start(done, status) {
9260
+ this._responseStream = new Promise(async (resolve, reject) => {
9261
+ try {
9262
+ let retries = 1;
9263
+ while (true) {
9264
+ try {
9265
+ const response = await this.startJob();
9266
+ if (response.status == 401) {
9267
+ status(response.status);
9268
+ if (retries--) {
9269
+ this._requestLogger.info("received 401 response, attempt to reauthorize once");
9270
+ } else {
9271
+ reject(`response ${response.status}: ${response.statusText}`);
9272
+ break;
9273
+ }
9274
+ } else if (response.status == 404) {
9275
+ status(response.status);
9276
+ if (retries--) {
9277
+ this._requestLogger.info("received 404 response, attempt to restart pop");
9278
+ } else {
9279
+ reject(`response ${response.status}: ${response.statusText}`);
9280
+ break;
9281
+ }
9282
+ } else if (response.status != 200) {
9283
+ reject(`upload error ${response.status}: ${await response.text()}`);
9284
+ break;
9285
+ } else if (response.body) {
9286
+ resolve(response.body);
9287
+ break;
9288
+ } else {
9289
+ const text = await response.text();
9290
+ if (text) {
9291
+ resolve(readableStreamFromString(text));
9292
+ break;
9293
+ } else {
9294
+ reject("response body is empty");
9295
+ break;
9296
+ }
9297
+ }
9298
+ } catch (error) {
9299
+ if (retries--) {
9300
+ this._requestLogger.info("unknown, attempt to restart pop (as if we received 404)", error);
9301
+ status(404);
9302
+ } else {
9303
+ status(500);
9304
+ reject(error);
9305
+ break;
9306
+ }
9307
+ }
9377
9308
  }
9378
- } else {
9379
- lines3.push(line);
9309
+ } catch (reason) {
9310
+ reject(reason);
9311
+ } finally {
9312
+ done();
9380
9313
  }
9381
- }
9382
- return lines3.join("\r\n");
9314
+ });
9315
+ return this;
9383
9316
  }
9384
- static setVideoBitrate(section, bitrate) {
9385
- let lines = section.split("\r\n");
9386
- for (let i = 0; i < lines.length; i++) {
9387
- if (lines[i].startsWith("c=")) {
9388
- lines = [...lines.slice(0, i + 1), "b=TIAS:" + (bitrate * 1024).toString(), ...lines.slice(i + 1)];
9389
- break;
9390
- }
9391
- }
9392
- return lines.join("\r\n");
9317
+ cancel() {
9318
+ this._controller.abort("cancelled");
9393
9319
  }
9394
- static setAudioBitrate(section, bitrate, voice) {
9395
- let opusPayloadFormat = "";
9396
- let lines = section.split("\r\n");
9397
- for (let i = 0; i < lines.length; i++) {
9398
- if (lines[i].startsWith("a=rtpmap:") && lines[i].toLowerCase().includes("opus/")) {
9399
- opusPayloadFormat = lines[i].slice("a=rtpmap:".length).split(" ")[0];
9400
- break;
9320
+ };
9321
+ AbstractJob.n = 0;
9322
+ var UploadJob = class extends AbstractJob {
9323
+ get [Symbol.toStringTag]() {
9324
+ return "uploadJob";
9325
+ }
9326
+ constructor(stream, mimeType, size, videoMode, params, getSession, client, requestLogger) {
9327
+ super(params, getSession, client, requestLogger);
9328
+ this._uploadStream = stream;
9329
+ this._mimeType = mimeType;
9330
+ this._size = size;
9331
+ this._videoMode = videoMode;
9332
+ this._needsFullDuplex = !client.isFullDuplex() && mimeType.startsWith("video/") && videoMode == "stream" /* STREAM */;
9333
+ }
9334
+ async onEvent(event) {
9335
+ if (event.type == "prepared") {
9336
+ if (!event.source_id) {
9337
+ return Promise.reject("did not get a prepared sourceId to simulate full duplex");
9401
9338
  }
9402
- }
9403
- if (opusPayloadFormat === "") {
9404
- return section;
9405
- }
9406
- for (let i = 0; i < lines.length; i++) {
9407
- if (lines[i].startsWith("a=fmtp:" + opusPayloadFormat + " ")) {
9408
- if (voice) {
9409
- lines[i] = "a=fmtp:" + opusPayloadFormat + " minptime=10;useinbandfec=1;maxaveragebitrate=" + ((bitrate ?? 0) * 1024).toString();
9410
- } else {
9411
- lines[i] = "a=fmtp:" + opusPayloadFormat + " maxplaybackrate=48000;stereo=1;sprop-stereo=1;maxaveragebitrate" + ((bitrate ?? 0) * 1024).toString();
9339
+ const session = await this._getSession();
9340
+ if (!session.baseUrl) {
9341
+ return Promise.reject("session.baseUrl must not ne null");
9342
+ }
9343
+ let request;
9344
+ const videoModeQuery = this._videoMode ? `&videoMode=${this._videoMode}` : "";
9345
+ const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=async&sourceId=${event.source_id}${videoModeQuery}`;
9346
+ if (this._params) {
9347
+ const params = JSON.stringify(this._params);
9348
+ const paramBlob = new Blob([params], { type: "application/json" });
9349
+ const formData = new FormData();
9350
+ const fileBlob = await new Response(this._uploadStream).blob();
9351
+ formData.append("params", paramBlob);
9352
+ formData.append("file", fileBlob);
9353
+ const headers = {
9354
+ ...session.authenticationHeaders(),
9355
+ Accept: "application/jsonl"
9356
+ };
9357
+ request = this._client.fetch(postUrl, {
9358
+ headers,
9359
+ method: "POST",
9360
+ body: formData,
9361
+ signal: this._controller.signal,
9362
+ // @ts-ignore
9363
+ duplex: "half",
9364
+ eyepop: { responseStreaming: true }
9365
+ });
9366
+ } else {
9367
+ const headers = {
9368
+ ...session.authenticationHeaders(),
9369
+ Accept: "application/jsonl",
9370
+ "Content-Type": this._mimeType
9371
+ };
9372
+ if (this._size) {
9373
+ headers["Content-Length"] = `${this._size}`;
9412
9374
  }
9375
+ request = this._client.fetch(postUrl, {
9376
+ headers,
9377
+ method: "POST",
9378
+ body: this._uploadStream,
9379
+ signal: this._controller.signal,
9380
+ // @ts-ignore
9381
+ duplex: "half",
9382
+ eyepop: { responseStreaming: true }
9383
+ });
9413
9384
  }
9385
+ request.then(async (value) => {
9386
+ if (value.status != 204) {
9387
+ this._requestLogger.debug(`unexpected status ${value.status} (${await value.text()}) for POST ${postUrl}`);
9388
+ }
9389
+ }).catch((reason) => {
9390
+ this._requestLogger.debug(`error ${reason} for POST ${postUrl}`);
9391
+ });
9414
9392
  }
9415
- return lines.join("\r\n");
9416
- }
9417
- static parseOffer(offer) {
9418
- let medias = [];
9419
- let iceUfrag = "";
9420
- let icePwd = "";
9421
- for (const line of offer.split("\r\n")) {
9422
- if (line.startsWith("m=")) {
9423
- medias.push(line.slice("m=".length));
9424
- } else if (iceUfrag === "" && line.startsWith("a=ice-ufrag:")) {
9425
- iceUfrag = line.slice("a=ice-ufrag:".length);
9426
- } else if (icePwd === "" && line.startsWith("a=ice-pwd:")) {
9427
- icePwd = line.slice("a=ice-pwd:".length);
9428
- }
9429
- }
9430
- return {
9431
- iceUfrag,
9432
- icePwd,
9433
- medias
9434
- };
9393
+ return Promise.resolve();
9435
9394
  }
9436
- static generateSdpFragment(offerData, candidates) {
9437
- const candidatesByMedia = /* @__PURE__ */ new Map();
9438
- for (const candidate of candidates) {
9439
- if (candidate.sdpMLineIndex !== null) {
9440
- const mid2 = candidate.sdpMLineIndex;
9441
- let matched = candidatesByMedia.get(mid2);
9442
- if (matched === void 0) {
9443
- matched = [];
9444
- candidatesByMedia.set(mid2, matched);
9445
- }
9446
- matched.push(candidate);
9447
- }
9395
+ async startJob() {
9396
+ const session = await this._getSession();
9397
+ if (!session.baseUrl) {
9398
+ return Promise.reject("session.baseUrl must not be null");
9448
9399
  }
9449
- let frag = "a=ice-ufrag:" + offerData.iceUfrag + "\r\na=ice-pwd:" + offerData.icePwd + "\r\n";
9450
- let mid = 0;
9451
- for (const media of offerData.medias) {
9452
- let matched = candidatesByMedia.get(mid);
9453
- if (matched !== void 0) {
9454
- frag += "m=" + media + "\r\na=mid:" + mid + "\r\n";
9455
- for (const candidate of matched) {
9456
- frag += "a=" + candidate.candidate + "\r\n";
9400
+ let response;
9401
+ if (this._needsFullDuplex) {
9402
+ const prepareUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/prepareSource?timeout=10s`;
9403
+ const headers = {
9404
+ ...session.authenticationHeaders(),
9405
+ Accept: "application/jsonl"
9406
+ };
9407
+ response = await this._client.fetch(prepareUrl, {
9408
+ headers,
9409
+ method: "POST",
9410
+ signal: this._controller.signal,
9411
+ // @ts-ignore
9412
+ duplex: "half",
9413
+ eyepop: { responseStreaming: true }
9414
+ });
9415
+ } else {
9416
+ const videoModeQuery = this._videoMode ? `&videoMode=${this._videoMode}` : "";
9417
+ const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=sync${videoModeQuery}`;
9418
+ if (this._params) {
9419
+ const params = JSON.stringify(this._params);
9420
+ const paramBlob = new Blob([params], { type: "application/json" });
9421
+ const formData = new FormData();
9422
+ const fileBlob = await new Response(this._uploadStream).blob();
9423
+ formData.append("params", paramBlob);
9424
+ formData.append("file", fileBlob);
9425
+ const headers = {
9426
+ ...session.authenticationHeaders(),
9427
+ Accept: "application/jsonl"
9428
+ };
9429
+ response = await this._client.fetch(postUrl, {
9430
+ headers,
9431
+ method: "POST",
9432
+ body: formData,
9433
+ signal: this._controller.signal,
9434
+ // @ts-ignore
9435
+ duplex: "half",
9436
+ eyepop: { responseStreaming: true }
9437
+ });
9438
+ } else {
9439
+ const headers = {
9440
+ ...session.authenticationHeaders(),
9441
+ Accept: "application/jsonl",
9442
+ "Content-Type": this._mimeType
9443
+ };
9444
+ if (this._size) {
9445
+ headers["Content-Length"] = `${this._size}`;
9457
9446
  }
9447
+ response = await this._client.fetch(postUrl, {
9448
+ headers,
9449
+ method: "POST",
9450
+ body: this._uploadStream,
9451
+ signal: this._controller.signal,
9452
+ // @ts-ignore
9453
+ duplex: "half"
9454
+ });
9458
9455
  }
9459
- mid++;
9460
- }
9461
- return frag;
9462
- }
9463
- static unquoteCredential(v) {
9464
- if (!v) {
9465
- return "";
9466
9456
  }
9467
- return JSON.parse(`"${v}"`);
9457
+ return response;
9468
9458
  }
9469
9459
  };
9470
-
9471
- // worker/webrtc_whip.ts
9472
- var WebrtcWhip = class extends WebrtcBase {
9473
- constructor(stream, getSession, client, requestLogger) {
9474
- super(getSession, client, uuidv4().toString(), "liveIngress/whip", requestLogger);
9475
- this._stream = stream;
9460
+ var LoadFromJob = class extends AbstractJob {
9461
+ constructor(location, params, getSession, client, requestLogger) {
9462
+ super(params, getSession, client, requestLogger);
9463
+ this._location = location;
9476
9464
  }
9477
- async start() {
9478
- const session = await this._getSession();
9479
- const ingressUrl = this.gresUrl(session);
9480
- const response = await this._client.fetch(ingressUrl, {
9481
- headers: session.authenticationHeaders(),
9482
- method: "GET"
9483
- });
9484
- if (response.status >= 300) {
9485
- return Promise.reject(`unknown status code for GET '${ingressUrl}': ${response.status} (${response.statusText})`);
9486
- }
9487
- return await this.onIceServers(response);
9465
+ get [Symbol.toStringTag]() {
9466
+ return "loadFromJob";
9488
9467
  }
9489
- async stream() {
9490
- if (this._stream) {
9491
- return Promise.resolve(this._stream);
9492
- } else {
9493
- return Promise.reject("fetching stream failed");
9468
+ async startJob() {
9469
+ const session = await this._getSession();
9470
+ if (!session.baseUrl) {
9471
+ return Promise.reject("session.baseUrl must not ne null");
9494
9472
  }
9473
+ const body = {
9474
+ sourceType: "URL",
9475
+ url: this._location,
9476
+ params: this._params
9477
+ };
9478
+ const headers = {
9479
+ ...session.authenticationHeaders(),
9480
+ Accept: "application/jsonl",
9481
+ "Content-Type": "application/json"
9482
+ };
9483
+ const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=sync`;
9484
+ return await this._client.fetch(patchUrl, {
9485
+ headers,
9486
+ method: "PATCH",
9487
+ body: JSON.stringify(body),
9488
+ signal: this._controller.signal,
9489
+ // @ts-ignore
9490
+ eyepop: { responseStreaming: true }
9491
+ });
9495
9492
  }
9496
- async onRemoteAnswer(answer) {
9497
- if (!this._pc) {
9498
- throw new Error("onLocalOffer no peer connection");
9499
- }
9500
- await this._pc.setRemoteDescription(new RTCSessionDescription(answer));
9501
- if (this._queuedCandidates.length !== 0) {
9502
- await this.sendLocalCandidates(this._queuedCandidates);
9503
- this._queuedCandidates = [];
9504
- }
9505
- return this;
9493
+ };
9494
+ var LoadFromAssetUuidJob = class extends AbstractJob {
9495
+ constructor(assetUuid, params, getSession, client, requestLogger) {
9496
+ super(params, getSession, client, requestLogger);
9497
+ this._assetUuid = assetUuid;
9506
9498
  }
9507
- async onIceServers(response) {
9508
- if (!this._stream) {
9509
- throw new Error("onIceServers no stream");
9510
- }
9511
- const stream = this._stream;
9512
- const pc = new RTCPeerConnection({
9513
- iceServers: WebrtcBase.linkToIceServers(response.headers.get("Link"))
9514
- });
9515
- this._pc = pc;
9516
- pc.onicecandidate = (evt) => this.onLocalCandidate(evt);
9517
- return new Promise(async (resolve, reject) => {
9518
- pc.oniceconnectionstatechange = () => {
9519
- switch (pc.iceConnectionState) {
9520
- case "failed":
9521
- case "disconnected":
9522
- reject(`peer connection: ${pc.iceConnectionState}`);
9523
- break;
9524
- case "connected":
9525
- resolve(this);
9526
- break;
9527
- }
9528
- };
9529
- stream.getTracks().forEach((track) => {
9530
- this._requestLogger.debug("onIceServers our stream has track: %s", track);
9531
- pc.addTrack(track, stream);
9532
- });
9533
- stream.addEventListener("addtrack", (trackEvent) => {
9534
- this._requestLogger.debug("onIceServers got lazy track: ", trackEvent.track);
9535
- pc.addTrack(trackEvent.track, stream);
9536
- });
9537
- await this.onLocalOffer(await pc.createOffer());
9538
- });
9499
+ get [Symbol.toStringTag]() {
9500
+ return "loadFromAssetUuidJob";
9539
9501
  }
9540
- async close() {
9541
- const stream = this._stream;
9542
- this._stream = null;
9543
- if (stream) {
9544
- const tracks = stream.getTracks();
9545
- tracks.forEach((track) => {
9546
- track.stop();
9547
- });
9502
+ async startJob() {
9503
+ const session = await this._getSession();
9504
+ if (!session.baseUrl) {
9505
+ return Promise.reject("session.baseUrl must not ne null");
9548
9506
  }
9549
- return super.close();
9507
+ const body = {
9508
+ sourceType: "ASSET_UUID",
9509
+ assetUuid: this._assetUuid,
9510
+ params: this._params
9511
+ };
9512
+ const headers = {
9513
+ ...session.authenticationHeaders(),
9514
+ Accept: "application/jsonl",
9515
+ "Content-Type": "application/json"
9516
+ };
9517
+ const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=sync`;
9518
+ return await this._client.fetch(patchUrl, {
9519
+ headers,
9520
+ method: "PATCH",
9521
+ body: JSON.stringify(body),
9522
+ signal: this._controller.signal,
9523
+ // @ts-ignore
9524
+ eyepop: { responseStreaming: true }
9525
+ });
9550
9526
  }
9551
9527
  };
9552
-
9553
- // worker/webrtc_whep.ts
9554
- var WebrtcWhep = class extends WebrtcBase {
9555
- constructor(ingressId, getSession, client, requestLogger) {
9556
- super(getSession, client, ingressId, "liveIngress/whep", requestLogger);
9557
- this._stream = null;
9558
- }
9559
- async close() {
9560
- const stream = this._stream;
9561
- this._stream = null;
9562
- if (stream) {
9563
- const tracks = stream.getTracks();
9564
- tracks.forEach((track) => {
9565
- track.stop();
9566
- });
9567
- }
9568
- return super.close();
9528
+ var LoadLiveIngressJob = class extends AbstractJob {
9529
+ constructor(ingressId, params, getSession, client, requestLogger) {
9530
+ super(params, getSession, client, requestLogger);
9531
+ this._ingressId = ingressId;
9569
9532
  }
9570
- async stream() {
9571
- if (this._stream) {
9572
- return Promise.resolve(this._stream);
9573
- } else {
9574
- return Promise.reject("fetching stream failed");
9575
- }
9533
+ get [Symbol.toStringTag]() {
9534
+ return "loadLiveIngressJob";
9576
9535
  }
9577
- async start() {
9536
+ async startJob() {
9578
9537
  const session = await this._getSession();
9579
- const egressUrl = this.gresUrl(session);
9580
- const response = await this._client.fetch(egressUrl, {
9581
- headers: session.authenticationHeaders(),
9582
- method: "GET"
9583
- });
9584
- if (response.status >= 300) {
9585
- return Promise.reject(`unknown status code for GET '${egressUrl}': ${response.status} (${response.statusText})`);
9538
+ if (!session.baseUrl) {
9539
+ return Promise.reject("session.baseUrl must not ne null");
9586
9540
  }
9587
- return this.onIceServers(response);
9541
+ const body = {
9542
+ sourceType: "LIVE_INGRESS",
9543
+ liveIngressId: this._ingressId,
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=preempt&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
+ });
9588
9560
  }
9589
- async onRemoteAnswer(answer) {
9590
- if (!this._pc) {
9591
- throw new Error("onLocalOffer no peer connection");
9592
- }
9593
- await this._pc.setRemoteDescription(new RTCSessionDescription(answer));
9594
- if (this._queuedCandidates.length !== 0) {
9595
- await this.sendLocalCandidates(this._queuedCandidates);
9596
- this._queuedCandidates = [];
9597
- }
9598
- return this;
9561
+ };
9562
+ var LoadMediaStreamJob = class extends AbstractJob {
9563
+ constructor(mediaStream, params, getSession, client, requestLogger) {
9564
+ super(params, getSession, client, requestLogger);
9565
+ this._mediaStream = mediaStream;
9599
9566
  }
9600
- async onIceServers(response) {
9601
- if (this._stream) {
9602
- throw new Error("onIceServers has stream");
9567
+ get [Symbol.toStringTag]() {
9568
+ return "loadLiveIngressJob";
9569
+ }
9570
+ async startJob() {
9571
+ const session = await this._getSession();
9572
+ if (!session.baseUrl) {
9573
+ return Promise.reject("session.baseUrl must not ne null");
9603
9574
  }
9604
- const pc = new RTCPeerConnection({
9605
- iceServers: WebrtcBase.linkToIceServers(response.headers.get("Link"))
9575
+ const whip = new WebrtcWhip(
9576
+ this._mediaStream,
9577
+ async () => {
9578
+ return session;
9579
+ },
9580
+ this._client,
9581
+ true,
9582
+ this._requestLogger
9583
+ );
9584
+ whip.start().then((value) => {
9585
+ this._requestLogger.debug("direct whip for ingressId=%s successful", whip.ingressId());
9586
+ }).catch((reason) => {
9587
+ this._controller.abort(reason);
9606
9588
  });
9607
- const direction = "sendrecv";
9608
- pc.addTransceiver("video", { direction });
9609
- pc.onicecandidate = (evt) => this.onLocalCandidate(evt);
9610
- this._pc = pc;
9611
- this._requestLogger.info(`onIceServers ${pc.iceConnectionState}`);
9612
- return new Promise(async (resolve, reject) => {
9613
- pc.ontrack = (evt) => {
9614
- this._requestLogger.debug(`new track: ${evt.track.kind}`);
9615
- this._stream = evt.streams[0];
9616
- resolve(this);
9617
- };
9618
- pc.oniceconnectionstatechange = () => {
9619
- switch (pc.iceConnectionState) {
9620
- case "failed":
9621
- case "disconnected":
9622
- this._requestLogger.debug("oniceconnectionstatechange disconnected");
9623
- reject(`peer connection: ${pc.iceConnectionState}`);
9624
- break;
9625
- case "connected":
9626
- this._requestLogger.debug("oniceconnectionstatechange connected");
9627
- break;
9628
- }
9629
- };
9630
- await this.onLocalOffer(await pc.createOffer());
9589
+ const body = {
9590
+ sourceType: "WHIP",
9591
+ whipIngressId: whip.ingressId(),
9592
+ params: this._params
9593
+ };
9594
+ const headers = {
9595
+ ...session.authenticationHeaders(),
9596
+ Accept: "application/jsonl",
9597
+ "Content-Type": "application/json"
9598
+ };
9599
+ const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=preempt&processing=sync`;
9600
+ return await this._client.fetch(patchUrl, {
9601
+ headers,
9602
+ method: "PATCH",
9603
+ body: JSON.stringify(body),
9604
+ signal: this._controller.signal,
9605
+ // @ts-ignore
9606
+ eyepop: { responseStreaming: true }
9631
9607
  });
9632
9608
  }
9633
9609
  };
@@ -10141,8 +10117,6 @@ var WorkerEndpoint = class extends Endpoint {
10141
10117
  this._pipelineId = null;
10142
10118
  this._popName = null;
10143
10119
  this._pipeline = null;
10144
- this._ingressEventHandler = null;
10145
- this._ingressEventWs = null;
10146
10120
  this._sandboxId = null;
10147
10121
  const sessionAuth = options.auth;
10148
10122
  if (sessionAuth !== void 0 && sessionAuth.session !== void 0) {
@@ -10168,7 +10142,7 @@ var WorkerEndpoint = class extends Endpoint {
10168
10142
  });
10169
10143
  if (response?.status != 204) {
10170
10144
  const message = await response?.text();
10171
- this._logger.info(`stoppping pipeline failed, status ${response?.status}: ${message}`);
10145
+ this._logger.info(`stopping pipeline failed, status ${response?.status}: ${message}`);
10172
10146
  }
10173
10147
  }
10174
10148
  if (this._sandboxId && this._baseUrl) {
@@ -10342,42 +10316,6 @@ var WorkerEndpoint = class extends Endpoint {
10342
10316
  popId() {
10343
10317
  return this.options().popId || null;
10344
10318
  }
10345
- onIngressEvent(handler) {
10346
- this._ingressEventHandler = handler;
10347
- this.startIngressWs().then(() => {
10348
- }).catch((reason) => {
10349
- this._logger.warn("unexpected error starting the ingress event handler: %s", reason);
10350
- });
10351
- return this;
10352
- }
10353
- async liveIngress(stream) {
10354
- if (!this._baseUrl || !this._client) {
10355
- return Promise.reject("endpoint not connected, use connect()");
10356
- }
10357
- const whip = new WebrtcWhip(
10358
- stream,
10359
- async () => {
10360
- return this.session();
10361
- },
10362
- this._client,
10363
- this._requestLogger
10364
- );
10365
- return whip.start();
10366
- }
10367
- async liveEgress(ingressId) {
10368
- if (!this._baseUrl || !this._client) {
10369
- return Promise.reject("endpoint not connected, use connect()");
10370
- }
10371
- const whep = new WebrtcWhep(
10372
- ingressId,
10373
- async () => {
10374
- return this.session();
10375
- },
10376
- this._client,
10377
- this._requestLogger
10378
- );
10379
- return whep.start();
10380
- }
10381
10319
  async process(source, params) {
10382
10320
  if (source.file !== void 0) {
10383
10321
  return this.uploadFile(source, params);
@@ -10387,45 +10325,14 @@ var WorkerEndpoint = class extends Endpoint {
10387
10325
  return this.uploadPath(source, params);
10388
10326
  } else if (source.url !== void 0) {
10389
10327
  return this.loadFrom(source, params);
10390
- } else if (source.ingressId !== void 0) {
10391
- return this.loadLiveIngress(source, params);
10392
10328
  } else if (source.assetUuid !== void 0) {
10393
10329
  return this.loadFromAssetUuid(source, params);
10330
+ } else if (source.mediaStream !== void 0) {
10331
+ return this.loadMediaStream(source, params);
10394
10332
  } else {
10395
10333
  return Promise.reject("unknown source type");
10396
10334
  }
10397
10335
  }
10398
- async startIngressWs() {
10399
- const client = this._client;
10400
- if (this._ingressEventWs || !this._baseUrl || !client) {
10401
- return;
10402
- }
10403
- try {
10404
- const session = await this.session();
10405
- let response = await this.fetchWithRetry(async () => {
10406
- const headers = {
10407
- ...this._authenticationHeaders(session)
10408
- };
10409
- const getTokenUrl = `${session.baseUrl}/liveIngress/events/token`;
10410
- return client.fetch(getTokenUrl, { headers });
10411
- });
10412
- if (response.status != 200) {
10413
- const message = await response.text();
10414
- return Promise.reject(`Unexpected status ${response.status}: ${message}`);
10415
- }
10416
- const wsAuthToken = await response.json();
10417
- const wsUrl = this._baseUrl.startsWith("https://") ? `${this._baseUrl.replace("https://", "wss://")}/liveIngress/events/${wsAuthToken.token}` : `${this._baseUrl.replace("http://", "ws://")}/liveIngress/events/${wsAuthToken.token}`;
10418
- this._ingressEventWs = new WebSocket(wsUrl);
10419
- this._ingressEventWs.addEventListener("message", (event) => {
10420
- const ingressEvent = JSON.parse(event.data);
10421
- if (this._ingressEventHandler) {
10422
- this._ingressEventHandler(ingressEvent);
10423
- }
10424
- });
10425
- } finally {
10426
- this.updateState();
10427
- }
10428
- }
10429
10336
  async uploadFile(source, params) {
10430
10337
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10431
10338
  return Promise.reject("endpoint not connected, use connect()");
@@ -10591,15 +10498,15 @@ var WorkerEndpoint = class extends Endpoint {
10591
10498
  throw e;
10592
10499
  }
10593
10500
  }
10594
- async loadLiveIngress(source, params) {
10501
+ async loadMediaStream(source, params) {
10595
10502
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10596
10503
  throw new Error("endpoint not connected, use connect()");
10597
10504
  }
10598
10505
  await this._limit.acquire();
10599
10506
  try {
10600
10507
  this.updateState();
10601
- const job = new LoadLiveIngressJob(
10602
- source.ingressId,
10508
+ const job = new LoadMediaStreamJob(
10509
+ source.mediaStream,
10603
10510
  params,
10604
10511
  async () => {
10605
10512
  return this.session();
@@ -10747,9 +10654,6 @@ var WorkerEndpoint = class extends Endpoint {
10747
10654
  }
10748
10655
  }
10749
10656
  }
10750
- if (!this._ingressEventWs && this._ingressEventHandler) {
10751
- await this.startIngressWs();
10752
- }
10753
10657
  this.updateState();
10754
10658
  return Promise.resolve(this);
10755
10659
  }
@@ -11658,6 +11562,88 @@ var DataEndpoint = class extends Endpoint {
11658
11562
  }
11659
11563
  };
11660
11564
 
11565
+ // worker/webrtc_whep.ts
11566
+ var WebrtcWhep = class extends WebrtcBase {
11567
+ constructor(ingressId, getSession, client, requestLogger) {
11568
+ super(getSession, client, ingressId, "liveIngress/whep", false, requestLogger);
11569
+ this._stream = null;
11570
+ }
11571
+ async close() {
11572
+ const stream = this._stream;
11573
+ this._stream = null;
11574
+ if (stream) {
11575
+ const tracks = stream.getTracks();
11576
+ tracks.forEach((track) => {
11577
+ track.stop();
11578
+ });
11579
+ }
11580
+ return super.close();
11581
+ }
11582
+ async stream() {
11583
+ if (this._stream) {
11584
+ return Promise.resolve(this._stream);
11585
+ } else {
11586
+ return Promise.reject("fetching stream failed");
11587
+ }
11588
+ }
11589
+ async start() {
11590
+ const session = await this._getSession();
11591
+ const egressUrl = this.gresUrl(session);
11592
+ const response = await this._client.fetch(egressUrl, {
11593
+ headers: session.authenticationHeaders(),
11594
+ method: "GET"
11595
+ });
11596
+ if (response.status >= 300) {
11597
+ return Promise.reject(`unknown status code for GET '${egressUrl}': ${response.status} (${response.statusText})`);
11598
+ }
11599
+ return this.onIceServers(response);
11600
+ }
11601
+ async onRemoteAnswer(answer) {
11602
+ if (!this._pc) {
11603
+ throw new Error("onLocalOffer no peer connection");
11604
+ }
11605
+ await this._pc.setRemoteDescription(new RTCSessionDescription(answer));
11606
+ if (this._queuedCandidates.length !== 0) {
11607
+ await this.sendLocalCandidates(this._queuedCandidates);
11608
+ this._queuedCandidates = [];
11609
+ }
11610
+ return this;
11611
+ }
11612
+ async onIceServers(response) {
11613
+ if (this._stream) {
11614
+ throw new Error("onIceServers has stream");
11615
+ }
11616
+ const pc = new RTCPeerConnection({
11617
+ iceServers: WebrtcBase.linkToIceServers(response.headers.get("Link"))
11618
+ });
11619
+ const direction = "sendrecv";
11620
+ pc.addTransceiver("video", { direction });
11621
+ pc.onicecandidate = (evt) => this.onLocalCandidate(evt);
11622
+ this._pc = pc;
11623
+ this._requestLogger.info(`onIceServers ${pc.iceConnectionState}`);
11624
+ return new Promise(async (resolve, reject) => {
11625
+ pc.ontrack = (evt) => {
11626
+ this._requestLogger.debug(`new track: ${evt.track.kind}`);
11627
+ this._stream = evt.streams[0];
11628
+ resolve(this);
11629
+ };
11630
+ pc.oniceconnectionstatechange = () => {
11631
+ switch (pc.iceConnectionState) {
11632
+ case "failed":
11633
+ case "disconnected":
11634
+ this._requestLogger.debug("oniceconnectionstatechange disconnected");
11635
+ reject(`peer connection: ${pc.iceConnectionState}`);
11636
+ break;
11637
+ case "connected":
11638
+ this._requestLogger.debug("oniceconnectionstatechange connected");
11639
+ break;
11640
+ }
11641
+ };
11642
+ await this.onLocalOffer(await pc.createOffer());
11643
+ });
11644
+ }
11645
+ };
11646
+
11661
11647
  // index.ts
11662
11648
  var readEnv = (env) => {
11663
11649
  if (typeof process !== "undefined") {
@@ -11792,6 +11778,7 @@ export {
11792
11778
  LoadFromAssetUuidJob,
11793
11779
  LoadFromJob,
11794
11780
  LoadLiveIngressJob,
11781
+ LoadMediaStreamJob,
11795
11782
  ModelExportStatus,
11796
11783
  ModelFormat,
11797
11784
  ModelPrecisionType,