function _mergeNamespaces(n, m){m.forEach(function(e){e&&typeof e!=='string'&&!Array.isArray(e)&&Object.keys(e).forEach(function(k){if(k!=='default'&&!(k in n)){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}})});return Object.freeze(n);}var e = Object.defineProperty; var h = (i, s, t) => s in i ? e(i, s, { enumerable: true, configurable: true, writable: true, value: t }) : i[s] = t; var o = (i, s, t) => h(i, typeof s != "symbol" ? s + "" : s, t); class _ { constructor() { o(this, "_locking"); o(this, "_locks"); this._locking = Promise.resolve(), this._locks = 0; } isLocked() { return this._locks > 0; } lock() { this._locks += 1; let s; const t = new Promise(l => s = () => { this._locks -= 1, l(); }), c = this._locking.then(() => s); return this._locking = this._locking.then(() => t), c; } }// Copyright 2021-2024 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * Assert that condition is truthy or throw error (with message) */ function assert(condition, msg) { // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions -- we want the implicit conversion to boolean if (!condition) { throw new Error(msg); } } const FLOAT32_MAX = 3.4028234663852886e38, FLOAT32_MIN = -34028234663852886e22, UINT32_MAX = 0xffffffff, INT32_MAX = 0x7fffffff, INT32_MIN = -2147483648; /** * Assert a valid signed protobuf 32-bit integer. */ function assertInt32(arg) { if (typeof arg !== "number") throw new Error("invalid int 32: " + typeof arg); if (!Number.isInteger(arg) || arg > INT32_MAX || arg < INT32_MIN) throw new Error("invalid int 32: " + arg); // eslint-disable-line @typescript-eslint/restrict-plus-operands -- we want the implicit conversion to string } /** * Assert a valid unsigned protobuf 32-bit integer. */ function assertUInt32(arg) { if (typeof arg !== "number") throw new Error("invalid uint 32: " + typeof arg); if (!Number.isInteger(arg) || arg > UINT32_MAX || arg < 0) throw new Error("invalid uint 32: " + arg); // eslint-disable-line @typescript-eslint/restrict-plus-operands -- we want the implicit conversion to string } /** * Assert a valid protobuf float value. */ function assertFloat32(arg) { if (typeof arg !== "number") throw new Error("invalid float 32: " + typeof arg); if (!Number.isFinite(arg)) return; if (arg > FLOAT32_MAX || arg < FLOAT32_MIN) throw new Error("invalid float 32: " + arg); // eslint-disable-line @typescript-eslint/restrict-plus-operands -- we want the implicit conversion to string }// Copyright 2021-2024 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. const enumTypeSymbol = Symbol("@bufbuild/protobuf/enum-type"); /** * Get reflection information from a generated enum. * If this function is called on something other than a generated * enum, it raises an error. */ function getEnumType(enumObject) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-explicit-any const t = enumObject[enumTypeSymbol]; assert(t, "missing enum type on enum object"); return t; // eslint-disable-line @typescript-eslint/no-unsafe-return } /** * Sets reflection information on a generated enum. */ function setEnumType(enumObject, typeName, values, opt) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any enumObject[enumTypeSymbol] = makeEnumType(typeName, values.map(v => ({ no: v.no, name: v.name, localName: enumObject[v.no] }))); } /** * Create a new EnumType with the given values. */ function makeEnumType(typeName, values, // eslint-disable-next-line @typescript-eslint/no-unused-vars _opt) { const names = Object.create(null); const numbers = Object.create(null); const normalValues = []; for (const value of values) { // We do not surface options at this time // const value: EnumValueInfo = {...v, options: v.options ?? emptyReadonlyObject}; const n = normalizeEnumValue(value); normalValues.push(n); names[value.name] = n; numbers[value.no] = n; } return { typeName, values: normalValues, // We do not surface options at this time // options: opt?.options ?? Object.create(null), findName(name) { return names[name]; }, findNumber(no) { return numbers[no]; } }; } /** * Create a new enum object with the given values. * Sets reflection information. */ function makeEnum(typeName, values, opt) { const enumObject = {}; for (const value of values) { const n = normalizeEnumValue(value); enumObject[n.localName] = n.no; enumObject[n.no] = n.localName; } setEnumType(enumObject, typeName, values); return enumObject; } function normalizeEnumValue(value) { if ("localName" in value) { return value; } return Object.assign(Object.assign({}, value), { localName: value.name }); }// Copyright 2021-2024 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * Message is the base class of every message, generated, or created at * runtime. * * It is _not_ safe to extend this class. If you want to create a message at * run time, use proto3.makeMessageType(). */ class Message { /** * Compare with a message of the same type. * Note that this function disregards extensions and unknown fields. */ equals(other) { return this.getType().runtime.util.equals(this.getType(), this, other); } /** * Create a deep copy. */ clone() { return this.getType().runtime.util.clone(this); } /** * Parse from binary data, merging fields. * * Repeated fields are appended. Map entries are added, overwriting * existing keys. * * If a message field is already present, it will be merged with the * new data. */ fromBinary(bytes, options) { const type = this.getType(), format = type.runtime.bin, opt = format.makeReadOptions(options); format.readMessage(this, opt.readerFactory(bytes), bytes.byteLength, opt); return this; } /** * Parse a message from a JSON value. */ fromJson(jsonValue, options) { const type = this.getType(), format = type.runtime.json, opt = format.makeReadOptions(options); format.readMessage(type, jsonValue, opt, this); return this; } /** * Parse a message from a JSON string. */ fromJsonString(jsonString, options) { let json; try { json = JSON.parse(jsonString); } catch (e) { throw new Error("cannot decode ".concat(this.getType().typeName, " from JSON: ").concat(e instanceof Error ? e.message : String(e))); } return this.fromJson(json, options); } /** * Serialize the message to binary data. */ toBinary(options) { const type = this.getType(), bin = type.runtime.bin, opt = bin.makeWriteOptions(options), writer = opt.writerFactory(); bin.writeMessage(this, writer, opt); return writer.finish(); } /** * Serialize the message to a JSON value, a JavaScript value that can be * passed to JSON.stringify(). */ toJson(options) { const type = this.getType(), json = type.runtime.json, opt = json.makeWriteOptions(options); return json.writeMessage(this, opt); } /** * Serialize the message to a JSON string. */ toJsonString(options) { var _a; const value = this.toJson(options); return JSON.stringify(value, null, (_a = options === null || options === void 0 ? void 0 : options.prettySpaces) !== null && _a !== void 0 ? _a : 0); } /** * Override for serialization behavior. This will be invoked when calling * JSON.stringify on this message (i.e. JSON.stringify(msg)). * * Note that this will not serialize google.protobuf.Any with a packed * message because the protobuf JSON format specifies that it needs to be * unpacked, and this is only possible with a type registry to look up the * message type. As a result, attempting to serialize a message with this * type will throw an Error. * * This method is protected because you should not need to invoke it * directly -- instead use JSON.stringify or toJsonString for * stringified JSON. Alternatively, if actual JSON is desired, you should * use toJson. */ toJSON() { return this.toJson({ emitDefaultValues: true }); } /** * Retrieve the MessageType of this message - a singleton that represents * the protobuf message declaration and provides metadata for reflection- * based operations. */ getType() { // Any class that extends Message _must_ provide a complete static // implementation of MessageType. // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-return return Object.getPrototypeOf(this).constructor; } }// Copyright 2021-2024 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * Create a new message type using the given runtime. */ function makeMessageType(runtime, typeName, fields, opt) { var _a; const localName = (_a = opt === null || opt === void 0 ? void 0 : opt.localName) !== null && _a !== void 0 ? _a : typeName.substring(typeName.lastIndexOf(".") + 1); const type = { [localName]: function (data) { runtime.util.initFields(this); runtime.util.initPartial(data, this); } }[localName]; Object.setPrototypeOf(type.prototype, new Message()); Object.assign(type, { runtime, typeName, fields: runtime.util.newFieldList(fields), fromBinary(bytes, options) { return new type().fromBinary(bytes, options); }, fromJson(jsonValue, options) { return new type().fromJson(jsonValue, options); }, fromJsonString(jsonString, options) { return new type().fromJsonString(jsonString, options); }, equals(a, b) { return runtime.util.equals(type, a, b); } }); return type; }// Copyright 2008 Google Inc. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // Code generated by the Protocol Buffer compiler is owned by the owner // of the input file used when generating it. This code is not // standalone and requires a support library to be linked with it. This // support library is itself covered by the above license. /* eslint-disable prefer-const,@typescript-eslint/restrict-plus-operands */ /** * Read a 64 bit varint as two JS numbers. * * Returns tuple: * [0]: low bits * [1]: high bits * * Copyright 2008 Google Inc. All rights reserved. * * See https://github.com/protocolbuffers/protobuf/blob/8a71927d74a4ce34efe2d8769fda198f52d20d12/js/experimental/runtime/kernel/buffer_decoder.js#L175 */ function varint64read() { let lowBits = 0; let highBits = 0; for (let shift = 0; shift < 28; shift += 7) { let b = this.buf[this.pos++]; lowBits |= (b & 0x7f) << shift; if ((b & 0x80) == 0) { this.assertBounds(); return [lowBits, highBits]; } } let middleByte = this.buf[this.pos++]; // last four bits of the first 32 bit number lowBits |= (middleByte & 0x0f) << 28; // 3 upper bits are part of the next 32 bit number highBits = (middleByte & 0x70) >> 4; if ((middleByte & 0x80) == 0) { this.assertBounds(); return [lowBits, highBits]; } for (let shift = 3; shift <= 31; shift += 7) { let b = this.buf[this.pos++]; highBits |= (b & 0x7f) << shift; if ((b & 0x80) == 0) { this.assertBounds(); return [lowBits, highBits]; } } throw new Error("invalid varint"); } /** * Write a 64 bit varint, given as two JS numbers, to the given bytes array. * * Copyright 2008 Google Inc. All rights reserved. * * See https://github.com/protocolbuffers/protobuf/blob/8a71927d74a4ce34efe2d8769fda198f52d20d12/js/experimental/runtime/kernel/writer.js#L344 */ function varint64write(lo, hi, bytes) { for (let i = 0; i < 28; i = i + 7) { const shift = lo >>> i; const hasNext = !(shift >>> 7 == 0 && hi == 0); const byte = (hasNext ? shift | 0x80 : shift) & 0xff; bytes.push(byte); if (!hasNext) { return; } } const splitBits = lo >>> 28 & 0x0f | (hi & 0x07) << 4; const hasMoreBits = !(hi >> 3 == 0); bytes.push((hasMoreBits ? splitBits | 0x80 : splitBits) & 0xff); if (!hasMoreBits) { return; } for (let i = 3; i < 31; i = i + 7) { const shift = hi >>> i; const hasNext = !(shift >>> 7 == 0); const byte = (hasNext ? shift | 0x80 : shift) & 0xff; bytes.push(byte); if (!hasNext) { return; } } bytes.push(hi >>> 31 & 0x01); } // constants for binary math const TWO_PWR_32_DBL = 0x100000000; /** * Parse decimal string of 64 bit integer value as two JS numbers. * * Copyright 2008 Google Inc. All rights reserved. * * See https://github.com/protocolbuffers/protobuf-javascript/blob/a428c58273abad07c66071d9753bc4d1289de426/experimental/runtime/int64.js#L10 */ function int64FromString(dec) { // Check for minus sign. const minus = dec[0] === "-"; if (minus) { dec = dec.slice(1); } // Work 6 decimal digits at a time, acting like we're converting base 1e6 // digits to binary. This is safe to do with floating point math because // Number.isSafeInteger(ALL_32_BITS * 1e6) == true. const base = 1e6; let lowBits = 0; let highBits = 0; function add1e6digit(begin, end) { // Note: Number('') is 0. const digit1e6 = Number(dec.slice(begin, end)); highBits *= base; lowBits = lowBits * base + digit1e6; // Carry bits from lowBits to if (lowBits >= TWO_PWR_32_DBL) { highBits = highBits + (lowBits / TWO_PWR_32_DBL | 0); lowBits = lowBits % TWO_PWR_32_DBL; } } add1e6digit(-24, -18); add1e6digit(-18, -12); add1e6digit(-12, -6); add1e6digit(-6); return minus ? negate(lowBits, highBits) : newBits(lowBits, highBits); } /** * Losslessly converts a 64-bit signed integer in 32:32 split representation * into a decimal string. * * Copyright 2008 Google Inc. All rights reserved. * * See https://github.com/protocolbuffers/protobuf-javascript/blob/a428c58273abad07c66071d9753bc4d1289de426/experimental/runtime/int64.js#L10 */ function int64ToString(lo, hi) { let bits = newBits(lo, hi); // If we're treating the input as a signed value and the high bit is set, do // a manual two's complement conversion before the decimal conversion. const negative = bits.hi & 0x80000000; if (negative) { bits = negate(bits.lo, bits.hi); } const result = uInt64ToString(bits.lo, bits.hi); return negative ? "-" + result : result; } /** * Losslessly converts a 64-bit unsigned integer in 32:32 split representation * into a decimal string. * * Copyright 2008 Google Inc. All rights reserved. * * See https://github.com/protocolbuffers/protobuf-javascript/blob/a428c58273abad07c66071d9753bc4d1289de426/experimental/runtime/int64.js#L10 */ function uInt64ToString(lo, hi) { // Skip the expensive conversion if the number is small enough to use the // built-in conversions. // Number.MAX_SAFE_INTEGER = 0x001FFFFF FFFFFFFF, thus any number with // highBits <= 0x1FFFFF can be safely expressed with a double and retain // integer precision. // Proven by: Number.isSafeInteger(0x1FFFFF * 2**32 + 0xFFFFFFFF) == true. var _toUnsigned = toUnsigned(lo, hi); lo = _toUnsigned.lo; hi = _toUnsigned.hi; if (hi <= 0x1FFFFF) { return String(TWO_PWR_32_DBL * hi + lo); } // What this code is doing is essentially converting the input number from // base-2 to base-1e7, which allows us to represent the 64-bit range with // only 3 (very large) digits. Those digits are then trivial to convert to // a base-10 string. // The magic numbers used here are - // 2^24 = 16777216 = (1,6777216) in base-1e7. // 2^48 = 281474976710656 = (2,8147497,6710656) in base-1e7. // Split 32:32 representation into 16:24:24 representation so our // intermediate digits don't overflow. const low = lo & 0xFFFFFF; const mid = (lo >>> 24 | hi << 8) & 0xFFFFFF; const high = hi >> 16 & 0xFFFF; // Assemble our three base-1e7 digits, ignoring carries. The maximum // value in a digit at this step is representable as a 48-bit integer, which // can be stored in a 64-bit floating point number. let digitA = low + mid * 6777216 + high * 6710656; let digitB = mid + high * 8147497; let digitC = high * 2; // Apply carries from A to B and from B to C. const base = 10000000; if (digitA >= base) { digitB += Math.floor(digitA / base); digitA %= base; } if (digitB >= base) { digitC += Math.floor(digitB / base); digitB %= base; } // If digitC is 0, then we should have returned in the trivial code path // at the top for non-safe integers. Given this, we can assume both digitB // and digitA need leading zeros. return digitC.toString() + decimalFrom1e7WithLeadingZeros(digitB) + decimalFrom1e7WithLeadingZeros(digitA); } function toUnsigned(lo, hi) { return { lo: lo >>> 0, hi: hi >>> 0 }; } function newBits(lo, hi) { return { lo: lo | 0, hi: hi | 0 }; } /** * Returns two's compliment negation of input. * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Signed_32-bit_integers */ function negate(lowBits, highBits) { highBits = ~highBits; if (lowBits) { lowBits = ~lowBits + 1; } else { // If lowBits is 0, then bitwise-not is 0xFFFFFFFF, // adding 1 to that, results in 0x100000000, which leaves // the low bits 0x0 and simply adds one to the high bits. highBits += 1; } return newBits(lowBits, highBits); } /** * Returns decimal representation of digit1e7 with leading zeros. */ const decimalFrom1e7WithLeadingZeros = digit1e7 => { const partial = String(digit1e7); return "0000000".slice(partial.length) + partial; }; /** * Write a 32 bit varint, signed or unsigned. Same as `varint64write(0, value, bytes)` * * Copyright 2008 Google Inc. All rights reserved. * * See https://github.com/protocolbuffers/protobuf/blob/1b18833f4f2a2f681f4e4a25cdf3b0a43115ec26/js/binary/encoder.js#L144 */ function varint32write(value, bytes) { if (value >= 0) { // write value as varint 32 while (value > 0x7f) { bytes.push(value & 0x7f | 0x80); value = value >>> 7; } bytes.push(value); } else { for (let i = 0; i < 9; i++) { bytes.push(value & 127 | 128); value = value >> 7; } bytes.push(1); } } /** * Read an unsigned 32 bit varint. * * See https://github.com/protocolbuffers/protobuf/blob/8a71927d74a4ce34efe2d8769fda198f52d20d12/js/experimental/runtime/kernel/buffer_decoder.js#L220 */ function varint32read() { let b = this.buf[this.pos++]; let result = b & 0x7f; if ((b & 0x80) == 0) { this.assertBounds(); return result; } b = this.buf[this.pos++]; result |= (b & 0x7f) << 7; if ((b & 0x80) == 0) { this.assertBounds(); return result; } b = this.buf[this.pos++]; result |= (b & 0x7f) << 14; if ((b & 0x80) == 0) { this.assertBounds(); return result; } b = this.buf[this.pos++]; result |= (b & 0x7f) << 21; if ((b & 0x80) == 0) { this.assertBounds(); return result; } // Extract only last 4 bits b = this.buf[this.pos++]; result |= (b & 0x0f) << 28; for (let readBytes = 5; (b & 0x80) !== 0 && readBytes < 10; readBytes++) b = this.buf[this.pos++]; if ((b & 0x80) != 0) throw new Error("invalid varint"); this.assertBounds(); // Result can have 32 bits, convert it to unsigned return result >>> 0; }// Copyright 2021-2024 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. function makeInt64Support() { const dv = new DataView(new ArrayBuffer(8)); // note that Safari 14 implements BigInt, but not the DataView methods const ok = typeof BigInt === "function" && typeof dv.getBigInt64 === "function" && typeof dv.getBigUint64 === "function" && typeof dv.setBigInt64 === "function" && typeof dv.setBigUint64 === "function" && (typeof process != "object" || typeof process.env != "object" || process.env.BUF_BIGINT_DISABLE !== "1"); if (ok) { const MIN = BigInt("-9223372036854775808"), MAX = BigInt("9223372036854775807"), UMIN = BigInt("0"), UMAX = BigInt("18446744073709551615"); return { zero: BigInt(0), supported: true, parse(value) { const bi = typeof value == "bigint" ? value : BigInt(value); if (bi > MAX || bi < MIN) { throw new Error("int64 invalid: ".concat(value)); } return bi; }, uParse(value) { const bi = typeof value == "bigint" ? value : BigInt(value); if (bi > UMAX || bi < UMIN) { throw new Error("uint64 invalid: ".concat(value)); } return bi; }, enc(value) { dv.setBigInt64(0, this.parse(value), true); return { lo: dv.getInt32(0, true), hi: dv.getInt32(4, true) }; }, uEnc(value) { dv.setBigInt64(0, this.uParse(value), true); return { lo: dv.getInt32(0, true), hi: dv.getInt32(4, true) }; }, dec(lo, hi) { dv.setInt32(0, lo, true); dv.setInt32(4, hi, true); return dv.getBigInt64(0, true); }, uDec(lo, hi) { dv.setInt32(0, lo, true); dv.setInt32(4, hi, true); return dv.getBigUint64(0, true); } }; } const assertInt64String = value => assert(/^-?[0-9]+$/.test(value), "int64 invalid: ".concat(value)); const assertUInt64String = value => assert(/^[0-9]+$/.test(value), "uint64 invalid: ".concat(value)); return { zero: "0", supported: false, parse(value) { if (typeof value != "string") { value = value.toString(); } assertInt64String(value); return value; }, uParse(value) { if (typeof value != "string") { value = value.toString(); } assertUInt64String(value); return value; }, enc(value) { if (typeof value != "string") { value = value.toString(); } assertInt64String(value); return int64FromString(value); }, uEnc(value) { if (typeof value != "string") { value = value.toString(); } assertUInt64String(value); return int64FromString(value); }, dec(lo, hi) { return int64ToString(lo, hi); }, uDec(lo, hi) { return uInt64ToString(lo, hi); } }; } const protoInt64 = makeInt64Support();// Copyright 2021-2024 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * Scalar value types. This is a subset of field types declared by protobuf * enum google.protobuf.FieldDescriptorProto.Type The types GROUP and MESSAGE * are omitted, but the numerical values are identical. */ var ScalarType; (function (ScalarType) { // 0 is reserved for errors. // Order is weird for historical reasons. ScalarType[ScalarType["DOUBLE"] = 1] = "DOUBLE"; ScalarType[ScalarType["FLOAT"] = 2] = "FLOAT"; // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if // negative values are likely. ScalarType[ScalarType["INT64"] = 3] = "INT64"; ScalarType[ScalarType["UINT64"] = 4] = "UINT64"; // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if // negative values are likely. ScalarType[ScalarType["INT32"] = 5] = "INT32"; ScalarType[ScalarType["FIXED64"] = 6] = "FIXED64"; ScalarType[ScalarType["FIXED32"] = 7] = "FIXED32"; ScalarType[ScalarType["BOOL"] = 8] = "BOOL"; ScalarType[ScalarType["STRING"] = 9] = "STRING"; // Tag-delimited aggregate. // Group type is deprecated and not supported in proto3. However, Proto3 // implementations should still be able to parse the group wire format and // treat group fields as unknown fields. // TYPE_GROUP = 10, // TYPE_MESSAGE = 11, // Length-delimited aggregate. // New in version 2. ScalarType[ScalarType["BYTES"] = 12] = "BYTES"; ScalarType[ScalarType["UINT32"] = 13] = "UINT32"; // TYPE_ENUM = 14, ScalarType[ScalarType["SFIXED32"] = 15] = "SFIXED32"; ScalarType[ScalarType["SFIXED64"] = 16] = "SFIXED64"; ScalarType[ScalarType["SINT32"] = 17] = "SINT32"; ScalarType[ScalarType["SINT64"] = 18] = "SINT64"; })(ScalarType || (ScalarType = {})); /** * JavaScript representation of fields with 64 bit integral types (int64, uint64, * sint64, fixed64, sfixed64). * * This is a subset of google.protobuf.FieldOptions.JSType, which defines JS_NORMAL, * JS_STRING, and JS_NUMBER. Protobuf-ES uses BigInt by default, but will use * String if `[jstype = JS_STRING]` is specified. * * ```protobuf * uint64 field_a = 1; // BigInt * uint64 field_b = 2 [jstype = JS_NORMAL]; // BigInt * uint64 field_b = 2 [jstype = JS_NUMBER]; // BigInt * uint64 field_b = 2 [jstype = JS_STRING]; // String * ``` */ var LongType; (function (LongType) { /** * Use JavaScript BigInt. */ LongType[LongType["BIGINT"] = 0] = "BIGINT"; /** * Use JavaScript String. * * Field option `[jstype = JS_STRING]`. */ LongType[LongType["STRING"] = 1] = "STRING"; })(LongType || (LongType = {}));// Copyright 2021-2024 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * Returns true if both scalar values are equal. */ function scalarEquals(type, a, b) { if (a === b) { // This correctly matches equal values except BYTES and (possibly) 64-bit integers. return true; } // Special case BYTES - we need to compare each byte individually if (type == ScalarType.BYTES) { if (!(a instanceof Uint8Array) || !(b instanceof Uint8Array)) { return false; } if (a.length !== b.length) { return false; } for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) { return false; } } return true; } // Special case 64-bit integers - we support number, string and bigint representation. // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check switch (type) { case ScalarType.UINT64: case ScalarType.FIXED64: case ScalarType.INT64: case ScalarType.SFIXED64: case ScalarType.SINT64: // Loose comparison will match between 0n, 0 and "0". return a == b; } // Anything that hasn't been caught by strict comparison or special cased // BYTES and 64-bit integers is not equal. return false; } /** * Returns the zero value for the given scalar type. */ function scalarZeroValue(type, longType) { switch (type) { case ScalarType.BOOL: return false; case ScalarType.UINT64: case ScalarType.FIXED64: case ScalarType.INT64: case ScalarType.SFIXED64: case ScalarType.SINT64: // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison -- acceptable since it's covered by tests return longType == 0 ? protoInt64.zero : "0"; case ScalarType.DOUBLE: case ScalarType.FLOAT: return 0.0; case ScalarType.BYTES: return new Uint8Array(0); case ScalarType.STRING: return ""; default: // Handles INT32, UINT32, SINT32, FIXED32, SFIXED32. // We do not use individual cases to save a few bytes code size. return 0; } } /** * Returns true for a zero-value. For example, an integer has the zero-value `0`, * a boolean is `false`, a string is `""`, and bytes is an empty Uint8Array. * * In proto3, zero-values are not written to the wire, unless the field is * optional or repeated. */ function isScalarZeroValue(type, value) { switch (type) { case ScalarType.BOOL: return value === false; case ScalarType.STRING: return value === ""; case ScalarType.BYTES: return value instanceof Uint8Array && !value.byteLength; default: return value == 0; // Loose comparison matches 0n, 0 and "0" } }function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: true, configurable: true, writable: true }) : e[r] = t, e; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = true, o = false; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = true, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }/* eslint-disable prefer-const,no-case-declarations,@typescript-eslint/restrict-plus-operands */ /** * Protobuf binary format wire types. * * A wire type provides just enough information to find the length of the * following value. * * See https://developers.google.com/protocol-buffers/docs/encoding#structure */ var WireType; (function (WireType) { /** * Used for int32, int64, uint32, uint64, sint32, sint64, bool, enum */ WireType[WireType["Varint"] = 0] = "Varint"; /** * Used for fixed64, sfixed64, double. * Always 8 bytes with little-endian byte order. */ WireType[WireType["Bit64"] = 1] = "Bit64"; /** * Used for string, bytes, embedded messages, packed repeated fields * * Only repeated numeric types (types which use the varint, 32-bit, * or 64-bit wire types) can be packed. In proto3, such fields are * packed by default. */ WireType[WireType["LengthDelimited"] = 2] = "LengthDelimited"; /** * Start of a tag-delimited aggregate, such as a proto2 group, or a message * in editions with message_encoding = DELIMITED. */ WireType[WireType["StartGroup"] = 3] = "StartGroup"; /** * End of a tag-delimited aggregate. */ WireType[WireType["EndGroup"] = 4] = "EndGroup"; /** * Used for fixed32, sfixed32, float. * Always 4 bytes with little-endian byte order. */ WireType[WireType["Bit32"] = 5] = "Bit32"; })(WireType || (WireType = {})); class BinaryWriter { constructor(textEncoder) { /** * Previous fork states. */ this.stack = []; this.textEncoder = textEncoder !== null && textEncoder !== void 0 ? textEncoder : new TextEncoder(); this.chunks = []; this.buf = []; } /** * Return all bytes written and reset this writer. */ finish() { this.chunks.push(new Uint8Array(this.buf)); // flush the buffer let len = 0; for (let i = 0; i < this.chunks.length; i++) len += this.chunks[i].length; let bytes = new Uint8Array(len); let offset = 0; for (let i = 0; i < this.chunks.length; i++) { bytes.set(this.chunks[i], offset); offset += this.chunks[i].length; } this.chunks = []; return bytes; } /** * Start a new fork for length-delimited data like a message * or a packed repeated field. * * Must be joined later with `join()`. */ fork() { this.stack.push({ chunks: this.chunks, buf: this.buf }); this.chunks = []; this.buf = []; return this; } /** * Join the last fork. Write its length and bytes, then * return to the previous state. */ join() { // get chunk of fork let chunk = this.finish(); // restore previous state let prev = this.stack.pop(); if (!prev) throw new Error("invalid state, fork stack empty"); this.chunks = prev.chunks; this.buf = prev.buf; // write length of chunk as varint this.uint32(chunk.byteLength); return this.raw(chunk); } /** * Writes a tag (field number and wire type). * * Equivalent to `uint32( (fieldNo << 3 | type) >>> 0 )`. * * Generated code should compute the tag ahead of time and call `uint32()`. */ tag(fieldNo, type) { return this.uint32((fieldNo << 3 | type) >>> 0); } /** * Write a chunk of raw bytes. */ raw(chunk) { if (this.buf.length) { this.chunks.push(new Uint8Array(this.buf)); this.buf = []; } this.chunks.push(chunk); return this; } /** * Write a `uint32` value, an unsigned 32 bit varint. */ uint32(value) { assertUInt32(value); // write value as varint 32, inlined for speed while (value > 0x7f) { this.buf.push(value & 0x7f | 0x80); value = value >>> 7; } this.buf.push(value); return this; } /** * Write a `int32` value, a signed 32 bit varint. */ int32(value) { assertInt32(value); varint32write(value, this.buf); return this; } /** * Write a `bool` value, a variant. */ bool(value) { this.buf.push(value ? 1 : 0); return this; } /** * Write a `bytes` value, length-delimited arbitrary data. */ bytes(value) { this.uint32(value.byteLength); // write length of chunk as varint return this.raw(value); } /** * Write a `string` value, length-delimited data converted to UTF-8 text. */ string(value) { let chunk = this.textEncoder.encode(value); this.uint32(chunk.byteLength); // write length of chunk as varint return this.raw(chunk); } /** * Write a `float` value, 32-bit floating point number. */ float(value) { assertFloat32(value); let chunk = new Uint8Array(4); new DataView(chunk.buffer).setFloat32(0, value, true); return this.raw(chunk); } /** * Write a `double` value, a 64-bit floating point number. */ double(value) { let chunk = new Uint8Array(8); new DataView(chunk.buffer).setFloat64(0, value, true); return this.raw(chunk); } /** * Write a `fixed32` value, an unsigned, fixed-length 32-bit integer. */ fixed32(value) { assertUInt32(value); let chunk = new Uint8Array(4); new DataView(chunk.buffer).setUint32(0, value, true); return this.raw(chunk); } /** * Write a `sfixed32` value, a signed, fixed-length 32-bit integer. */ sfixed32(value) { assertInt32(value); let chunk = new Uint8Array(4); new DataView(chunk.buffer).setInt32(0, value, true); return this.raw(chunk); } /** * Write a `sint32` value, a signed, zigzag-encoded 32-bit varint. */ sint32(value) { assertInt32(value); // zigzag encode value = (value << 1 ^ value >> 31) >>> 0; varint32write(value, this.buf); return this; } /** * Write a `fixed64` value, a signed, fixed-length 64-bit integer. */ sfixed64(value) { let chunk = new Uint8Array(8), view = new DataView(chunk.buffer), tc = protoInt64.enc(value); view.setInt32(0, tc.lo, true); view.setInt32(4, tc.hi, true); return this.raw(chunk); } /** * Write a `fixed64` value, an unsigned, fixed-length 64 bit integer. */ fixed64(value) { let chunk = new Uint8Array(8), view = new DataView(chunk.buffer), tc = protoInt64.uEnc(value); view.setInt32(0, tc.lo, true); view.setInt32(4, tc.hi, true); return this.raw(chunk); } /** * Write a `int64` value, a signed 64-bit varint. */ int64(value) { let tc = protoInt64.enc(value); varint64write(tc.lo, tc.hi, this.buf); return this; } /** * Write a `sint64` value, a signed, zig-zag-encoded 64-bit varint. */ sint64(value) { let tc = protoInt64.enc(value), // zigzag encode sign = tc.hi >> 31, lo = tc.lo << 1 ^ sign, hi = (tc.hi << 1 | tc.lo >>> 31) ^ sign; varint64write(lo, hi, this.buf); return this; } /** * Write a `uint64` value, an unsigned 64-bit varint. */ uint64(value) { let tc = protoInt64.uEnc(value); varint64write(tc.lo, tc.hi, this.buf); return this; } } class BinaryReader { constructor(buf, textDecoder) { this.varint64 = varint64read; // dirty cast for `this` /** * Read a `uint32` field, an unsigned 32 bit varint. */ this.uint32 = varint32read; // dirty cast for `this` and access to protected `buf` this.buf = buf; this.len = buf.length; this.pos = 0; this.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength); this.textDecoder = textDecoder !== null && textDecoder !== void 0 ? textDecoder : new TextDecoder(); } /** * Reads a tag - field number and wire type. */ tag() { let tag = this.uint32(), fieldNo = tag >>> 3, wireType = tag & 7; if (fieldNo <= 0 || wireType < 0 || wireType > 5) throw new Error("illegal tag: field no " + fieldNo + " wire type " + wireType); return [fieldNo, wireType]; } /** * Skip one element and return the skipped data. * * When skipping StartGroup, provide the tags field number to check for * matching field number in the EndGroup tag. */ skip(wireType, fieldNo) { let start = this.pos; switch (wireType) { case WireType.Varint: while (this.buf[this.pos++] & 0x80) { // ignore } break; // eslint-disable-next-line // @ts-ignore TS7029: Fallthrough case in switch case WireType.Bit64: this.pos += 4; // eslint-disable-next-line // @ts-ignore TS7029: Fallthrough case in switch case WireType.Bit32: this.pos += 4; break; case WireType.LengthDelimited: let len = this.uint32(); this.pos += len; break; case WireType.StartGroup: for (;;) { const _this$tag = this.tag(), _this$tag2 = _slicedToArray(_this$tag, 2), fn = _this$tag2[0], wt = _this$tag2[1]; if (wt === WireType.EndGroup) { if (fieldNo !== undefined && fn !== fieldNo) { throw new Error("invalid end group tag"); } break; } this.skip(wt, fn); } break; default: throw new Error("cant skip wire type " + wireType); } this.assertBounds(); return this.buf.subarray(start, this.pos); } /** * Throws error if position in byte array is out of range. */ assertBounds() { if (this.pos > this.len) throw new RangeError("premature EOF"); } /** * Read a `int32` field, a signed 32 bit varint. */ int32() { return this.uint32() | 0; } /** * Read a `sint32` field, a signed, zigzag-encoded 32-bit varint. */ sint32() { let zze = this.uint32(); // decode zigzag return zze >>> 1 ^ -(zze & 1); } /** * Read a `int64` field, a signed 64-bit varint. */ int64() { return protoInt64.dec(...this.varint64()); } /** * Read a `uint64` field, an unsigned 64-bit varint. */ uint64() { return protoInt64.uDec(...this.varint64()); } /** * Read a `sint64` field, a signed, zig-zag-encoded 64-bit varint. */ sint64() { let _this$varint = this.varint64(), _this$varint2 = _slicedToArray(_this$varint, 2), lo = _this$varint2[0], hi = _this$varint2[1]; // decode zig zag let s = -(lo & 1); lo = (lo >>> 1 | (hi & 1) << 31) ^ s; hi = hi >>> 1 ^ s; return protoInt64.dec(lo, hi); } /** * Read a `bool` field, a variant. */ bool() { let _this$varint3 = this.varint64(), _this$varint4 = _slicedToArray(_this$varint3, 2), lo = _this$varint4[0], hi = _this$varint4[1]; return lo !== 0 || hi !== 0; } /** * Read a `fixed32` field, an unsigned, fixed-length 32-bit integer. */ fixed32() { return this.view.getUint32((this.pos += 4) - 4, true); } /** * Read a `sfixed32` field, a signed, fixed-length 32-bit integer. */ sfixed32() { return this.view.getInt32((this.pos += 4) - 4, true); } /** * Read a `fixed64` field, an unsigned, fixed-length 64 bit integer. */ fixed64() { return protoInt64.uDec(this.sfixed32(), this.sfixed32()); } /** * Read a `fixed64` field, a signed, fixed-length 64-bit integer. */ sfixed64() { return protoInt64.dec(this.sfixed32(), this.sfixed32()); } /** * Read a `float` field, 32-bit floating point number. */ float() { return this.view.getFloat32((this.pos += 4) - 4, true); } /** * Read a `double` field, a 64-bit floating point number. */ double() { return this.view.getFloat64((this.pos += 8) - 8, true); } /** * Read a `bytes` field, length-delimited arbitrary data. */ bytes() { let len = this.uint32(), start = this.pos; this.pos += len; this.assertBounds(); return this.buf.subarray(start, start + len); } /** * Read a `string` field, length-delimited data converted to UTF-8 text. */ string() { return this.textDecoder.decode(this.bytes()); } }// Copyright 2021-2024 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * Create a new extension using the given runtime. */ function makeExtension(runtime, typeName, extendee, field) { let fi; return { typeName, extendee, get field() { if (!fi) { const i = typeof field == "function" ? field() : field; i.name = typeName.split(".").pop(); i.jsonName = "[".concat(typeName, "]"); fi = runtime.util.newFieldList([i]).list()[0]; } return fi; }, runtime }; } /** * Create a container that allows us to read extension fields into it with the * same logic as regular fields. */ function createExtensionContainer(extension) { const localName = extension.field.localName; const container = Object.create(null); container[localName] = initExtensionField(extension); return [container, () => container[localName]]; } function initExtensionField(ext) { const field = ext.field; if (field.repeated) { return []; } if (field.default !== undefined) { return field.default; } switch (field.kind) { case "enum": return field.T.values[0].no; case "scalar": return scalarZeroValue(field.T, field.L); case "message": // eslint-disable-next-line no-case-declarations const T = field.T, value = new T(); return T.fieldWrapper ? T.fieldWrapper.unwrapField(value) : value; case "map": throw "map fields are not allowed to be extensions"; } } /** * Helper to filter unknown fields, optimized based on field type. */ function filterUnknownFields(unknownFields, field) { if (!field.repeated && (field.kind == "enum" || field.kind == "scalar")) { // singular scalar fields do not merge, we pick the last for (let i = unknownFields.length - 1; i >= 0; --i) { if (unknownFields[i].no == field.no) { return [unknownFields[i]]; } } return []; } return unknownFields.filter(uf => uf.no === field.no); }// Copyright 2021-2024 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* eslint-disable @typescript-eslint/ban-ts-comment, @typescript-eslint/no-unnecessary-condition, prefer-const */ // lookup table from base64 character to byte let encTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split(""); // lookup table from base64 character *code* to byte because lookup by number is fast let decTable = []; for (let i = 0; i < encTable.length; i++) decTable[encTable[i].charCodeAt(0)] = i; // support base64url variants decTable["-".charCodeAt(0)] = encTable.indexOf("+"); decTable["_".charCodeAt(0)] = encTable.indexOf("/"); const protoBase64 = { /** * Decodes a base64 string to a byte array. * * - ignores white-space, including line breaks and tabs * - allows inner padding (can decode concatenated base64 strings) * - does not require padding * - understands base64url encoding: * "-" instead of "+", * "_" instead of "/", * no padding */ dec(base64Str) { // estimate byte size, not accounting for inner padding and whitespace let es = base64Str.length * 3 / 4; if (base64Str[base64Str.length - 2] == "=") es -= 2;else if (base64Str[base64Str.length - 1] == "=") es -= 1; let bytes = new Uint8Array(es), bytePos = 0, // position in byte array groupPos = 0, // position in base64 group b, // current byte p = 0; // previous byte for (let i = 0; i < base64Str.length; i++) { b = decTable[base64Str.charCodeAt(i)]; if (b === undefined) { switch (base64Str[i]) { // @ts-ignore TS7029: Fallthrough case in switch case "=": groupPos = 0; // reset state when padding found // @ts-ignore TS7029: Fallthrough case in switch case "\n": case "\r": case "\t": case " ": continue; // skip white-space, and padding default: throw Error("invalid base64 string."); } } switch (groupPos) { case 0: p = b; groupPos = 1; break; case 1: bytes[bytePos++] = p << 2 | (b & 48) >> 4; p = b; groupPos = 2; break; case 2: bytes[bytePos++] = (p & 15) << 4 | (b & 60) >> 2; p = b; groupPos = 3; break; case 3: bytes[bytePos++] = (p & 3) << 6 | b; groupPos = 0; break; } } if (groupPos == 1) throw Error("invalid base64 string."); return bytes.subarray(0, bytePos); }, /** * Encode a byte array to a base64 string. */ enc(bytes) { let base64 = "", groupPos = 0, // position in base64 group b, // current byte p = 0; // carry over from previous byte for (let i = 0; i < bytes.length; i++) { b = bytes[i]; switch (groupPos) { case 0: base64 += encTable[b >> 2]; p = (b & 3) << 4; groupPos = 1; break; case 1: base64 += encTable[p | b >> 4]; p = (b & 15) << 2; groupPos = 2; break; case 2: base64 += encTable[p | b >> 6]; base64 += encTable[b & 63]; groupPos = 0; break; } } // add output padding if (groupPos) { base64 += encTable[p]; base64 += "="; if (groupPos == 1) base64 += "="; } return base64; } };/** * Retrieve an extension value from a message. * * The function never returns undefined. Use hasExtension() to check whether an * extension is set. If the extension is not set, this function returns the * default value (if one was specified in the protobuf source), or the zero value * (for example `0` for numeric types, `[]` for repeated extension fields, and * an empty message instance for message fields). * * Extensions are stored as unknown fields on a message. To mutate an extension * value, make sure to store the new value with setExtension() after mutating. * * If the extension does not extend the given message, an error is raised. */ function getExtension(message, extension, options) { assertExtendee(extension, message); const opt = extension.runtime.bin.makeReadOptions(options); const ufs = filterUnknownFields(message.getType().runtime.bin.listUnknownFields(message), extension.field); const _createExtensionConta = createExtensionContainer(extension), _createExtensionConta2 = _slicedToArray(_createExtensionConta, 2), container = _createExtensionConta2[0], get = _createExtensionConta2[1]; for (const uf of ufs) { extension.runtime.bin.readField(container, opt.readerFactory(uf.data), extension.field, uf.wireType, opt); } return get(); } /** * Set an extension value on a message. If the message already has a value for * this extension, the value is replaced. * * If the extension does not extend the given message, an error is raised. */ function setExtension(message, extension, value, options) { assertExtendee(extension, message); const readOpt = extension.runtime.bin.makeReadOptions(options); const writeOpt = extension.runtime.bin.makeWriteOptions(options); if (hasExtension(message, extension)) { const ufs = message.getType().runtime.bin.listUnknownFields(message).filter(uf => uf.no != extension.field.no); message.getType().runtime.bin.discardUnknownFields(message); for (const uf of ufs) { message.getType().runtime.bin.onUnknownField(message, uf.no, uf.wireType, uf.data); } } const writer = writeOpt.writerFactory(); let f = extension.field; // Implicit presence does not apply to extensions, see https://github.com/protocolbuffers/protobuf/issues/8234 // We patch the field info to use explicit presence: if (!f.opt && !f.repeated && (f.kind == "enum" || f.kind == "scalar")) { f = Object.assign(Object.assign({}, extension.field), { opt: true }); } extension.runtime.bin.writeField(f, value, writer, writeOpt); const reader = readOpt.readerFactory(writer.finish()); while (reader.pos < reader.len) { const _reader$tag = reader.tag(), _reader$tag2 = _slicedToArray(_reader$tag, 2), no = _reader$tag2[0], wireType = _reader$tag2[1]; const data = reader.skip(wireType, no); message.getType().runtime.bin.onUnknownField(message, no, wireType, data); } } /** * Check whether an extension is set on a message. */ function hasExtension(message, extension) { const messageType = message.getType(); return extension.extendee.typeName === messageType.typeName && !!messageType.runtime.bin.listUnknownFields(message).find(uf => uf.no == extension.field.no); } function assertExtendee(extension, message) { assert(extension.extendee.typeName == message.getType().typeName, "extension ".concat(extension.typeName, " can only be applied to message ").concat(extension.extendee.typeName)); }// Copyright 2021-2024 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * Returns true if the field is set. */ function isFieldSet(field, target) { const localName = field.localName; if (field.repeated) { return target[localName].length > 0; } if (field.oneof) { return target[field.oneof.localName].case === localName; // eslint-disable-line @typescript-eslint/no-unsafe-member-access } switch (field.kind) { case "enum": case "scalar": if (field.opt || field.req) { // explicit presence return target[localName] !== undefined; } // implicit presence if (field.kind == "enum") { return target[localName] !== field.T.values[0].no; } return !isScalarZeroValue(field.T, target[localName]); case "message": return target[localName] !== undefined; case "map": return Object.keys(target[localName]).length > 0; // eslint-disable-line @typescript-eslint/no-unsafe-argument } } /** * Resets the field, so that isFieldSet() will return false. */ function clearField(field, target) { const localName = field.localName; const implicitPresence = !field.opt && !field.req; if (field.repeated) { target[localName] = []; } else if (field.oneof) { target[field.oneof.localName] = { case: undefined }; } else { switch (field.kind) { case "map": target[localName] = {}; break; case "enum": target[localName] = implicitPresence ? field.T.values[0].no : undefined; break; case "scalar": target[localName] = implicitPresence ? scalarZeroValue(field.T, field.L) : undefined; break; case "message": target[localName] = undefined; break; } } }// Copyright 2021-2024 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * Check whether the given object is any subtype of Message or is a specific * Message by passing the type. * * Just like `instanceof`, `isMessage` narrows the type. The advantage of * `isMessage` is that it compares identity by the message type name, not by * class identity. This makes it robust against the dual package hazard and * similar situations, where the same message is duplicated. * * This function is _mostly_ equivalent to the `instanceof` operator. For * example, `isMessage(foo, MyMessage)` is the same as `foo instanceof MyMessage`, * and `isMessage(foo)` is the same as `foo instanceof Message`. In most cases, * `isMessage` should be preferred over `instanceof`. * * However, due to the fact that `isMessage` does not use class identity, there * are subtle differences between this function and `instanceof`. Notably, * calling `isMessage` on an explicit type of Message will return false. */ function isMessage(arg, type) { if (arg === null || typeof arg != "object") { return false; } if (!Object.getOwnPropertyNames(Message.prototype).every(m => m in arg && typeof arg[m] == "function")) { return false; } const actualType = arg.getType(); if (actualType === null || typeof actualType != "function" || !("typeName" in actualType) || typeof actualType.typeName != "string") { return false; } return type === undefined ? true : actualType.typeName == type.typeName; }// Copyright 2021-2024 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * Wrap a primitive message field value in its corresponding wrapper * message. This function is idempotent. */ function wrapField(type, value) { if (isMessage(value) || !type.fieldWrapper) { return value; } return type.fieldWrapper.wrapField(value); } ({ "google.protobuf.DoubleValue": ScalarType.DOUBLE, "google.protobuf.FloatValue": ScalarType.FLOAT, "google.protobuf.Int64Value": ScalarType.INT64, "google.protobuf.UInt64Value": ScalarType.UINT64, "google.protobuf.Int32Value": ScalarType.INT32, "google.protobuf.UInt32Value": ScalarType.UINT32, "google.protobuf.BoolValue": ScalarType.BOOL, "google.protobuf.StringValue": ScalarType.STRING, "google.protobuf.BytesValue": ScalarType.BYTES });/* eslint-disable no-case-declarations,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call */ // Default options for parsing JSON. const jsonReadDefaults = { ignoreUnknownFields: false }; // Default options for serializing to JSON. const jsonWriteDefaults = { emitDefaultValues: false, enumAsInteger: false, useProtoFieldName: false, prettySpaces: 0 }; function makeReadOptions$1(options) { return options ? Object.assign(Object.assign({}, jsonReadDefaults), options) : jsonReadDefaults; } function makeWriteOptions$1(options) { return options ? Object.assign(Object.assign({}, jsonWriteDefaults), options) : jsonWriteDefaults; } const tokenNull = Symbol(); const tokenIgnoredUnknownEnum = Symbol(); function makeJsonFormat() { return { makeReadOptions: makeReadOptions$1, makeWriteOptions: makeWriteOptions$1, readMessage(type, json, options, message) { if (json == null || Array.isArray(json) || typeof json != "object") { throw new Error("cannot decode message ".concat(type.typeName, " from JSON: ").concat(debugJsonValue(json))); } message = message !== null && message !== void 0 ? message : new type(); const oneofSeen = new Map(); const registry = options.typeRegistry; for (const _ref of Object.entries(json)) { var _ref2 = _slicedToArray(_ref, 2); const jsonKey = _ref2[0]; const jsonValue = _ref2[1]; const field = type.fields.findJsonName(jsonKey); if (field) { if (field.oneof) { if (jsonValue === null && field.kind == "scalar") { // see conformance test Required.Proto3.JsonInput.OneofFieldNull{First,Second} continue; } const seen = oneofSeen.get(field.oneof); if (seen !== undefined) { throw new Error("cannot decode message ".concat(type.typeName, " from JSON: multiple keys for oneof \"").concat(field.oneof.name, "\" present: \"").concat(seen, "\", \"").concat(jsonKey, "\"")); } oneofSeen.set(field.oneof, jsonKey); } readField$1(message, jsonValue, field, options, type); } else { let found = false; if ((registry === null || registry === void 0 ? void 0 : registry.findExtension) && jsonKey.startsWith("[") && jsonKey.endsWith("]")) { const ext = registry.findExtension(jsonKey.substring(1, jsonKey.length - 1)); if (ext && ext.extendee.typeName == type.typeName) { found = true; const _createExtensionConta = createExtensionContainer(ext), _createExtensionConta2 = _slicedToArray(_createExtensionConta, 2), container = _createExtensionConta2[0], get = _createExtensionConta2[1]; readField$1(container, jsonValue, ext.field, options, ext); // We pass on the options as BinaryReadOptions/BinaryWriteOptions, // so that users can bring their own binary reader and writer factories // if necessary. setExtension(message, ext, get(), options); } } if (!found && !options.ignoreUnknownFields) { throw new Error("cannot decode message ".concat(type.typeName, " from JSON: key \"").concat(jsonKey, "\" is unknown")); } } } return message; }, writeMessage(message, options) { const type = message.getType(); const json = {}; let field; try { for (field of type.fields.byNumber()) { if (!isFieldSet(field, message)) { // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (field.req) { throw "required field not set"; } if (!options.emitDefaultValues) { continue; } if (!canEmitFieldDefaultValue(field)) { continue; } } const value = field.oneof ? message[field.oneof.localName].value : message[field.localName]; const jsonValue = writeField$1(field, value, options); if (jsonValue !== undefined) { json[options.useProtoFieldName ? field.name : field.jsonName] = jsonValue; } } const registry = options.typeRegistry; if (registry === null || registry === void 0 ? void 0 : registry.findExtensionFor) { for (const uf of type.runtime.bin.listUnknownFields(message)) { const ext = registry.findExtensionFor(type.typeName, uf.no); if (ext && hasExtension(message, ext)) { // We pass on the options as BinaryReadOptions, so that users can bring their own // binary reader factory if necessary. const value = getExtension(message, ext, options); const jsonValue = writeField$1(ext.field, value, options); if (jsonValue !== undefined) { json[ext.field.jsonName] = jsonValue; } } } } } catch (e) { const m = field ? "cannot encode field ".concat(type.typeName, ".").concat(field.name, " to JSON") : "cannot encode message ".concat(type.typeName, " to JSON"); const r = e instanceof Error ? e.message : String(e); throw new Error(m + (r.length > 0 ? ": ".concat(r) : "")); } return json; }, readScalar(type, json, longType) { // The signature of our internal function has changed. For backwards- // compatibility, we support the old form that is part of the public API // through the interface JsonFormat. return readScalar$1(type, json, longType !== null && longType !== void 0 ? longType : LongType.BIGINT, true); }, writeScalar(type, value, emitDefaultValues) { // The signature of our internal function has changed. For backwards- // compatibility, we support the old form that is part of the public API // through the interface JsonFormat. if (value === undefined) { return undefined; } if (emitDefaultValues || isScalarZeroValue(type, value)) { return writeScalar$1(type, value); } return undefined; }, debug: debugJsonValue }; } function debugJsonValue(json) { if (json === null) { return "null"; } switch (typeof json) { case "object": return Array.isArray(json) ? "array" : "object"; case "string": return json.length > 100 ? "string" : "\"".concat(json.split('"').join('\\"'), "\""); default: return String(json); } } // Read a JSON value for a field. // The "parentType" argument is only used to provide context in errors. function readField$1(target, jsonValue, field, options, parentType) { let localName = field.localName; if (field.repeated) { assert(field.kind != "map"); if (jsonValue === null) { return; } if (!Array.isArray(jsonValue)) { throw new Error("cannot decode field ".concat(parentType.typeName, ".").concat(field.name, " from JSON: ").concat(debugJsonValue(jsonValue))); } const targetArray = target[localName]; for (const jsonItem of jsonValue) { if (jsonItem === null) { throw new Error("cannot decode field ".concat(parentType.typeName, ".").concat(field.name, " from JSON: ").concat(debugJsonValue(jsonItem))); } switch (field.kind) { case "message": targetArray.push(field.T.fromJson(jsonItem, options)); break; case "enum": const enumValue = readEnum(field.T, jsonItem, options.ignoreUnknownFields, true); if (enumValue !== tokenIgnoredUnknownEnum) { targetArray.push(enumValue); } break; case "scalar": try { targetArray.push(readScalar$1(field.T, jsonItem, field.L, true)); } catch (e) { let m = "cannot decode field ".concat(parentType.typeName, ".").concat(field.name, " from JSON: ").concat(debugJsonValue(jsonItem)); if (e instanceof Error && e.message.length > 0) { m += ": ".concat(e.message); } throw new Error(m); } break; } } } else if (field.kind == "map") { if (jsonValue === null) { return; } if (typeof jsonValue != "object" || Array.isArray(jsonValue)) { throw new Error("cannot decode field ".concat(parentType.typeName, ".").concat(field.name, " from JSON: ").concat(debugJsonValue(jsonValue))); } const targetMap = target[localName]; for (const _ref3 of Object.entries(jsonValue)) { var _ref4 = _slicedToArray(_ref3, 2); const jsonMapKey = _ref4[0]; const jsonMapValue = _ref4[1]; if (jsonMapValue === null) { throw new Error("cannot decode field ".concat(parentType.typeName, ".").concat(field.name, " from JSON: map value null")); } let key; try { key = readMapKey(field.K, jsonMapKey); } catch (e) { let m = "cannot decode map key for field ".concat(parentType.typeName, ".").concat(field.name, " from JSON: ").concat(debugJsonValue(jsonValue)); if (e instanceof Error && e.message.length > 0) { m += ": ".concat(e.message); } throw new Error(m); } switch (field.V.kind) { case "message": targetMap[key] = field.V.T.fromJson(jsonMapValue, options); break; case "enum": const enumValue = readEnum(field.V.T, jsonMapValue, options.ignoreUnknownFields, true); if (enumValue !== tokenIgnoredUnknownEnum) { targetMap[key] = enumValue; } break; case "scalar": try { targetMap[key] = readScalar$1(field.V.T, jsonMapValue, LongType.BIGINT, true); } catch (e) { let m = "cannot decode map value for field ".concat(parentType.typeName, ".").concat(field.name, " from JSON: ").concat(debugJsonValue(jsonValue)); if (e instanceof Error && e.message.length > 0) { m += ": ".concat(e.message); } throw new Error(m); } break; } } } else { if (field.oneof) { target = target[field.oneof.localName] = { case: localName }; localName = "value"; } switch (field.kind) { case "message": const messageType = field.T; if (jsonValue === null && messageType.typeName != "google.protobuf.Value") { return; } let currentValue = target[localName]; if (isMessage(currentValue)) { currentValue.fromJson(jsonValue, options); } else { target[localName] = currentValue = messageType.fromJson(jsonValue, options); if (messageType.fieldWrapper && !field.oneof) { target[localName] = messageType.fieldWrapper.unwrapField(currentValue); } } break; case "enum": const enumValue = readEnum(field.T, jsonValue, options.ignoreUnknownFields, false); switch (enumValue) { case tokenNull: clearField(field, target); break; case tokenIgnoredUnknownEnum: break; default: target[localName] = enumValue; break; } break; case "scalar": try { const scalarValue = readScalar$1(field.T, jsonValue, field.L, false); switch (scalarValue) { case tokenNull: clearField(field, target); break; default: target[localName] = scalarValue; break; } } catch (e) { let m = "cannot decode field ".concat(parentType.typeName, ".").concat(field.name, " from JSON: ").concat(debugJsonValue(jsonValue)); if (e instanceof Error && e.message.length > 0) { m += ": ".concat(e.message); } throw new Error(m); } break; } } } function readMapKey(type, json) { if (type === ScalarType.BOOL) { // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check switch (json) { case "true": json = true; break; case "false": json = false; break; } } return readScalar$1(type, json, LongType.BIGINT, true).toString(); } function readScalar$1(type, json, longType, nullAsZeroValue) { if (json === null) { if (nullAsZeroValue) { return scalarZeroValue(type, longType); } return tokenNull; } // every valid case in the switch below returns, and every fall // through is regarded as a failure. switch (type) { // float, double: JSON value will be a number or one of the special string values "NaN", "Infinity", and "-Infinity". // Either numbers or strings are accepted. Exponent notation is also accepted. case ScalarType.DOUBLE: case ScalarType.FLOAT: if (json === "NaN") return Number.NaN; if (json === "Infinity") return Number.POSITIVE_INFINITY; if (json === "-Infinity") return Number.NEGATIVE_INFINITY; if (json === "") { // empty string is not a number break; } if (typeof json == "string" && json.trim().length !== json.length) { // extra whitespace break; } if (typeof json != "string" && typeof json != "number") { break; } const float = Number(json); if (Number.isNaN(float)) { // not a number break; } if (!Number.isFinite(float)) { // infinity and -infinity are handled by string representation above, so this is an error break; } if (type == ScalarType.FLOAT) assertFloat32(float); return float; // int32, fixed32, uint32: JSON value will be a decimal number. Either numbers or strings are accepted. case ScalarType.INT32: case ScalarType.FIXED32: case ScalarType.SFIXED32: case ScalarType.SINT32: case ScalarType.UINT32: let int32; if (typeof json == "number") int32 = json;else if (typeof json == "string" && json.length > 0) { if (json.trim().length === json.length) int32 = Number(json); } if (int32 === undefined) break; if (type == ScalarType.UINT32 || type == ScalarType.FIXED32) assertUInt32(int32);else assertInt32(int32); return int32; // int64, fixed64, uint64: JSON value will be a decimal string. Either numbers or strings are accepted. case ScalarType.INT64: case ScalarType.SFIXED64: case ScalarType.SINT64: if (typeof json != "number" && typeof json != "string") break; const long = protoInt64.parse(json); // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions return longType ? long.toString() : long; case ScalarType.FIXED64: case ScalarType.UINT64: if (typeof json != "number" && typeof json != "string") break; const uLong = protoInt64.uParse(json); // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions return longType ? uLong.toString() : uLong; // bool: case ScalarType.BOOL: if (typeof json !== "boolean") break; return json; // string: case ScalarType.STRING: if (typeof json !== "string") { break; } // A string must always contain UTF-8 encoded or 7-bit ASCII. // We validate with encodeURIComponent, which appears to be the fastest widely available option. try { encodeURIComponent(json); } catch (e) { throw new Error("invalid UTF8"); } return json; // bytes: JSON value will be the data encoded as a string using standard base64 encoding with paddings. // Either standard or URL-safe base64 encoding with/without paddings are accepted. case ScalarType.BYTES: if (json === "") return new Uint8Array(0); if (typeof json !== "string") break; return protoBase64.dec(json); } throw new Error(); } function readEnum(type, json, ignoreUnknownFields, nullAsZeroValue) { if (json === null) { if (type.typeName == "google.protobuf.NullValue") { return 0; // google.protobuf.NullValue.NULL_VALUE = 0 } return nullAsZeroValue ? type.values[0].no : tokenNull; } // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check switch (typeof json) { case "number": if (Number.isInteger(json)) { return json; } break; case "string": const value = type.findName(json); if (value !== undefined) { return value.no; } if (ignoreUnknownFields) { return tokenIgnoredUnknownEnum; } break; } throw new Error("cannot decode enum ".concat(type.typeName, " from JSON: ").concat(debugJsonValue(json))); } // Decide whether an unset field should be emitted with JSON write option `emitDefaultValues` function canEmitFieldDefaultValue(field) { if (field.repeated || field.kind == "map") { // maps are {}, repeated fields are [] return true; } if (field.oneof) { // oneof fields are never emitted return false; } if (field.kind == "message") { // singular message field are allowed to emit JSON null, but we do not return false; } // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (field.opt || field.req) { // the field uses explicit presence, so we cannot emit a zero value return false; } return true; } function writeField$1(field, value, options) { if (field.kind == "map") { assert(typeof value == "object" && value != null); const jsonObj = {}; const entries = Object.entries(value); switch (field.V.kind) { case "scalar": for (const _ref5 of entries) { var _ref6 = _slicedToArray(_ref5, 2); const entryKey = _ref6[0]; const entryValue = _ref6[1]; jsonObj[entryKey.toString()] = writeScalar$1(field.V.T, entryValue); // JSON standard allows only (double quoted) string as property key } break; case "message": for (const _ref7 of entries) { var _ref8 = _slicedToArray(_ref7, 2); const entryKey = _ref8[0]; const entryValue = _ref8[1]; // JSON standard allows only (double quoted) string as property key jsonObj[entryKey.toString()] = entryValue.toJson(options); } break; case "enum": const enumType = field.V.T; for (const _ref9 of entries) { var _ref0 = _slicedToArray(_ref9, 2); const entryKey = _ref0[0]; const entryValue = _ref0[1]; // JSON standard allows only (double quoted) string as property key jsonObj[entryKey.toString()] = writeEnum(enumType, entryValue, options.enumAsInteger); } break; } return options.emitDefaultValues || entries.length > 0 ? jsonObj : undefined; } if (field.repeated) { assert(Array.isArray(value)); const jsonArr = []; switch (field.kind) { case "scalar": for (let i = 0; i < value.length; i++) { jsonArr.push(writeScalar$1(field.T, value[i])); } break; case "enum": for (let i = 0; i < value.length; i++) { jsonArr.push(writeEnum(field.T, value[i], options.enumAsInteger)); } break; case "message": for (let i = 0; i < value.length; i++) { jsonArr.push(value[i].toJson(options)); } break; } return options.emitDefaultValues || jsonArr.length > 0 ? jsonArr : undefined; } switch (field.kind) { case "scalar": return writeScalar$1(field.T, value); case "enum": return writeEnum(field.T, value, options.enumAsInteger); case "message": return wrapField(field.T, value).toJson(options); } } function writeEnum(type, value, enumAsInteger) { var _a; assert(typeof value == "number"); if (type.typeName == "google.protobuf.NullValue") { return null; } if (enumAsInteger) { return value; } const val = type.findNumber(value); return (_a = val === null || val === void 0 ? void 0 : val.name) !== null && _a !== void 0 ? _a : value; // if we don't know the enum value, just return the number } function writeScalar$1(type, value) { switch (type) { // int32, fixed32, uint32: JSON value will be a decimal number. Either numbers or strings are accepted. case ScalarType.INT32: case ScalarType.SFIXED32: case ScalarType.SINT32: case ScalarType.FIXED32: case ScalarType.UINT32: assert(typeof value == "number"); return value; // float, double: JSON value will be a number or one of the special string values "NaN", "Infinity", and "-Infinity". // Either numbers or strings are accepted. Exponent notation is also accepted. case ScalarType.FLOAT: // assertFloat32(value); case ScalarType.DOUBLE: // eslint-disable-line no-fallthrough assert(typeof value == "number"); if (Number.isNaN(value)) return "NaN"; if (value === Number.POSITIVE_INFINITY) return "Infinity"; if (value === Number.NEGATIVE_INFINITY) return "-Infinity"; return value; // string: case ScalarType.STRING: assert(typeof value == "string"); return value; // bool: case ScalarType.BOOL: assert(typeof value == "boolean"); return value; // JSON value will be a decimal string. Either numbers or strings are accepted. case ScalarType.UINT64: case ScalarType.FIXED64: case ScalarType.INT64: case ScalarType.SFIXED64: case ScalarType.SINT64: assert(typeof value == "bigint" || typeof value == "string" || typeof value == "number"); return value.toString(); // bytes: JSON value will be the data encoded as a string using standard base64 encoding with paddings. // Either standard or URL-safe base64 encoding with/without paddings are accepted. case ScalarType.BYTES: assert(value instanceof Uint8Array); return protoBase64.enc(value); } }/* eslint-disable prefer-const,no-case-declarations,@typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-return */ const unknownFieldsSymbol = Symbol("@bufbuild/protobuf/unknown-fields"); // Default options for parsing binary data. const readDefaults = { readUnknownFields: true, readerFactory: bytes => new BinaryReader(bytes) }; // Default options for serializing binary data. const writeDefaults = { writeUnknownFields: true, writerFactory: () => new BinaryWriter() }; function makeReadOptions(options) { return options ? Object.assign(Object.assign({}, readDefaults), options) : readDefaults; } function makeWriteOptions(options) { return options ? Object.assign(Object.assign({}, writeDefaults), options) : writeDefaults; } function makeBinaryFormat() { return { makeReadOptions, makeWriteOptions, listUnknownFields(message) { var _a; return (_a = message[unknownFieldsSymbol]) !== null && _a !== void 0 ? _a : []; }, discardUnknownFields(message) { delete message[unknownFieldsSymbol]; }, writeUnknownFields(message, writer) { const m = message; const c = m[unknownFieldsSymbol]; if (c) { for (const f of c) { writer.tag(f.no, f.wireType).raw(f.data); } } }, onUnknownField(message, no, wireType, data) { const m = message; if (!Array.isArray(m[unknownFieldsSymbol])) { m[unknownFieldsSymbol] = []; } m[unknownFieldsSymbol].push({ no, wireType, data }); }, readMessage(message, reader, lengthOrEndTagFieldNo, options, delimitedMessageEncoding) { const type = message.getType(); // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions const end = delimitedMessageEncoding ? reader.len : reader.pos + lengthOrEndTagFieldNo; let fieldNo, wireType; while (reader.pos < end) { var _reader$tag = reader.tag(); var _reader$tag2 = _slicedToArray(_reader$tag, 2); fieldNo = _reader$tag2[0]; wireType = _reader$tag2[1]; if (delimitedMessageEncoding === true && wireType == WireType.EndGroup) { break; } const field = type.fields.find(fieldNo); if (!field) { const data = reader.skip(wireType, fieldNo); if (options.readUnknownFields) { this.onUnknownField(message, fieldNo, wireType, data); } continue; } readField(message, reader, field, wireType, options); } if (delimitedMessageEncoding && ( // eslint-disable-line @typescript-eslint/strict-boolean-expressions wireType != WireType.EndGroup || fieldNo !== lengthOrEndTagFieldNo)) { throw new Error("invalid end group tag"); } }, readField, writeMessage(message, writer, options) { const type = message.getType(); for (const field of type.fields.byNumber()) { if (!isFieldSet(field, message)) { if (field.req) { throw new Error("cannot encode field ".concat(type.typeName, ".").concat(field.name, " to binary: required field not set")); } continue; } const value = field.oneof ? message[field.oneof.localName].value : message[field.localName]; writeField(field, value, writer, options); } if (options.writeUnknownFields) { this.writeUnknownFields(message, writer); } return writer; }, writeField(field, value, writer, options) { // The behavior of our internal function has changed, it does no longer // accept `undefined` values for singular scalar and map. // For backwards-compatibility, we support the old form that is part of // the public API through the interface BinaryFormat. if (value === undefined) { return undefined; } writeField(field, value, writer, options); } }; } function readField(target, // eslint-disable-line @typescript-eslint/no-explicit-any -- `any` is the best choice for dynamic access reader, field, wireType, options) { let repeated = field.repeated, localName = field.localName; if (field.oneof) { target = target[field.oneof.localName]; if (target.case != localName) { delete target.value; } target.case = localName; localName = "value"; } switch (field.kind) { case "scalar": case "enum": const scalarType = field.kind == "enum" ? ScalarType.INT32 : field.T; let read = readScalar; // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison -- acceptable since it's covered by tests if (field.kind == "scalar" && field.L > 0) { read = readScalarLTString; } if (repeated) { let arr = target[localName]; // safe to assume presence of array, oneof cannot contain repeated values const isPacked = wireType == WireType.LengthDelimited && scalarType != ScalarType.STRING && scalarType != ScalarType.BYTES; if (isPacked) { let e = reader.uint32() + reader.pos; while (reader.pos < e) { arr.push(read(reader, scalarType)); } } else { arr.push(read(reader, scalarType)); } } else { target[localName] = read(reader, scalarType); } break; case "message": const messageType = field.T; if (repeated) { // safe to assume presence of array, oneof cannot contain repeated values target[localName].push(readMessageField(reader, new messageType(), options, field)); } else { if (isMessage(target[localName])) { readMessageField(reader, target[localName], options, field); } else { target[localName] = readMessageField(reader, new messageType(), options, field); if (messageType.fieldWrapper && !field.oneof && !field.repeated) { target[localName] = messageType.fieldWrapper.unwrapField(target[localName]); } } } break; case "map": let _readMapEntry = readMapEntry(field, reader, options), _readMapEntry2 = _slicedToArray(_readMapEntry, 2), mapKey = _readMapEntry2[0], mapVal = _readMapEntry2[1]; // safe to assume presence of map object, oneof cannot contain repeated values target[localName][mapKey] = mapVal; break; } } // Read a message, avoiding MessageType.fromBinary() to re-use the // BinaryReadOptions and the IBinaryReader. function readMessageField(reader, message, options, field) { const format = message.getType().runtime.bin; const delimited = field === null || field === void 0 ? void 0 : field.delimited; format.readMessage(message, reader, delimited ? field.no : reader.uint32(), // eslint-disable-line @typescript-eslint/strict-boolean-expressions options, delimited); return message; } // Read a map field, expecting key field = 1, value field = 2 function readMapEntry(field, reader, options) { const length = reader.uint32(), end = reader.pos + length; let key, val; while (reader.pos < end) { const _reader$tag3 = reader.tag(), _reader$tag4 = _slicedToArray(_reader$tag3, 1), fieldNo = _reader$tag4[0]; switch (fieldNo) { case 1: key = readScalar(reader, field.K); break; case 2: switch (field.V.kind) { case "scalar": val = readScalar(reader, field.V.T); break; case "enum": val = reader.int32(); break; case "message": val = readMessageField(reader, new field.V.T(), options, undefined); break; } break; } } if (key === undefined) { key = scalarZeroValue(field.K, LongType.BIGINT); } if (typeof key != "string" && typeof key != "number") { key = key.toString(); } if (val === undefined) { switch (field.V.kind) { case "scalar": val = scalarZeroValue(field.V.T, LongType.BIGINT); break; case "enum": val = field.V.T.values[0].no; break; case "message": val = new field.V.T(); break; } } return [key, val]; } // Read a scalar value, but return 64 bit integral types (int64, uint64, // sint64, fixed64, sfixed64) as string instead of bigint. function readScalarLTString(reader, type) { const v = readScalar(reader, type); return typeof v == "bigint" ? v.toString() : v; } // Does not use scalarTypeInfo() for better performance. function readScalar(reader, type) { switch (type) { case ScalarType.STRING: return reader.string(); case ScalarType.BOOL: return reader.bool(); case ScalarType.DOUBLE: return reader.double(); case ScalarType.FLOAT: return reader.float(); case ScalarType.INT32: return reader.int32(); case ScalarType.INT64: return reader.int64(); case ScalarType.UINT64: return reader.uint64(); case ScalarType.FIXED64: return reader.fixed64(); case ScalarType.BYTES: return reader.bytes(); case ScalarType.FIXED32: return reader.fixed32(); case ScalarType.SFIXED32: return reader.sfixed32(); case ScalarType.SFIXED64: return reader.sfixed64(); case ScalarType.SINT64: return reader.sint64(); case ScalarType.UINT32: return reader.uint32(); case ScalarType.SINT32: return reader.sint32(); } } function writeField(field, value, writer, options) { assert(value !== undefined); const repeated = field.repeated; switch (field.kind) { case "scalar": case "enum": let scalarType = field.kind == "enum" ? ScalarType.INT32 : field.T; if (repeated) { assert(Array.isArray(value)); if (field.packed) { writePacked(writer, scalarType, field.no, value); } else { for (const item of value) { writeScalar(writer, scalarType, field.no, item); } } } else { writeScalar(writer, scalarType, field.no, value); } break; case "message": if (repeated) { assert(Array.isArray(value)); for (const item of value) { writeMessageField(writer, options, field, item); } } else { writeMessageField(writer, options, field, value); } break; case "map": assert(typeof value == "object" && value != null); for (const _ref of Object.entries(value)) { var _ref2 = _slicedToArray(_ref, 2); const key = _ref2[0]; const val = _ref2[1]; writeMapEntry(writer, options, field, key, val); } break; } } function writeMapEntry(writer, options, field, key, value) { writer.tag(field.no, WireType.LengthDelimited); writer.fork(); // javascript only allows number or string for object properties // we convert from our representation to the protobuf type let keyValue = key; // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- we deliberately handle just the special cases for map keys switch (field.K) { case ScalarType.INT32: case ScalarType.FIXED32: case ScalarType.UINT32: case ScalarType.SFIXED32: case ScalarType.SINT32: keyValue = Number.parseInt(key); break; case ScalarType.BOOL: assert(key == "true" || key == "false"); keyValue = key == "true"; break; } // write key, expecting key field number = 1 writeScalar(writer, field.K, 1, keyValue); // write value, expecting value field number = 2 switch (field.V.kind) { case "scalar": writeScalar(writer, field.V.T, 2, value); break; case "enum": writeScalar(writer, ScalarType.INT32, 2, value); break; case "message": assert(value !== undefined); writer.tag(2, WireType.LengthDelimited).bytes(value.toBinary(options)); break; } writer.join(); } // Value must not be undefined function writeMessageField(writer, options, field, value) { const message = wrapField(field.T, value); // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (field.delimited) writer.tag(field.no, WireType.StartGroup).raw(message.toBinary(options)).tag(field.no, WireType.EndGroup);else writer.tag(field.no, WireType.LengthDelimited).bytes(message.toBinary(options)); } function writeScalar(writer, type, fieldNo, value) { assert(value !== undefined); let _scalarTypeInfo = scalarTypeInfo(type), _scalarTypeInfo2 = _slicedToArray(_scalarTypeInfo, 2), wireType = _scalarTypeInfo2[0], method = _scalarTypeInfo2[1]; writer.tag(fieldNo, wireType)[method](value); } function writePacked(writer, type, fieldNo, value) { if (!value.length) { return; } writer.tag(fieldNo, WireType.LengthDelimited).fork(); let _scalarTypeInfo3 = scalarTypeInfo(type), _scalarTypeInfo4 = _slicedToArray(_scalarTypeInfo3, 2), method = _scalarTypeInfo4[1]; for (let i = 0; i < value.length; i++) { writer[method](value[i]); } writer.join(); } /** * Get information for writing a scalar value. * * Returns tuple: * [0]: appropriate WireType * [1]: name of the appropriate method of IBinaryWriter * [2]: whether the given value is a default value for proto3 semantics * * If argument `value` is omitted, [2] is always false. */ // TODO replace call-sites writeScalar() and writePacked(), then remove function scalarTypeInfo(type) { let wireType = WireType.Varint; // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- INT32, UINT32, SINT32 are covered by the defaults switch (type) { case ScalarType.BYTES: case ScalarType.STRING: wireType = WireType.LengthDelimited; break; case ScalarType.DOUBLE: case ScalarType.FIXED64: case ScalarType.SFIXED64: wireType = WireType.Bit64; break; case ScalarType.FIXED32: case ScalarType.SFIXED32: case ScalarType.FLOAT: wireType = WireType.Bit32; break; } const method = ScalarType[type].toLowerCase(); return [wireType, method]; }/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-argument,no-case-declarations */ function makeUtilCommon() { return { setEnumType, initPartial(source, target) { if (source === undefined) { return; } const type = target.getType(); for (const member of type.fields.byMember()) { const localName = member.localName, t = target, s = source; if (s[localName] == null) { // TODO if source is a Message instance, we should use isFieldSet() here to support future field presence continue; } switch (member.kind) { case "oneof": const sk = s[localName].case; if (sk === undefined) { continue; } const sourceField = member.findField(sk); let val = s[localName].value; if (sourceField && sourceField.kind == "message" && !isMessage(val, sourceField.T)) { val = new sourceField.T(val); } else if (sourceField && sourceField.kind === "scalar" && sourceField.T === ScalarType.BYTES) { val = toU8Arr(val); } t[localName] = { case: sk, value: val }; break; case "scalar": case "enum": let copy = s[localName]; if (member.T === ScalarType.BYTES) { copy = member.repeated ? copy.map(toU8Arr) : toU8Arr(copy); } t[localName] = copy; break; case "map": switch (member.V.kind) { case "scalar": case "enum": if (member.V.T === ScalarType.BYTES) { for (const _ref of Object.entries(s[localName])) { var _ref2 = _slicedToArray(_ref, 2); const k = _ref2[0]; const v = _ref2[1]; t[localName][k] = toU8Arr(v); } } else { Object.assign(t[localName], s[localName]); } break; case "message": const messageType = member.V.T; for (const k of Object.keys(s[localName])) { let val = s[localName][k]; if (!messageType.fieldWrapper) { // We only take partial input for messages that are not a wrapper type. // For those messages, we recursively normalize the partial input. val = new messageType(val); } t[localName][k] = val; } break; } break; case "message": const mt = member.T; if (member.repeated) { t[localName] = s[localName].map(val => isMessage(val, mt) ? val : new mt(val)); } else { const val = s[localName]; if (mt.fieldWrapper) { if ( // We can't use BytesValue.typeName as that will create a circular import mt.typeName === "google.protobuf.BytesValue") { t[localName] = toU8Arr(val); } else { t[localName] = val; } } else { t[localName] = isMessage(val, mt) ? val : new mt(val); } } break; } } }, // TODO use isFieldSet() here to support future field presence equals(type, a, b) { if (a === b) { return true; } if (!a || !b) { return false; } return type.fields.byMember().every(m => { const va = a[m.localName]; const vb = b[m.localName]; if (m.repeated) { if (va.length !== vb.length) { return false; } // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- repeated fields are never "map" switch (m.kind) { case "message": return va.every((a, i) => m.T.equals(a, vb[i])); case "scalar": return va.every((a, i) => scalarEquals(m.T, a, vb[i])); case "enum": return va.every((a, i) => scalarEquals(ScalarType.INT32, a, vb[i])); } throw new Error("repeated cannot contain ".concat(m.kind)); } switch (m.kind) { case "message": let a = va; let b = vb; if (m.T.fieldWrapper) { if (a !== undefined && !isMessage(a)) { a = m.T.fieldWrapper.wrapField(a); } if (b !== undefined && !isMessage(b)) { b = m.T.fieldWrapper.wrapField(b); } } return m.T.equals(a, b); case "enum": return scalarEquals(ScalarType.INT32, va, vb); case "scalar": return scalarEquals(m.T, va, vb); case "oneof": if (va.case !== vb.case) { return false; } const s = m.findField(va.case); if (s === undefined) { return true; } // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- oneof fields are never "map" switch (s.kind) { case "message": return s.T.equals(va.value, vb.value); case "enum": return scalarEquals(ScalarType.INT32, va.value, vb.value); case "scalar": return scalarEquals(s.T, va.value, vb.value); } throw new Error("oneof cannot contain ".concat(s.kind)); case "map": const keys = Object.keys(va).concat(Object.keys(vb)); switch (m.V.kind) { case "message": const messageType = m.V.T; return keys.every(k => messageType.equals(va[k], vb[k])); case "enum": return keys.every(k => scalarEquals(ScalarType.INT32, va[k], vb[k])); case "scalar": const scalarType = m.V.T; return keys.every(k => scalarEquals(scalarType, va[k], vb[k])); } break; } }); }, // TODO use isFieldSet() here to support future field presence clone(message) { const type = message.getType(), target = new type(), any = target; for (const member of type.fields.byMember()) { const source = message[member.localName]; let copy; if (member.repeated) { copy = source.map(cloneSingularField); } else if (member.kind == "map") { copy = any[member.localName]; for (const _ref3 of Object.entries(source)) { var _ref4 = _slicedToArray(_ref3, 2); const key = _ref4[0]; const v = _ref4[1]; copy[key] = cloneSingularField(v); } } else if (member.kind == "oneof") { const f = member.findField(source.case); copy = f ? { case: source.case, value: cloneSingularField(source.value) } : { case: undefined }; } else { copy = cloneSingularField(source); } any[member.localName] = copy; } for (const uf of type.runtime.bin.listUnknownFields(message)) { type.runtime.bin.onUnknownField(any, uf.no, uf.wireType, uf.data); } return target; } }; } // clone a single field value - i.e. the element type of repeated fields, the value type of maps function cloneSingularField(value) { if (value === undefined) { return value; } if (isMessage(value)) { return value.clone(); } if (value instanceof Uint8Array) { const c = new Uint8Array(value.byteLength); c.set(value); return c; } return value; } // converts any ArrayLike to Uint8Array if necessary. function toU8Arr(input) { return input instanceof Uint8Array ? input : new Uint8Array(input); }// Copyright 2021-2024 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. function makeProtoRuntime(syntax, newFieldList, initFields) { return { syntax, json: makeJsonFormat(), bin: makeBinaryFormat(), util: Object.assign(Object.assign({}, makeUtilCommon()), { newFieldList, initFields }), makeMessageType(typeName, fields, opt) { return makeMessageType(this, typeName, fields, opt); }, makeEnum, makeEnumType, getEnumType, makeExtension(typeName, extendee, field) { return makeExtension(this, typeName, extendee, field); } }; }// Copyright 2021-2024 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. class InternalFieldList { constructor(fields, normalizer) { this._fields = fields; this._normalizer = normalizer; } findJsonName(jsonName) { if (!this.jsonNames) { const t = {}; for (const f of this.list()) { t[f.jsonName] = t[f.name] = f; } this.jsonNames = t; } return this.jsonNames[jsonName]; } find(fieldNo) { if (!this.numbers) { const t = {}; for (const f of this.list()) { t[f.no] = f; } this.numbers = t; } return this.numbers[fieldNo]; } list() { if (!this.all) { this.all = this._normalizer(this._fields); } return this.all; } byNumber() { if (!this.numbersAsc) { this.numbersAsc = this.list().concat().sort((a, b) => a.no - b.no); } return this.numbersAsc; } byMember() { if (!this.members) { this.members = []; const a = this.members; let o; for (const f of this.list()) { if (f.oneof) { if (f.oneof !== o) { o = f.oneof; a.push(o); } } else { a.push(f); } } } return this.members; } }// Copyright 2021-2024 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * Returns the name of a protobuf element in generated code. * * Field names - including oneofs - are converted to lowerCamelCase. For * messages, enumerations and services, the package name is stripped from * the type name. For nested messages and enumerations, the names are joined * with an underscore. For methods, the first character is made lowercase. */ /** * Returns the name of a field in generated code. */ function localFieldName(protoName, inOneof) { const name = protoCamelCase(protoName); if (inOneof) { // oneof member names are not properties, but values of the `case` property. return name; } return safeObjectProperty(safeMessageProperty(name)); } /** * Returns the name of a oneof group in generated code. */ function localOneofName(protoName) { return localFieldName(protoName, false); } /** * Returns the JSON name for a protobuf field, exactly like protoc does. */ const fieldJsonName = protoCamelCase; /** * Converts snake_case to protoCamelCase according to the convention * used by protoc to convert a field name to a JSON name. */ function protoCamelCase(snakeCase) { let capNext = false; const b = []; for (let i = 0; i < snakeCase.length; i++) { let c = snakeCase.charAt(i); switch (c) { case "_": capNext = true; break; case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": b.push(c); capNext = false; break; default: if (capNext) { capNext = false; c = c.toUpperCase(); } b.push(c); break; } } return b.join(""); } /** * Names that cannot be used for object properties because they are reserved * by built-in JavaScript properties. */ const reservedObjectProperties = new Set([ // names reserved by JavaScript "constructor", "toString", "toJSON", "valueOf"]); /** * Names that cannot be used for object properties because they are reserved * by the runtime. */ const reservedMessageProperties = new Set([ // names reserved by the runtime "getType", "clone", "equals", "fromBinary", "fromJson", "fromJsonString", "toBinary", "toJson", "toJsonString", // names reserved by the runtime for the future "toObject"]); const fallback = name => "".concat(name, "$"); /** * Will wrap names that are Object prototype properties or names reserved * for `Message`s. */ const safeMessageProperty = name => { if (reservedMessageProperties.has(name)) { return fallback(name); } return name; }; /** * Names that cannot be used for object properties because they are reserved * by built-in JavaScript properties. */ const safeObjectProperty = name => { if (reservedObjectProperties.has(name)) { return fallback(name); } return name; };// Copyright 2021-2024 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. class InternalOneofInfo { constructor(name) { this.kind = "oneof"; this.repeated = false; this.packed = false; this.opt = false; this.req = false; this.default = undefined; this.fields = []; this.name = name; this.localName = localOneofName(name); } addField(field) { assert(field.oneof === this, "field ".concat(field.name, " not one of ").concat(this.name)); this.fields.push(field); } findField(localName) { if (!this._lookup) { this._lookup = Object.create(null); for (let i = 0; i < this.fields.length; i++) { this._lookup[this.fields[i].localName] = this.fields[i]; } } return this._lookup[localName]; } }// Copyright 2021-2024 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * Convert a collection of field info to an array of normalized FieldInfo. * * The argument `packedByDefault` specifies whether fields that do not specify * `packed` should be packed (proto3) or unpacked (proto2). */ function normalizeFieldInfos(fieldInfos, packedByDefault) { var _a, _b, _c, _d, _e, _f; const r = []; let o; for (const field of typeof fieldInfos == "function" ? fieldInfos() : fieldInfos) { const f = field; f.localName = localFieldName(field.name, field.oneof !== undefined); f.jsonName = (_a = field.jsonName) !== null && _a !== void 0 ? _a : fieldJsonName(field.name); f.repeated = (_b = field.repeated) !== null && _b !== void 0 ? _b : false; if (field.kind == "scalar") { f.L = (_c = field.L) !== null && _c !== void 0 ? _c : LongType.BIGINT; } f.delimited = (_d = field.delimited) !== null && _d !== void 0 ? _d : false; f.req = (_e = field.req) !== null && _e !== void 0 ? _e : false; f.opt = (_f = field.opt) !== null && _f !== void 0 ? _f : false; if (field.packed === undefined) { { f.packed = field.kind == "enum" || field.kind == "scalar" && field.T != ScalarType.BYTES && field.T != ScalarType.STRING; } } // We do not surface options at this time // f.options = field.options ?? emptyReadonlyObject; if (field.oneof !== undefined) { const ooname = typeof field.oneof == "string" ? field.oneof : field.oneof.name; if (!o || o.name != ooname) { o = new InternalOneofInfo(ooname); } f.oneof = o; o.addField(f); } r.push(f); } return r; }// Copyright 2021-2024 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * Provides functionality for messages defined with the proto3 syntax. */ const proto3 = makeProtoRuntime("proto3", fields => { return new InternalFieldList(fields, source => normalizeFieldInfos(source)); }, // TODO merge with proto2 and initExtensionField, also see initPartial, equals, clone target => { for (const member of target.getType().fields.byMember()) { if (member.opt) { continue; } const name = member.localName, t = target; if (member.repeated) { t[name] = []; continue; } switch (member.kind) { case "oneof": t[name] = { case: undefined }; break; case "enum": t[name] = 0; break; case "map": t[name] = {}; break; case "scalar": t[name] = scalarZeroValue(member.T, member.L); break; } } });// Copyright 2021-2024 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * A Timestamp represents a point in time independent of any time zone or local * calendar, encoded as a count of seconds and fractions of seconds at * nanosecond resolution. The count is relative to an epoch at UTC midnight on * January 1, 1970, in the proleptic Gregorian calendar which extends the * Gregorian calendar backwards to year one. * * All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap * second table is needed for interpretation, using a [24-hour linear * smear](https://developers.google.com/time/smear). * * The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By * restricting to that range, we ensure that we can convert to and from [RFC * 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. * * # Examples * * Example 1: Compute Timestamp from POSIX `time()`. * * Timestamp timestamp; * timestamp.set_seconds(time(NULL)); * timestamp.set_nanos(0); * * Example 2: Compute Timestamp from POSIX `gettimeofday()`. * * struct timeval tv; * gettimeofday(&tv, NULL); * * Timestamp timestamp; * timestamp.set_seconds(tv.tv_sec); * timestamp.set_nanos(tv.tv_usec * 1000); * * Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. * * FILETIME ft; * GetSystemTimeAsFileTime(&ft); * UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; * * // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z * // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. * Timestamp timestamp; * timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); * timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); * * Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. * * long millis = System.currentTimeMillis(); * * Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) * .setNanos((int) ((millis % 1000) * 1000000)).build(); * * Example 5: Compute Timestamp from Java `Instant.now()`. * * Instant now = Instant.now(); * * Timestamp timestamp = * Timestamp.newBuilder().setSeconds(now.getEpochSecond()) * .setNanos(now.getNano()).build(); * * Example 6: Compute Timestamp from current time in Python. * * timestamp = Timestamp() * timestamp.GetCurrentTime() * * # JSON Mapping * * In JSON format, the Timestamp type is encoded as a string in the * [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the * format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" * where {year} is always expressed using four digits while {month}, {day}, * {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional * seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), * are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone * is required. A proto3 JSON serializer should always use UTC (as indicated by * "Z") when printing the Timestamp type and a proto3 JSON parser should be * able to accept both UTC and other timezones (as indicated by an offset). * * For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past * 01:30 UTC on January 15, 2017. * * In JavaScript, one can convert a Date object to this format using the * standard * [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) * method. In Python, a standard `datetime.datetime` object can be converted * to this format using * [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with * the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use * the Joda Time's [`ISODateTimeFormat.dateTime()`]( * http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime() * ) to obtain a formatter capable of generating timestamps in this format. * * * @generated from message google.protobuf.Timestamp */ class Timestamp extends Message { constructor(data) { super(); /** * Represents seconds of UTC time since Unix epoch * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to * 9999-12-31T23:59:59Z inclusive. * * @generated from field: int64 seconds = 1; */ this.seconds = protoInt64.zero; /** * Non-negative fractions of a second at nanosecond resolution. Negative * second values with fractions must still have non-negative nanos values * that count forward in time. Must be from 0 to 999,999,999 * inclusive. * * @generated from field: int32 nanos = 2; */ this.nanos = 0; proto3.util.initPartial(data, this); } fromJson(json, options) { if (typeof json !== "string") { throw new Error("cannot decode google.protobuf.Timestamp from JSON: ".concat(proto3.json.debug(json))); } const matches = json.match(/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(?:Z|\.([0-9]{3,9})Z|([+-][0-9][0-9]:[0-9][0-9]))$/); if (!matches) { throw new Error("cannot decode google.protobuf.Timestamp from JSON: invalid RFC 3339 string"); } const ms = Date.parse(matches[1] + "-" + matches[2] + "-" + matches[3] + "T" + matches[4] + ":" + matches[5] + ":" + matches[6] + (matches[8] ? matches[8] : "Z")); if (Number.isNaN(ms)) { throw new Error("cannot decode google.protobuf.Timestamp from JSON: invalid RFC 3339 string"); } if (ms < Date.parse("0001-01-01T00:00:00Z") || ms > Date.parse("9999-12-31T23:59:59Z")) { throw new Error("cannot decode message google.protobuf.Timestamp from JSON: must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive"); } this.seconds = protoInt64.parse(ms / 1000); this.nanos = 0; if (matches[7]) { this.nanos = parseInt("1" + matches[7] + "0".repeat(9 - matches[7].length)) - 1000000000; } return this; } toJson(options) { const ms = Number(this.seconds) * 1000; if (ms < Date.parse("0001-01-01T00:00:00Z") || ms > Date.parse("9999-12-31T23:59:59Z")) { throw new Error("cannot encode google.protobuf.Timestamp to JSON: must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive"); } if (this.nanos < 0) { throw new Error("cannot encode google.protobuf.Timestamp to JSON: nanos must not be negative"); } let z = "Z"; if (this.nanos > 0) { const nanosStr = (this.nanos + 1000000000).toString().substring(1); if (nanosStr.substring(3) === "000000") { z = "." + nanosStr.substring(0, 3) + "Z"; } else if (nanosStr.substring(6) === "000") { z = "." + nanosStr.substring(0, 6) + "Z"; } else { z = "." + nanosStr + "Z"; } } return new Date(ms).toISOString().replace(".000Z", z); } toDate() { return new Date(Number(this.seconds) * 1000 + Math.ceil(this.nanos / 1000000)); } static now() { return Timestamp.fromDate(new Date()); } static fromDate(date) { const ms = date.getTime(); return new Timestamp({ seconds: protoInt64.parse(Math.floor(ms / 1000)), nanos: ms % 1000 * 1000000 }); } static fromBinary(bytes, options) { return new Timestamp().fromBinary(bytes, options); } static fromJson(jsonValue, options) { return new Timestamp().fromJson(jsonValue, options); } static fromJsonString(jsonString, options) { return new Timestamp().fromJsonString(jsonString, options); } static equals(a, b) { return proto3.util.equals(Timestamp, a, b); } } Timestamp.runtime = proto3; Timestamp.typeName = "google.protobuf.Timestamp"; Timestamp.fields = proto3.util.newFieldList(() => [{ no: 1, name: "seconds", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, { no: 2, name: "nanos", kind: "scalar", T: 5 /* ScalarType.INT32 */ }]);const MetricsBatch = /* @__PURE__ */proto3.makeMessageType("livekit.MetricsBatch", () => [{ no: 1, name: "timestamp_ms", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, { no: 2, name: "normalized_timestamp", kind: "message", T: Timestamp }, { no: 3, name: "str_data", kind: "scalar", T: 9, repeated: true }, { no: 4, name: "time_series", kind: "message", T: TimeSeriesMetric, repeated: true }, { no: 5, name: "events", kind: "message", T: EventMetric, repeated: true }]); const TimeSeriesMetric = /* @__PURE__ */proto3.makeMessageType("livekit.TimeSeriesMetric", () => [{ no: 1, name: "label", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 2, name: "participant_identity", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 3, name: "track_sid", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 4, name: "samples", kind: "message", T: MetricSample, repeated: true }, { no: 5, name: "rid", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }]); const MetricSample = /* @__PURE__ */proto3.makeMessageType("livekit.MetricSample", () => [{ no: 1, name: "timestamp_ms", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, { no: 2, name: "normalized_timestamp", kind: "message", T: Timestamp }, { no: 3, name: "value", kind: "scalar", T: 2 /* ScalarType.FLOAT */ }]); const EventMetric = /* @__PURE__ */proto3.makeMessageType("livekit.EventMetric", () => [{ no: 1, name: "label", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 2, name: "participant_identity", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 3, name: "track_sid", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 4, name: "start_timestamp_ms", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, { no: 5, name: "end_timestamp_ms", kind: "scalar", T: 3, opt: true }, { no: 6, name: "normalized_start_timestamp", kind: "message", T: Timestamp }, { no: 7, name: "normalized_end_timestamp", kind: "message", T: Timestamp, opt: true }, { no: 8, name: "metadata", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 9, name: "rid", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }]); const AudioCodec = /* @__PURE__ */proto3.makeEnum("livekit.AudioCodec", [{ no: 0, name: "DEFAULT_AC" }, { no: 1, name: "OPUS" }, { no: 2, name: "AAC" }, { no: 3, name: "AC_MP3" }]); const VideoCodec = /* @__PURE__ */proto3.makeEnum("livekit.VideoCodec", [{ no: 0, name: "DEFAULT_VC" }, { no: 1, name: "H264_BASELINE" }, { no: 2, name: "H264_MAIN" }, { no: 3, name: "H264_HIGH" }, { no: 4, name: "VP8" }]); const ImageCodec = /* @__PURE__ */proto3.makeEnum("livekit.ImageCodec", [{ no: 0, name: "IC_DEFAULT" }, { no: 1, name: "IC_JPEG" }]); const BackupCodecPolicy$1 = /* @__PURE__ */proto3.makeEnum("livekit.BackupCodecPolicy", [{ no: 0, name: "PREFER_REGRESSION" }, { no: 1, name: "SIMULCAST" }, { no: 2, name: "REGRESSION" }]); const TrackType = /* @__PURE__ */proto3.makeEnum("livekit.TrackType", [{ no: 0, name: "AUDIO" }, { no: 1, name: "VIDEO" }, { no: 2, name: "DATA" }]); const TrackSource = /* @__PURE__ */proto3.makeEnum("livekit.TrackSource", [{ no: 0, name: "UNKNOWN" }, { no: 1, name: "CAMERA" }, { no: 2, name: "MICROPHONE" }, { no: 3, name: "SCREEN_SHARE" }, { no: 4, name: "SCREEN_SHARE_AUDIO" }]); const VideoQuality$1 = /* @__PURE__ */proto3.makeEnum("livekit.VideoQuality", [{ no: 0, name: "LOW" }, { no: 1, name: "MEDIUM" }, { no: 2, name: "HIGH" }, { no: 3, name: "OFF" }]); const ConnectionQuality$1 = /* @__PURE__ */proto3.makeEnum("livekit.ConnectionQuality", [{ no: 0, name: "POOR" }, { no: 1, name: "GOOD" }, { no: 2, name: "EXCELLENT" }, { no: 3, name: "LOST" }]); const ClientConfigSetting = /* @__PURE__ */proto3.makeEnum("livekit.ClientConfigSetting", [{ no: 0, name: "UNSET" }, { no: 1, name: "DISABLED" }, { no: 2, name: "ENABLED" }]); const DisconnectReason = /* @__PURE__ */proto3.makeEnum("livekit.DisconnectReason", [{ no: 0, name: "UNKNOWN_REASON" }, { no: 1, name: "CLIENT_INITIATED" }, { no: 2, name: "DUPLICATE_IDENTITY" }, { no: 3, name: "SERVER_SHUTDOWN" }, { no: 4, name: "PARTICIPANT_REMOVED" }, { no: 5, name: "ROOM_DELETED" }, { no: 6, name: "STATE_MISMATCH" }, { no: 7, name: "JOIN_FAILURE" }, { no: 8, name: "MIGRATION" }, { no: 9, name: "SIGNAL_CLOSE" }, { no: 10, name: "ROOM_CLOSED" }, { no: 11, name: "USER_UNAVAILABLE" }, { no: 12, name: "USER_REJECTED" }, { no: 13, name: "SIP_TRUNK_FAILURE" }, { no: 14, name: "CONNECTION_TIMEOUT" }, { no: 15, name: "MEDIA_FAILURE" }, { no: 16, name: "AGENT_ERROR" }]); const ReconnectReason = /* @__PURE__ */proto3.makeEnum("livekit.ReconnectReason", [{ no: 0, name: "RR_UNKNOWN" }, { no: 1, name: "RR_SIGNAL_DISCONNECTED" }, { no: 2, name: "RR_PUBLISHER_FAILED" }, { no: 3, name: "RR_SUBSCRIBER_FAILED" }, { no: 4, name: "RR_SWITCH_CANDIDATE" }]); const SubscriptionError = /* @__PURE__ */proto3.makeEnum("livekit.SubscriptionError", [{ no: 0, name: "SE_UNKNOWN" }, { no: 1, name: "SE_CODEC_UNSUPPORTED" }, { no: 2, name: "SE_TRACK_NOTFOUND" }]); const AudioTrackFeature = /* @__PURE__ */proto3.makeEnum("livekit.AudioTrackFeature", [{ no: 0, name: "TF_STEREO" }, { no: 1, name: "TF_NO_DTX" }, { no: 2, name: "TF_AUTO_GAIN_CONTROL" }, { no: 3, name: "TF_ECHO_CANCELLATION" }, { no: 4, name: "TF_NOISE_SUPPRESSION" }, { no: 5, name: "TF_ENHANCED_NOISE_CANCELLATION" }, { no: 6, name: "TF_PRECONNECT_BUFFER" }]); const PacketTrailerFeature = /* @__PURE__ */proto3.makeEnum("livekit.PacketTrailerFeature", [{ no: 0, name: "PTF_USER_TIMESTAMP" }, { no: 1, name: "PTF_FRAME_ID" }]); const Room$1 = /* @__PURE__ */proto3.makeMessageType("livekit.Room", () => [{ no: 1, name: "sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "empty_timeout", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 14, name: "departure_timeout", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 4, name: "max_participants", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 5, name: "creation_time", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, { no: 15, name: "creation_time_ms", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, { no: 6, name: "turn_password", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 7, name: "enabled_codecs", kind: "message", T: Codec, repeated: true }, { no: 8, name: "metadata", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 9, name: "num_participants", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 11, name: "num_publishers", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 10, name: "active_recording", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 13, name: "version", kind: "message", T: TimedVersion }]); const Codec = /* @__PURE__ */proto3.makeMessageType("livekit.Codec", () => [{ no: 1, name: "mime", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "fmtp_line", kind: "scalar", T: 9 /* ScalarType.STRING */ }]); const ParticipantPermission = /* @__PURE__ */proto3.makeMessageType("livekit.ParticipantPermission", () => [{ no: 1, name: "can_subscribe", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 2, name: "can_publish", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 3, name: "can_publish_data", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 9, name: "can_publish_sources", kind: "enum", T: proto3.getEnumType(TrackSource), repeated: true }, { no: 7, name: "hidden", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 8, name: "recorder", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 10, name: "can_update_metadata", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 11, name: "agent", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 12, name: "can_subscribe_metrics", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 13, name: "can_manage_agent_session", kind: "scalar", T: 8 /* ScalarType.BOOL */ }]); const ParticipantInfo = /* @__PURE__ */proto3.makeMessageType("livekit.ParticipantInfo", () => [{ no: 1, name: "sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "identity", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "state", kind: "enum", T: proto3.getEnumType(ParticipantInfo_State) }, { no: 4, name: "tracks", kind: "message", T: TrackInfo, repeated: true }, { no: 5, name: "metadata", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 6, name: "joined_at", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, { no: 17, name: "joined_at_ms", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, { no: 9, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 10, name: "version", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 11, name: "permission", kind: "message", T: ParticipantPermission }, { no: 12, name: "region", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 13, name: "is_publisher", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 14, name: "kind", kind: "enum", T: proto3.getEnumType(ParticipantInfo_Kind) }, { no: 15, name: "attributes", kind: "map", K: 9, V: { kind: "scalar", T: 9 /* ScalarType.STRING */ } }, { no: 16, name: "disconnect_reason", kind: "enum", T: proto3.getEnumType(DisconnectReason) }, { no: 18, name: "kind_details", kind: "enum", T: proto3.getEnumType(ParticipantInfo_KindDetail), repeated: true }, { no: 19, name: "data_tracks", kind: "message", T: DataTrackInfo$1, repeated: true }, { no: 20, name: "client_protocol", kind: "scalar", T: 5 /* ScalarType.INT32 */ }]); const ParticipantInfo_State = /* @__PURE__ */proto3.makeEnum("livekit.ParticipantInfo.State", [{ no: 0, name: "JOINING" }, { no: 1, name: "JOINED" }, { no: 2, name: "ACTIVE" }, { no: 3, name: "DISCONNECTED" }]); const ParticipantInfo_Kind = /* @__PURE__ */proto3.makeEnum("livekit.ParticipantInfo.Kind", [{ no: 0, name: "STANDARD" }, { no: 1, name: "INGRESS" }, { no: 2, name: "EGRESS" }, { no: 3, name: "SIP" }, { no: 4, name: "AGENT" }, { no: 7, name: "CONNECTOR" }, { no: 8, name: "BRIDGE" }]); const ParticipantInfo_KindDetail = /* @__PURE__ */proto3.makeEnum("livekit.ParticipantInfo.KindDetail", [{ no: 0, name: "CLOUD_AGENT" }, { no: 1, name: "FORWARDED" }, { no: 2, name: "CONNECTOR_WHATSAPP" }, { no: 3, name: "CONNECTOR_TWILIO" }, { no: 4, name: "BRIDGE_RTSP" }]); const Encryption_Type = /* @__PURE__ */proto3.makeEnum("livekit.Encryption.Type", [{ no: 0, name: "NONE" }, { no: 1, name: "GCM" }, { no: 2, name: "CUSTOM" }]); const SimulcastCodecInfo = /* @__PURE__ */proto3.makeMessageType("livekit.SimulcastCodecInfo", () => [{ no: 1, name: "mime_type", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "mid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "cid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 4, name: "layers", kind: "message", T: VideoLayer, repeated: true }, { no: 5, name: "video_layer_mode", kind: "enum", T: proto3.getEnumType(VideoLayer_Mode) }, { no: 6, name: "sdp_cid", kind: "scalar", T: 9 /* ScalarType.STRING */ }]); const TrackInfo = /* @__PURE__ */proto3.makeMessageType("livekit.TrackInfo", () => [{ no: 1, name: "sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "type", kind: "enum", T: proto3.getEnumType(TrackType) }, { no: 3, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 4, name: "muted", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 5, name: "width", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 6, name: "height", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 7, name: "simulcast", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 8, name: "disable_dtx", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 9, name: "source", kind: "enum", T: proto3.getEnumType(TrackSource) }, { no: 10, name: "layers", kind: "message", T: VideoLayer, repeated: true }, { no: 11, name: "mime_type", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 12, name: "mid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 13, name: "codecs", kind: "message", T: SimulcastCodecInfo, repeated: true }, { no: 14, name: "stereo", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 15, name: "disable_red", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 16, name: "encryption", kind: "enum", T: proto3.getEnumType(Encryption_Type) }, { no: 17, name: "stream", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 18, name: "version", kind: "message", T: TimedVersion }, { no: 19, name: "audio_features", kind: "enum", T: proto3.getEnumType(AudioTrackFeature), repeated: true }, { no: 20, name: "backup_codec_policy", kind: "enum", T: proto3.getEnumType(BackupCodecPolicy$1) }, { no: 21, name: "packet_trailer_features", kind: "enum", T: proto3.getEnumType(PacketTrailerFeature), repeated: true }]); const DataTrackInfo$1 = /* @__PURE__ */proto3.makeMessageType("livekit.DataTrackInfo", () => [{ no: 1, name: "pub_handle", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 2, name: "sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 4, name: "encryption", kind: "enum", T: proto3.getEnumType(Encryption_Type) }]); const DataTrackSubscriptionOptions = /* @__PURE__ */proto3.makeMessageType("livekit.DataTrackSubscriptionOptions", () => [{ no: 1, name: "target_fps", kind: "scalar", T: 13, opt: true }]); const VideoLayer = /* @__PURE__ */proto3.makeMessageType("livekit.VideoLayer", () => [{ no: 1, name: "quality", kind: "enum", T: proto3.getEnumType(VideoQuality$1) }, { no: 2, name: "width", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 3, name: "height", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 4, name: "bitrate", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 5, name: "ssrc", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 6, name: "spatial_layer", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, { no: 7, name: "rid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 8, name: "repair_ssrc", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }]); const VideoLayer_Mode = /* @__PURE__ */proto3.makeEnum("livekit.VideoLayer.Mode", [{ no: 0, name: "MODE_UNUSED" }, { no: 1, name: "ONE_SPATIAL_LAYER_PER_STREAM" }, { no: 2, name: "MULTIPLE_SPATIAL_LAYERS_PER_STREAM" }, { no: 3, name: "ONE_SPATIAL_LAYER_PER_STREAM_INCOMPLETE_RTCP_SR" }]); const DataPacket = /* @__PURE__ */proto3.makeMessageType("livekit.DataPacket", () => [{ no: 1, name: "kind", kind: "enum", T: proto3.getEnumType(DataPacket_Kind) }, { no: 4, name: "participant_identity", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 5, name: "destination_identities", kind: "scalar", T: 9, repeated: true }, { no: 2, name: "user", kind: "message", T: UserPacket, oneof: "value" }, { no: 3, name: "speaker", kind: "message", T: ActiveSpeakerUpdate, oneof: "value" }, { no: 6, name: "sip_dtmf", kind: "message", T: SipDTMF, oneof: "value" }, { no: 7, name: "transcription", kind: "message", T: Transcription, oneof: "value" }, { no: 8, name: "metrics", kind: "message", T: MetricsBatch, oneof: "value" }, { no: 9, name: "chat_message", kind: "message", T: ChatMessage, oneof: "value" }, { no: 10, name: "rpc_request", kind: "message", T: RpcRequest, oneof: "value" }, { no: 11, name: "rpc_ack", kind: "message", T: RpcAck, oneof: "value" }, { no: 12, name: "rpc_response", kind: "message", T: RpcResponse, oneof: "value" }, { no: 13, name: "stream_header", kind: "message", T: DataStream_Header, oneof: "value" }, { no: 14, name: "stream_chunk", kind: "message", T: DataStream_Chunk, oneof: "value" }, { no: 15, name: "stream_trailer", kind: "message", T: DataStream_Trailer, oneof: "value" }, { no: 18, name: "encrypted_packet", kind: "message", T: EncryptedPacket, oneof: "value" }, { no: 16, name: "sequence", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 17, name: "participant_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }]); const DataPacket_Kind = /* @__PURE__ */proto3.makeEnum("livekit.DataPacket.Kind", [{ no: 0, name: "RELIABLE" }, { no: 1, name: "LOSSY" }]); const EncryptedPacket = /* @__PURE__ */proto3.makeMessageType("livekit.EncryptedPacket", () => [{ no: 1, name: "encryption_type", kind: "enum", T: proto3.getEnumType(Encryption_Type) }, { no: 2, name: "iv", kind: "scalar", T: 12 /* ScalarType.BYTES */ }, { no: 3, name: "key_index", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 4, name: "encrypted_value", kind: "scalar", T: 12 /* ScalarType.BYTES */ }]); const EncryptedPacketPayload = /* @__PURE__ */proto3.makeMessageType("livekit.EncryptedPacketPayload", () => [{ no: 1, name: "user", kind: "message", T: UserPacket, oneof: "value" }, { no: 3, name: "chat_message", kind: "message", T: ChatMessage, oneof: "value" }, { no: 4, name: "rpc_request", kind: "message", T: RpcRequest, oneof: "value" }, { no: 5, name: "rpc_ack", kind: "message", T: RpcAck, oneof: "value" }, { no: 6, name: "rpc_response", kind: "message", T: RpcResponse, oneof: "value" }, { no: 7, name: "stream_header", kind: "message", T: DataStream_Header, oneof: "value" }, { no: 8, name: "stream_chunk", kind: "message", T: DataStream_Chunk, oneof: "value" }, { no: 9, name: "stream_trailer", kind: "message", T: DataStream_Trailer, oneof: "value" }]); const ActiveSpeakerUpdate = /* @__PURE__ */proto3.makeMessageType("livekit.ActiveSpeakerUpdate", () => [{ no: 1, name: "speakers", kind: "message", T: SpeakerInfo, repeated: true }]); const SpeakerInfo = /* @__PURE__ */proto3.makeMessageType("livekit.SpeakerInfo", () => [{ no: 1, name: "sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "level", kind: "scalar", T: 2 /* ScalarType.FLOAT */ }, { no: 3, name: "active", kind: "scalar", T: 8 /* ScalarType.BOOL */ }]); const UserPacket = /* @__PURE__ */proto3.makeMessageType("livekit.UserPacket", () => [{ no: 1, name: "participant_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 5, name: "participant_identity", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "payload", kind: "scalar", T: 12 /* ScalarType.BYTES */ }, { no: 3, name: "destination_sids", kind: "scalar", T: 9, repeated: true }, { no: 6, name: "destination_identities", kind: "scalar", T: 9, repeated: true }, { no: 4, name: "topic", kind: "scalar", T: 9, opt: true }, { no: 8, name: "id", kind: "scalar", T: 9, opt: true }, { no: 9, name: "start_time", kind: "scalar", T: 4, opt: true }, { no: 10, name: "end_time", kind: "scalar", T: 4, opt: true }, { no: 11, name: "nonce", kind: "scalar", T: 12 /* ScalarType.BYTES */ }]); const SipDTMF = /* @__PURE__ */proto3.makeMessageType("livekit.SipDTMF", () => [{ no: 3, name: "code", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 4, name: "digit", kind: "scalar", T: 9 /* ScalarType.STRING */ }]); const Transcription = /* @__PURE__ */proto3.makeMessageType("livekit.Transcription", () => [{ no: 2, name: "transcribed_participant_identity", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "track_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 4, name: "segments", kind: "message", T: TranscriptionSegment, repeated: true }]); const TranscriptionSegment = /* @__PURE__ */proto3.makeMessageType("livekit.TranscriptionSegment", () => [{ no: 1, name: "id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "text", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "start_time", kind: "scalar", T: 4 /* ScalarType.UINT64 */ }, { no: 4, name: "end_time", kind: "scalar", T: 4 /* ScalarType.UINT64 */ }, { no: 5, name: "final", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 6, name: "language", kind: "scalar", T: 9 /* ScalarType.STRING */ }]); const ChatMessage = /* @__PURE__ */proto3.makeMessageType("livekit.ChatMessage", () => [{ no: 1, name: "id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "timestamp", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, { no: 3, name: "edit_timestamp", kind: "scalar", T: 3, opt: true }, { no: 4, name: "message", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 5, name: "deleted", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 6, name: "generated", kind: "scalar", T: 8 /* ScalarType.BOOL */ }]); const RpcRequest = /* @__PURE__ */proto3.makeMessageType("livekit.RpcRequest", () => [{ no: 1, name: "id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "method", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "payload", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 4, name: "response_timeout_ms", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 5, name: "version", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 6, name: "compressed_payload", kind: "scalar", T: 12 /* ScalarType.BYTES */ }]); const RpcAck = /* @__PURE__ */proto3.makeMessageType("livekit.RpcAck", () => [{ no: 1, name: "request_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }]); const RpcResponse = /* @__PURE__ */proto3.makeMessageType("livekit.RpcResponse", () => [{ no: 1, name: "request_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "payload", kind: "scalar", T: 9, oneof: "value" }, { no: 3, name: "error", kind: "message", T: RpcError$1, oneof: "value" }, { no: 4, name: "compressed_payload", kind: "scalar", T: 12, oneof: "value" }]); const RpcError$1 = /* @__PURE__ */proto3.makeMessageType("livekit.RpcError", () => [{ no: 1, name: "code", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 2, name: "message", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "data", kind: "scalar", T: 9 /* ScalarType.STRING */ }]); const ParticipantTracks = /* @__PURE__ */proto3.makeMessageType("livekit.ParticipantTracks", () => [{ no: 1, name: "participant_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "track_sids", kind: "scalar", T: 9, repeated: true }]); const ServerInfo = /* @__PURE__ */proto3.makeMessageType("livekit.ServerInfo", () => [{ no: 1, name: "edition", kind: "enum", T: proto3.getEnumType(ServerInfo_Edition) }, { no: 2, name: "version", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "protocol", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, { no: 4, name: "region", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 5, name: "node_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 6, name: "debug_info", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 7, name: "agent_protocol", kind: "scalar", T: 5 /* ScalarType.INT32 */ }]); const ServerInfo_Edition = /* @__PURE__ */proto3.makeEnum("livekit.ServerInfo.Edition", [{ no: 0, name: "Standard" }, { no: 1, name: "Cloud" }]); const ClientInfo = /* @__PURE__ */proto3.makeMessageType("livekit.ClientInfo", () => [{ no: 1, name: "sdk", kind: "enum", T: proto3.getEnumType(ClientInfo_SDK) }, { no: 2, name: "version", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "protocol", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, { no: 4, name: "os", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 5, name: "os_version", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 6, name: "device_model", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 7, name: "browser", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 8, name: "browser_version", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 9, name: "address", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 10, name: "network", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 11, name: "other_sdks", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 12, name: "client_protocol", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, { no: 13, name: "capabilities", kind: "enum", T: proto3.getEnumType(ClientInfo_Capability), repeated: true }]); const ClientInfo_SDK = /* @__PURE__ */proto3.makeEnum("livekit.ClientInfo.SDK", [{ no: 0, name: "UNKNOWN" }, { no: 1, name: "JS" }, { no: 2, name: "SWIFT" }, { no: 3, name: "ANDROID" }, { no: 4, name: "FLUTTER" }, { no: 5, name: "GO" }, { no: 6, name: "UNITY" }, { no: 7, name: "REACT_NATIVE" }, { no: 8, name: "RUST" }, { no: 9, name: "PYTHON" }, { no: 10, name: "CPP" }, { no: 11, name: "UNITY_WEB" }, { no: 12, name: "NODE" }, { no: 13, name: "UNREAL" }, { no: 14, name: "ESP32" }]); const ClientInfo_Capability = /* @__PURE__ */proto3.makeEnum("livekit.ClientInfo.Capability", [{ no: 0, name: "CAP_UNUSED" }, { no: 1, name: "CAP_PACKET_TRAILER" }]); const ClientConfiguration = /* @__PURE__ */proto3.makeMessageType("livekit.ClientConfiguration", () => [{ no: 1, name: "video", kind: "message", T: VideoConfiguration }, { no: 2, name: "screen", kind: "message", T: VideoConfiguration }, { no: 3, name: "resume_connection", kind: "enum", T: proto3.getEnumType(ClientConfigSetting) }, { no: 4, name: "disabled_codecs", kind: "message", T: DisabledCodecs }, { no: 5, name: "force_relay", kind: "enum", T: proto3.getEnumType(ClientConfigSetting) }]); const VideoConfiguration = /* @__PURE__ */proto3.makeMessageType("livekit.VideoConfiguration", () => [{ no: 1, name: "hardware_encoder", kind: "enum", T: proto3.getEnumType(ClientConfigSetting) }]); const DisabledCodecs = /* @__PURE__ */proto3.makeMessageType("livekit.DisabledCodecs", () => [{ no: 1, name: "codecs", kind: "message", T: Codec, repeated: true }, { no: 2, name: "publish", kind: "message", T: Codec, repeated: true }]); const TimedVersion = /* @__PURE__ */proto3.makeMessageType("livekit.TimedVersion", () => [{ no: 1, name: "unix_micro", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, { no: 2, name: "ticks", kind: "scalar", T: 5 /* ScalarType.INT32 */ }]); const DataStream_OperationType = /* @__PURE__ */proto3.makeEnum("livekit.DataStream.OperationType", [{ no: 0, name: "CREATE" }, { no: 1, name: "UPDATE" }, { no: 2, name: "DELETE" }, { no: 3, name: "REACTION" }]); const DataStream_TextHeader = /* @__PURE__ */proto3.makeMessageType("livekit.DataStream.TextHeader", () => [{ no: 1, name: "operation_type", kind: "enum", T: proto3.getEnumType(DataStream_OperationType) }, { no: 2, name: "version", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, { no: 3, name: "reply_to_stream_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 4, name: "attached_stream_ids", kind: "scalar", T: 9, repeated: true }, { no: 5, name: "generated", kind: "scalar", T: 8 /* ScalarType.BOOL */ }], { localName: "DataStream_TextHeader" }); const DataStream_ByteHeader = /* @__PURE__ */proto3.makeMessageType("livekit.DataStream.ByteHeader", () => [{ no: 1, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }], { localName: "DataStream_ByteHeader" }); const DataStream_Header = /* @__PURE__ */proto3.makeMessageType("livekit.DataStream.Header", () => [{ no: 1, name: "stream_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "timestamp", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, { no: 3, name: "topic", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 4, name: "mime_type", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 5, name: "total_length", kind: "scalar", T: 4, opt: true }, { no: 7, name: "encryption_type", kind: "enum", T: proto3.getEnumType(Encryption_Type) }, { no: 8, name: "attributes", kind: "map", K: 9, V: { kind: "scalar", T: 9 /* ScalarType.STRING */ } }, { no: 9, name: "text_header", kind: "message", T: DataStream_TextHeader, oneof: "content_header" }, { no: 10, name: "byte_header", kind: "message", T: DataStream_ByteHeader, oneof: "content_header" }], { localName: "DataStream_Header" }); const DataStream_Chunk = /* @__PURE__ */proto3.makeMessageType("livekit.DataStream.Chunk", () => [{ no: 1, name: "stream_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "chunk_index", kind: "scalar", T: 4 /* ScalarType.UINT64 */ }, { no: 3, name: "content", kind: "scalar", T: 12 /* ScalarType.BYTES */ }, { no: 4, name: "version", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, { no: 5, name: "iv", kind: "scalar", T: 12, opt: true }], { localName: "DataStream_Chunk" }); const DataStream_Trailer = /* @__PURE__ */proto3.makeMessageType("livekit.DataStream.Trailer", () => [{ no: 1, name: "stream_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "reason", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "attributes", kind: "map", K: 9, V: { kind: "scalar", T: 9 /* ScalarType.STRING */ } }], { localName: "DataStream_Trailer" }); const FilterParams = /* @__PURE__ */proto3.makeMessageType("livekit.FilterParams", () => [{ no: 1, name: "include_events", kind: "scalar", T: 9, repeated: true }, { no: 2, name: "exclude_events", kind: "scalar", T: 9, repeated: true }]); const WebhookConfig = /* @__PURE__ */proto3.makeMessageType("livekit.WebhookConfig", () => [{ no: 1, name: "url", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "signing_key", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "filter_params", kind: "message", T: FilterParams }]); const SubscribedAudioCodec = /* @__PURE__ */proto3.makeMessageType("livekit.SubscribedAudioCodec", () => [{ no: 1, name: "codec", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "enabled", kind: "scalar", T: 8 /* ScalarType.BOOL */ }]); const JobRestartPolicy = /* @__PURE__ */proto3.makeEnum("livekit.JobRestartPolicy", [{ no: 0, name: "JRP_ON_FAILURE" }, { no: 1, name: "JRP_NEVER" }]); const RoomAgentDispatch = /* @__PURE__ */proto3.makeMessageType("livekit.RoomAgentDispatch", () => [{ no: 1, name: "agent_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "metadata", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "restart_policy", kind: "enum", T: proto3.getEnumType(JobRestartPolicy) }, { no: 4, name: "deployment", kind: "scalar", T: 9 /* ScalarType.STRING */ }]); const EncodingOptionsPreset = /* @__PURE__ */proto3.makeEnum("livekit.EncodingOptionsPreset", [{ no: 0, name: "H264_720P_30" }, { no: 1, name: "H264_720P_60" }, { no: 2, name: "H264_1080P_30" }, { no: 3, name: "H264_1080P_60" }, { no: 4, name: "PORTRAIT_H264_720P_30" }, { no: 5, name: "PORTRAIT_H264_720P_60" }, { no: 6, name: "PORTRAIT_H264_1080P_30" }, { no: 7, name: "PORTRAIT_H264_1080P_60" }]); const EncodedFileType = /* @__PURE__ */proto3.makeEnum("livekit.EncodedFileType", [{ no: 0, name: "DEFAULT_FILETYPE" }, { no: 1, name: "MP4" }, { no: 2, name: "OGG" }, { no: 3, name: "MP3" }]); const StreamProtocol = /* @__PURE__ */proto3.makeEnum("livekit.StreamProtocol", [{ no: 0, name: "DEFAULT_PROTOCOL" }, { no: 1, name: "RTMP" }, { no: 2, name: "SRT" }, { no: 3, name: "WEBSOCKET" }]); const SegmentedFileProtocol = /* @__PURE__ */proto3.makeEnum("livekit.SegmentedFileProtocol", [{ no: 0, name: "DEFAULT_SEGMENTED_FILE_PROTOCOL" }, { no: 1, name: "HLS_PROTOCOL" }]); const SegmentedFileSuffix = /* @__PURE__ */proto3.makeEnum("livekit.SegmentedFileSuffix", [{ no: 0, name: "INDEX" }, { no: 1, name: "TIMESTAMP" }]); const ImageFileSuffix = /* @__PURE__ */proto3.makeEnum("livekit.ImageFileSuffix", [{ no: 0, name: "IMAGE_SUFFIX_INDEX" }, { no: 1, name: "IMAGE_SUFFIX_TIMESTAMP" }, { no: 2, name: "IMAGE_SUFFIX_NONE_OVERWRITE" }]); const AudioMixing = /* @__PURE__ */proto3.makeEnum("livekit.AudioMixing", [{ no: 0, name: "DEFAULT_MIXING" }, { no: 1, name: "DUAL_CHANNEL_AGENT" }, { no: 2, name: "DUAL_CHANNEL_ALTERNATE" }]); const EncodingOptions = /* @__PURE__ */proto3.makeMessageType("livekit.EncodingOptions", () => [{ no: 1, name: "width", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, { no: 2, name: "height", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, { no: 3, name: "depth", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, { no: 4, name: "framerate", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, { no: 5, name: "audio_codec", kind: "enum", T: proto3.getEnumType(AudioCodec) }, { no: 6, name: "audio_bitrate", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, { no: 7, name: "audio_frequency", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, { no: 8, name: "video_codec", kind: "enum", T: proto3.getEnumType(VideoCodec) }, { no: 9, name: "video_bitrate", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, { no: 10, name: "key_frame_interval", kind: "scalar", T: 1 /* ScalarType.DOUBLE */ }, { no: 11, name: "audio_quality", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, { no: 12, name: "video_quality", kind: "scalar", T: 5 /* ScalarType.INT32 */ }]); const StreamOutput = /* @__PURE__ */proto3.makeMessageType("livekit.StreamOutput", () => [{ no: 1, name: "protocol", kind: "enum", T: proto3.getEnumType(StreamProtocol) }, { no: 2, name: "urls", kind: "scalar", T: 9, repeated: true }]); const SegmentedFileOutput = /* @__PURE__ */proto3.makeMessageType("livekit.SegmentedFileOutput", () => [{ no: 1, name: "protocol", kind: "enum", T: proto3.getEnumType(SegmentedFileProtocol) }, { no: 2, name: "filename_prefix", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "playlist_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 11, name: "live_playlist_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 4, name: "segment_duration", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 10, name: "filename_suffix", kind: "enum", T: proto3.getEnumType(SegmentedFileSuffix) }, { no: 8, name: "disable_manifest", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 5, name: "s3", kind: "message", T: S3Upload, oneof: "output" }, { no: 6, name: "gcp", kind: "message", T: GCPUpload, oneof: "output" }, { no: 7, name: "azure", kind: "message", T: AzureBlobUpload, oneof: "output" }, { no: 9, name: "aliOSS", kind: "message", T: AliOSSUpload, oneof: "output" }]); const ImageOutput = /* @__PURE__ */proto3.makeMessageType("livekit.ImageOutput", () => [{ no: 1, name: "capture_interval", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 2, name: "width", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, { no: 3, name: "height", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, { no: 4, name: "filename_prefix", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 5, name: "filename_suffix", kind: "enum", T: proto3.getEnumType(ImageFileSuffix) }, { no: 6, name: "image_codec", kind: "enum", T: proto3.getEnumType(ImageCodec) }, { no: 7, name: "disable_manifest", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 8, name: "s3", kind: "message", T: S3Upload, oneof: "output" }, { no: 9, name: "gcp", kind: "message", T: GCPUpload, oneof: "output" }, { no: 10, name: "azure", kind: "message", T: AzureBlobUpload, oneof: "output" }, { no: 11, name: "aliOSS", kind: "message", T: AliOSSUpload, oneof: "output" }]); const S3Upload = /* @__PURE__ */proto3.makeMessageType("livekit.S3Upload", () => [{ no: 1, name: "access_key", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "secret", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 11, name: "session_token", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 12, name: "assume_role_arn", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 13, name: "assume_role_external_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "region", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 4, name: "endpoint", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 5, name: "bucket", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 6, name: "force_path_style", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 7, name: "metadata", kind: "map", K: 9, V: { kind: "scalar", T: 9 /* ScalarType.STRING */ } }, { no: 8, name: "tagging", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 9, name: "content_disposition", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 10, name: "proxy", kind: "message", T: ProxyConfig }]); const GCPUpload = /* @__PURE__ */proto3.makeMessageType("livekit.GCPUpload", () => [{ no: 1, name: "credentials", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "bucket", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "proxy", kind: "message", T: ProxyConfig }]); const AzureBlobUpload = /* @__PURE__ */proto3.makeMessageType("livekit.AzureBlobUpload", () => [{ no: 1, name: "account_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "account_key", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "container_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }]); const AliOSSUpload = /* @__PURE__ */proto3.makeMessageType("livekit.AliOSSUpload", () => [{ no: 1, name: "access_key", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "secret", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "region", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 4, name: "endpoint", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 5, name: "bucket", kind: "scalar", T: 9 /* ScalarType.STRING */ }]); const ProxyConfig = /* @__PURE__ */proto3.makeMessageType("livekit.ProxyConfig", () => [{ no: 1, name: "url", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "username", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "password", kind: "scalar", T: 9 /* ScalarType.STRING */ }]); const AutoParticipantEgress = /* @__PURE__ */proto3.makeMessageType("livekit.AutoParticipantEgress", () => [{ no: 1, name: "preset", kind: "enum", T: proto3.getEnumType(EncodingOptionsPreset), oneof: "options" }, { no: 2, name: "advanced", kind: "message", T: EncodingOptions, oneof: "options" }, { no: 3, name: "file_outputs", kind: "message", T: EncodedFileOutput, repeated: true }, { no: 4, name: "segment_outputs", kind: "message", T: SegmentedFileOutput, repeated: true }]); const AutoTrackEgress = /* @__PURE__ */proto3.makeMessageType("livekit.AutoTrackEgress", () => [{ no: 1, name: "filepath", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 5, name: "disable_manifest", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 2, name: "s3", kind: "message", T: S3Upload, oneof: "output" }, { no: 3, name: "gcp", kind: "message", T: GCPUpload, oneof: "output" }, { no: 4, name: "azure", kind: "message", T: AzureBlobUpload, oneof: "output" }, { no: 6, name: "aliOSS", kind: "message", T: AliOSSUpload, oneof: "output" }]); const RoomCompositeEgressRequest = /* @__PURE__ */proto3.makeMessageType("livekit.RoomCompositeEgressRequest", () => [{ no: 1, name: "room_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "layout", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "audio_only", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 15, name: "audio_mixing", kind: "enum", T: proto3.getEnumType(AudioMixing) }, { no: 4, name: "video_only", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 5, name: "custom_base_url", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 6, name: "file", kind: "message", T: EncodedFileOutput, oneof: "output" }, { no: 7, name: "stream", kind: "message", T: StreamOutput, oneof: "output" }, { no: 10, name: "segments", kind: "message", T: SegmentedFileOutput, oneof: "output" }, { no: 8, name: "preset", kind: "enum", T: proto3.getEnumType(EncodingOptionsPreset), oneof: "options" }, { no: 9, name: "advanced", kind: "message", T: EncodingOptions, oneof: "options" }, { no: 11, name: "file_outputs", kind: "message", T: EncodedFileOutput, repeated: true }, { no: 12, name: "stream_outputs", kind: "message", T: StreamOutput, repeated: true }, { no: 13, name: "segment_outputs", kind: "message", T: SegmentedFileOutput, repeated: true }, { no: 14, name: "image_outputs", kind: "message", T: ImageOutput, repeated: true }, { no: 16, name: "webhooks", kind: "message", T: WebhookConfig, repeated: true }]); const EncodedFileOutput = /* @__PURE__ */proto3.makeMessageType("livekit.EncodedFileOutput", () => [{ no: 1, name: "file_type", kind: "enum", T: proto3.getEnumType(EncodedFileType) }, { no: 2, name: "filepath", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 6, name: "disable_manifest", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 3, name: "s3", kind: "message", T: S3Upload, oneof: "output" }, { no: 4, name: "gcp", kind: "message", T: GCPUpload, oneof: "output" }, { no: 5, name: "azure", kind: "message", T: AzureBlobUpload, oneof: "output" }, { no: 7, name: "aliOSS", kind: "message", T: AliOSSUpload, oneof: "output" }]); const RoomEgress = /* @__PURE__ */proto3.makeMessageType("livekit.RoomEgress", () => [{ no: 1, name: "room", kind: "message", T: RoomCompositeEgressRequest }, { no: 3, name: "participant", kind: "message", T: AutoParticipantEgress }, { no: 2, name: "tracks", kind: "message", T: AutoTrackEgress }]); const RoomConfiguration = /* @__PURE__ */proto3.makeMessageType("livekit.RoomConfiguration", () => [{ no: 1, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "empty_timeout", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 3, name: "departure_timeout", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 4, name: "max_participants", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 11, name: "metadata", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 5, name: "egress", kind: "message", T: RoomEgress }, { no: 7, name: "min_playout_delay", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 8, name: "max_playout_delay", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 9, name: "sync_streams", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 10, name: "agents", kind: "message", T: RoomAgentDispatch, repeated: true }, { no: 12, name: "tags", kind: "map", K: 9, V: { kind: "scalar", T: 9 /* ScalarType.STRING */ } }]); const SignalTarget = /* @__PURE__ */proto3.makeEnum("livekit.SignalTarget", [{ no: 0, name: "PUBLISHER" }, { no: 1, name: "SUBSCRIBER" }]); const StreamState = /* @__PURE__ */proto3.makeEnum("livekit.StreamState", [{ no: 0, name: "ACTIVE" }, { no: 1, name: "PAUSED" }]); const CandidateProtocol = /* @__PURE__ */proto3.makeEnum("livekit.CandidateProtocol", [{ no: 0, name: "UDP" }, { no: 1, name: "TCP" }, { no: 2, name: "TLS" }]); const SignalRequest = /* @__PURE__ */proto3.makeMessageType("livekit.SignalRequest", () => [{ no: 1, name: "offer", kind: "message", T: SessionDescription, oneof: "message" }, { no: 2, name: "answer", kind: "message", T: SessionDescription, oneof: "message" }, { no: 3, name: "trickle", kind: "message", T: TrickleRequest, oneof: "message" }, { no: 4, name: "add_track", kind: "message", T: AddTrackRequest, oneof: "message" }, { no: 5, name: "mute", kind: "message", T: MuteTrackRequest, oneof: "message" }, { no: 6, name: "subscription", kind: "message", T: UpdateSubscription, oneof: "message" }, { no: 7, name: "track_setting", kind: "message", T: UpdateTrackSettings, oneof: "message" }, { no: 8, name: "leave", kind: "message", T: LeaveRequest, oneof: "message" }, { no: 10, name: "update_layers", kind: "message", T: UpdateVideoLayers, oneof: "message" }, { no: 11, name: "subscription_permission", kind: "message", T: SubscriptionPermission, oneof: "message" }, { no: 12, name: "sync_state", kind: "message", T: SyncState, oneof: "message" }, { no: 13, name: "simulate", kind: "message", T: SimulateScenario, oneof: "message" }, { no: 14, name: "ping", kind: "scalar", T: 3, oneof: "message" }, { no: 15, name: "update_metadata", kind: "message", T: UpdateParticipantMetadata, oneof: "message" }, { no: 16, name: "ping_req", kind: "message", T: Ping, oneof: "message" }, { no: 17, name: "update_audio_track", kind: "message", T: UpdateLocalAudioTrack, oneof: "message" }, { no: 18, name: "update_video_track", kind: "message", T: UpdateLocalVideoTrack, oneof: "message" }, { no: 19, name: "publish_data_track_request", kind: "message", T: PublishDataTrackRequest, oneof: "message" }, { no: 20, name: "unpublish_data_track_request", kind: "message", T: UnpublishDataTrackRequest, oneof: "message" }, { no: 21, name: "update_data_subscription", kind: "message", T: UpdateDataSubscription, oneof: "message" }]); const SignalResponse = /* @__PURE__ */proto3.makeMessageType("livekit.SignalResponse", () => [{ no: 1, name: "join", kind: "message", T: JoinResponse, oneof: "message" }, { no: 2, name: "answer", kind: "message", T: SessionDescription, oneof: "message" }, { no: 3, name: "offer", kind: "message", T: SessionDescription, oneof: "message" }, { no: 4, name: "trickle", kind: "message", T: TrickleRequest, oneof: "message" }, { no: 5, name: "update", kind: "message", T: ParticipantUpdate, oneof: "message" }, { no: 6, name: "track_published", kind: "message", T: TrackPublishedResponse, oneof: "message" }, { no: 8, name: "leave", kind: "message", T: LeaveRequest, oneof: "message" }, { no: 9, name: "mute", kind: "message", T: MuteTrackRequest, oneof: "message" }, { no: 10, name: "speakers_changed", kind: "message", T: SpeakersChanged, oneof: "message" }, { no: 11, name: "room_update", kind: "message", T: RoomUpdate, oneof: "message" }, { no: 12, name: "connection_quality", kind: "message", T: ConnectionQualityUpdate, oneof: "message" }, { no: 13, name: "stream_state_update", kind: "message", T: StreamStateUpdate, oneof: "message" }, { no: 14, name: "subscribed_quality_update", kind: "message", T: SubscribedQualityUpdate, oneof: "message" }, { no: 15, name: "subscription_permission_update", kind: "message", T: SubscriptionPermissionUpdate, oneof: "message" }, { no: 16, name: "refresh_token", kind: "scalar", T: 9, oneof: "message" }, { no: 17, name: "track_unpublished", kind: "message", T: TrackUnpublishedResponse, oneof: "message" }, { no: 18, name: "pong", kind: "scalar", T: 3, oneof: "message" }, { no: 19, name: "reconnect", kind: "message", T: ReconnectResponse, oneof: "message" }, { no: 20, name: "pong_resp", kind: "message", T: Pong, oneof: "message" }, { no: 21, name: "subscription_response", kind: "message", T: SubscriptionResponse, oneof: "message" }, { no: 22, name: "request_response", kind: "message", T: RequestResponse, oneof: "message" }, { no: 23, name: "track_subscribed", kind: "message", T: TrackSubscribed, oneof: "message" }, { no: 24, name: "room_moved", kind: "message", T: RoomMovedResponse, oneof: "message" }, { no: 25, name: "media_sections_requirement", kind: "message", T: MediaSectionsRequirement, oneof: "message" }, { no: 26, name: "subscribed_audio_codec_update", kind: "message", T: SubscribedAudioCodecUpdate, oneof: "message" }, { no: 27, name: "publish_data_track_response", kind: "message", T: PublishDataTrackResponse, oneof: "message" }, { no: 28, name: "unpublish_data_track_response", kind: "message", T: UnpublishDataTrackResponse, oneof: "message" }, { no: 29, name: "data_track_subscriber_handles", kind: "message", T: DataTrackSubscriberHandles, oneof: "message" }]); const SimulcastCodec = /* @__PURE__ */proto3.makeMessageType("livekit.SimulcastCodec", () => [{ no: 1, name: "codec", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "cid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 4, name: "layers", kind: "message", T: VideoLayer, repeated: true }, { no: 5, name: "video_layer_mode", kind: "enum", T: proto3.getEnumType(VideoLayer_Mode) }]); const AddTrackRequest = /* @__PURE__ */proto3.makeMessageType("livekit.AddTrackRequest", () => [{ no: 1, name: "cid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "type", kind: "enum", T: proto3.getEnumType(TrackType) }, { no: 4, name: "width", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 5, name: "height", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 6, name: "muted", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 7, name: "disable_dtx", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 8, name: "source", kind: "enum", T: proto3.getEnumType(TrackSource) }, { no: 9, name: "layers", kind: "message", T: VideoLayer, repeated: true }, { no: 10, name: "simulcast_codecs", kind: "message", T: SimulcastCodec, repeated: true }, { no: 11, name: "sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 12, name: "stereo", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 13, name: "disable_red", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 14, name: "encryption", kind: "enum", T: proto3.getEnumType(Encryption_Type) }, { no: 15, name: "stream", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 16, name: "backup_codec_policy", kind: "enum", T: proto3.getEnumType(BackupCodecPolicy$1) }, { no: 17, name: "audio_features", kind: "enum", T: proto3.getEnumType(AudioTrackFeature), repeated: true }, { no: 18, name: "packet_trailer_features", kind: "enum", T: proto3.getEnumType(PacketTrailerFeature), repeated: true }]); const PublishDataTrackRequest = /* @__PURE__ */proto3.makeMessageType("livekit.PublishDataTrackRequest", () => [{ no: 1, name: "pub_handle", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 2, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "encryption", kind: "enum", T: proto3.getEnumType(Encryption_Type) }]); const PublishDataTrackResponse = /* @__PURE__ */proto3.makeMessageType("livekit.PublishDataTrackResponse", () => [{ no: 1, name: "info", kind: "message", T: DataTrackInfo$1 }]); const UnpublishDataTrackRequest = /* @__PURE__ */proto3.makeMessageType("livekit.UnpublishDataTrackRequest", () => [{ no: 1, name: "pub_handle", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }]); const UnpublishDataTrackResponse = /* @__PURE__ */proto3.makeMessageType("livekit.UnpublishDataTrackResponse", () => [{ no: 1, name: "info", kind: "message", T: DataTrackInfo$1 }]); const DataTrackSubscriberHandles = /* @__PURE__ */proto3.makeMessageType("livekit.DataTrackSubscriberHandles", () => [{ no: 1, name: "sub_handles", kind: "map", K: 13, V: { kind: "message", T: DataTrackSubscriberHandles_PublishedDataTrack } }]); const DataTrackSubscriberHandles_PublishedDataTrack = /* @__PURE__ */proto3.makeMessageType("livekit.DataTrackSubscriberHandles.PublishedDataTrack", () => [{ no: 1, name: "publisher_identity", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "publisher_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "track_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }], { localName: "DataTrackSubscriberHandles_PublishedDataTrack" }); const TrickleRequest = /* @__PURE__ */proto3.makeMessageType("livekit.TrickleRequest", () => [{ no: 1, name: "candidateInit", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "target", kind: "enum", T: proto3.getEnumType(SignalTarget) }, { no: 3, name: "final", kind: "scalar", T: 8 /* ScalarType.BOOL */ }]); const MuteTrackRequest = /* @__PURE__ */proto3.makeMessageType("livekit.MuteTrackRequest", () => [{ no: 1, name: "sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "muted", kind: "scalar", T: 8 /* ScalarType.BOOL */ }]); const JoinResponse = /* @__PURE__ */proto3.makeMessageType("livekit.JoinResponse", () => [{ no: 1, name: "room", kind: "message", T: Room$1 }, { no: 2, name: "participant", kind: "message", T: ParticipantInfo }, { no: 3, name: "other_participants", kind: "message", T: ParticipantInfo, repeated: true }, { no: 4, name: "server_version", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 5, name: "ice_servers", kind: "message", T: ICEServer, repeated: true }, { no: 6, name: "subscriber_primary", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 7, name: "alternative_url", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 8, name: "client_configuration", kind: "message", T: ClientConfiguration }, { no: 9, name: "server_region", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 10, name: "ping_timeout", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, { no: 11, name: "ping_interval", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, { no: 12, name: "server_info", kind: "message", T: ServerInfo }, { no: 13, name: "sif_trailer", kind: "scalar", T: 12 /* ScalarType.BYTES */ }, { no: 14, name: "enabled_publish_codecs", kind: "message", T: Codec, repeated: true }, { no: 15, name: "fast_publish", kind: "scalar", T: 8 /* ScalarType.BOOL */ }]); const ReconnectResponse = /* @__PURE__ */proto3.makeMessageType("livekit.ReconnectResponse", () => [{ no: 1, name: "ice_servers", kind: "message", T: ICEServer, repeated: true }, { no: 2, name: "client_configuration", kind: "message", T: ClientConfiguration }, { no: 3, name: "server_info", kind: "message", T: ServerInfo }, { no: 4, name: "last_message_seq", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }]); const TrackPublishedResponse = /* @__PURE__ */proto3.makeMessageType("livekit.TrackPublishedResponse", () => [{ no: 1, name: "cid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "track", kind: "message", T: TrackInfo }]); const TrackUnpublishedResponse = /* @__PURE__ */proto3.makeMessageType("livekit.TrackUnpublishedResponse", () => [{ no: 1, name: "track_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }]); const SessionDescription = /* @__PURE__ */proto3.makeMessageType("livekit.SessionDescription", () => [{ no: 1, name: "type", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "sdp", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "id", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 4, name: "mid_to_track_id", kind: "map", K: 9, V: { kind: "scalar", T: 9 /* ScalarType.STRING */ } }]); const ParticipantUpdate = /* @__PURE__ */proto3.makeMessageType("livekit.ParticipantUpdate", () => [{ no: 1, name: "participants", kind: "message", T: ParticipantInfo, repeated: true }]); const UpdateSubscription = /* @__PURE__ */proto3.makeMessageType("livekit.UpdateSubscription", () => [{ no: 1, name: "track_sids", kind: "scalar", T: 9, repeated: true }, { no: 2, name: "subscribe", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 3, name: "participant_tracks", kind: "message", T: ParticipantTracks, repeated: true }]); const UpdateDataSubscription = /* @__PURE__ */proto3.makeMessageType("livekit.UpdateDataSubscription", () => [{ no: 1, name: "updates", kind: "message", T: UpdateDataSubscription_Update, repeated: true }]); const UpdateDataSubscription_Update = /* @__PURE__ */proto3.makeMessageType("livekit.UpdateDataSubscription.Update", () => [{ no: 1, name: "track_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "subscribe", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 3, name: "options", kind: "message", T: DataTrackSubscriptionOptions }], { localName: "UpdateDataSubscription_Update" }); const UpdateTrackSettings = /* @__PURE__ */proto3.makeMessageType("livekit.UpdateTrackSettings", () => [{ no: 1, name: "track_sids", kind: "scalar", T: 9, repeated: true }, { no: 3, name: "disabled", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 4, name: "quality", kind: "enum", T: proto3.getEnumType(VideoQuality$1) }, { no: 5, name: "width", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 6, name: "height", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 7, name: "fps", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 8, name: "priority", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }]); const UpdateLocalAudioTrack = /* @__PURE__ */proto3.makeMessageType("livekit.UpdateLocalAudioTrack", () => [{ no: 1, name: "track_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "features", kind: "enum", T: proto3.getEnumType(AudioTrackFeature), repeated: true }]); const UpdateLocalVideoTrack = /* @__PURE__ */proto3.makeMessageType("livekit.UpdateLocalVideoTrack", () => [{ no: 1, name: "track_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "width", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 3, name: "height", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }]); const LeaveRequest = /* @__PURE__ */proto3.makeMessageType("livekit.LeaveRequest", () => [{ no: 1, name: "can_reconnect", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 2, name: "reason", kind: "enum", T: proto3.getEnumType(DisconnectReason) }, { no: 3, name: "action", kind: "enum", T: proto3.getEnumType(LeaveRequest_Action) }, { no: 4, name: "regions", kind: "message", T: RegionSettings }]); const LeaveRequest_Action = /* @__PURE__ */proto3.makeEnum("livekit.LeaveRequest.Action", [{ no: 0, name: "DISCONNECT" }, { no: 1, name: "RESUME" }, { no: 2, name: "RECONNECT" }]); const UpdateVideoLayers = /* @__PURE__ */proto3.makeMessageType("livekit.UpdateVideoLayers", () => [{ no: 1, name: "track_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "layers", kind: "message", T: VideoLayer, repeated: true }]); const UpdateParticipantMetadata = /* @__PURE__ */proto3.makeMessageType("livekit.UpdateParticipantMetadata", () => [{ no: 1, name: "metadata", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "attributes", kind: "map", K: 9, V: { kind: "scalar", T: 9 /* ScalarType.STRING */ } }, { no: 4, name: "request_id", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }]); const ICEServer = /* @__PURE__ */proto3.makeMessageType("livekit.ICEServer", () => [{ no: 1, name: "urls", kind: "scalar", T: 9, repeated: true }, { no: 2, name: "username", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "credential", kind: "scalar", T: 9 /* ScalarType.STRING */ }]); const SpeakersChanged = /* @__PURE__ */proto3.makeMessageType("livekit.SpeakersChanged", () => [{ no: 1, name: "speakers", kind: "message", T: SpeakerInfo, repeated: true }]); const RoomUpdate = /* @__PURE__ */proto3.makeMessageType("livekit.RoomUpdate", () => [{ no: 1, name: "room", kind: "message", T: Room$1 }]); const ConnectionQualityInfo = /* @__PURE__ */proto3.makeMessageType("livekit.ConnectionQualityInfo", () => [{ no: 1, name: "participant_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "quality", kind: "enum", T: proto3.getEnumType(ConnectionQuality$1) }, { no: 3, name: "score", kind: "scalar", T: 2 /* ScalarType.FLOAT */ }]); const ConnectionQualityUpdate = /* @__PURE__ */proto3.makeMessageType("livekit.ConnectionQualityUpdate", () => [{ no: 1, name: "updates", kind: "message", T: ConnectionQualityInfo, repeated: true }]); const StreamStateInfo = /* @__PURE__ */proto3.makeMessageType("livekit.StreamStateInfo", () => [{ no: 1, name: "participant_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "track_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "state", kind: "enum", T: proto3.getEnumType(StreamState) }]); const StreamStateUpdate = /* @__PURE__ */proto3.makeMessageType("livekit.StreamStateUpdate", () => [{ no: 1, name: "stream_states", kind: "message", T: StreamStateInfo, repeated: true }]); const SubscribedQuality = /* @__PURE__ */proto3.makeMessageType("livekit.SubscribedQuality", () => [{ no: 1, name: "quality", kind: "enum", T: proto3.getEnumType(VideoQuality$1) }, { no: 2, name: "enabled", kind: "scalar", T: 8 /* ScalarType.BOOL */ }]); const SubscribedCodec = /* @__PURE__ */proto3.makeMessageType("livekit.SubscribedCodec", () => [{ no: 1, name: "codec", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "qualities", kind: "message", T: SubscribedQuality, repeated: true }]); const SubscribedQualityUpdate = /* @__PURE__ */proto3.makeMessageType("livekit.SubscribedQualityUpdate", () => [{ no: 1, name: "track_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "subscribed_qualities", kind: "message", T: SubscribedQuality, repeated: true }, { no: 3, name: "subscribed_codecs", kind: "message", T: SubscribedCodec, repeated: true }]); const SubscribedAudioCodecUpdate = /* @__PURE__ */proto3.makeMessageType("livekit.SubscribedAudioCodecUpdate", () => [{ no: 1, name: "track_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "subscribed_audio_codecs", kind: "message", T: SubscribedAudioCodec, repeated: true }]); const TrackPermission = /* @__PURE__ */proto3.makeMessageType("livekit.TrackPermission", () => [{ no: 1, name: "participant_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "all_tracks", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 3, name: "track_sids", kind: "scalar", T: 9, repeated: true }, { no: 4, name: "participant_identity", kind: "scalar", T: 9 /* ScalarType.STRING */ }]); const SubscriptionPermission = /* @__PURE__ */proto3.makeMessageType("livekit.SubscriptionPermission", () => [{ no: 1, name: "all_participants", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 2, name: "track_permissions", kind: "message", T: TrackPermission, repeated: true }]); const SubscriptionPermissionUpdate = /* @__PURE__ */proto3.makeMessageType("livekit.SubscriptionPermissionUpdate", () => [{ no: 1, name: "participant_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "track_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "allowed", kind: "scalar", T: 8 /* ScalarType.BOOL */ }]); const RoomMovedResponse = /* @__PURE__ */proto3.makeMessageType("livekit.RoomMovedResponse", () => [{ no: 1, name: "room", kind: "message", T: Room$1 }, { no: 2, name: "token", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "participant", kind: "message", T: ParticipantInfo }, { no: 4, name: "other_participants", kind: "message", T: ParticipantInfo, repeated: true }]); const SyncState = /* @__PURE__ */proto3.makeMessageType("livekit.SyncState", () => [{ no: 1, name: "answer", kind: "message", T: SessionDescription }, { no: 2, name: "subscription", kind: "message", T: UpdateSubscription }, { no: 3, name: "publish_tracks", kind: "message", T: TrackPublishedResponse, repeated: true }, { no: 4, name: "data_channels", kind: "message", T: DataChannelInfo, repeated: true }, { no: 5, name: "offer", kind: "message", T: SessionDescription }, { no: 6, name: "track_sids_disabled", kind: "scalar", T: 9, repeated: true }, { no: 7, name: "datachannel_receive_states", kind: "message", T: DataChannelReceiveState, repeated: true }, { no: 8, name: "publish_data_tracks", kind: "message", T: PublishDataTrackResponse, repeated: true }]); const DataChannelReceiveState = /* @__PURE__ */proto3.makeMessageType("livekit.DataChannelReceiveState", () => [{ no: 1, name: "publisher_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "last_seq", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }]); const DataChannelInfo = /* @__PURE__ */proto3.makeMessageType("livekit.DataChannelInfo", () => [{ no: 1, name: "label", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "id", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 3, name: "target", kind: "enum", T: proto3.getEnumType(SignalTarget) }]); const SimulateScenario = /* @__PURE__ */proto3.makeMessageType("livekit.SimulateScenario", () => [{ no: 1, name: "speaker_update", kind: "scalar", T: 5, oneof: "scenario" }, { no: 2, name: "node_failure", kind: "scalar", T: 8, oneof: "scenario" }, { no: 3, name: "migration", kind: "scalar", T: 8, oneof: "scenario" }, { no: 4, name: "server_leave", kind: "scalar", T: 8, oneof: "scenario" }, { no: 5, name: "switch_candidate_protocol", kind: "enum", T: proto3.getEnumType(CandidateProtocol), oneof: "scenario" }, { no: 6, name: "subscriber_bandwidth", kind: "scalar", T: 3, oneof: "scenario" }, { no: 7, name: "disconnect_signal_on_resume", kind: "scalar", T: 8, oneof: "scenario" }, { no: 8, name: "disconnect_signal_on_resume_no_messages", kind: "scalar", T: 8, oneof: "scenario" }, { no: 9, name: "leave_request_full_reconnect", kind: "scalar", T: 8, oneof: "scenario" }]); const Ping = /* @__PURE__ */proto3.makeMessageType("livekit.Ping", () => [{ no: 1, name: "timestamp", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, { no: 2, name: "rtt", kind: "scalar", T: 3 /* ScalarType.INT64 */ }]); const Pong = /* @__PURE__ */proto3.makeMessageType("livekit.Pong", () => [{ no: 1, name: "last_ping_timestamp", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, { no: 2, name: "timestamp", kind: "scalar", T: 3 /* ScalarType.INT64 */ }]); const RegionSettings = /* @__PURE__ */proto3.makeMessageType("livekit.RegionSettings", () => [{ no: 1, name: "regions", kind: "message", T: RegionInfo, repeated: true }]); const RegionInfo = /* @__PURE__ */proto3.makeMessageType("livekit.RegionInfo", () => [{ no: 1, name: "region", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "url", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "distance", kind: "scalar", T: 3 /* ScalarType.INT64 */ }]); const SubscriptionResponse = /* @__PURE__ */proto3.makeMessageType("livekit.SubscriptionResponse", () => [{ no: 1, name: "track_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "err", kind: "enum", T: proto3.getEnumType(SubscriptionError) }]); const RequestResponse = /* @__PURE__ */proto3.makeMessageType("livekit.RequestResponse", () => [{ no: 1, name: "request_id", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 2, name: "reason", kind: "enum", T: proto3.getEnumType(RequestResponse_Reason) }, { no: 3, name: "message", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 4, name: "trickle", kind: "message", T: TrickleRequest, oneof: "request" }, { no: 5, name: "add_track", kind: "message", T: AddTrackRequest, oneof: "request" }, { no: 6, name: "mute", kind: "message", T: MuteTrackRequest, oneof: "request" }, { no: 7, name: "update_metadata", kind: "message", T: UpdateParticipantMetadata, oneof: "request" }, { no: 8, name: "update_audio_track", kind: "message", T: UpdateLocalAudioTrack, oneof: "request" }, { no: 9, name: "update_video_track", kind: "message", T: UpdateLocalVideoTrack, oneof: "request" }, { no: 10, name: "publish_data_track", kind: "message", T: PublishDataTrackRequest, oneof: "request" }, { no: 11, name: "unpublish_data_track", kind: "message", T: UnpublishDataTrackRequest, oneof: "request" }]); const RequestResponse_Reason = /* @__PURE__ */proto3.makeEnum("livekit.RequestResponse.Reason", [{ no: 0, name: "OK" }, { no: 1, name: "NOT_FOUND" }, { no: 2, name: "NOT_ALLOWED" }, { no: 3, name: "LIMIT_EXCEEDED" }, { no: 4, name: "QUEUED" }, { no: 5, name: "UNSUPPORTED_TYPE" }, { no: 6, name: "UNCLASSIFIED_ERROR" }, { no: 7, name: "INVALID_HANDLE" }, { no: 8, name: "INVALID_NAME" }, { no: 9, name: "DUPLICATE_HANDLE" }, { no: 10, name: "DUPLICATE_NAME" }]); const TrackSubscribed = /* @__PURE__ */proto3.makeMessageType("livekit.TrackSubscribed", () => [{ no: 1, name: "track_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }]); const ConnectionSettings = /* @__PURE__ */proto3.makeMessageType("livekit.ConnectionSettings", () => [{ no: 1, name: "auto_subscribe", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 2, name: "adaptive_stream", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 3, name: "subscriber_allow_pause", kind: "scalar", T: 8, opt: true }, { no: 4, name: "disable_ice_lite", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 5, name: "auto_subscribe_data_track", kind: "scalar", T: 8, opt: true }]); const JoinRequest = /* @__PURE__ */proto3.makeMessageType("livekit.JoinRequest", () => [{ no: 1, name: "client_info", kind: "message", T: ClientInfo }, { no: 2, name: "connection_settings", kind: "message", T: ConnectionSettings }, { no: 3, name: "metadata", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 4, name: "participant_attributes", kind: "map", K: 9, V: { kind: "scalar", T: 9 /* ScalarType.STRING */ } }, { no: 5, name: "add_track_requests", kind: "message", T: AddTrackRequest, repeated: true }, { no: 6, name: "publisher_offer", kind: "message", T: SessionDescription }, { no: 7, name: "reconnect", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 8, name: "reconnect_reason", kind: "enum", T: proto3.getEnumType(ReconnectReason) }, { no: 9, name: "participant_sid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 10, name: "sync_state", kind: "message", T: SyncState }]); const WrappedJoinRequest = /* @__PURE__ */proto3.makeMessageType("livekit.WrappedJoinRequest", () => [{ no: 1, name: "compression", kind: "enum", T: proto3.getEnumType(WrappedJoinRequest_Compression) }, { no: 2, name: "join_request", kind: "scalar", T: 12 /* ScalarType.BYTES */ }]); const WrappedJoinRequest_Compression = /* @__PURE__ */proto3.makeEnum("livekit.WrappedJoinRequest.Compression", [{ no: 0, name: "NONE" }, { no: 1, name: "GZIP" }]); const MediaSectionsRequirement = /* @__PURE__ */proto3.makeMessageType("livekit.MediaSectionsRequirement", () => [{ no: 1, name: "num_audios", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, { no: 2, name: "num_videos", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }]); const TokenSourceRequest = /* @__PURE__ */proto3.makeMessageType("livekit.TokenSourceRequest", () => [{ no: 1, name: "room_name", kind: "scalar", T: 9, opt: true }, { no: 2, name: "participant_name", kind: "scalar", T: 9, opt: true }, { no: 3, name: "participant_identity", kind: "scalar", T: 9, opt: true }, { no: 4, name: "participant_metadata", kind: "scalar", T: 9, opt: true }, { no: 5, name: "participant_attributes", kind: "map", K: 9, V: { kind: "scalar", T: 9 /* ScalarType.STRING */ } }, { no: 6, name: "room_config", kind: "message", T: RoomConfiguration, opt: true }]); const TokenSourceResponse = /* @__PURE__ */proto3.makeMessageType("livekit.TokenSourceResponse", () => [{ no: 1, name: "server_url", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "participant_token", kind: "scalar", T: 9 /* ScalarType.STRING */ }]);function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; }var loglevel$1 = {exports: {}};/* * loglevel - https://github.com/pimterry/loglevel * * Copyright (c) 2013 Tim Perry * Licensed under the MIT license. */ var loglevel = loglevel$1.exports; var hasRequiredLoglevel; function requireLoglevel() { if (hasRequiredLoglevel) return loglevel$1.exports; hasRequiredLoglevel = 1; (function (module) { (function (root, definition) { if (module.exports) { module.exports = definition(); } else { root.log = definition(); } })(loglevel, function () { // Slightly dubious tricks to cut down minimized file size var noop = function () {}; var undefinedType = "undefined"; var isIE = typeof window !== undefinedType && typeof window.navigator !== undefinedType && /Trident\/|MSIE /.test(window.navigator.userAgent); var logMethods = ["trace", "debug", "info", "warn", "error"]; var _loggersByName = {}; var defaultLogger = null; // Cross-browser bind equivalent that works at least back to IE6 function bindMethod(obj, methodName) { var method = obj[methodName]; if (typeof method.bind === 'function') { return method.bind(obj); } else { try { return Function.prototype.bind.call(method, obj); } catch (e) { // Missing bind shim or IE8 + Modernizr, fallback to wrapping return function () { return Function.prototype.apply.apply(method, [obj, arguments]); }; } } } // Trace() doesn't print the message in IE, so for that case we need to wrap it function traceForIE() { if (console.log) { if (console.log.apply) { console.log.apply(console, arguments); } else { // In old IE, native console methods themselves don't have apply(). Function.prototype.apply.apply(console.log, [console, arguments]); } } if (console.trace) console.trace(); } // Build the best logging method possible for this env // Wherever possible we want to bind, not wrap, to preserve stack traces function realMethod(methodName) { if (methodName === 'debug') { methodName = 'log'; } if (typeof console === undefinedType) { return false; // No method possible, for now - fixed later by enableLoggingWhenConsoleArrives } else if (methodName === 'trace' && isIE) { return traceForIE; } else if (console[methodName] !== undefined) { return bindMethod(console, methodName); } else if (console.log !== undefined) { return bindMethod(console, 'log'); } else { return noop; } } // These private functions always need `this` to be set properly function replaceLoggingMethods() { /*jshint validthis:true */ var level = this.getLevel(); // Replace the actual methods. for (var i = 0; i < logMethods.length; i++) { var methodName = logMethods[i]; this[methodName] = i < level ? noop : this.methodFactory(methodName, level, this.name); } // Define log.log as an alias for log.debug this.log = this.debug; // Return any important warnings. if (typeof console === undefinedType && level < this.levels.SILENT) { return "No console available for logging"; } } // In old IE versions, the console isn't present until you first open it. // We build realMethod() replacements here that regenerate logging methods function enableLoggingWhenConsoleArrives(methodName) { return function () { if (typeof console !== undefinedType) { replaceLoggingMethods.call(this); this[methodName].apply(this, arguments); } }; } // By default, we use closely bound real methods wherever possible, and // otherwise we wait for a console to appear, and then try again. function defaultMethodFactory(methodName, _level, _loggerName) { /*jshint validthis:true */ return realMethod(methodName) || enableLoggingWhenConsoleArrives.apply(this, arguments); } function Logger(name, factory) { // Private instance variables. var self = this; /** * The level inherited from a parent logger (or a global default). We * cache this here rather than delegating to the parent so that it stays * in sync with the actual logging methods that we have installed (the * parent could change levels but we might not have rebuilt the loggers * in this child yet). * @type {number} */ var inheritedLevel; /** * The default level for this logger, if any. If set, this overrides * `inheritedLevel`. * @type {number|null} */ var defaultLevel; /** * A user-specific level for this logger. If set, this overrides * `defaultLevel`. * @type {number|null} */ var userLevel; var storageKey = "loglevel"; if (typeof name === "string") { storageKey += ":" + name; } else if (typeof name === "symbol") { storageKey = undefined; } function persistLevelIfPossible(levelNum) { var levelName = (logMethods[levelNum] || 'silent').toUpperCase(); if (typeof window === undefinedType || !storageKey) return; // Use localStorage if available try { window.localStorage[storageKey] = levelName; return; } catch (ignore) {} // Use session cookie as fallback try { window.document.cookie = encodeURIComponent(storageKey) + "=" + levelName + ";"; } catch (ignore) {} } function getPersistedLevel() { var storedLevel; if (typeof window === undefinedType || !storageKey) return; try { storedLevel = window.localStorage[storageKey]; } catch (ignore) {} // Fallback to cookies if local storage gives us nothing if (typeof storedLevel === undefinedType) { try { var cookie = window.document.cookie; var cookieName = encodeURIComponent(storageKey); var location = cookie.indexOf(cookieName + "="); if (location !== -1) { storedLevel = /^([^;]+)/.exec(cookie.slice(location + cookieName.length + 1))[1]; } } catch (ignore) {} } // If the stored level is not valid, treat it as if nothing was stored. if (self.levels[storedLevel] === undefined) { storedLevel = undefined; } return storedLevel; } function clearPersistedLevel() { if (typeof window === undefinedType || !storageKey) return; // Use localStorage if available try { window.localStorage.removeItem(storageKey); } catch (ignore) {} // Use session cookie as fallback try { window.document.cookie = encodeURIComponent(storageKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC"; } catch (ignore) {} } function normalizeLevel(input) { var level = input; if (typeof level === "string" && self.levels[level.toUpperCase()] !== undefined) { level = self.levels[level.toUpperCase()]; } if (typeof level === "number" && level >= 0 && level <= self.levels.SILENT) { return level; } else { throw new TypeError("log.setLevel() called with invalid level: " + input); } } /* * * Public logger API - see https://github.com/pimterry/loglevel for details * */ self.name = name; self.levels = { "TRACE": 0, "DEBUG": 1, "INFO": 2, "WARN": 3, "ERROR": 4, "SILENT": 5 }; self.methodFactory = factory || defaultMethodFactory; self.getLevel = function () { if (userLevel != null) { return userLevel; } else if (defaultLevel != null) { return defaultLevel; } else { return inheritedLevel; } }; self.setLevel = function (level, persist) { userLevel = normalizeLevel(level); if (persist !== false) { // defaults to true persistLevelIfPossible(userLevel); } // NOTE: in v2, this should call rebuild(), which updates children. return replaceLoggingMethods.call(self); }; self.setDefaultLevel = function (level) { defaultLevel = normalizeLevel(level); if (!getPersistedLevel()) { self.setLevel(level, false); } }; self.resetLevel = function () { userLevel = null; clearPersistedLevel(); replaceLoggingMethods.call(self); }; self.enableAll = function (persist) { self.setLevel(self.levels.TRACE, persist); }; self.disableAll = function (persist) { self.setLevel(self.levels.SILENT, persist); }; self.rebuild = function () { if (defaultLogger !== self) { inheritedLevel = normalizeLevel(defaultLogger.getLevel()); } replaceLoggingMethods.call(self); if (defaultLogger === self) { for (var childName in _loggersByName) { _loggersByName[childName].rebuild(); } } }; // Initialize all the internal levels. inheritedLevel = normalizeLevel(defaultLogger ? defaultLogger.getLevel() : "WARN"); var initialLevel = getPersistedLevel(); if (initialLevel != null) { userLevel = normalizeLevel(initialLevel); } replaceLoggingMethods.call(self); } /* * * Top-level API * */ defaultLogger = new Logger(); defaultLogger.getLogger = function getLogger(name) { if (typeof name !== "symbol" && typeof name !== "string" || name === "") { throw new TypeError("You must supply a name when creating a logger."); } var logger = _loggersByName[name]; if (!logger) { logger = _loggersByName[name] = new Logger(name, defaultLogger.methodFactory); } return logger; }; // Grab the current global log variable in case of overwrite var _log = typeof window !== undefinedType ? window.log : undefined; defaultLogger.noConflict = function () { if (typeof window !== undefinedType && window.log === defaultLogger) { window.log = _log; } return defaultLogger; }; defaultLogger.getLoggers = function getLoggers() { return _loggersByName; }; // ES6 default export, for compatibility defaultLogger['default'] = defaultLogger; return defaultLogger; }); })(loglevel$1); return loglevel$1.exports; }var loglevelExports = requireLoglevel();var LogLevel; (function (LogLevel) { LogLevel[LogLevel["trace"] = 0] = "trace"; LogLevel[LogLevel["debug"] = 1] = "debug"; LogLevel[LogLevel["info"] = 2] = "info"; LogLevel[LogLevel["warn"] = 3] = "warn"; LogLevel[LogLevel["error"] = 4] = "error"; LogLevel[LogLevel["silent"] = 5] = "silent"; })(LogLevel || (LogLevel = {})); var LoggerNames; (function (LoggerNames) { LoggerNames["Default"] = "livekit"; LoggerNames["Room"] = "livekit-room"; LoggerNames["TokenSource"] = "livekit-token-source"; LoggerNames["Participant"] = "livekit-participant"; LoggerNames["Track"] = "livekit-track"; LoggerNames["Publication"] = "livekit-track-publication"; LoggerNames["Engine"] = "livekit-engine"; LoggerNames["Signal"] = "livekit-signal"; LoggerNames["PCManager"] = "livekit-pc-manager"; LoggerNames["PCTransport"] = "livekit-pc-transport"; LoggerNames["E2EE"] = "lk-e2ee"; LoggerNames["DataTracks"] = "livekit-data-tracks"; })(LoggerNames || (LoggerNames = {})); let livekitLogger = loglevelExports.getLogger(LoggerNames.Default); const livekitLoggers = Object.values(LoggerNames).map(name => loglevelExports.getLogger(name)); livekitLogger.setDefaultLevel(LogLevel.info); /** * @internal * * Get a named logger. When `ctxFn` is supplied, every log call * automatically: * 1. prepends a `[key=value ...]` prefix derived from `ctxFn()` to the * message string, so identifiers are visible in browser devtools * without expanding the structured context object, and * 2. merges `ctxFn()` into the structured context passed to any * `setLogExtension` consumer, so ingestion pipelines continue to * receive the full metadata unchanged. */ function getLogger(name, ctxFn) { const logger = loglevelExports.getLogger(name); logger.setDefaultLevel(livekitLogger.getLevel()); if (!ctxFn) { return logger; } return wrapWithContext(logger, ctxFn); } function wrapWithContext(base, ctxFn) { // Resolve the underlying method on every call so that later // setLogExtension installations (which replace the base logger's // methods via loglevel's methodFactory) are picked up. const wrap = method => (msg, extra) => { const ctx = ctxFn(); const merged = ctx || extra ? Object.assign(Object.assign({}, ctx), extra) : undefined; base[method](msg, merged); }; const proxy = Object.create(base); proxy.trace = wrap('trace'); proxy.debug = wrap('debug'); proxy.info = wrap('info'); proxy.warn = wrap('warn'); proxy.error = wrap('error'); return proxy; } function setLogLevel(level, loggerName) { if (loggerName) { loglevelExports.getLogger(loggerName).setLevel(level); } else { for (const logger of livekitLoggers) { logger.setLevel(level); } } } /** * use this to hook into the logging function to allow sending internal livekit logs to third party services * if set, the browser logs will lose their stacktrace information (see https://github.com/pimterry/loglevel#writing-plugins) */ function setLogExtension(extension, logger) { const loggers = logger ? [logger] : livekitLoggers; loggers.forEach(logR => { const originalFactory = logR.methodFactory; logR.methodFactory = (methodName, configLevel, loggerName) => { const rawMethod = originalFactory(methodName, configLevel, loggerName); const logLevel = LogLevel[methodName]; const needLog = logLevel >= configLevel && logLevel < LogLevel.silent; return (msg, context) => { if (context) rawMethod(msg, context);else rawMethod(msg); if (needLog) { extension(logLevel, msg, context); } }; }; logR.setLevel(logR.getLevel()); }); } const workerLogger = loglevelExports.getLogger(LoggerNames.E2EE);const maxRetryDelay = 7000; const DEFAULT_RETRY_DELAYS_IN_MS = [0, 300, 2 * 2 * 300, 3 * 3 * 300, 4 * 4 * 300, maxRetryDelay, maxRetryDelay, maxRetryDelay, maxRetryDelay, maxRetryDelay]; class DefaultReconnectPolicy { constructor(retryDelays) { this._retryDelays = retryDelays !== undefined ? [...retryDelays] : DEFAULT_RETRY_DELAYS_IN_MS; } nextRetryDelayInMs(context) { if (context.retryCount >= this._retryDelays.length) return null; const retryDelay = this._retryDelays[context.retryCount]; if (context.retryCount <= 1) return retryDelay; return retryDelay + Math.random() * 1000; } }/****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ function __rest(s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; } function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __values(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); } function __await(v) { return this instanceof __await ? (this.v = v, this) : new __await(v); } function __asyncGenerator(thisArg, _arguments, generator) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var g = generator.apply(thisArg, _arguments || []), i, q = []; return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i; function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; } function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } } function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } function fulfill(value) { resume("next", value); } function reject(value) { resume("throw", value); } function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } } function __asyncDelegator(o) { var i, p; return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; } } function __asyncValues(o) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var m = o[Symbol.asyncIterator], i; return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; };var events = {exports: {}};var hasRequiredEvents; function requireEvents() { if (hasRequiredEvents) return events.exports; hasRequiredEvents = 1; var R = typeof Reflect === 'object' ? Reflect : null; var ReflectApply = R && typeof R.apply === 'function' ? R.apply : function ReflectApply(target, receiver, args) { return Function.prototype.apply.call(target, receiver, args); }; var ReflectOwnKeys; if (R && typeof R.ownKeys === 'function') { ReflectOwnKeys = R.ownKeys; } else if (Object.getOwnPropertySymbols) { ReflectOwnKeys = function ReflectOwnKeys(target) { return Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target)); }; } else { ReflectOwnKeys = function ReflectOwnKeys(target) { return Object.getOwnPropertyNames(target); }; } function ProcessEmitWarning(warning) { if (console && console.warn) console.warn(warning); } var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) { return value !== value; }; function EventEmitter() { EventEmitter.init.call(this); } events.exports = EventEmitter; events.exports.once = once; // Backwards-compat with node 0.10.x EventEmitter.EventEmitter = EventEmitter; EventEmitter.prototype._events = undefined; EventEmitter.prototype._eventsCount = 0; EventEmitter.prototype._maxListeners = undefined; // By default EventEmitters will print a warning if more than 10 listeners are // added to it. This is a useful default which helps finding memory leaks. var defaultMaxListeners = 10; function checkListener(listener) { if (typeof listener !== 'function') { throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener); } } Object.defineProperty(EventEmitter, 'defaultMaxListeners', { enumerable: true, get: function () { return defaultMaxListeners; }, set: function (arg) { if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) { throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.'); } defaultMaxListeners = arg; } }); EventEmitter.init = function () { if (this._events === undefined || this._events === Object.getPrototypeOf(this)._events) { this._events = Object.create(null); this._eventsCount = 0; } this._maxListeners = this._maxListeners || undefined; }; // Obviously not all Emitters should be limited to 10. This function allows // that to be increased. Set to zero for unlimited. EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) { throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.'); } this._maxListeners = n; return this; }; function _getMaxListeners(that) { if (that._maxListeners === undefined) return EventEmitter.defaultMaxListeners; return that._maxListeners; } EventEmitter.prototype.getMaxListeners = function getMaxListeners() { return _getMaxListeners(this); }; EventEmitter.prototype.emit = function emit(type) { var args = []; for (var i = 1; i < arguments.length; i++) args.push(arguments[i]); var doError = type === 'error'; var events = this._events; if (events !== undefined) doError = doError && events.error === undefined;else if (!doError) return false; // If there is no 'error' event listener then throw. if (doError) { var er; if (args.length > 0) er = args[0]; if (er instanceof Error) { // Note: The comments on the `throw` lines are intentional, they show // up in Node's output if this results in an unhandled exception. throw er; // Unhandled 'error' event } // At least give some kind of context to the user var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : '')); err.context = er; throw err; // Unhandled 'error' event } var handler = events[type]; if (handler === undefined) return false; if (typeof handler === 'function') { ReflectApply(handler, this, args); } else { var len = handler.length; var listeners = arrayClone(handler, len); for (var i = 0; i < len; ++i) ReflectApply(listeners[i], this, args); } return true; }; function _addListener(target, type, listener, prepend) { var m; var events; var existing; checkListener(listener); events = target._events; if (events === undefined) { events = target._events = Object.create(null); target._eventsCount = 0; } else { // To avoid recursion in the case that type === "newListener"! Before // adding it to the listeners, first emit "newListener". if (events.newListener !== undefined) { target.emit('newListener', type, listener.listener ? listener.listener : listener); // Re-assign `events` because a newListener handler could have caused the // this._events to be assigned to a new object events = target._events; } existing = events[type]; } if (existing === undefined) { // Optimize the case of one listener. Don't need the extra array object. existing = events[type] = listener; ++target._eventsCount; } else { if (typeof existing === 'function') { // Adding the second element, need to change to array. existing = events[type] = prepend ? [listener, existing] : [existing, listener]; // If we've already got an array, just append. } else if (prepend) { existing.unshift(listener); } else { existing.push(listener); } // Check for listener leak m = _getMaxListeners(target); if (m > 0 && existing.length > m && !existing.warned) { existing.warned = true; // No error code for this since it is a Warning // eslint-disable-next-line no-restricted-syntax var w = new Error('Possible EventEmitter memory leak detected. ' + existing.length + ' ' + String(type) + ' listeners ' + 'added. Use emitter.setMaxListeners() to ' + 'increase limit'); w.name = 'MaxListenersExceededWarning'; w.emitter = target; w.type = type; w.count = existing.length; ProcessEmitWarning(w); } } return target; } EventEmitter.prototype.addListener = function addListener(type, listener) { return _addListener(this, type, listener, false); }; EventEmitter.prototype.on = EventEmitter.prototype.addListener; EventEmitter.prototype.prependListener = function prependListener(type, listener) { return _addListener(this, type, listener, true); }; function onceWrapper() { if (!this.fired) { this.target.removeListener(this.type, this.wrapFn); this.fired = true; if (arguments.length === 0) return this.listener.call(this.target); return this.listener.apply(this.target, arguments); } } function _onceWrap(target, type, listener) { var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener }; var wrapped = onceWrapper.bind(state); wrapped.listener = listener; state.wrapFn = wrapped; return wrapped; } EventEmitter.prototype.once = function once(type, listener) { checkListener(listener); this.on(type, _onceWrap(this, type, listener)); return this; }; EventEmitter.prototype.prependOnceListener = function prependOnceListener(type, listener) { checkListener(listener); this.prependListener(type, _onceWrap(this, type, listener)); return this; }; // Emits a 'removeListener' event if and only if the listener was removed. EventEmitter.prototype.removeListener = function removeListener(type, listener) { var list, events, position, i, originalListener; checkListener(listener); events = this._events; if (events === undefined) return this; list = events[type]; if (list === undefined) return this; if (list === listener || list.listener === listener) { if (--this._eventsCount === 0) this._events = Object.create(null);else { delete events[type]; if (events.removeListener) this.emit('removeListener', type, list.listener || listener); } } else if (typeof list !== 'function') { position = -1; for (i = list.length - 1; i >= 0; i--) { if (list[i] === listener || list[i].listener === listener) { originalListener = list[i].listener; position = i; break; } } if (position < 0) return this; if (position === 0) list.shift();else { spliceOne(list, position); } if (list.length === 1) events[type] = list[0]; if (events.removeListener !== undefined) this.emit('removeListener', type, originalListener || listener); } return this; }; EventEmitter.prototype.off = EventEmitter.prototype.removeListener; EventEmitter.prototype.removeAllListeners = function removeAllListeners(type) { var listeners, events, i; events = this._events; if (events === undefined) return this; // not listening for removeListener, no need to emit if (events.removeListener === undefined) { if (arguments.length === 0) { this._events = Object.create(null); this._eventsCount = 0; } else if (events[type] !== undefined) { if (--this._eventsCount === 0) this._events = Object.create(null);else delete events[type]; } return this; } // emit removeListener for all listeners on all events if (arguments.length === 0) { var keys = Object.keys(events); var key; for (i = 0; i < keys.length; ++i) { key = keys[i]; if (key === 'removeListener') continue; this.removeAllListeners(key); } this.removeAllListeners('removeListener'); this._events = Object.create(null); this._eventsCount = 0; return this; } listeners = events[type]; if (typeof listeners === 'function') { this.removeListener(type, listeners); } else if (listeners !== undefined) { // LIFO order for (i = listeners.length - 1; i >= 0; i--) { this.removeListener(type, listeners[i]); } } return this; }; function _listeners(target, type, unwrap) { var events = target._events; if (events === undefined) return []; var evlistener = events[type]; if (evlistener === undefined) return []; if (typeof evlistener === 'function') return unwrap ? [evlistener.listener || evlistener] : [evlistener]; return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length); } EventEmitter.prototype.listeners = function listeners(type) { return _listeners(this, type, true); }; EventEmitter.prototype.rawListeners = function rawListeners(type) { return _listeners(this, type, false); }; EventEmitter.listenerCount = function (emitter, type) { if (typeof emitter.listenerCount === 'function') { return emitter.listenerCount(type); } else { return listenerCount.call(emitter, type); } }; EventEmitter.prototype.listenerCount = listenerCount; function listenerCount(type) { var events = this._events; if (events !== undefined) { var evlistener = events[type]; if (typeof evlistener === 'function') { return 1; } else if (evlistener !== undefined) { return evlistener.length; } } return 0; } EventEmitter.prototype.eventNames = function eventNames() { return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : []; }; function arrayClone(arr, n) { var copy = new Array(n); for (var i = 0; i < n; ++i) copy[i] = arr[i]; return copy; } function spliceOne(list, index) { for (; index + 1 < list.length; index++) list[index] = list[index + 1]; list.pop(); } function unwrapListeners(arr) { var ret = new Array(arr.length); for (var i = 0; i < ret.length; ++i) { ret[i] = arr[i].listener || arr[i]; } return ret; } function once(emitter, name) { return new Promise(function (resolve, reject) { function errorListener(err) { emitter.removeListener(name, resolver); reject(err); } function resolver() { if (typeof emitter.removeListener === 'function') { emitter.removeListener('error', errorListener); } resolve([].slice.call(arguments)); } eventTargetAgnosticAddListener(emitter, name, resolver, { once: true }); if (name !== 'error') { addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true }); } }); } function addErrorHandlerIfEventEmitter(emitter, handler, flags) { if (typeof emitter.on === 'function') { eventTargetAgnosticAddListener(emitter, 'error', handler, flags); } } function eventTargetAgnosticAddListener(emitter, name, listener, flags) { if (typeof emitter.on === 'function') { if (flags.once) { emitter.once(name, listener); } else { emitter.on(name, listener); } } else if (typeof emitter.addEventListener === 'function') { // EventTarget does not have `error` event semantics like Node // EventEmitters, we do not listen for `error` events here. emitter.addEventListener(name, function wrapListener(arg) { // IE does not have builtin `{ once: true }` support so we // have to do it manually. if (flags.once) { emitter.removeEventListener(name, wrapListener); } listener(arg); }); } else { throw new TypeError('The "emitter" argument must be of type EventEmitter. Received type ' + typeof emitter); } } return events.exports; }var eventsExports = requireEvents();/* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ /* eslint-env node */ let logDisabled_ = true; let deprecationWarnings_ = true; /** * Extract browser version out of the provided user agent string. * * @param {!string} uastring userAgent string. * @param {!string} expr Regular expression used as match criteria. * @param {!number} pos position in the version string to be returned. * @return {!number} browser version. */ function extractVersion(uastring, expr, pos) { const match = uastring.match(expr); return match && match.length >= pos && parseFloat(match[pos], 10); } // Wraps the peerconnection event eventNameToWrap in a function // which returns the modified event object (or false to prevent // the event). function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) { if (!window.RTCPeerConnection) { return; } const addEventListener = Object.getOwnPropertyDescriptor(EventTarget.prototype, 'addEventListener'); if (!addEventListener.writable) { log$4('Unable to polyfill events'); return; } const proto = window.RTCPeerConnection.prototype; const nativeAddEventListener = proto.addEventListener; proto.addEventListener = function (nativeEventName, cb) { if (nativeEventName !== eventNameToWrap) { return nativeAddEventListener.apply(this, arguments); } const wrappedCallback = e => { const modifiedEvent = wrapper(e); if (modifiedEvent) { if (cb.handleEvent) { cb.handleEvent(modifiedEvent); } else { cb(modifiedEvent); } } }; this._eventMap = this._eventMap || {}; if (!this._eventMap[eventNameToWrap]) { this._eventMap[eventNameToWrap] = new Map(); } this._eventMap[eventNameToWrap].set(cb, wrappedCallback); return nativeAddEventListener.apply(this, [nativeEventName, wrappedCallback]); }; const nativeRemoveEventListener = proto.removeEventListener; proto.removeEventListener = function (nativeEventName, cb) { if (nativeEventName !== eventNameToWrap || !this._eventMap || !this._eventMap[eventNameToWrap]) { return nativeRemoveEventListener.apply(this, arguments); } if (!this._eventMap[eventNameToWrap].has(cb)) { return nativeRemoveEventListener.apply(this, arguments); } const unwrappedCb = this._eventMap[eventNameToWrap].get(cb); this._eventMap[eventNameToWrap].delete(cb); if (this._eventMap[eventNameToWrap].size === 0) { delete this._eventMap[eventNameToWrap]; } if (Object.keys(this._eventMap).length === 0) { delete this._eventMap; } return nativeRemoveEventListener.apply(this, [nativeEventName, unwrappedCb]); }; Object.defineProperty(proto, 'on' + eventNameToWrap, { get() { return this['_on' + eventNameToWrap]; }, set(cb) { if (this['_on' + eventNameToWrap]) { this.removeEventListener(eventNameToWrap, this['_on' + eventNameToWrap]); delete this['_on' + eventNameToWrap]; } if (cb) { this.addEventListener(eventNameToWrap, this['_on' + eventNameToWrap] = cb); } }, enumerable: true, configurable: true }); } function disableLog(bool) { if (typeof bool !== 'boolean') { return new Error('Argument type: ' + typeof bool + '. Please use a boolean.'); } logDisabled_ = bool; return bool ? 'adapter.js logging disabled' : 'adapter.js logging enabled'; } /** * Disable or enable deprecation warnings * @param {!boolean} bool set to true to disable warnings. */ function disableWarnings(bool) { if (typeof bool !== 'boolean') { return new Error('Argument type: ' + typeof bool + '. Please use a boolean.'); } deprecationWarnings_ = !bool; return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled'); } function log$4() { if (typeof window === 'object') { if (logDisabled_) { return; } if (typeof console !== 'undefined' && typeof console.log === 'function') { console.log.apply(console, arguments); } } } /** * Shows a deprecation warning suggesting the modern and spec-compatible API. */ function deprecated(oldMethod, newMethod) { if (!deprecationWarnings_) { return; } console.warn(oldMethod + ' is deprecated, please use ' + newMethod + ' instead.'); } /** * Browser detector. * * @return {object} result containing browser and version * properties. */ function detectBrowser(window) { // Returned result object. const result = { browser: null, version: null }; // Fail early if it's not a browser if (typeof window === 'undefined' || !window.navigator || !window.navigator.userAgent) { result.browser = 'Not a browser.'; return result; } const navigator = window.navigator; // Prefer navigator.userAgentData. if (navigator.userAgentData && navigator.userAgentData.brands) { const chromium = navigator.userAgentData.brands.find(brand => { return brand.brand === 'Chromium'; }); if (chromium) { return { browser: 'chrome', version: parseInt(chromium.version, 10) }; } } if (navigator.mozGetUserMedia) { // Firefox. result.browser = 'firefox'; result.version = parseInt(extractVersion(navigator.userAgent, /Firefox\/(\d+)\./, 1)); } else if (navigator.webkitGetUserMedia || window.isSecureContext === false && window.webkitRTCPeerConnection) { // Chrome, Chromium, Webview, Opera. // Version matches Chrome/WebRTC version. // Chrome 74 removed webkitGetUserMedia on http as well so we need the // more complicated fallback to webkitRTCPeerConnection. result.browser = 'chrome'; result.version = parseInt(extractVersion(navigator.userAgent, /Chrom(e|ium)\/(\d+)\./, 2)) || null; } else if (window.RTCPeerConnection && navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { // Safari. result.browser = 'safari'; result.version = parseInt(extractVersion(navigator.userAgent, /AppleWebKit\/(\d+)\./, 1)); result.supportsUnifiedPlan = window.RTCRtpTransceiver && 'currentDirection' in window.RTCRtpTransceiver.prototype; // Only for internal usage. result._safariVersion = extractVersion(navigator.userAgent, /Version\/(\d+(\.?\d+))/, 1); } else { // Default fallthrough: not supported. result.browser = 'Not a supported browser.'; return result; } return result; } /** * Checks if something is an object. * * @param {*} val The something you want to check. * @return true if val is an object, false otherwise. */ function isObject$1(val) { return Object.prototype.toString.call(val) === '[object Object]'; } /** * Remove all empty objects and undefined values * from a nested object -- an enhanced and vanilla version * of Lodash's `compact`. */ function compactObject(data) { if (!isObject$1(data)) { return data; } return Object.keys(data).reduce(function (accumulator, key) { const isObj = isObject$1(data[key]); const value = isObj ? compactObject(data[key]) : data[key]; const isEmptyObject = isObj && !Object.keys(value).length; if (value === undefined || isEmptyObject) { return accumulator; } return Object.assign(accumulator, { [key]: value }); }, {}); } /* iterates the stats graph recursively. */ function walkStats(stats, base, resultSet) { if (!base || resultSet.has(base.id)) { return; } resultSet.set(base.id, base); Object.keys(base).forEach(name => { if (name.endsWith('Id')) { walkStats(stats, stats.get(base[name]), resultSet); } else if (name.endsWith('Ids')) { base[name].forEach(id => { walkStats(stats, stats.get(id), resultSet); }); } }); } /* filter getStats for a sender/receiver track. */ function filterStats(result, track, outbound) { const streamStatsType = outbound ? 'outbound-rtp' : 'inbound-rtp'; const filteredResult = new Map(); if (track === null) { return filteredResult; } const trackStats = []; result.forEach(value => { if (value.type === 'track' && value.trackIdentifier === track.id) { trackStats.push(value); } }); trackStats.forEach(trackStat => { result.forEach(stats => { if (stats.type === streamStatsType && stats.trackId === trackStat.id) { walkStats(result, stats, filteredResult); } }); }); return filteredResult; }/* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ /* eslint-env node */ const logging = log$4; function shimGetUserMedia$2(window, browserDetails) { const navigator = window && window.navigator; if (!navigator.mediaDevices) { return; } const constraintsToChrome_ = function (c) { if (typeof c !== 'object' || c.mandatory || c.optional) { return c; } const cc = {}; Object.keys(c).forEach(key => { if (key === 'require' || key === 'advanced' || key === 'mediaSource') { return; } const r = typeof c[key] === 'object' ? c[key] : { ideal: c[key] }; if (r.exact !== undefined && typeof r.exact === 'number') { r.min = r.max = r.exact; } const oldname_ = function (prefix, name) { if (prefix) { return prefix + name.charAt(0).toUpperCase() + name.slice(1); } return name === 'deviceId' ? 'sourceId' : name; }; if (r.ideal !== undefined) { cc.optional = cc.optional || []; let oc = {}; if (typeof r.ideal === 'number') { oc[oldname_('min', key)] = r.ideal; cc.optional.push(oc); oc = {}; oc[oldname_('max', key)] = r.ideal; cc.optional.push(oc); } else { oc[oldname_('', key)] = r.ideal; cc.optional.push(oc); } } if (r.exact !== undefined && typeof r.exact !== 'number') { cc.mandatory = cc.mandatory || {}; cc.mandatory[oldname_('', key)] = r.exact; } else { ['min', 'max'].forEach(mix => { if (r[mix] !== undefined) { cc.mandatory = cc.mandatory || {}; cc.mandatory[oldname_(mix, key)] = r[mix]; } }); } }); if (c.advanced) { cc.optional = (cc.optional || []).concat(c.advanced); } return cc; }; const shimConstraints_ = function (constraints, func) { if (browserDetails.version >= 61) { return func(constraints); } constraints = JSON.parse(JSON.stringify(constraints)); if (constraints && typeof constraints.audio === 'object') { const remap = function (obj, a, b) { if (a in obj && !(b in obj)) { obj[b] = obj[a]; delete obj[a]; } }; constraints = JSON.parse(JSON.stringify(constraints)); remap(constraints.audio, 'autoGainControl', 'googAutoGainControl'); remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression'); constraints.audio = constraintsToChrome_(constraints.audio); } if (constraints && typeof constraints.video === 'object') { // Shim facingMode for mobile & surface pro. let face = constraints.video.facingMode; face = face && (typeof face === 'object' ? face : { ideal: face }); const getSupportedFacingModeLies = browserDetails.version < 66; if (face && (face.exact === 'user' || face.exact === 'environment' || face.ideal === 'user' || face.ideal === 'environment') && !(navigator.mediaDevices.getSupportedConstraints && navigator.mediaDevices.getSupportedConstraints().facingMode && !getSupportedFacingModeLies)) { delete constraints.video.facingMode; let matches; if (face.exact === 'environment' || face.ideal === 'environment') { matches = ['back', 'rear']; } else if (face.exact === 'user' || face.ideal === 'user') { matches = ['front']; } if (matches) { // Look for matches in label, or use last cam for back (typical). return navigator.mediaDevices.enumerateDevices().then(devices => { devices = devices.filter(d => d.kind === 'videoinput'); let dev = devices.find(d => matches.some(match => d.label.toLowerCase().includes(match))); if (!dev && devices.length && matches.includes('back')) { dev = devices[devices.length - 1]; // more likely the back cam } if (dev) { constraints.video.deviceId = face.exact ? { exact: dev.deviceId } : { ideal: dev.deviceId }; } constraints.video = constraintsToChrome_(constraints.video); logging('chrome: ' + JSON.stringify(constraints)); return func(constraints); }); } } constraints.video = constraintsToChrome_(constraints.video); } logging('chrome: ' + JSON.stringify(constraints)); return func(constraints); }; const shimError_ = function (e) { if (browserDetails.version >= 64) { return e; } return { name: { PermissionDeniedError: 'NotAllowedError', PermissionDismissedError: 'NotAllowedError', InvalidStateError: 'NotAllowedError', DevicesNotFoundError: 'NotFoundError', ConstraintNotSatisfiedError: 'OverconstrainedError', TrackStartError: 'NotReadableError', MediaDeviceFailedDueToShutdown: 'NotAllowedError', MediaDeviceKillSwitchOn: 'NotAllowedError', TabCaptureError: 'AbortError', ScreenCaptureError: 'AbortError', DeviceCaptureError: 'AbortError' }[e.name] || e.name, message: e.message, constraint: e.constraint || e.constraintName, toString() { return this.name + (this.message && ': ') + this.message; } }; }; const getUserMedia_ = function (constraints, onSuccess, onError) { shimConstraints_(constraints, c => { navigator.webkitGetUserMedia(c, onSuccess, e => { if (onError) { onError(shimError_(e)); } }); }); }; navigator.getUserMedia = getUserMedia_.bind(navigator); // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia // function which returns a Promise, it does not accept spec-style // constraints. if (navigator.mediaDevices.getUserMedia) { const origGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices); navigator.mediaDevices.getUserMedia = function (cs) { return shimConstraints_(cs, c => origGetUserMedia(c).then(stream => { if (c.audio && !stream.getAudioTracks().length || c.video && !stream.getVideoTracks().length) { stream.getTracks().forEach(track => { track.stop(); }); throw new DOMException('', 'NotFoundError'); } return stream; }, e => Promise.reject(shimError_(e)))); }; } }/* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ /* eslint-env node */ function shimMediaStream(window) { window.MediaStream = window.MediaStream || window.webkitMediaStream; } function shimOnTrack$1(window, browserDetails) { if (browserDetails.version > 102) { // Unified plan is supported so no need to do anything. return; } if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in window.RTCPeerConnection.prototype)) { Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { get() { return this._ontrack; }, set(f) { if (this._ontrack) { this.removeEventListener('track', this._ontrack); } this.addEventListener('track', this._ontrack = f); }, enumerable: true, configurable: true }); const origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() { if (!this._ontrackpoly) { this._ontrackpoly = e => { // onaddstream does not fire when a track is added to an existing // stream. But stream.onaddtrack is implemented so we use that. e.stream.addEventListener('addtrack', te => { let receiver; if (window.RTCPeerConnection.prototype.getReceivers) { receiver = this.getReceivers().find(r => r.track && r.track.id === te.track.id); } else { receiver = { track: te.track }; } const event = new Event('track'); event.track = te.track; event.receiver = receiver; event.transceiver = { receiver }; event.streams = [e.stream]; this.dispatchEvent(event); }); e.stream.getTracks().forEach(track => { let receiver; if (window.RTCPeerConnection.prototype.getReceivers) { receiver = this.getReceivers().find(r => r.track && r.track.id === track.id); } else { receiver = { track }; } const event = new Event('track'); event.track = track; event.receiver = receiver; event.transceiver = { receiver }; event.streams = [e.stream]; this.dispatchEvent(event); }); }; this.addEventListener('addstream', this._ontrackpoly); } return origSetRemoteDescription.apply(this, arguments); }; } else { // even if RTCRtpTransceiver is in window, it is only used and // emitted in unified-plan. Unfortunately this means we need // to unconditionally wrap the event. wrapPeerConnectionEvent(window, 'track', e => { if (!e.transceiver) { Object.defineProperty(e, 'transceiver', { value: { receiver: e.receiver } }); } return e; }); } } function shimGetSendersWithDtmf(window) { // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack. if (typeof window === 'object' && window.RTCPeerConnection && !('getSenders' in window.RTCPeerConnection.prototype) && 'createDTMFSender' in window.RTCPeerConnection.prototype) { const shimSenderWithDtmf = function (pc, track) { return { track, get dtmf() { if (this._dtmf === undefined) { if (track.kind === 'audio') { this._dtmf = pc.createDTMFSender(track); } else { this._dtmf = null; } } return this._dtmf; }, _pc: pc }; }; // augment addTrack when getSenders is not available. if (!window.RTCPeerConnection.prototype.getSenders) { window.RTCPeerConnection.prototype.getSenders = function getSenders() { this._senders = this._senders || []; return this._senders.slice(); // return a copy of the internal state. }; const origAddTrack = window.RTCPeerConnection.prototype.addTrack; window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) { let sender = origAddTrack.apply(this, arguments); if (!sender) { sender = shimSenderWithDtmf(this, track); this._senders.push(sender); } return sender; }; const origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { origRemoveTrack.apply(this, arguments); const idx = this._senders.indexOf(sender); if (idx !== -1) { this._senders.splice(idx, 1); } }; } const origAddStream = window.RTCPeerConnection.prototype.addStream; window.RTCPeerConnection.prototype.addStream = function addStream(stream) { this._senders = this._senders || []; origAddStream.apply(this, [stream]); stream.getTracks().forEach(track => { this._senders.push(shimSenderWithDtmf(this, track)); }); }; const origRemoveStream = window.RTCPeerConnection.prototype.removeStream; window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { this._senders = this._senders || []; origRemoveStream.apply(this, [stream]); stream.getTracks().forEach(track => { const sender = this._senders.find(s => s.track === track); if (sender) { // remove sender this._senders.splice(this._senders.indexOf(sender), 1); } }); }; } else if (typeof window === 'object' && window.RTCPeerConnection && 'getSenders' in window.RTCPeerConnection.prototype && 'createDTMFSender' in window.RTCPeerConnection.prototype && window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) { const origGetSenders = window.RTCPeerConnection.prototype.getSenders; window.RTCPeerConnection.prototype.getSenders = function getSenders() { const senders = origGetSenders.apply(this, []); senders.forEach(sender => sender._pc = this); return senders; }; Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { get() { if (this._dtmf === undefined) { if (this.track.kind === 'audio') { this._dtmf = this._pc.createDTMFSender(this.track); } else { this._dtmf = null; } } return this._dtmf; } }); } } function shimSenderReceiverGetStats(window, browserDetails) { if (browserDetails.version >= 67) { return; } if (!(typeof window === 'object' && window.RTCPeerConnection && window.RTCRtpSender && window.RTCRtpReceiver)) { return; } // shim sender stats. if (!('getStats' in window.RTCRtpSender.prototype)) { const origGetSenders = window.RTCPeerConnection.prototype.getSenders; if (origGetSenders) { window.RTCPeerConnection.prototype.getSenders = function getSenders() { const senders = origGetSenders.apply(this, []); senders.forEach(sender => sender._pc = this); return senders; }; } const origAddTrack = window.RTCPeerConnection.prototype.addTrack; if (origAddTrack) { window.RTCPeerConnection.prototype.addTrack = function addTrack() { const sender = origAddTrack.apply(this, arguments); sender._pc = this; return sender; }; } window.RTCRtpSender.prototype.getStats = function getStats() { const sender = this; return this._pc.getStats().then(result => /* Note: this will include stats of all senders that * send a track with the same id as sender.track as * it is not possible to identify the RTCRtpSender. */ filterStats(result, sender.track, true)); }; } // shim receiver stats. if (!('getStats' in window.RTCRtpReceiver.prototype)) { const origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; if (origGetReceivers) { window.RTCPeerConnection.prototype.getReceivers = function getReceivers() { const receivers = origGetReceivers.apply(this, []); receivers.forEach(receiver => receiver._pc = this); return receivers; }; } wrapPeerConnectionEvent(window, 'track', e => { e.receiver._pc = e.srcElement; return e; }); window.RTCRtpReceiver.prototype.getStats = function getStats() { const receiver = this; return this._pc.getStats().then(result => filterStats(result, receiver.track, false)); }; } if (!('getStats' in window.RTCRtpSender.prototype && 'getStats' in window.RTCRtpReceiver.prototype)) { return; } // shim RTCPeerConnection.getStats(track). const origGetStats = window.RTCPeerConnection.prototype.getStats; window.RTCPeerConnection.prototype.getStats = function getStats() { if (arguments.length > 0 && arguments[0] instanceof window.MediaStreamTrack) { const track = arguments[0]; let sender; let receiver; let err; this.getSenders().forEach(s => { if (s.track === track) { if (sender) { err = true; } else { sender = s; } } }); this.getReceivers().forEach(r => { if (r.track === track) { if (receiver) { err = true; } else { receiver = r; } } return r.track === track; }); if (err || sender && receiver) { return Promise.reject(new DOMException('There are more than one sender or receiver for the track.', 'InvalidAccessError')); } else if (sender) { return sender.getStats(); } else if (receiver) { return receiver.getStats(); } return Promise.reject(new DOMException('There is no sender or receiver for the track.', 'InvalidAccessError')); } return origGetStats.apply(this, arguments); }; } function shimAddTrackRemoveTrackWithNative(window) { // shim addTrack/removeTrack with native variants in order to make // the interactions with legacy getLocalStreams behave as in other browsers. // Keeps a mapping stream.id => [stream, rtpsenders...] window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() { this._shimmedLocalStreams = this._shimmedLocalStreams || {}; return Object.keys(this._shimmedLocalStreams).map(streamId => this._shimmedLocalStreams[streamId][0]); }; const origAddTrack = window.RTCPeerConnection.prototype.addTrack; window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) { if (!stream) { return origAddTrack.apply(this, arguments); } this._shimmedLocalStreams = this._shimmedLocalStreams || {}; const sender = origAddTrack.apply(this, arguments); if (!this._shimmedLocalStreams[stream.id]) { this._shimmedLocalStreams[stream.id] = [stream, sender]; } else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) { this._shimmedLocalStreams[stream.id].push(sender); } return sender; }; const origAddStream = window.RTCPeerConnection.prototype.addStream; window.RTCPeerConnection.prototype.addStream = function addStream(stream) { this._shimmedLocalStreams = this._shimmedLocalStreams || {}; stream.getTracks().forEach(track => { const alreadyExists = this.getSenders().find(s => s.track === track); if (alreadyExists) { throw new DOMException('Track already exists.', 'InvalidAccessError'); } }); const existingSenders = this.getSenders(); origAddStream.apply(this, arguments); const newSenders = this.getSenders().filter(newSender => existingSenders.indexOf(newSender) === -1); this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders); }; const origRemoveStream = window.RTCPeerConnection.prototype.removeStream; window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { this._shimmedLocalStreams = this._shimmedLocalStreams || {}; delete this._shimmedLocalStreams[stream.id]; return origRemoveStream.apply(this, arguments); }; const origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { this._shimmedLocalStreams = this._shimmedLocalStreams || {}; if (sender) { Object.keys(this._shimmedLocalStreams).forEach(streamId => { const idx = this._shimmedLocalStreams[streamId].indexOf(sender); if (idx !== -1) { this._shimmedLocalStreams[streamId].splice(idx, 1); } if (this._shimmedLocalStreams[streamId].length === 1) { delete this._shimmedLocalStreams[streamId]; } }); } return origRemoveTrack.apply(this, arguments); }; } function shimAddTrackRemoveTrack(window, browserDetails) { if (!window.RTCPeerConnection) { return; } // shim addTrack and removeTrack. if (window.RTCPeerConnection.prototype.addTrack && browserDetails.version >= 65) { return shimAddTrackRemoveTrackWithNative(window); } // also shim pc.getLocalStreams when addTrack is shimmed // to return the original streams. const origGetLocalStreams = window.RTCPeerConnection.prototype.getLocalStreams; window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() { const nativeStreams = origGetLocalStreams.apply(this); this._reverseStreams = this._reverseStreams || {}; return nativeStreams.map(stream => this._reverseStreams[stream.id]); }; const origAddStream = window.RTCPeerConnection.prototype.addStream; window.RTCPeerConnection.prototype.addStream = function addStream(stream) { this._streams = this._streams || {}; this._reverseStreams = this._reverseStreams || {}; stream.getTracks().forEach(track => { const alreadyExists = this.getSenders().find(s => s.track === track); if (alreadyExists) { throw new DOMException('Track already exists.', 'InvalidAccessError'); } }); // Add identity mapping for consistency with addTrack. // Unless this is being used with a stream from addTrack. if (!this._reverseStreams[stream.id]) { const newStream = new window.MediaStream(stream.getTracks()); this._streams[stream.id] = newStream; this._reverseStreams[newStream.id] = stream; stream = newStream; } origAddStream.apply(this, [stream]); }; const origRemoveStream = window.RTCPeerConnection.prototype.removeStream; window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { this._streams = this._streams || {}; this._reverseStreams = this._reverseStreams || {}; origRemoveStream.apply(this, [this._streams[stream.id] || stream]); delete this._reverseStreams[this._streams[stream.id] ? this._streams[stream.id].id : stream.id]; delete this._streams[stream.id]; }; window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) { if (this.signalingState === 'closed') { throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.', 'InvalidStateError'); } const streams = [].slice.call(arguments, 1); if (streams.length !== 1 || !streams[0].getTracks().find(t => t === track)) { // this is not fully correct but all we can manage without // [[associated MediaStreams]] internal slot. throw new DOMException('The adapter.js addTrack polyfill only supports a single ' + ' stream which is associated with the specified track.', 'NotSupportedError'); } const alreadyExists = this.getSenders().find(s => s.track === track); if (alreadyExists) { throw new DOMException('Track already exists.', 'InvalidAccessError'); } this._streams = this._streams || {}; this._reverseStreams = this._reverseStreams || {}; const oldStream = this._streams[stream.id]; if (oldStream) { // this is using odd Chrome behaviour, use with caution: // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815 // Note: we rely on the high-level addTrack/dtmf shim to // create the sender with a dtmf sender. oldStream.addTrack(track); // Trigger ONN async. Promise.resolve().then(() => { this.dispatchEvent(new Event('negotiationneeded')); }); } else { const newStream = new window.MediaStream([track]); this._streams[stream.id] = newStream; this._reverseStreams[newStream.id] = stream; this.addStream(newStream); } return this.getSenders().find(s => s.track === track); }; // replace the internal stream id with the external one and // vice versa. function replaceInternalStreamId(pc, description) { let sdp = description.sdp; Object.keys(pc._reverseStreams || []).forEach(internalId => { const externalStream = pc._reverseStreams[internalId]; const internalStream = pc._streams[externalStream.id]; sdp = sdp.replace(new RegExp(internalStream.id, 'g'), externalStream.id); }); return new RTCSessionDescription({ type: description.type, sdp }); } function replaceExternalStreamId(pc, description) { let sdp = description.sdp; Object.keys(pc._reverseStreams || []).forEach(internalId => { const externalStream = pc._reverseStreams[internalId]; const internalStream = pc._streams[externalStream.id]; sdp = sdp.replace(new RegExp(externalStream.id, 'g'), internalStream.id); }); return new RTCSessionDescription({ type: description.type, sdp }); } ['createOffer', 'createAnswer'].forEach(function (method) { const nativeMethod = window.RTCPeerConnection.prototype[method]; const methodObj = { [method]() { const args = arguments; const isLegacyCall = arguments.length && typeof arguments[0] === 'function'; if (isLegacyCall) { return nativeMethod.apply(this, [description => { const desc = replaceInternalStreamId(this, description); args[0].apply(null, [desc]); }, err => { if (args[1]) { args[1].apply(null, err); } }, arguments[2]]); } return nativeMethod.apply(this, arguments).then(description => replaceInternalStreamId(this, description)); } }; window.RTCPeerConnection.prototype[method] = methodObj[method]; }); const origSetLocalDescription = window.RTCPeerConnection.prototype.setLocalDescription; window.RTCPeerConnection.prototype.setLocalDescription = function setLocalDescription() { if (!arguments.length || !arguments[0].type) { return origSetLocalDescription.apply(this, arguments); } arguments[0] = replaceExternalStreamId(this, arguments[0]); return origSetLocalDescription.apply(this, arguments); }; // TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier const origLocalDescription = Object.getOwnPropertyDescriptor(window.RTCPeerConnection.prototype, 'localDescription'); Object.defineProperty(window.RTCPeerConnection.prototype, 'localDescription', { get() { const description = origLocalDescription.get.apply(this); if (description.type === '') { return description; } return replaceInternalStreamId(this, description); } }); window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { if (this.signalingState === 'closed') { throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.', 'InvalidStateError'); } // We can not yet check for sender instanceof RTCRtpSender // since we shim RTPSender. So we check if sender._pc is set. if (!sender._pc) { throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' + 'does not implement interface RTCRtpSender.', 'TypeError'); } const isLocal = sender._pc === this; if (!isLocal) { throw new DOMException('Sender was not created by this connection.', 'InvalidAccessError'); } // Search for the native stream the senders track belongs to. this._streams = this._streams || {}; let stream; Object.keys(this._streams).forEach(streamid => { const hasTrack = this._streams[streamid].getTracks().find(track => sender.track === track); if (hasTrack) { stream = this._streams[streamid]; } }); if (stream) { if (stream.getTracks().length === 1) { // if this is the last track of the stream, remove the stream. This // takes care of any shimmed _senders. this.removeStream(this._reverseStreams[stream.id]); } else { // relying on the same odd chrome behaviour as above. stream.removeTrack(sender.track); } this.dispatchEvent(new Event('negotiationneeded')); } }; } function shimPeerConnection$1(window, browserDetails) { if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) { // very basic support for old versions. window.RTCPeerConnection = window.webkitRTCPeerConnection; } if (!window.RTCPeerConnection) { return; } // shim implicit creation of RTCSessionDescription/RTCIceCandidate if (browserDetails.version < 53) { ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'].forEach(function (method) { const nativeMethod = window.RTCPeerConnection.prototype[method]; const methodObj = { [method]() { arguments[0] = new (method === 'addIceCandidate' ? window.RTCIceCandidate : window.RTCSessionDescription)(arguments[0]); return nativeMethod.apply(this, arguments); } }; window.RTCPeerConnection.prototype[method] = methodObj[method]; }); } } // Attempt to fix ONN in plan-b mode. function fixNegotiationNeeded(window, browserDetails) { if (browserDetails.version > 102) { // Plan-B is no longer supported. return; } wrapPeerConnectionEvent(window, 'negotiationneeded', e => { const pc = e.target; if (browserDetails.version < 72 || pc.getConfiguration && pc.getConfiguration().sdpSemantics === 'plan-b') { if (pc.signalingState !== 'stable') { return; } } return e; }); }var chromeShim=/*#__PURE__*/Object.freeze({__proto__:null,fixNegotiationNeeded:fixNegotiationNeeded,shimAddTrackRemoveTrack:shimAddTrackRemoveTrack,shimAddTrackRemoveTrackWithNative:shimAddTrackRemoveTrackWithNative,shimGetSendersWithDtmf:shimGetSendersWithDtmf,shimGetUserMedia:shimGetUserMedia$2,shimMediaStream:shimMediaStream,shimOnTrack:shimOnTrack$1,shimPeerConnection:shimPeerConnection$1,shimSenderReceiverGetStats:shimSenderReceiverGetStats});/* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ /* eslint-env node */ function shimGetUserMedia$1(window, browserDetails) { const navigator = window && window.navigator; const MediaStreamTrack = window && window.MediaStreamTrack; navigator.getUserMedia = function (constraints, onSuccess, onError) { // Replace Firefox 44+'s deprecation warning with unprefixed version. deprecated('navigator.getUserMedia', 'navigator.mediaDevices.getUserMedia'); navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError); }; if (!(browserDetails.version > 55 && 'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) { const remap = function (obj, a, b) { if (a in obj && !(b in obj)) { obj[b] = obj[a]; delete obj[a]; } }; const nativeGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices); navigator.mediaDevices.getUserMedia = function (c) { if (typeof c === 'object' && typeof c.audio === 'object') { c = JSON.parse(JSON.stringify(c)); remap(c.audio, 'autoGainControl', 'mozAutoGainControl'); remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression'); } return nativeGetUserMedia(c); }; if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) { const nativeGetSettings = MediaStreamTrack.prototype.getSettings; MediaStreamTrack.prototype.getSettings = function () { const obj = nativeGetSettings.apply(this, arguments); remap(obj, 'mozAutoGainControl', 'autoGainControl'); remap(obj, 'mozNoiseSuppression', 'noiseSuppression'); return obj; }; } if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) { const nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints; MediaStreamTrack.prototype.applyConstraints = function (c) { if (this.kind === 'audio' && typeof c === 'object') { c = JSON.parse(JSON.stringify(c)); remap(c, 'autoGainControl', 'mozAutoGainControl'); remap(c, 'noiseSuppression', 'mozNoiseSuppression'); } return nativeApplyConstraints.apply(this, [c]); }; } } }/* * Copyright (c) 2018 The adapter.js project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ /* eslint-env node */ function shimGetDisplayMedia(window, preferredMediaSource) { if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) { return; } if (!window.navigator.mediaDevices) { return; } window.navigator.mediaDevices.getDisplayMedia = function getDisplayMedia(constraints) { if (!(constraints && constraints.video)) { const err = new DOMException('getDisplayMedia without video ' + 'constraints is undefined'); err.name = 'NotFoundError'; // from https://heycam.github.io/webidl/#idl-DOMException-error-names err.code = 8; return Promise.reject(err); } if (constraints.video === true) { constraints.video = { mediaSource: preferredMediaSource }; } else { constraints.video.mediaSource = preferredMediaSource; } return window.navigator.mediaDevices.getUserMedia(constraints); }; }/* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ /* eslint-env node */ function shimOnTrack(window) { if (typeof window === 'object' && window.RTCTrackEvent && 'receiver' in window.RTCTrackEvent.prototype && !('transceiver' in window.RTCTrackEvent.prototype)) { Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { get() { return { receiver: this.receiver }; } }); } } function shimPeerConnection(window, browserDetails) { if (typeof window !== 'object' || !(window.RTCPeerConnection || window.mozRTCPeerConnection)) { return; // probably media.peerconnection.enabled=false in about:config } if (!window.RTCPeerConnection && window.mozRTCPeerConnection) { // very basic support for old versions. window.RTCPeerConnection = window.mozRTCPeerConnection; } if (browserDetails.version < 53) { // shim away need for obsolete RTCIceCandidate/RTCSessionDescription. ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'].forEach(function (method) { const nativeMethod = window.RTCPeerConnection.prototype[method]; const methodObj = { [method]() { arguments[0] = new (method === 'addIceCandidate' ? window.RTCIceCandidate : window.RTCSessionDescription)(arguments[0]); return nativeMethod.apply(this, arguments); } }; window.RTCPeerConnection.prototype[method] = methodObj[method]; }); } } function shimGetStats(window, browserDetails) { if (typeof window !== 'object' || !(window.RTCPeerConnection || window.mozRTCPeerConnection)) { return; // probably media.peerconnection.enabled=false in about:config } if (browserDetails.version >= 151) { // https://bugzilla.mozilla.org/show_bug.cgi?id=1056433 return; } const modernStatsTypes = { inboundrtp: 'inbound-rtp', outboundrtp: 'outbound-rtp', candidatepair: 'candidate-pair', localcandidate: 'local-candidate', remotecandidate: 'remote-candidate' }; const nativeGetStats = window.RTCPeerConnection.prototype.getStats; window.RTCPeerConnection.prototype.getStats = function getStats() { const _arguments = Array.prototype.slice.call(arguments), selector = _arguments[0], onSucc = _arguments[1], onErr = _arguments[2]; if (this.signalingState === 'closed') { // No longer required in FF151+ return Promise.resolve(new Map()); } return nativeGetStats.apply(this, [selector || null]).then(stats => { if (browserDetails.version < 53 && !onSucc) { // Shim only promise getStats with spec-hyphens in type names // Leave callback version alone; misc old uses of forEach before Map try { stats.forEach(stat => { stat.type = modernStatsTypes[stat.type] || stat.type; }); } catch (e) { if (e.name !== 'TypeError') { throw e; } // Avoid TypeError: "type" is read-only, in old versions. 34-43ish stats.forEach((stat, i) => { stats.set(i, Object.assign({}, stat, { type: modernStatsTypes[stat.type] || stat.type })); }); } } return stats; }).then(onSucc, onErr); }; } function shimSenderGetStats(window) { if (!(typeof window === 'object' && window.RTCPeerConnection && window.RTCRtpSender)) { return; } if (window.RTCRtpSender && 'getStats' in window.RTCRtpSender.prototype) { return; } const origGetSenders = window.RTCPeerConnection.prototype.getSenders; if (origGetSenders) { window.RTCPeerConnection.prototype.getSenders = function getSenders() { const senders = origGetSenders.apply(this, []); senders.forEach(sender => sender._pc = this); return senders; }; } const origAddTrack = window.RTCPeerConnection.prototype.addTrack; if (origAddTrack) { window.RTCPeerConnection.prototype.addTrack = function addTrack() { const sender = origAddTrack.apply(this, arguments); sender._pc = this; return sender; }; } window.RTCRtpSender.prototype.getStats = function getStats() { return this.track ? this._pc.getStats(this.track) : Promise.resolve(new Map()); }; } function shimReceiverGetStats(window) { if (!(typeof window === 'object' && window.RTCPeerConnection && window.RTCRtpSender)) { return; } if (window.RTCRtpSender && 'getStats' in window.RTCRtpReceiver.prototype) { return; } const origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; if (origGetReceivers) { window.RTCPeerConnection.prototype.getReceivers = function getReceivers() { const receivers = origGetReceivers.apply(this, []); receivers.forEach(receiver => receiver._pc = this); return receivers; }; } wrapPeerConnectionEvent(window, 'track', e => { e.receiver._pc = e.srcElement; return e; }); window.RTCRtpReceiver.prototype.getStats = function getStats() { return this._pc.getStats(this.track); }; } function shimRemoveStream(window) { if (!window.RTCPeerConnection || 'removeStream' in window.RTCPeerConnection.prototype) { return; } window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { deprecated('removeStream', 'removeTrack'); this.getSenders().forEach(sender => { if (sender.track && stream.getTracks().includes(sender.track)) { this.removeTrack(sender); } }); }; } function shimRTCDataChannel(window) { // rename DataChannel to RTCDataChannel (native fix in FF60): // https://bugzilla.mozilla.org/show_bug.cgi?id=1173851 if (window.DataChannel && !window.RTCDataChannel) { window.RTCDataChannel = window.DataChannel; } } function shimAddTransceiver(window) { // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 // Firefox ignores the init sendEncodings options passed to addTransceiver // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 if (!(typeof window === 'object' && window.RTCPeerConnection)) { return; } const origAddTransceiver = window.RTCPeerConnection.prototype.addTransceiver; if (origAddTransceiver) { window.RTCPeerConnection.prototype.addTransceiver = function addTransceiver() { this.setParametersPromises = []; // WebIDL input coercion and validation let sendEncodings = arguments[1] && arguments[1].sendEncodings; if (sendEncodings === undefined) { sendEncodings = []; } sendEncodings = [...sendEncodings]; const shouldPerformCheck = sendEncodings.length > 0; if (shouldPerformCheck) { // If sendEncodings params are provided, validate grammar sendEncodings.forEach(encodingParam => { if ('rid' in encodingParam) { const ridRegex = /^[a-z0-9]{0,16}$/i; if (!ridRegex.test(encodingParam.rid)) { throw new TypeError('Invalid RID value provided.'); } } if ('scaleResolutionDownBy' in encodingParam) { if (!(parseFloat(encodingParam.scaleResolutionDownBy) >= 1.0)) { throw new RangeError('scale_resolution_down_by must be >= 1.0'); } } if ('maxFramerate' in encodingParam) { if (!(parseFloat(encodingParam.maxFramerate) >= 0)) { throw new RangeError('max_framerate must be >= 0.0'); } } }); } const transceiver = origAddTransceiver.apply(this, arguments); if (shouldPerformCheck) { // Check if the init options were applied. If not we do this in an // asynchronous way and save the promise reference in a global object. // This is an ugly hack, but at the same time is way more robust than // checking the sender parameters before and after the createOffer // Also note that after the createoffer we are not 100% sure that // the params were asynchronously applied so we might miss the // opportunity to recreate offer. const sender = transceiver.sender; const params = sender.getParameters(); if (!('encodings' in params) || // Avoid being fooled by patched getParameters() below. params.encodings.length === 1 && Object.keys(params.encodings[0]).length === 0) { params.encodings = sendEncodings; sender.sendEncodings = sendEncodings; this.setParametersPromises.push(sender.setParameters(params).then(() => { delete sender.sendEncodings; }).catch(() => { delete sender.sendEncodings; })); } } return transceiver; }; } } function shimGetParameters(window) { if (!(typeof window === 'object' && window.RTCRtpSender)) { return; } const origGetParameters = window.RTCRtpSender.prototype.getParameters; if (origGetParameters) { window.RTCRtpSender.prototype.getParameters = function getParameters() { const params = origGetParameters.apply(this, arguments); if (!('encodings' in params)) { params.encodings = [].concat(this.sendEncodings || [{}]); } return params; }; } } function shimCreateOffer(window) { // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 // Firefox ignores the init sendEncodings options passed to addTransceiver // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 if (!(typeof window === 'object' && window.RTCPeerConnection)) { return; } const origCreateOffer = window.RTCPeerConnection.prototype.createOffer; window.RTCPeerConnection.prototype.createOffer = function createOffer() { if (this.setParametersPromises && this.setParametersPromises.length) { return Promise.all(this.setParametersPromises).then(() => { return origCreateOffer.apply(this, arguments); }).finally(() => { this.setParametersPromises = []; }); } return origCreateOffer.apply(this, arguments); }; } function shimCreateAnswer(window) { // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 // Firefox ignores the init sendEncodings options passed to addTransceiver // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 if (!(typeof window === 'object' && window.RTCPeerConnection)) { return; } const origCreateAnswer = window.RTCPeerConnection.prototype.createAnswer; window.RTCPeerConnection.prototype.createAnswer = function createAnswer() { if (this.setParametersPromises && this.setParametersPromises.length) { return Promise.all(this.setParametersPromises).then(() => { return origCreateAnswer.apply(this, arguments); }).finally(() => { this.setParametersPromises = []; }); } return origCreateAnswer.apply(this, arguments); }; }var firefoxShim=/*#__PURE__*/Object.freeze({__proto__:null,shimAddTransceiver:shimAddTransceiver,shimCreateAnswer:shimCreateAnswer,shimCreateOffer:shimCreateOffer,shimGetDisplayMedia:shimGetDisplayMedia,shimGetParameters:shimGetParameters,shimGetStats:shimGetStats,shimGetUserMedia:shimGetUserMedia$1,shimOnTrack:shimOnTrack,shimPeerConnection:shimPeerConnection,shimRTCDataChannel:shimRTCDataChannel,shimReceiverGetStats:shimReceiverGetStats,shimRemoveStream:shimRemoveStream,shimSenderGetStats:shimSenderGetStats});/* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ function shimLocalStreamsAPI(window) { if (typeof window !== 'object' || !window.RTCPeerConnection) { return; } if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) { window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() { if (!this._localStreams) { this._localStreams = []; } return this._localStreams; }; } if (!('addStream' in window.RTCPeerConnection.prototype)) { const _addTrack = window.RTCPeerConnection.prototype.addTrack; window.RTCPeerConnection.prototype.addStream = function addStream(stream) { if (!this._localStreams) { this._localStreams = []; } if (!this._localStreams.includes(stream)) { this._localStreams.push(stream); } // Try to emulate Chrome's behaviour of adding in audio-video order. // Safari orders by track id. stream.getAudioTracks().forEach(track => _addTrack.call(this, track, stream)); stream.getVideoTracks().forEach(track => _addTrack.call(this, track, stream)); }; window.RTCPeerConnection.prototype.addTrack = function addTrack(track) { for (var _len = arguments.length, streams = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { streams[_key - 1] = arguments[_key]; } if (streams) { streams.forEach(stream => { if (!this._localStreams) { this._localStreams = [stream]; } else if (!this._localStreams.includes(stream)) { this._localStreams.push(stream); } }); } return _addTrack.apply(this, arguments); }; } if (!('removeStream' in window.RTCPeerConnection.prototype)) { window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { if (!this._localStreams) { this._localStreams = []; } const index = this._localStreams.indexOf(stream); if (index === -1) { return; } this._localStreams.splice(index, 1); const tracks = stream.getTracks(); this.getSenders().forEach(sender => { if (tracks.includes(sender.track)) { this.removeTrack(sender); } }); }; } } function shimRemoteStreamsAPI(window) { if (typeof window !== 'object' || !window.RTCPeerConnection) { return; } if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) { window.RTCPeerConnection.prototype.getRemoteStreams = function getRemoteStreams() { return this._remoteStreams ? this._remoteStreams : []; }; } if (!('onaddstream' in window.RTCPeerConnection.prototype)) { Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', { get() { return this._onaddstream; }, set(f) { if (this._onaddstream) { this.removeEventListener('addstream', this._onaddstream); this.removeEventListener('track', this._onaddstreampoly); } this.addEventListener('addstream', this._onaddstream = f); this.addEventListener('track', this._onaddstreampoly = e => { e.streams.forEach(stream => { if (!this._remoteStreams) { this._remoteStreams = []; } if (this._remoteStreams.includes(stream)) { return; } this._remoteStreams.push(stream); const event = new Event('addstream'); event.stream = stream; this.dispatchEvent(event); }); }); } }); const origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() { const pc = this; if (!this._onaddstreampoly) { this.addEventListener('track', this._onaddstreampoly = function (e) { e.streams.forEach(stream => { if (!pc._remoteStreams) { pc._remoteStreams = []; } if (pc._remoteStreams.indexOf(stream) >= 0) { return; } pc._remoteStreams.push(stream); const event = new Event('addstream'); event.stream = stream; pc.dispatchEvent(event); }); }); } return origSetRemoteDescription.apply(pc, arguments); }; } } function shimCallbacksAPI(window) { if (typeof window !== 'object' || !window.RTCPeerConnection) { return; } const prototype = window.RTCPeerConnection.prototype; const origCreateOffer = prototype.createOffer; const origCreateAnswer = prototype.createAnswer; const setLocalDescription = prototype.setLocalDescription; const setRemoteDescription = prototype.setRemoteDescription; const addIceCandidate = prototype.addIceCandidate; prototype.createOffer = function createOffer(successCallback, failureCallback) { const options = arguments.length >= 2 ? arguments[2] : arguments[0]; const promise = origCreateOffer.apply(this, [options]); if (!failureCallback) { return promise; } promise.then(successCallback, failureCallback); return Promise.resolve(); }; prototype.createAnswer = function createAnswer(successCallback, failureCallback) { const options = arguments.length >= 2 ? arguments[2] : arguments[0]; const promise = origCreateAnswer.apply(this, [options]); if (!failureCallback) { return promise; } promise.then(successCallback, failureCallback); return Promise.resolve(); }; let withCallback = function (description, successCallback, failureCallback) { const promise = setLocalDescription.apply(this, [description]); if (!failureCallback) { return promise; } promise.then(successCallback, failureCallback); return Promise.resolve(); }; prototype.setLocalDescription = withCallback; withCallback = function (description, successCallback, failureCallback) { const promise = setRemoteDescription.apply(this, [description]); if (!failureCallback) { return promise; } promise.then(successCallback, failureCallback); return Promise.resolve(); }; prototype.setRemoteDescription = withCallback; withCallback = function (candidate, successCallback, failureCallback) { const promise = addIceCandidate.apply(this, [candidate]); if (!failureCallback) { return promise; } promise.then(successCallback, failureCallback); return Promise.resolve(); }; prototype.addIceCandidate = withCallback; } function shimGetUserMedia(window) { const navigator = window && window.navigator; if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { // shim not needed in Safari 12.1 const mediaDevices = navigator.mediaDevices; const _getUserMedia = mediaDevices.getUserMedia.bind(mediaDevices); navigator.mediaDevices.getUserMedia = constraints => { return _getUserMedia(shimConstraints(constraints)); }; } if (!navigator.getUserMedia && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { navigator.getUserMedia = function getUserMedia(constraints, cb, errcb) { navigator.mediaDevices.getUserMedia(constraints).then(cb, errcb); }.bind(navigator); } } function shimConstraints(constraints) { if (constraints && constraints.video !== undefined) { return Object.assign({}, constraints, { video: compactObject(constraints.video) }); } return constraints; } function shimRTCIceServerUrls(window) { if (!window.RTCPeerConnection) { return; } // migrate from non-spec RTCIceServer.url to RTCIceServer.urls const OrigPeerConnection = window.RTCPeerConnection; window.RTCPeerConnection = function RTCPeerConnection(pcConfig, pcConstraints) { if (pcConfig && pcConfig.iceServers) { const newIceServers = []; for (let i = 0; i < pcConfig.iceServers.length; i++) { let server = pcConfig.iceServers[i]; if (server.urls === undefined && server.url) { deprecated('RTCIceServer.url', 'RTCIceServer.urls'); server = JSON.parse(JSON.stringify(server)); server.urls = server.url; delete server.url; newIceServers.push(server); } else { newIceServers.push(pcConfig.iceServers[i]); } } pcConfig.iceServers = newIceServers; } return new OrigPeerConnection(pcConfig, pcConstraints); }; window.RTCPeerConnection.prototype = OrigPeerConnection.prototype; // wrap static methods. Currently just generateCertificate. if ('generateCertificate' in OrigPeerConnection) { Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { get() { return OrigPeerConnection.generateCertificate; } }); } } function shimTrackEventTransceiver(window) { // Add event.transceiver member over deprecated event.receiver if (typeof window === 'object' && window.RTCTrackEvent && 'receiver' in window.RTCTrackEvent.prototype && !('transceiver' in window.RTCTrackEvent.prototype)) { Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { get() { return { receiver: this.receiver }; } }); } } function shimCreateOfferLegacy(window) { const origCreateOffer = window.RTCPeerConnection.prototype.createOffer; window.RTCPeerConnection.prototype.createOffer = function createOffer(offerOptions) { if (offerOptions) { if (typeof offerOptions.offerToReceiveAudio !== 'undefined') { // support bit values offerOptions.offerToReceiveAudio = !!offerOptions.offerToReceiveAudio; } const audioTransceiver = this.getTransceivers().find(transceiver => transceiver.receiver.track.kind === 'audio'); if (offerOptions.offerToReceiveAudio === false && audioTransceiver) { if (audioTransceiver.direction === 'sendrecv') { if (audioTransceiver.setDirection) { audioTransceiver.setDirection('sendonly'); } else { audioTransceiver.direction = 'sendonly'; } } else if (audioTransceiver.direction === 'recvonly') { if (audioTransceiver.setDirection) { audioTransceiver.setDirection('inactive'); } else { audioTransceiver.direction = 'inactive'; } } } else if (offerOptions.offerToReceiveAudio === true && !audioTransceiver) { this.addTransceiver('audio', { direction: 'recvonly' }); } if (typeof offerOptions.offerToReceiveVideo !== 'undefined') { // support bit values offerOptions.offerToReceiveVideo = !!offerOptions.offerToReceiveVideo; } const videoTransceiver = this.getTransceivers().find(transceiver => transceiver.receiver.track.kind === 'video'); if (offerOptions.offerToReceiveVideo === false && videoTransceiver) { if (videoTransceiver.direction === 'sendrecv') { if (videoTransceiver.setDirection) { videoTransceiver.setDirection('sendonly'); } else { videoTransceiver.direction = 'sendonly'; } } else if (videoTransceiver.direction === 'recvonly') { if (videoTransceiver.setDirection) { videoTransceiver.setDirection('inactive'); } else { videoTransceiver.direction = 'inactive'; } } } else if (offerOptions.offerToReceiveVideo === true && !videoTransceiver) { this.addTransceiver('video', { direction: 'recvonly' }); } } return origCreateOffer.apply(this, arguments); }; } function shimAudioContext(window) { if (typeof window !== 'object' || window.AudioContext) { return; } window.AudioContext = window.webkitAudioContext; }var safariShim=/*#__PURE__*/Object.freeze({__proto__:null,shimAudioContext:shimAudioContext,shimCallbacksAPI:shimCallbacksAPI,shimConstraints:shimConstraints,shimCreateOfferLegacy:shimCreateOfferLegacy,shimGetUserMedia:shimGetUserMedia,shimLocalStreamsAPI:shimLocalStreamsAPI,shimRTCIceServerUrls:shimRTCIceServerUrls,shimRemoteStreamsAPI:shimRemoteStreamsAPI,shimTrackEventTransceiver:shimTrackEventTransceiver});var sdp$1 = {exports: {}};/* eslint-env node */ var hasRequiredSdp; function requireSdp() { if (hasRequiredSdp) return sdp$1.exports; hasRequiredSdp = 1; (function (module) { // SDP helpers. const SDPUtils = {}; // Generate an alphanumeric identifier for cname or mids. // TODO: use UUIDs instead? https://gist.github.com/jed/982883 SDPUtils.generateIdentifier = function () { return Math.random().toString(36).substring(2, 12); }; // The RTCP CNAME used by all peerconnections from the same JS. SDPUtils.localCName = SDPUtils.generateIdentifier(); // Splits SDP into lines, dealing with both CRLF and LF. SDPUtils.splitLines = function (blob) { return blob.trim().split('\n').map(line => line.trim()); }; // Splits SDP into sessionpart and mediasections. Ensures CRLF. SDPUtils.splitSections = function (blob) { const parts = blob.split('\nm='); return parts.map((part, index) => (index > 0 ? 'm=' + part : part).trim() + '\r\n'); }; // Returns the session description. SDPUtils.getDescription = function (blob) { const sections = SDPUtils.splitSections(blob); return sections && sections[0]; }; // Returns the individual media sections. SDPUtils.getMediaSections = function (blob) { const sections = SDPUtils.splitSections(blob); sections.shift(); return sections; }; // Returns lines that start with a certain prefix. SDPUtils.matchPrefix = function (blob, prefix) { return SDPUtils.splitLines(blob).filter(line => line.indexOf(prefix) === 0); }; // Parses an ICE candidate line. Sample input: // candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8 // rport 55996" // Input can be prefixed with a=. SDPUtils.parseCandidate = function (line) { let parts; // Parse both variants. if (line.indexOf('a=candidate:') === 0) { parts = line.substring(12).split(' '); } else { parts = line.substring(10).split(' '); } const candidate = { foundation: parts[0], component: { 1: 'rtp', 2: 'rtcp' }[parts[1]] || parts[1], protocol: parts[2].toLowerCase(), priority: parseInt(parts[3], 10), ip: parts[4], address: parts[4], // address is an alias for ip. port: parseInt(parts[5], 10), // skip parts[6] == 'typ' type: parts[7] }; for (let i = 8; i < parts.length; i += 2) { switch (parts[i]) { case 'raddr': candidate.relatedAddress = parts[i + 1]; break; case 'rport': candidate.relatedPort = parseInt(parts[i + 1], 10); break; case 'tcptype': candidate.tcpType = parts[i + 1]; break; case 'ufrag': candidate.ufrag = parts[i + 1]; // for backward compatibility. candidate.usernameFragment = parts[i + 1]; break; default: // extension handling, in particular ufrag. Don't overwrite. if (candidate[parts[i]] === undefined) { candidate[parts[i]] = parts[i + 1]; } break; } } return candidate; }; // Translates a candidate object into SDP candidate attribute. // This does not include the a= prefix! SDPUtils.writeCandidate = function (candidate) { const sdp = []; sdp.push(candidate.foundation); const component = candidate.component; if (component === 'rtp') { sdp.push(1); } else if (component === 'rtcp') { sdp.push(2); } else { sdp.push(component); } sdp.push(candidate.protocol.toUpperCase()); sdp.push(candidate.priority); sdp.push(candidate.address || candidate.ip); sdp.push(candidate.port); const type = candidate.type; sdp.push('typ'); sdp.push(type); if (type !== 'host' && candidate.relatedAddress && candidate.relatedPort !== undefined) { sdp.push('raddr'); sdp.push(candidate.relatedAddress); sdp.push('rport'); sdp.push(candidate.relatedPort); } if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') { sdp.push('tcptype'); sdp.push(candidate.tcpType); } if (candidate.usernameFragment || candidate.ufrag) { sdp.push('ufrag'); sdp.push(candidate.usernameFragment || candidate.ufrag); } return 'candidate:' + sdp.join(' '); }; // Parses an ice-options line, returns an array of option tags. // Sample input: // a=ice-options:foo bar SDPUtils.parseIceOptions = function (line) { return line.substring(14).split(' '); }; // Parses a rtpmap line, returns RTCRtpCoddecParameters. Sample input: // a=rtpmap:111 opus/48000/2 SDPUtils.parseRtpMap = function (line) { let parts = line.substring(9).split(' '); const parsed = { payloadType: parseInt(parts.shift(), 10) // was: id }; parts = parts[0].split('/'); parsed.name = parts[0]; parsed.clockRate = parseInt(parts[1], 10); // was: clockrate parsed.channels = parts.length === 3 ? parseInt(parts[2], 10) : 1; // legacy alias, got renamed back to channels in ORTC. parsed.numChannels = parsed.channels; return parsed; }; // Generates a rtpmap line from RTCRtpCodecCapability or // RTCRtpCodecParameters. SDPUtils.writeRtpMap = function (codec) { let pt = codec.payloadType; if (codec.preferredPayloadType !== undefined) { pt = codec.preferredPayloadType; } const channels = codec.channels || codec.numChannels || 1; return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate + (channels !== 1 ? '/' + channels : '') + '\r\n'; }; // Parses a extmap line (headerextension from RFC 5285). Sample input: // a=extmap:2 urn:ietf:params:rtp-hdrext:toffset // a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset SDPUtils.parseExtmap = function (line) { const parts = line.substring(9).split(' '); return { id: parseInt(parts[0], 10), direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv', uri: parts[1], attributes: parts.slice(2).join(' ') }; }; // Generates an extmap line from RTCRtpHeaderExtensionParameters or // RTCRtpHeaderExtension. SDPUtils.writeExtmap = function (headerExtension) { return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) + (headerExtension.direction && headerExtension.direction !== 'sendrecv' ? '/' + headerExtension.direction : '') + ' ' + headerExtension.uri + (headerExtension.attributes ? ' ' + headerExtension.attributes : '') + '\r\n'; }; // Parses a fmtp line, returns dictionary. Sample input: // a=fmtp:96 vbr=on;cng=on // Also deals with vbr=on; cng=on // Non-key-value such as telephone-events `0-15` get parsed as // {`0-15`:undefined} SDPUtils.parseFmtp = function (line) { const parsed = {}; let kv; const parts = line.substring(line.indexOf(' ') + 1).split(';'); for (let j = 0; j < parts.length; j++) { kv = parts[j].trim().split('='); parsed[kv[0].trim()] = kv[1]; } return parsed; }; // Generates a fmtp line from RTCRtpCodecCapability or RTCRtpCodecParameters. SDPUtils.writeFmtp = function (codec) { let line = ''; let pt = codec.payloadType; if (codec.preferredPayloadType !== undefined) { pt = codec.preferredPayloadType; } if (codec.parameters && Object.keys(codec.parameters).length) { const params = []; Object.keys(codec.parameters).forEach(param => { if (codec.parameters[param] !== undefined) { params.push(param + '=' + codec.parameters[param]); } else { params.push(param); } }); line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n'; } return line; }; // Parses a rtcp-fb line, returns RTCPRtcpFeedback object. Sample input: // a=rtcp-fb:98 nack rpsi SDPUtils.parseRtcpFb = function (line) { const parts = line.substring(line.indexOf(' ') + 1).split(' '); return { type: parts.shift(), parameter: parts.join(' ') }; }; // Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters. SDPUtils.writeRtcpFb = function (codec) { let lines = ''; let pt = codec.payloadType; if (codec.preferredPayloadType !== undefined) { pt = codec.preferredPayloadType; } if (codec.rtcpFeedback && codec.rtcpFeedback.length) { // FIXME: special handling for trr-int? codec.rtcpFeedback.forEach(fb => { lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') + '\r\n'; }); } return lines; }; // Parses a RFC 5576 ssrc media attribute. Sample input: // a=ssrc:3735928559 cname:something SDPUtils.parseSsrcMedia = function (line) { const sp = line.indexOf(' '); const parts = { ssrc: parseInt(line.substring(7, sp), 10) }; const colon = line.indexOf(':', sp); if (colon > -1) { parts.attribute = line.substring(sp + 1, colon); parts.value = line.substring(colon + 1); } else { parts.attribute = line.substring(sp + 1); } return parts; }; // Parse a ssrc-group line (see RFC 5576). Sample input: // a=ssrc-group:semantics 12 34 SDPUtils.parseSsrcGroup = function (line) { const parts = line.substring(13).split(' '); return { semantics: parts.shift(), ssrcs: parts.map(ssrc => parseInt(ssrc, 10)) }; }; // Extracts the MID (RFC 5888) from a media section. // Returns the MID or undefined if no mid line was found. SDPUtils.getMid = function (mediaSection) { const mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0]; if (mid) { return mid.substring(6); } }; // Parses a fingerprint line for DTLS-SRTP. SDPUtils.parseFingerprint = function (line) { const parts = line.substring(14).split(' '); return { algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge. value: parts[1].toUpperCase() // the definition is upper-case in RFC 4572. }; }; // Extracts DTLS parameters from SDP media section or sessionpart. // FIXME: for consistency with other functions this should only // get the fingerprint line as input. See also getIceParameters. SDPUtils.getDtlsParameters = function (mediaSection, sessionpart) { const lines = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=fingerprint:'); // Note: a=setup line is ignored since we use the 'auto' role in Edge. return { role: 'auto', fingerprints: lines.map(SDPUtils.parseFingerprint) }; }; // Serializes DTLS parameters to SDP. SDPUtils.writeDtlsParameters = function (params, setupType) { let sdp = 'a=setup:' + setupType + '\r\n'; params.fingerprints.forEach(fp => { sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n'; }); return sdp; }; // Parses a=crypto lines into // https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#dictionary-rtcsrtpsdesparameters-members SDPUtils.parseCryptoLine = function (line) { const parts = line.substring(9).split(' '); return { tag: parseInt(parts[0], 10), cryptoSuite: parts[1], keyParams: parts[2], sessionParams: parts.slice(3) }; }; SDPUtils.writeCryptoLine = function (parameters) { return 'a=crypto:' + parameters.tag + ' ' + parameters.cryptoSuite + ' ' + (typeof parameters.keyParams === 'object' ? SDPUtils.writeCryptoKeyParams(parameters.keyParams) : parameters.keyParams) + (parameters.sessionParams ? ' ' + parameters.sessionParams.join(' ') : '') + '\r\n'; }; // Parses the crypto key parameters into // https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#rtcsrtpkeyparam* SDPUtils.parseCryptoKeyParams = function (keyParams) { if (keyParams.indexOf('inline:') !== 0) { return null; } const parts = keyParams.substring(7).split('|'); return { keyMethod: 'inline', keySalt: parts[0], lifeTime: parts[1], mkiValue: parts[2] ? parts[2].split(':')[0] : undefined, mkiLength: parts[2] ? parts[2].split(':')[1] : undefined }; }; SDPUtils.writeCryptoKeyParams = function (keyParams) { return keyParams.keyMethod + ':' + keyParams.keySalt + (keyParams.lifeTime ? '|' + keyParams.lifeTime : '') + (keyParams.mkiValue && keyParams.mkiLength ? '|' + keyParams.mkiValue + ':' + keyParams.mkiLength : ''); }; // Extracts all SDES parameters. SDPUtils.getCryptoParameters = function (mediaSection, sessionpart) { const lines = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=crypto:'); return lines.map(SDPUtils.parseCryptoLine); }; // Parses ICE information from SDP media section or sessionpart. // FIXME: for consistency with other functions this should only // get the ice-ufrag and ice-pwd lines as input. SDPUtils.getIceParameters = function (mediaSection, sessionpart) { const ufrag = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=ice-ufrag:')[0]; const pwd = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=ice-pwd:')[0]; if (!(ufrag && pwd)) { return null; } return { usernameFragment: ufrag.substring(12), password: pwd.substring(10) }; }; // Serializes ICE parameters to SDP. SDPUtils.writeIceParameters = function (params) { let sdp = 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + 'a=ice-pwd:' + params.password + '\r\n'; if (params.iceLite) { sdp += 'a=ice-lite\r\n'; } return sdp; }; // Parses the SDP media section and returns RTCRtpParameters. SDPUtils.parseRtpParameters = function (mediaSection) { const description = { codecs: [], headerExtensions: [], fecMechanisms: [], rtcp: [] }; const lines = SDPUtils.splitLines(mediaSection); const mline = lines[0].split(' '); description.profile = mline[2]; for (let i = 3; i < mline.length; i++) { // find all codecs from mline[3..] const pt = mline[i]; const rtpmapline = SDPUtils.matchPrefix(mediaSection, 'a=rtpmap:' + pt + ' ')[0]; if (rtpmapline) { const codec = SDPUtils.parseRtpMap(rtpmapline); const fmtps = SDPUtils.matchPrefix(mediaSection, 'a=fmtp:' + pt + ' '); // Only the first a=fmtp: is considered. codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {}; codec.rtcpFeedback = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-fb:' + pt + ' ').map(SDPUtils.parseRtcpFb); description.codecs.push(codec); // parse FEC mechanisms from rtpmap lines. switch (codec.name.toUpperCase()) { case 'RED': case 'ULPFEC': description.fecMechanisms.push(codec.name.toUpperCase()); break; } } } SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(line => { description.headerExtensions.push(SDPUtils.parseExtmap(line)); }); const wildcardRtcpFb = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-fb:* ').map(SDPUtils.parseRtcpFb); description.codecs.forEach(codec => { wildcardRtcpFb.forEach(fb => { const duplicate = codec.rtcpFeedback.find(existingFeedback => { return existingFeedback.type === fb.type && existingFeedback.parameter === fb.parameter; }); if (!duplicate) { codec.rtcpFeedback.push(fb); } }); }); // FIXME: parse rtcp. return description; }; // Generates parts of the SDP media section describing the capabilities / // parameters. SDPUtils.writeRtpDescription = function (kind, caps) { let sdp = ''; // Build the mline. sdp += 'm=' + kind + ' '; sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs. sdp += ' ' + (caps.profile || 'UDP/TLS/RTP/SAVPF') + ' '; sdp += caps.codecs.map(codec => { if (codec.preferredPayloadType !== undefined) { return codec.preferredPayloadType; } return codec.payloadType; }).join(' ') + '\r\n'; sdp += 'c=IN IP4 0.0.0.0\r\n'; sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n'; // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. caps.codecs.forEach(codec => { sdp += SDPUtils.writeRtpMap(codec); sdp += SDPUtils.writeFmtp(codec); sdp += SDPUtils.writeRtcpFb(codec); }); let maxptime = 0; caps.codecs.forEach(codec => { if (codec.maxptime > maxptime) { maxptime = codec.maxptime; } }); if (maxptime > 0) { sdp += 'a=maxptime:' + maxptime + '\r\n'; } if (caps.headerExtensions) { caps.headerExtensions.forEach(extension => { sdp += SDPUtils.writeExtmap(extension); }); } // FIXME: write fecMechanisms. return sdp; }; // Parses the SDP media section and returns an array of // RTCRtpEncodingParameters. SDPUtils.parseRtpEncodingParameters = function (mediaSection) { const encodingParameters = []; const description = SDPUtils.parseRtpParameters(mediaSection); const hasRed = description.fecMechanisms.indexOf('RED') !== -1; const hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1; // filter a=ssrc:... cname:, ignore PlanB-msid const ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:').map(line => SDPUtils.parseSsrcMedia(line)).filter(parts => parts.attribute === 'cname'); const primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc; let secondarySsrc; const flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID').map(line => { const parts = line.substring(17).split(' '); return parts.map(part => parseInt(part, 10)); }); if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) { secondarySsrc = flows[0][1]; } description.codecs.forEach(codec => { if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) { let encParam = { ssrc: primarySsrc, codecPayloadType: parseInt(codec.parameters.apt, 10) }; if (primarySsrc && secondarySsrc) { encParam.rtx = { ssrc: secondarySsrc }; } encodingParameters.push(encParam); if (hasRed) { encParam = JSON.parse(JSON.stringify(encParam)); encParam.fec = { ssrc: primarySsrc, mechanism: hasUlpfec ? 'red+ulpfec' : 'red' }; encodingParameters.push(encParam); } } }); if (encodingParameters.length === 0 && primarySsrc) { encodingParameters.push({ ssrc: primarySsrc }); } // we support both b=AS and b=TIAS but interpret AS as TIAS. let bandwidth = SDPUtils.matchPrefix(mediaSection, 'b='); if (bandwidth.length) { if (bandwidth[0].indexOf('b=TIAS:') === 0) { bandwidth = parseInt(bandwidth[0].substring(7), 10); } else if (bandwidth[0].indexOf('b=AS:') === 0) { // use formula from JSEP to convert b=AS to TIAS value. bandwidth = parseInt(bandwidth[0].substring(5), 10) * 1000 * 0.95 - 50 * 40 * 8; } else { bandwidth = undefined; } encodingParameters.forEach(params => { params.maxBitrate = bandwidth; }); } return encodingParameters; }; // parses http://draft.ortc.org/#rtcrtcpparameters* SDPUtils.parseRtcpParameters = function (mediaSection) { const rtcpParameters = {}; // Gets the first SSRC. Note that with RTX there might be multiple // SSRCs. const remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:').map(line => SDPUtils.parseSsrcMedia(line)).filter(obj => obj.attribute === 'cname')[0]; if (remoteSsrc) { rtcpParameters.cname = remoteSsrc.value; rtcpParameters.ssrc = remoteSsrc.ssrc; } // Edge uses the compound attribute instead of reducedSize // compound is !reducedSize const rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize'); rtcpParameters.reducedSize = rsize.length > 0; rtcpParameters.compound = rsize.length === 0; // parses the rtcp-mux attrіbute. // Note that Edge does not support unmuxed RTCP. const mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux'); rtcpParameters.mux = mux.length > 0; return rtcpParameters; }; SDPUtils.writeRtcpParameters = function (rtcpParameters) { let sdp = ''; if (rtcpParameters.reducedSize) { sdp += 'a=rtcp-rsize\r\n'; } if (rtcpParameters.mux) { sdp += 'a=rtcp-mux\r\n'; } if (rtcpParameters.ssrc !== undefined && rtcpParameters.cname) { sdp += 'a=ssrc:' + rtcpParameters.ssrc + ' cname:' + rtcpParameters.cname + '\r\n'; } return sdp; }; // parses either a=msid: or a=ssrc:... msid lines and returns // the id of the MediaStream and MediaStreamTrack. SDPUtils.parseMsid = function (mediaSection) { let parts; const spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:'); if (spec.length === 1) { parts = spec[0].substring(7).split(' '); return { stream: parts[0], track: parts[1] }; } const planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:').map(line => SDPUtils.parseSsrcMedia(line)).filter(msidParts => msidParts.attribute === 'msid'); if (planB.length > 0) { parts = planB[0].value.split(' '); return { stream: parts[0], track: parts[1] }; } }; // SCTP // parses draft-ietf-mmusic-sctp-sdp-26 first and falls back // to draft-ietf-mmusic-sctp-sdp-05 SDPUtils.parseSctpDescription = function (mediaSection) { const mline = SDPUtils.parseMLine(mediaSection); const maxSizeLine = SDPUtils.matchPrefix(mediaSection, 'a=max-message-size:'); let maxMessageSize; if (maxSizeLine.length > 0) { maxMessageSize = parseInt(maxSizeLine[0].substring(19), 10); } if (isNaN(maxMessageSize)) { maxMessageSize = 65536; } const sctpPort = SDPUtils.matchPrefix(mediaSection, 'a=sctp-port:'); if (sctpPort.length > 0) { return { port: parseInt(sctpPort[0].substring(12), 10), protocol: mline.fmt, maxMessageSize }; } const sctpMapLines = SDPUtils.matchPrefix(mediaSection, 'a=sctpmap:'); if (sctpMapLines.length > 0) { const parts = sctpMapLines[0].substring(10).split(' '); return { port: parseInt(parts[0], 10), protocol: parts[1], maxMessageSize }; } }; // SCTP // outputs the draft-ietf-mmusic-sctp-sdp-26 version that all browsers // support by now receiving in this format, unless we originally parsed // as the draft-ietf-mmusic-sctp-sdp-05 format (indicated by the m-line // protocol of DTLS/SCTP -- without UDP/ or TCP/) SDPUtils.writeSctpDescription = function (media, sctp) { let output = []; if (media.protocol !== 'DTLS/SCTP') { output = ['m=' + media.kind + ' 9 ' + media.protocol + ' ' + sctp.protocol + '\r\n', 'c=IN IP4 0.0.0.0\r\n', 'a=sctp-port:' + sctp.port + '\r\n']; } else { output = ['m=' + media.kind + ' 9 ' + media.protocol + ' ' + sctp.port + '\r\n', 'c=IN IP4 0.0.0.0\r\n', 'a=sctpmap:' + sctp.port + ' ' + sctp.protocol + ' 65535\r\n']; } if (sctp.maxMessageSize !== undefined) { output.push('a=max-message-size:' + sctp.maxMessageSize + '\r\n'); } return output.join(''); }; // Generate a session ID for SDP. // https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1 // recommends using a cryptographically random +ve 64-bit value // but right now this should be acceptable and within the right range SDPUtils.generateSessionId = function () { return Math.random().toString().substr(2, 22); }; // Write boiler plate for start of SDP // sessId argument is optional - if not supplied it will // be generated randomly // sessVersion is optional and defaults to 2 // sessUser is optional and defaults to 'thisisadapterortc' SDPUtils.writeSessionBoilerplate = function (sessId, sessVer, sessUser) { let sessionId; const version = sessVer !== undefined ? sessVer : 2; if (sessId) { sessionId = sessId; } else { sessionId = SDPUtils.generateSessionId(); } const user = sessUser || 'thisisadapterortc'; // FIXME: sess-id should be an NTP timestamp. return 'v=0\r\n' + 'o=' + user + ' ' + sessionId + ' ' + version + ' IN IP4 127.0.0.1\r\n' + 's=-\r\n' + 't=0 0\r\n'; }; // Gets the direction from the mediaSection or the sessionpart. SDPUtils.getDirection = function (mediaSection, sessionpart) { // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv. const lines = SDPUtils.splitLines(mediaSection); for (let i = 0; i < lines.length; i++) { switch (lines[i]) { case 'a=sendrecv': case 'a=sendonly': case 'a=recvonly': case 'a=inactive': return lines[i].substring(2); // FIXME: What should happen here? } } if (sessionpart) { return SDPUtils.getDirection(sessionpart); } return 'sendrecv'; }; SDPUtils.getKind = function (mediaSection) { const lines = SDPUtils.splitLines(mediaSection); const mline = lines[0].split(' '); return mline[0].substring(2); }; SDPUtils.isRejected = function (mediaSection) { return mediaSection.split(' ', 2)[1] === '0'; }; SDPUtils.parseMLine = function (mediaSection) { const lines = SDPUtils.splitLines(mediaSection); const parts = lines[0].substring(2).split(' '); return { kind: parts[0], port: parseInt(parts[1], 10), protocol: parts[2], fmt: parts.slice(3).join(' ') }; }; SDPUtils.parseOLine = function (mediaSection) { const line = SDPUtils.matchPrefix(mediaSection, 'o=')[0]; const parts = line.substring(2).split(' '); return { username: parts[0], sessionId: parts[1], sessionVersion: parseInt(parts[2], 10), netType: parts[3], addressType: parts[4], address: parts[5] }; }; // a very naive interpretation of a valid SDP. SDPUtils.isValidSDP = function (blob) { if (typeof blob !== 'string' || blob.length === 0) { return false; } const lines = SDPUtils.splitLines(blob); for (let i = 0; i < lines.length; i++) { if (lines[i].length < 2 || lines[i].charAt(1) !== '=') { return false; } // TODO: check the modifier a bit more. } return true; }; // Expose public methods. { module.exports = SDPUtils; } })(sdp$1); return sdp$1.exports; }var sdpExports = requireSdp(); var SDPUtils = /*@__PURE__*/getDefaultExportFromCjs(sdpExports);var sdp=/*#__PURE__*/_mergeNamespaces({__proto__:null,default:SDPUtils},[sdpExports]);/* * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ /* eslint-env node */ function shimRTCIceCandidate(window) { // foundation is arbitrarily chosen as an indicator for full support for // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface if (!window.RTCIceCandidate || window.RTCIceCandidate && 'foundation' in window.RTCIceCandidate.prototype) { return; } const NativeRTCIceCandidate = window.RTCIceCandidate; window.RTCIceCandidate = function RTCIceCandidate(args) { // Remove the a= which shouldn't be part of the candidate string. if (typeof args === 'object' && args.candidate && args.candidate.indexOf('a=') === 0) { args = JSON.parse(JSON.stringify(args)); args.candidate = args.candidate.substring(2); } if (args.candidate && args.candidate.length) { // Augment the native candidate with the parsed fields. const nativeCandidate = new NativeRTCIceCandidate(args); const parsedCandidate = SDPUtils.parseCandidate(args.candidate); for (const key in parsedCandidate) { if (!(key in nativeCandidate)) { Object.defineProperty(nativeCandidate, key, { value: parsedCandidate[key] }); } } // Override serializer to not serialize the extra attributes. nativeCandidate.toJSON = function toJSON() { return { candidate: nativeCandidate.candidate, sdpMid: nativeCandidate.sdpMid, sdpMLineIndex: nativeCandidate.sdpMLineIndex, usernameFragment: nativeCandidate.usernameFragment }; }; return nativeCandidate; } return new NativeRTCIceCandidate(args); }; window.RTCIceCandidate.prototype = NativeRTCIceCandidate.prototype; // Hook up the augmented candidate in onicecandidate and // addEventListener('icecandidate', ...) wrapPeerConnectionEvent(window, 'icecandidate', e => { if (e.candidate) { Object.defineProperty(e, 'candidate', { value: new window.RTCIceCandidate(e.candidate), writable: 'false' }); } return e; }); } function shimRTCIceCandidateRelayProtocol(window) { if (!window.RTCIceCandidate || window.RTCIceCandidate && 'relayProtocol' in window.RTCIceCandidate.prototype) { return; } // Hook up the augmented candidate in onicecandidate and // addEventListener('icecandidate', ...) wrapPeerConnectionEvent(window, 'icecandidate', e => { if (e.candidate) { const parsedCandidate = SDPUtils.parseCandidate(e.candidate.candidate); if (parsedCandidate.type === 'relay') { // This is a libwebrtc-specific mapping of local type preference // to relayProtocol. e.candidate.relayProtocol = { 0: 'tls', 1: 'tcp', 2: 'udp' }[parsedCandidate.priority >> 24]; } } return e; }); } function shimMaxMessageSize(window, browserDetails) { if (!window.RTCPeerConnection) { return; } if (!('sctp' in window.RTCPeerConnection.prototype)) { Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', { get() { return typeof this._sctp === 'undefined' ? null : this._sctp; } }); } const sctpInDescription = function (description) { if (!description || !description.sdp) { return false; } const sections = SDPUtils.splitSections(description.sdp); sections.shift(); return sections.some(mediaSection => { const mLine = SDPUtils.parseMLine(mediaSection); return mLine && mLine.kind === 'application' && mLine.protocol.indexOf('SCTP') !== -1; }); }; const getRemoteFirefoxVersion = function (description) { // TODO: Is there a better solution for detecting Firefox? const match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/); if (match === null || match.length < 2) { return -1; } const version = parseInt(match[1], 10); // Test for NaN (yes, this is ugly) return version !== version ? -1 : version; }; const getCanSendMaxMessageSize = function (remoteIsFirefox) { // Every implementation we know can send at least 64 KiB. // Note: Although Chrome is technically able to send up to 256 KiB, the // data does not reach the other peer reliably. // See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419 let canSendMaxMessageSize = 65536; if (browserDetails.browser === 'firefox') { if (browserDetails.version < 57) { if (remoteIsFirefox === -1) { // FF < 57 will send in 16 KiB chunks using the deprecated PPID // fragmentation. canSendMaxMessageSize = 16384; } else { // However, other FF (and RAWRTC) can reassemble PPID-fragmented // messages. Thus, supporting ~2 GiB when sending. canSendMaxMessageSize = 2147483637; } } else if (browserDetails.version < 60) { // Currently, all FF >= 57 will reset the remote maximum message size // to the default value when a data channel is created at a later // stage. :( // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 canSendMaxMessageSize = browserDetails.version === 57 ? 65535 : 65536; } else { // FF >= 60 supports sending ~2 GiB canSendMaxMessageSize = 2147483637; } } return canSendMaxMessageSize; }; const getMaxMessageSize = function (description, remoteIsFirefox) { // Note: 65536 bytes is the default value from the SDP spec. Also, // every implementation we know supports receiving 65536 bytes. let maxMessageSize = 65536; // FF 57 has a slightly incorrect default remote max message size, so // we need to adjust it here to avoid a failure when sending. // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697 if (browserDetails.browser === 'firefox' && browserDetails.version === 57) { maxMessageSize = 65535; } const match = SDPUtils.matchPrefix(description.sdp, 'a=max-message-size:'); if (match.length > 0) { maxMessageSize = parseInt(match[0].substring(19), 10); } else if (browserDetails.browser === 'firefox' && remoteIsFirefox !== -1) { // If the maximum message size is not present in the remote SDP and // both local and remote are Firefox, the remote peer can receive // ~2 GiB. maxMessageSize = 2147483637; } return maxMessageSize; }; const origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() { this._sctp = null; // Chrome decided to not expose .sctp in plan-b mode. // As usual, adapter.js has to do an 'ugly worakaround' // to cover up the mess. if (browserDetails.browser === 'chrome' && browserDetails.version >= 76) { const _this$getConfiguratio = this.getConfiguration(), sdpSemantics = _this$getConfiguratio.sdpSemantics; if (sdpSemantics === 'plan-b') { Object.defineProperty(this, 'sctp', { get() { return typeof this._sctp === 'undefined' ? null : this._sctp; }, enumerable: true, configurable: true }); } } if (sctpInDescription(arguments[0])) { // Check if the remote is FF. const isFirefox = getRemoteFirefoxVersion(arguments[0]); // Get the maximum message size the local peer is capable of sending const canSendMMS = getCanSendMaxMessageSize(isFirefox); // Get the maximum message size of the remote peer. const remoteMMS = getMaxMessageSize(arguments[0], isFirefox); // Determine final maximum message size let maxMessageSize; if (canSendMMS === 0 && remoteMMS === 0) { maxMessageSize = Number.POSITIVE_INFINITY; } else if (canSendMMS === 0 || remoteMMS === 0) { maxMessageSize = Math.max(canSendMMS, remoteMMS); } else { maxMessageSize = Math.min(canSendMMS, remoteMMS); } // Create a dummy RTCSctpTransport object and the 'maxMessageSize' // attribute. const sctp = {}; Object.defineProperty(sctp, 'maxMessageSize', { get() { return maxMessageSize; } }); this._sctp = sctp; } return origSetRemoteDescription.apply(this, arguments); }; } function shimSendThrowTypeError(window, browserDetails) { if (!(window.RTCPeerConnection && 'createDataChannel' in window.RTCPeerConnection.prototype)) { return; } if (browserDetails.browser === 'chrome' && browserDetails.version > 149) { // Fixed by https://issues.chromium.org/issues/490588131 return; } // Note: Although Firefox >= 57 has a native implementation, the maximum // message size can be reset for all data channels at a later stage. // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 if (browserDetails.browser === 'firefox' && browserDetails.version > 60) { return; } function wrapDcSend(dc, pc) { const origDataChannelSend = dc.send; dc.send = function send() { const data = arguments[0]; const length = data.length || data.size || data.byteLength; if (dc.readyState === 'open' && pc.sctp && length > pc.sctp.maxMessageSize) { throw new TypeError('Message too large (can send a maximum of ' + pc.sctp.maxMessageSize + ' bytes)'); } return origDataChannelSend.apply(dc, arguments); }; } const origCreateDataChannel = window.RTCPeerConnection.prototype.createDataChannel; window.RTCPeerConnection.prototype.createDataChannel = function createDataChannel() { const dataChannel = origCreateDataChannel.apply(this, arguments); wrapDcSend(dataChannel, this); return dataChannel; }; wrapPeerConnectionEvent(window, 'datachannel', e => { wrapDcSend(e.channel, e.target); return e; }); } /* shims RTCConnectionState by pretending it is the same as iceConnectionState. * See https://bugs.chromium.org/p/webrtc/issues/detail?id=6145#c12 * for why this is a valid hack in Chrome. In Firefox it is slightly incorrect * since DTLS failures would be hidden. See * https://bugzilla.mozilla.org/show_bug.cgi?id=1265827 * for the Firefox tracking bug. */ function shimConnectionState(window) { if (!window.RTCPeerConnection || 'connectionState' in window.RTCPeerConnection.prototype) { return; } const proto = window.RTCPeerConnection.prototype; Object.defineProperty(proto, 'connectionState', { get() { return { completed: 'connected', checking: 'connecting' }[this.iceConnectionState] || this.iceConnectionState; }, enumerable: true, configurable: true }); Object.defineProperty(proto, 'onconnectionstatechange', { get() { return this._onconnectionstatechange || null; }, set(cb) { if (this._onconnectionstatechange) { this.removeEventListener('connectionstatechange', this._onconnectionstatechange); delete this._onconnectionstatechange; } if (cb) { this.addEventListener('connectionstatechange', this._onconnectionstatechange = cb); } }, enumerable: true, configurable: true }); ['setLocalDescription', 'setRemoteDescription'].forEach(method => { const origMethod = proto[method]; proto[method] = function () { if (!this._connectionstatechangepoly) { this._connectionstatechangepoly = e => { const pc = e.target; if (pc._lastConnectionState !== pc.connectionState) { pc._lastConnectionState = pc.connectionState; const newEvent = new Event('connectionstatechange', e); pc.dispatchEvent(newEvent); } return e; }; this.addEventListener('iceconnectionstatechange', this._connectionstatechangepoly); } return origMethod.apply(this, arguments); }; }); } function removeExtmapAllowMixed(window, browserDetails) { /* remove a=extmap-allow-mixed for webrtc.org < M71 */ if (!window.RTCPeerConnection) { return; } if (browserDetails.browser === 'chrome' && browserDetails.version >= 71) { return; } if (browserDetails.browser === 'safari' && browserDetails._safariVersion >= 13.1) { return; } const nativeSRD = window.RTCPeerConnection.prototype.setRemoteDescription; window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription(desc) { if (desc && desc.sdp && desc.sdp.indexOf('\na=extmap-allow-mixed') !== -1) { const sdp = desc.sdp.split('\n').filter(line => { return line.trim() !== 'a=extmap-allow-mixed'; }).join('\n'); // Safari enforces read-only-ness of RTCSessionDescription fields. if (window.RTCSessionDescription && desc instanceof window.RTCSessionDescription) { arguments[0] = new window.RTCSessionDescription({ type: desc.type, sdp }); } else { desc.sdp = sdp; } } return nativeSRD.apply(this, arguments); }; } function shimAddIceCandidateNullOrEmpty(window, browserDetails) { // Support for addIceCandidate(null or undefined) // as well as addIceCandidate({candidate: "", ...}) // https://bugs.chromium.org/p/chromium/issues/detail?id=978582 // Note: must be called before other polyfills which change the signature. if (!(window.RTCPeerConnection && window.RTCPeerConnection.prototype)) { return; } const nativeAddIceCandidate = window.RTCPeerConnection.prototype.addIceCandidate; if (!nativeAddIceCandidate || nativeAddIceCandidate.length === 0) { return; } window.RTCPeerConnection.prototype.addIceCandidate = function addIceCandidate() { if (!arguments[0]) { if (arguments[1]) { arguments[1].apply(null); } return Promise.resolve(); } // Firefox 68+ emits and processes {candidate: "", ...}, ignore // in older versions. // Native support for ignoring exists for Chrome M77+. // Safari ignores as well, exact version unknown but works in the same // version that also ignores addIceCandidate(null). if ((browserDetails.browser === 'chrome' && browserDetails.version < 78 || browserDetails.browser === 'firefox' && browserDetails.version < 68 || browserDetails.browser === 'safari') && arguments[0] && arguments[0].candidate === '') { return Promise.resolve(); } return nativeAddIceCandidate.apply(this, arguments); }; } // Note: Make sure to call this ahead of APIs that modify // setLocalDescription.length function shimParameterlessSetLocalDescription(window, browserDetails) { if (!(window.RTCPeerConnection && window.RTCPeerConnection.prototype)) { return; } const nativeSetLocalDescription = window.RTCPeerConnection.prototype.setLocalDescription; if (!nativeSetLocalDescription || nativeSetLocalDescription.length === 0) { return; } window.RTCPeerConnection.prototype.setLocalDescription = function setLocalDescription() { let desc = arguments[0] || {}; if (typeof desc !== 'object' || desc.type && desc.sdp) { return nativeSetLocalDescription.apply(this, arguments); } // The remaining steps should technically happen when SLD comes off the // RTCPeerConnection's operations chain (not ahead of going on it), but // this is too difficult to shim. Instead, this shim only covers the // common case where the operations chain is empty. This is imperfect, but // should cover many cases. Rationale: Even if we can't reduce the glare // window to zero on imperfect implementations, there's value in tapping // into the perfect negotiation pattern that several browsers support. desc = { type: desc.type, sdp: desc.sdp }; if (!desc.type) { switch (this.signalingState) { case 'stable': case 'have-local-offer': case 'have-remote-pranswer': desc.type = 'offer'; break; default: desc.type = 'answer'; break; } } if (desc.sdp || desc.type !== 'offer' && desc.type !== 'answer') { return nativeSetLocalDescription.apply(this, [desc]); } const func = desc.type === 'offer' ? this.createOffer : this.createAnswer; return func.apply(this).then(d => nativeSetLocalDescription.apply(this, [d])); }; }var commonShim=/*#__PURE__*/Object.freeze({__proto__:null,removeExtmapAllowMixed:removeExtmapAllowMixed,shimAddIceCandidateNullOrEmpty:shimAddIceCandidateNullOrEmpty,shimConnectionState:shimConnectionState,shimMaxMessageSize:shimMaxMessageSize,shimParameterlessSetLocalDescription:shimParameterlessSetLocalDescription,shimRTCIceCandidate:shimRTCIceCandidate,shimRTCIceCandidateRelayProtocol:shimRTCIceCandidateRelayProtocol,shimSendThrowTypeError:shimSendThrowTypeError});/* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ // Shimming starts here. function adapterFactory() { let _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, window = _ref.window; let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { shimChrome: true, shimFirefox: true, shimSafari: true }; // Utils. const logging = log$4; const browserDetails = detectBrowser(window); const adapter = { browserDetails, commonShim, extractVersion: extractVersion, disableLog: disableLog, disableWarnings: disableWarnings, // Expose sdp as a convenience. For production apps include directly. sdp }; // Shim browser if found. switch (browserDetails.browser) { case 'chrome': if (!chromeShim || !shimPeerConnection$1 || !options.shimChrome) { logging('Chrome shim is not included in this adapter release.'); return adapter; } if (browserDetails.version === null) { logging('Chrome shim can not determine version, not shimming.'); return adapter; } logging('adapter.js shimming chrome.'); // Export to the adapter global object visible in the browser. adapter.browserShim = chromeShim; // Must be called before shimPeerConnection. shimAddIceCandidateNullOrEmpty(window, browserDetails); shimParameterlessSetLocalDescription(window); shimGetUserMedia$2(window, browserDetails); shimMediaStream(window); shimPeerConnection$1(window, browserDetails); shimOnTrack$1(window, browserDetails); shimAddTrackRemoveTrack(window, browserDetails); shimGetSendersWithDtmf(window); shimSenderReceiverGetStats(window, browserDetails); fixNegotiationNeeded(window, browserDetails); shimRTCIceCandidate(window); shimRTCIceCandidateRelayProtocol(window); shimConnectionState(window); shimMaxMessageSize(window, browserDetails); shimSendThrowTypeError(window, browserDetails); removeExtmapAllowMixed(window, browserDetails); break; case 'firefox': if (!firefoxShim || !shimPeerConnection || !options.shimFirefox) { logging('Firefox shim is not included in this adapter release.'); return adapter; } logging('adapter.js shimming firefox.'); // Export to the adapter global object visible in the browser. adapter.browserShim = firefoxShim; // Must be called before shimPeerConnection. shimAddIceCandidateNullOrEmpty(window, browserDetails); shimParameterlessSetLocalDescription(window); shimGetUserMedia$1(window, browserDetails); shimPeerConnection(window, browserDetails); shimGetStats(window, browserDetails); shimOnTrack(window); shimRemoveStream(window); shimSenderGetStats(window); shimReceiverGetStats(window); shimRTCDataChannel(window); shimAddTransceiver(window); shimGetParameters(window); shimCreateOffer(window); shimCreateAnswer(window); shimRTCIceCandidate(window); shimConnectionState(window); shimMaxMessageSize(window, browserDetails); shimSendThrowTypeError(window, browserDetails); break; case 'safari': if (!safariShim || !options.shimSafari) { logging('Safari shim is not included in this adapter release.'); return adapter; } logging('adapter.js shimming safari.'); // Export to the adapter global object visible in the browser. adapter.browserShim = safariShim; // Must be called before shimCallbackAPI. shimAddIceCandidateNullOrEmpty(window, browserDetails); shimParameterlessSetLocalDescription(window); shimRTCIceServerUrls(window); shimCreateOfferLegacy(window); shimCallbacksAPI(window); shimLocalStreamsAPI(window); shimRemoteStreamsAPI(window); shimTrackEventTransceiver(window); shimGetUserMedia(window); shimAudioContext(window); shimRTCIceCandidate(window); shimRTCIceCandidateRelayProtocol(window); shimMaxMessageSize(window, browserDetails); shimSendThrowTypeError(window, browserDetails); removeExtmapAllowMixed(window, browserDetails); break; default: logging('Unsupported browser!'); break; } return adapter; }/* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ /* eslint-env node */ adapterFactory({ window: typeof window === 'undefined' ? undefined : window });var _a, _b; class TypedPromise extends (_b = Promise) { // eslint-disable-next-line @typescript-eslint/no-useless-constructor constructor(executor) { super(executor); } catch(onrejected) { return super.catch(onrejected); } static reject(reason) { return super.reject(reason); } static all(values) { return super.all(values); } static race(values) { return super.race(values); } } _a = TypedPromise; TypedPromise.resolve = value => { return Reflect.get(_b, "resolve", _a).call(_a, value); };// tiny, simplified version of https://github.com/lancedikson/bowser/blob/master/src/parser-browsers.js // reduced to only differentiate Chrome(ium) based browsers / Firefox / Safari const commonVersionIdentifier = /version\/(\d+(\.?_?\d+)+)/i; let browserDetails; /** * @internal */ function getBrowser(userAgent) { let force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; if (typeof userAgent === 'undefined' && typeof navigator === 'undefined') { return; } const ua = (userAgent !== null && userAgent !== void 0 ? userAgent : navigator.userAgent).toLowerCase(); if (browserDetails === undefined || force) { const browser = browsersList.find(_ref => { let test = _ref.test; return test.test(ua); }); browserDetails = browser === null || browser === void 0 ? void 0 : browser.describe(ua); } return browserDetails; } const browsersList = [{ test: /firefox|iceweasel|fxios/i, describe(ua) { const browser = { name: 'Firefox', version: getMatch(/(?:firefox|iceweasel|fxios)[\s/](\d+(\.?_?\d+)+)/i, ua), os: ua.toLowerCase().includes('fxios') ? 'iOS' : undefined, osVersion: getOSVersion(ua) }; return browser; } }, { test: /chrom|crios|crmo/i, describe(ua) { const browser = { name: 'Chrome', version: getMatch(/(?:chrome|chromium|crios|crmo)\/(\d+(\.?_?\d+)+)/i, ua), os: ua.toLowerCase().includes('crios') ? 'iOS' : undefined, osVersion: getOSVersion(ua) }; return browser; } }, /* Safari */ { test: /safari|applewebkit/i, describe(ua) { const browser = { name: 'Safari', version: getMatch(commonVersionIdentifier, ua), os: ua.includes('mobile/') ? 'iOS' : 'macOS', osVersion: getOSVersion(ua) }; return browser; } }]; function getMatch(exp, ua) { let id = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1; const match = ua.match(exp); return match && match.length >= id && match[id] || ''; } function getOSVersion(ua) { return ua.includes('mac os') ? getMatch(/\(.+?(\d+_\d+(:?_\d+)?)/, ua, 1).replace(/_/g, '.') : undefined; }var version$1 = "2.20.0";const version = version$1; const protocolVersion = 17; /** Initial client protocol. */ const CLIENT_PROTOCOL_DEFAULT = 0; /** Replaces RPC v1 protocol with a v2 data streams based one to support unlimited request / * response payload length. */ const CLIENT_PROTOCOL_DATA_STREAM_RPC = 1; /** The client protocol version indicates what level of support that the client has for * client <-> client api interactions. */ const clientProtocol = CLIENT_PROTOCOL_DATA_STREAM_RPC;/** Base error that all LiveKit specific custom errors inherit from. */ class LivekitError extends Error { constructor(code, message, options) { super(message || 'an error has occurred'); this.name = 'LiveKitError'; this.code = code; if (typeof (options === null || options === void 0 ? void 0 : options.cause) !== 'undefined') { this.cause = options === null || options === void 0 ? void 0 : options.cause; } } } /** * LiveKit specific error type representing an error with an associated set of reasons. * Use this to represent an error with multiple different but contextually related variants. * */ class LivekitReasonedError extends LivekitError {} class SimulatedError extends LivekitError { constructor() { let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'Simulated failure'; super(-1, message); this.name = 'simulated'; } } var ConnectionErrorReason; (function (ConnectionErrorReason) { ConnectionErrorReason[ConnectionErrorReason["NotAllowed"] = 0] = "NotAllowed"; ConnectionErrorReason[ConnectionErrorReason["ServerUnreachable"] = 1] = "ServerUnreachable"; ConnectionErrorReason[ConnectionErrorReason["InternalError"] = 2] = "InternalError"; ConnectionErrorReason[ConnectionErrorReason["Cancelled"] = 3] = "Cancelled"; ConnectionErrorReason[ConnectionErrorReason["LeaveRequest"] = 4] = "LeaveRequest"; ConnectionErrorReason[ConnectionErrorReason["Timeout"] = 5] = "Timeout"; ConnectionErrorReason[ConnectionErrorReason["WebSocket"] = 6] = "WebSocket"; ConnectionErrorReason[ConnectionErrorReason["ServiceNotFound"] = 7] = "ServiceNotFound"; })(ConnectionErrorReason || (ConnectionErrorReason = {})); class ConnectionError extends LivekitReasonedError { constructor(message, reason, status, context) { super(1, message); this.name = 'ConnectionError'; this.status = status; this.reason = reason; this.context = context; this.reasonName = ConnectionErrorReason[reason]; } static notAllowed(message, status, context) { return new ConnectionError(message, ConnectionErrorReason.NotAllowed, status, context); } static timeout(message) { return new ConnectionError(message, ConnectionErrorReason.Timeout); } static leaveRequest(message, context) { return new ConnectionError(message, ConnectionErrorReason.LeaveRequest, undefined, context); } static internal(message, context) { return new ConnectionError(message, ConnectionErrorReason.InternalError, undefined, context); } static cancelled(message) { return new ConnectionError(message, ConnectionErrorReason.Cancelled); } static serverUnreachable(message, status) { return new ConnectionError(message, ConnectionErrorReason.ServerUnreachable, status); } static websocket(message, status, reason) { return new ConnectionError(message, ConnectionErrorReason.WebSocket, status, reason); } static serviceNotFound(message, serviceName) { return new ConnectionError(message, ConnectionErrorReason.ServiceNotFound, undefined, serviceName); } } class DeviceUnsupportedError extends LivekitError { constructor(message) { super(21, message !== null && message !== void 0 ? message : 'device is unsupported'); this.name = 'DeviceUnsupportedError'; } } class TrackInvalidError extends LivekitError { constructor(message) { super(20, message !== null && message !== void 0 ? message : 'track is invalid'); this.name = 'TrackInvalidError'; } } class UnsupportedServer extends LivekitError { constructor(message) { super(10, message !== null && message !== void 0 ? message : 'unsupported server'); this.name = 'UnsupportedServer'; } } class UnexpectedConnectionState extends LivekitError { constructor(message) { super(12, message !== null && message !== void 0 ? message : 'unexpected connection state'); this.name = 'UnexpectedConnectionState'; } } class NegotiationError extends LivekitError { constructor(message) { super(13, message !== null && message !== void 0 ? message : 'unable to negotiate'); this.name = 'NegotiationError'; } } class PublishDataError extends LivekitError { constructor(message) { super(14, message !== null && message !== void 0 ? message : 'unable to publish data'); this.name = 'PublishDataError'; } } class PublishTrackError extends LivekitError { constructor(message, status) { super(15, message); this.name = 'PublishTrackError'; this.status = status; } } class SignalRequestError extends LivekitReasonedError { constructor(message, reason) { super(15, message); this.name = 'SignalRequestError'; this.reason = reason; this.reasonName = typeof reason === 'string' ? reason : RequestResponse_Reason[reason]; } } // NOTE: matches with https://github.com/livekit/client-sdk-swift/blob/f37bbd260d61e165084962db822c79f995f1a113/Sources/LiveKit/DataStream/StreamError.swift#L17 var DataStreamErrorReason; (function (DataStreamErrorReason) { // Unable to open a stream with the same ID more than once. DataStreamErrorReason[DataStreamErrorReason["AlreadyOpened"] = 0] = "AlreadyOpened"; // Stream closed abnormally by remote participant. DataStreamErrorReason[DataStreamErrorReason["AbnormalEnd"] = 1] = "AbnormalEnd"; // Incoming chunk data could not be decoded. DataStreamErrorReason[DataStreamErrorReason["DecodeFailed"] = 2] = "DecodeFailed"; // Read length exceeded total length specified in stream header. DataStreamErrorReason[DataStreamErrorReason["LengthExceeded"] = 3] = "LengthExceeded"; // Read length less than total length specified in stream header. DataStreamErrorReason[DataStreamErrorReason["Incomplete"] = 4] = "Incomplete"; // Unable to register a stream handler more than once. DataStreamErrorReason[DataStreamErrorReason["HandlerAlreadyRegistered"] = 7] = "HandlerAlreadyRegistered"; // Encryption type mismatch. DataStreamErrorReason[DataStreamErrorReason["EncryptionTypeMismatch"] = 8] = "EncryptionTypeMismatch"; })(DataStreamErrorReason || (DataStreamErrorReason = {})); class DataStreamError extends LivekitReasonedError { constructor(message, reason) { super(16, message); this.name = 'DataStreamError'; this.reason = reason; this.reasonName = DataStreamErrorReason[reason]; } } class SignalReconnectError extends LivekitError { constructor(message) { super(18, message); this.name = 'SignalReconnectError'; } } var MediaDeviceFailure; (function (MediaDeviceFailure) { // user rejected permissions MediaDeviceFailure["PermissionDenied"] = "PermissionDenied"; // device is not available MediaDeviceFailure["NotFound"] = "NotFound"; // device is in use. On Windows, only a single tab may get access to a device at a time. MediaDeviceFailure["DeviceInUse"] = "DeviceInUse"; MediaDeviceFailure["Other"] = "Other"; })(MediaDeviceFailure || (MediaDeviceFailure = {})); (function (MediaDeviceFailure) { function getFailure(error) { if (error && 'name' in error) { if (error.name === 'NotFoundError' || error.name === 'DevicesNotFoundError') { return MediaDeviceFailure.NotFound; } if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError') { return MediaDeviceFailure.PermissionDenied; } if (error.name === 'NotReadableError' || error.name === 'TrackStartError') { return MediaDeviceFailure.DeviceInUse; } return MediaDeviceFailure.Other; } } MediaDeviceFailure.getFailure = getFailure; })(MediaDeviceFailure || (MediaDeviceFailure = {}));/** * Timers that can be overridden with platform specific implementations * that ensure that they are fired. These should be used when it is critical * that the timer fires on time. */ class CriticalTimers {} CriticalTimers.setTimeout = function () { return setTimeout(...arguments); }; CriticalTimers.setInterval = // eslint-disable-next-line @typescript-eslint/no-implied-eval function () { return setInterval(...arguments); }; CriticalTimers.clearTimeout = function () { return clearTimeout(...arguments); }; CriticalTimers.clearInterval = function () { return clearInterval(...arguments); };/** * Events are the primary way LiveKit notifies your application of changes. * * The following are events emitted by [[Room]], listen to room events like * * ```typescript * room.on(RoomEvent.TrackPublished, (track, publication, participant) => {}) * ``` */ var RoomEvent; (function (RoomEvent) { /** * When the connection to the server has been established */ RoomEvent["Connected"] = "connected"; /** * When the connection to the server has been interrupted and it's attempting * to reconnect. */ RoomEvent["Reconnecting"] = "reconnecting"; /** * When the signal connection to the server has been interrupted. This isn't noticeable to users most of the time. * It will resolve with a `RoomEvent.Reconnected` once the signal connection has been re-established. * If media fails additionally it an additional `RoomEvent.Reconnecting` will be emitted. */ RoomEvent["SignalReconnecting"] = "signalReconnecting"; /** * Fires when a reconnection has been successful. */ RoomEvent["Reconnected"] = "reconnected"; /** * When disconnected from room. This fires when room.disconnect() is called or * when an unrecoverable connection issue had occurred. * * DisconnectReason can be used to determine why the participant was disconnected. Notable reasons are * - DUPLICATE_IDENTITY: another client with the same identity has joined the room * - PARTICIPANT_REMOVED: participant was removed by RemoveParticipant API * - ROOM_DELETED: the room has ended via DeleteRoom API * * args: ([[DisconnectReason]]) */ RoomEvent["Disconnected"] = "disconnected"; /** * Whenever the connection state of the room changes * * args: ([[ConnectionState]]) */ RoomEvent["ConnectionStateChanged"] = "connectionStateChanged"; /** * When participant has been moved to a different room by the service request. * The behavior looks like the participant has been disconnected and reconnected to a different room * seamlessly without connection state transition. * A new token will be provided for reconnecting to the new room if needed. * * args: ([[room: string, token: string]]) */ RoomEvent["Moved"] = "moved"; /** * When input or output devices on the machine have changed. */ RoomEvent["MediaDevicesChanged"] = "mediaDevicesChanged"; /** * When a [[RemoteParticipant]] joins *after* the local * participant. It will not emit events for participants that are already * in the room * * args: ([[RemoteParticipant]]) */ RoomEvent["ParticipantConnected"] = "participantConnected"; /** * When a [[RemoteParticipant]] leaves *after* the local * participant has joined. * * args: ([[RemoteParticipant]]) */ RoomEvent["ParticipantDisconnected"] = "participantDisconnected"; /** * When a new track is published to room *after* the local * participant has joined. It will not fire for tracks that are already published. * * A track published doesn't mean the participant has subscribed to it. It's * simply reflecting the state of the room. * * args: ([[RemoteTrackPublication]], [[RemoteParticipant]]) */ RoomEvent["TrackPublished"] = "trackPublished"; /** * The [[LocalParticipant]] has subscribed to a new track. This event will **always** * fire as long as new tracks are ready for use. * * args: ([[RemoteTrack]], [[RemoteTrackPublication]], [[RemoteParticipant]]) */ RoomEvent["TrackSubscribed"] = "trackSubscribed"; /** * Could not subscribe to a track * * args: (track sid, [[RemoteParticipant]]) */ RoomEvent["TrackSubscriptionFailed"] = "trackSubscriptionFailed"; /** * A [[RemoteParticipant]] has unpublished a track * * args: ([[RemoteTrackPublication]], [[RemoteParticipant]]) */ RoomEvent["TrackUnpublished"] = "trackUnpublished"; /** * A subscribed track is no longer available. Clients should listen to this * event and ensure they detach tracks. * * args: ([[Track]], [[RemoteTrackPublication]], [[RemoteParticipant]]) */ RoomEvent["TrackUnsubscribed"] = "trackUnsubscribed"; /** * A track that was muted, fires on both [[RemoteParticipant]]s and [[LocalParticipant]] * * args: ([[TrackPublication]], [[Participant]]) */ RoomEvent["TrackMuted"] = "trackMuted"; /** * A track that was unmuted, fires on both [[RemoteParticipant]]s and [[LocalParticipant]] * * args: ([[TrackPublication]], [[Participant]]) */ RoomEvent["TrackUnmuted"] = "trackUnmuted"; /** * A local track was published successfully. This event is helpful to know * when to update your local UI with the newly published track. * * args: ([[LocalTrackPublication]], [[LocalParticipant]]) */ RoomEvent["LocalTrackPublished"] = "localTrackPublished"; /** * A local track was unpublished. This event is helpful to know when to remove * the local track from your UI. * * When a user stops sharing their screen by pressing "End" on the browser UI, * this event will also fire. * * args: ([[LocalTrackPublication]], [[LocalParticipant]]) */ RoomEvent["LocalTrackUnpublished"] = "localTrackUnpublished"; /** * When a local audio track is published the SDK checks whether there is complete silence * on that track and emits the LocalAudioSilenceDetected event in that case. * This allows for applications to show UI informing users that they might have to * reset their audio hardware or check for proper device connectivity. */ RoomEvent["LocalAudioSilenceDetected"] = "localAudioSilenceDetected"; /** * Active speakers changed. List of speakers are ordered by their audio level. * loudest speakers first. This will include the LocalParticipant too. * * Speaker updates are sent only to the publishing participant and their subscribers. * * args: (Array<[[Participant]]>) */ RoomEvent["ActiveSpeakersChanged"] = "activeSpeakersChanged"; /** * Participant metadata is a simple way for app-specific state to be pushed to * all users. * When RoomService.UpdateParticipantMetadata is called to change a participant's * state, *all* participants in the room will fire this event. * * args: (prevMetadata: string, [[Participant]]) * */ RoomEvent["ParticipantMetadataChanged"] = "participantMetadataChanged"; /** * Participant's display name changed * * args: (name: string, [[Participant]]) * */ RoomEvent["ParticipantNameChanged"] = "participantNameChanged"; /** * Participant attributes is an app-specific key value state to be pushed to * all users. * When a participant's attributes changed, this event will be emitted with the changed attributes and the participant * args: (changedAttributes: [[Record backup === codec); } /** @deprecated Use {@link isBackupVideoCodec} instead */ const isBackupCodec = isBackupVideoCodec; var BackupCodecPolicy; (function (BackupCodecPolicy) { // codec regression is preferred, the sfu will try to regress codec if possible but not guaranteed BackupCodecPolicy[BackupCodecPolicy["PREFER_REGRESSION"] = 0] = "PREFER_REGRESSION"; // multi-codec simulcast, publish both primary and backup codec at the same time BackupCodecPolicy[BackupCodecPolicy["SIMULCAST"] = 1] = "SIMULCAST"; // always use backup codec only BackupCodecPolicy[BackupCodecPolicy["REGRESSION"] = 2] = "REGRESSION"; })(BackupCodecPolicy || (BackupCodecPolicy = {})); var AudioPresets; (function (AudioPresets) { AudioPresets.telephone = { maxBitrate: 12000 }; AudioPresets.speech = { maxBitrate: 24000 }; AudioPresets.music = { maxBitrate: 48000 }; AudioPresets.musicStereo = { maxBitrate: 64000 }; AudioPresets.musicHighQuality = { maxBitrate: 96000 }; AudioPresets.musicHighQualityStereo = { maxBitrate: 128000 }; })(AudioPresets || (AudioPresets = {})); /** * Sane presets for video resolution/encoding */ const VideoPresets = { h90: new VideoPreset(160, 90, 90000, 20), h180: new VideoPreset(320, 180, 160000, 20), h216: new VideoPreset(384, 216, 180000, 20), h360: new VideoPreset(640, 360, 450000, 20), h540: new VideoPreset(960, 540, 800000, 25), h720: new VideoPreset(1280, 720, 1700000, 30), h1080: new VideoPreset(1920, 1080, 3000000, 30), h1440: new VideoPreset(2560, 1440, 5000000, 30), h2160: new VideoPreset(3840, 2160, 8000000, 30) }; /** * Four by three presets */ const VideoPresets43 = { h120: new VideoPreset(160, 120, 70000, 20), h180: new VideoPreset(240, 180, 125000, 20), h240: new VideoPreset(320, 240, 140000, 20), h360: new VideoPreset(480, 360, 330000, 20), h480: new VideoPreset(640, 480, 500000, 20), h540: new VideoPreset(720, 540, 600000, 25), h720: new VideoPreset(960, 720, 1300000, 30), h1080: new VideoPreset(1440, 1080, 2300000, 30), h1440: new VideoPreset(1920, 1440, 3800000, 30) }; const ScreenSharePresets = { h360fps3: new VideoPreset(640, 360, 200000, 3, 'medium'), h360fps15: new VideoPreset(640, 360, 400000, 15, 'medium'), h720fps5: new VideoPreset(1280, 720, 800000, 5, 'medium'), h720fps15: new VideoPreset(1280, 720, 1500000, 15, 'medium'), h720fps30: new VideoPreset(1280, 720, 2000000, 30, 'medium'), h1080fps15: new VideoPreset(1920, 1080, 2500000, 15, 'medium'), h1080fps30: new VideoPreset(1920, 1080, 5000000, 30, 'medium'), // original resolution, without resizing original: new VideoPreset(0, 0, 7000000, 30, 'medium') };function mergeDefaultOptions(options, audioDefaults, videoDefaults) { var _a, _b; var _c, _d; const _extractProcessorsFro = extractProcessorsFromOptions(options !== null && options !== void 0 ? options : {}), optionsWithoutProcessor = _extractProcessorsFro.optionsWithoutProcessor, audioProcessor = _extractProcessorsFro.audioProcessor, videoProcessor = _extractProcessorsFro.videoProcessor; const defaultAudioProcessor = audioDefaults === null || audioDefaults === void 0 ? void 0 : audioDefaults.processor; const defaultVideoProcessor = videoDefaults === null || videoDefaults === void 0 ? void 0 : videoDefaults.processor; const clonedOptions = optionsWithoutProcessor !== null && optionsWithoutProcessor !== void 0 ? optionsWithoutProcessor : {}; if (clonedOptions.audio === true) clonedOptions.audio = {}; if (clonedOptions.video === true) clonedOptions.video = {}; // use defaults if (clonedOptions.audio) { mergeObjectWithoutOverwriting(clonedOptions.audio, audioDefaults); (_a = (_c = clonedOptions.audio).deviceId) !== null && _a !== void 0 ? _a : _c.deviceId = { ideal: 'default' }; if (audioProcessor || defaultAudioProcessor) { clonedOptions.audio.processor = audioProcessor !== null && audioProcessor !== void 0 ? audioProcessor : defaultAudioProcessor; } } if (clonedOptions.video) { mergeObjectWithoutOverwriting(clonedOptions.video, videoDefaults); (_b = (_d = clonedOptions.video).deviceId) !== null && _b !== void 0 ? _b : _d.deviceId = { ideal: 'default' }; if (videoProcessor || defaultVideoProcessor) { clonedOptions.video.processor = videoProcessor !== null && videoProcessor !== void 0 ? videoProcessor : defaultVideoProcessor; } } return clonedOptions; } function mergeObjectWithoutOverwriting(mainObject, objectToMerge) { Object.keys(objectToMerge).forEach(key => { if (mainObject[key] === undefined) mainObject[key] = objectToMerge[key]; }); return mainObject; } function constraintsForOptions(options) { var _a, _b; var _c, _d; const constraints = {}; if (options.video) { // default video options if (typeof options.video === 'object') { const videoOptions = {}; const target = videoOptions; const source = options.video; Object.keys(source).forEach(key => { switch (key) { case 'resolution': // flatten VideoResolution fields mergeObjectWithoutOverwriting(target, source.resolution); break; default: target[key] = source[key]; } }); constraints.video = videoOptions; (_a = (_c = constraints.video).deviceId) !== null && _a !== void 0 ? _a : _c.deviceId = { ideal: 'default' }; } else { constraints.video = options.video ? { deviceId: { ideal: 'default' } } : false; } } else { constraints.video = false; } if (options.audio) { if (typeof options.audio === 'object') { constraints.audio = options.audio; (_b = (_d = constraints.audio).deviceId) !== null && _b !== void 0 ? _b : _d.deviceId = { ideal: 'default' }; } else { constraints.audio = { deviceId: { ideal: 'default' } }; } } else { constraints.audio = false; } return constraints; } /** * This function detects silence on a given [[Track]] instance. * Returns true if the track seems to be entirely silent. */ function detectSilence(track_1) { return __awaiter(this, arguments, void 0, function (track) { let timeOffset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 200; return function* () { const ctx = getNewAudioContext(); if (ctx) { const analyser = ctx.createAnalyser(); analyser.fftSize = 2048; const bufferLength = analyser.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); const source = ctx.createMediaStreamSource(new MediaStream([track.mediaStreamTrack])); source.connect(analyser); yield sleep(timeOffset); analyser.getByteTimeDomainData(dataArray); const someNoise = dataArray.some(sample => sample !== 128 && sample !== 0); ctx.close(); return !someNoise; } return false; }(); }); } /** * @internal */ function getNewAudioContext() { var _a; const AudioContext = // @ts-ignore typeof window !== 'undefined' && (window.AudioContext || window.webkitAudioContext); if (AudioContext) { const audioContext = new AudioContext({ latencyHint: 'interactive' }); // If the audio context is suspended, we need to resume it when the user clicks on the page if (audioContext.state === 'suspended' && typeof window !== 'undefined' && ((_a = window.document) === null || _a === void 0 ? void 0 : _a.body)) { const handleResume = () => __awaiter(this, void 0, void 0, function* () { var _a; try { if (audioContext.state === 'suspended') { yield audioContext.resume(); } } catch (e) { console.warn('Error trying to auto-resume audio context', e); } finally { (_a = window.document.body) === null || _a === void 0 ? void 0 : _a.removeEventListener('click', handleResume); } }); // https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/statechange_event audioContext.addEventListener('statechange', () => { var _a; if (audioContext.state === 'closed') { (_a = window.document.body) === null || _a === void 0 ? void 0 : _a.removeEventListener('click', handleResume); } }); window.document.body.addEventListener('click', handleResume); } return audioContext; } } /** * @internal */ function kindToSource(kind) { if (kind === 'audioinput') { return Track.Source.Microphone; } else if (kind === 'videoinput') { return Track.Source.Camera; } else { return Track.Source.Unknown; } } /** * @internal */ function sourceToKind(source) { if (source === Track.Source.Microphone) { return 'audioinput'; } else if (source === Track.Source.Camera) { return 'videoinput'; } else { return undefined; } } /** * @internal */ function screenCaptureToDisplayMediaStreamOptions(options) { var _a, _b; let videoConstraints = (_a = options.video) !== null && _a !== void 0 ? _a : true; // treat 0 as uncapped if (options.resolution && options.resolution.width > 0 && options.resolution.height > 0) { videoConstraints = typeof videoConstraints === 'boolean' ? {} : videoConstraints; if (isSafari()) { videoConstraints = Object.assign(Object.assign({}, videoConstraints), { width: { max: options.resolution.width }, height: { max: options.resolution.height }, frameRate: options.resolution.frameRate }); } else { videoConstraints = Object.assign(Object.assign({}, videoConstraints), { width: { ideal: options.resolution.width }, height: { ideal: options.resolution.height }, frameRate: options.resolution.frameRate }); } } return { audio: (_b = options.audio) !== null && _b !== void 0 ? _b : false, video: videoConstraints, // @ts-expect-error support for experimental display media features controller: options.controller, selfBrowserSurface: options.selfBrowserSurface, surfaceSwitching: options.surfaceSwitching, systemAudio: options.systemAudio, preferCurrentTab: options.preferCurrentTab }; } function mimeTypeToVideoCodecString(mimeType) { return mimeType.split('/')[1].toLowerCase(); } function getTrackPublicationInfo(tracks) { const infos = []; tracks.forEach(track => { if (track.track !== undefined) { infos.push(new TrackPublishedResponse({ cid: track.track.mediaStreamID, track: track.trackInfo })); } }); return infos; } function getLogContextFromTrack(track) { if ('mediaStreamTrack' in track) { return { trackID: track.sid, source: track.source, muted: track.isMuted, enabled: track.mediaStreamTrack.enabled, kind: track.kind, streamID: track.mediaStreamID, streamTrackID: track.mediaStreamTrack.id }; } else { return { trackID: track.trackSid, enabled: track.isEnabled, muted: track.isMuted, trackInfo: Object.assign({ mimeType: track.mimeType, name: track.trackName, encrypted: track.isEncrypted, kind: track.kind, source: track.source }, track.track ? getLogContextFromTrack(track.track) : {}) }; } } function supportsSynchronizationSources() { return typeof RTCRtpReceiver !== 'undefined' && typeof RTCRtpReceiver.prototype.getSynchronizationSources === 'function'; } function diffAttributes(oldValues, newValues) { var _a; if (oldValues === undefined) { oldValues = {}; } if (newValues === undefined) { newValues = {}; } const allKeys = [...Object.keys(newValues), ...Object.keys(oldValues)]; const diff = {}; for (const key of allKeys) { if (oldValues[key] !== newValues[key]) { diff[key] = (_a = newValues[key]) !== null && _a !== void 0 ? _a : ''; } } return diff; } /** @internal */ function extractProcessorsFromOptions(options) { const newOptions = Object.assign({}, options); let audioProcessor; let videoProcessor; if (typeof newOptions.audio === 'object' && newOptions.audio.processor) { audioProcessor = newOptions.audio.processor; newOptions.audio = Object.assign(Object.assign({}, newOptions.audio), { processor: undefined }); } if (typeof newOptions.video === 'object' && newOptions.video.processor) { videoProcessor = newOptions.video.processor; newOptions.video = Object.assign(Object.assign({}, newOptions.video), { processor: undefined }); } return { audioProcessor, videoProcessor, optionsWithoutProcessor: cloneDeep(newOptions) }; } function getTrackSourceFromProto(source) { switch (source) { case TrackSource.CAMERA: return Track.Source.Camera; case TrackSource.MICROPHONE: return Track.Source.Microphone; case TrackSource.SCREEN_SHARE: return Track.Source.ScreenShare; case TrackSource.SCREEN_SHARE_AUDIO: return Track.Source.ScreenShareAudio; default: return Track.Source.Unknown; } } function areDimensionsSmaller(a, b) { return a.width * a.height < b.width * b.height; } function layerDimensionsFor(trackInfo, quality) { var _a; return (_a = trackInfo.layers) === null || _a === void 0 ? void 0 : _a.find(l => l.quality === quality); }const BACKGROUND_REACTION_DELAY = 5000; // keep old audio elements when detached, we would re-use them since on iOS // Safari tracks which audio elements have been "blessed" by the user. const recycledElements = []; var VideoQuality; (function (VideoQuality) { VideoQuality[VideoQuality["LOW"] = 0] = "LOW"; VideoQuality[VideoQuality["MEDIUM"] = 1] = "MEDIUM"; VideoQuality[VideoQuality["HIGH"] = 2] = "HIGH"; })(VideoQuality || (VideoQuality = {})); class Track extends eventsExports.EventEmitter { /** * indicates current state of stream, it'll indicate `paused` if the track * has been paused by congestion controller */ get streamState() { return this._streamState; } /** @internal */ setStreamState(value) { this._streamState = value; } constructor(mediaTrack, kind) { let loggerOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var _a; super(); this.attachedElements = []; this.isMuted = false; this._streamState = Track.StreamState.Active; this.isInBackground = false; this._currentBitrate = 0; this.log = livekitLogger; this.appVisibilityChangedListener = () => { if (this.backgroundTimeout) { clearTimeout(this.backgroundTimeout); } // delay app visibility update if it goes to hidden // update immediately if it comes back to focus if (document.visibilityState === 'hidden') { this.backgroundTimeout = setTimeout(() => this.handleAppVisibilityChanged(), BACKGROUND_REACTION_DELAY); } else { this.handleAppVisibilityChanged(); } }; this.log = getLogger((_a = loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.Track); this.loggerContextCb = loggerOptions.loggerContextCb; this.setMaxListeners(100); this.kind = kind; this._mediaStreamTrack = mediaTrack; this._mediaStreamID = mediaTrack.id; this.source = Track.Source.Unknown; } get logContext() { var _a; return Object.assign(Object.assign({}, (_a = this.loggerContextCb) === null || _a === void 0 ? void 0 : _a.call(this)), getLogContextFromTrack(this)); } /** current receive bits per second */ get currentBitrate() { return this._currentBitrate; } get mediaStreamTrack() { return this._mediaStreamTrack; } /** * @internal * used for keep mediaStream's first id, since it's id might change * if we disable/enable a track */ get mediaStreamID() { return this._mediaStreamID; } attach(element) { let elementType = 'audio'; if (this.kind === Track.Kind.Video) { elementType = 'video'; } if (this.attachedElements.length === 0 && this.kind === Track.Kind.Video) { this.addAppVisibilityListener(); } if (!element) { if (elementType === 'audio') { recycledElements.forEach(e => { if (e.parentElement === null && !element) { element = e; } }); if (element) { // remove it from pool recycledElements.splice(recycledElements.indexOf(element), 1); } } if (!element) { element = document.createElement(elementType); } } if (!this.attachedElements.includes(element)) { this.attachedElements.push(element); } // even if we believe it's already attached to the element, it's possible // the element's srcObject was set to something else out of band. // we'll want to re-attach it in that case attachToElement(this.mediaStreamTrack, element); // handle auto playback failures const allMediaStreamTracks = element.srcObject.getTracks(); const hasAudio = allMediaStreamTracks.some(tr => tr.kind === 'audio'); // manually play media to detect auto playback status element.play().then(() => { this.emit(hasAudio ? TrackEvent.AudioPlaybackStarted : TrackEvent.VideoPlaybackStarted); }).catch(e => { if (e.name === 'NotAllowedError') { this.emit(hasAudio ? TrackEvent.AudioPlaybackFailed : TrackEvent.VideoPlaybackFailed, e); } else if (e.name === 'AbortError') { // commonly triggered by another `play` request, only log for debugging purposes livekitLogger.debug("".concat(hasAudio ? 'audio' : 'video', " playback aborted, likely due to new play request")); } else { livekitLogger.warn("could not playback ".concat(hasAudio ? 'audio' : 'video'), e); } // If audio playback isn't allowed make sure we still play back the video if (hasAudio && element && allMediaStreamTracks.some(tr => tr.kind === 'video') && e.name === 'NotAllowedError') { element.muted = true; element.play().catch(() => { // catch for Safari, exceeded options at this point to automatically play the media element }); } }); this.emit(TrackEvent.ElementAttached, element); return element; } detach(element) { try { // detach from a single element if (element) { detachTrack(this.mediaStreamTrack, element); const idx = this.attachedElements.indexOf(element); if (idx >= 0) { this.attachedElements.splice(idx, 1); this.recycleElement(element); this.emit(TrackEvent.ElementDetached, element); } return element; } const detached = []; this.attachedElements.forEach(elm => { detachTrack(this.mediaStreamTrack, elm); detached.push(elm); this.recycleElement(elm); this.emit(TrackEvent.ElementDetached, elm); }); // remove all tracks this.attachedElements = []; return detached; } finally { if (this.attachedElements.length === 0) { this.removeAppVisibilityListener(); } } } stop() { this.stopMonitor(); this._mediaStreamTrack.stop(); } enable() { this._mediaStreamTrack.enabled = true; } disable() { this._mediaStreamTrack.enabled = false; } /* @internal */ stopMonitor() { if (this.monitorInterval) { clearInterval(this.monitorInterval); } if (this.timeSyncHandle) { cancelAnimationFrame(this.timeSyncHandle); } } /** @internal */ updateLoggerOptions(loggerOptions) { if (loggerOptions.loggerName) { this.log = getLogger(loggerOptions.loggerName); } if (loggerOptions.loggerContextCb) { this.loggerContextCb = loggerOptions.loggerContextCb; } } recycleElement(element) { if (element instanceof HTMLAudioElement) { // we only need to re-use a single element let shouldCache = true; element.pause(); recycledElements.forEach(e => { if (!e.parentElement) { shouldCache = false; } }); if (shouldCache) { recycledElements.push(element); } } } handleAppVisibilityChanged() { return __awaiter(this, void 0, void 0, function* () { this.isInBackground = document.visibilityState === 'hidden'; if (!this.isInBackground && this.kind === Track.Kind.Video) { setTimeout(() => this.attachedElements.forEach(el => el.play().catch(() => { /** catch clause necessary for Safari */ })), 0); } }); } addAppVisibilityListener() { if (isWeb()) { this.isInBackground = document.visibilityState === 'hidden'; document.addEventListener('visibilitychange', this.appVisibilityChangedListener); } else { this.isInBackground = false; } } removeAppVisibilityListener() { if (isWeb()) { document.removeEventListener('visibilitychange', this.appVisibilityChangedListener); } } } function attachToElement(track, element) { let mediaStream; if (element.srcObject instanceof MediaStream) { mediaStream = element.srcObject; } else { mediaStream = new MediaStream(); } // check if track matches existing track let existingTracks; if (track.kind === 'audio') { existingTracks = mediaStream.getAudioTracks(); } else { existingTracks = mediaStream.getVideoTracks(); } if (!existingTracks.includes(track)) { existingTracks.forEach(et => { mediaStream.removeTrack(et); }); mediaStream.addTrack(track); } if (!isSafari() || !(element instanceof HTMLVideoElement)) { // when in low power mode (applies to both macOS and iOS), Safari will show a play/pause overlay // when a video starts that has the `autoplay` attribute is set. // we work around this by _not_ setting the autoplay attribute on safari and instead call `setTimeout(() => el.play(),0)` further down element.autoplay = true; } // In case there are no audio tracks present on the mediastream, we set the element as muted to ensure autoplay works element.muted = mediaStream.getAudioTracks().length === 0; if (element instanceof HTMLVideoElement) { element.playsInline = true; } // avoid flicker if (element.srcObject !== mediaStream) { element.srcObject = mediaStream; if ((isSafari() || isFireFox()) && element instanceof HTMLVideoElement) { // Firefox also has a timing issue where video doesn't actually get attached unless // performed out-of-band // Safari 15 has a bug where in certain layouts, video element renders // black until the page is resized or other changes take place. // Resetting the src triggers it to render. // https://developer.apple.com/forums/thread/690523 setTimeout(() => { element.srcObject = mediaStream; // Safari 15 sometimes fails to start a video // when the window is backgrounded before the first frame is drawn // manually calling play here seems to fix that element.play().catch(() => { /** do nothing */ }); }, 0); } } } /** @internal */ function detachTrack(track, element) { if (element.srcObject instanceof MediaStream) { const mediaStream = element.srcObject; mediaStream.removeTrack(track); if (mediaStream.getTracks().length > 0) { element.srcObject = mediaStream; } else { element.srcObject = null; } } } (function (Track) { let Kind; (function (Kind) { Kind["Audio"] = "audio"; Kind["Video"] = "video"; Kind["Unknown"] = "unknown"; })(Kind = Track.Kind || (Track.Kind = {})); let Source; (function (Source) { Source["Camera"] = "camera"; Source["Microphone"] = "microphone"; Source["ScreenShare"] = "screen_share"; Source["ScreenShareAudio"] = "screen_share_audio"; Source["Unknown"] = "unknown"; })(Source = Track.Source || (Track.Source = {})); let StreamState$1; (function (StreamState) { StreamState["Active"] = "active"; StreamState["Paused"] = "paused"; StreamState["Unknown"] = "unknown"; })(StreamState$1 = Track.StreamState || (Track.StreamState = {})); /** @internal */ function kindToProto(k) { switch (k) { case Kind.Audio: return TrackType.AUDIO; case Kind.Video: return TrackType.VIDEO; default: // FIXME this was UNRECOGNIZED before return TrackType.DATA; } } Track.kindToProto = kindToProto; /** @internal */ function kindFromProto(t) { switch (t) { case TrackType.AUDIO: return Kind.Audio; case TrackType.VIDEO: return Kind.Video; default: return Kind.Unknown; } } Track.kindFromProto = kindFromProto; /** @internal */ function sourceToProto(s) { switch (s) { case Source.Camera: return TrackSource.CAMERA; case Source.Microphone: return TrackSource.MICROPHONE; case Source.ScreenShare: return TrackSource.SCREEN_SHARE; case Source.ScreenShareAudio: return TrackSource.SCREEN_SHARE_AUDIO; default: return TrackSource.UNKNOWN; } } Track.sourceToProto = sourceToProto; /** @internal */ function sourceFromProto(s) { switch (s) { case TrackSource.CAMERA: return Source.Camera; case TrackSource.MICROPHONE: return Source.Microphone; case TrackSource.SCREEN_SHARE: return Source.ScreenShare; case TrackSource.SCREEN_SHARE_AUDIO: return Source.ScreenShareAudio; default: return Source.Unknown; } } Track.sourceFromProto = sourceFromProto; /** @internal */ function streamStateFromProto(s) { switch (s) { case StreamState.ACTIVE: return StreamState$1.Active; case StreamState.PAUSED: return StreamState$1.Paused; default: return StreamState$1.Unknown; } } Track.streamStateFromProto = streamStateFromProto; })(Track || (Track = {}));const separator = '|'; const ddExtensionURI = 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension'; function unpackStreamId(packed) { const parts = packed.split(separator); if (parts.length > 1) { return [parts[0], packed.substr(parts[0].length + 1)]; } return [packed, '']; } function sleep(duration) { return new TypedPromise(resolve => CriticalTimers.setTimeout(resolve, duration)); } /** @internal */ function supportsTransceiver() { return 'addTransceiver' in RTCPeerConnection.prototype; } /** @internal */ function supportsAddTrack() { return 'addTrack' in RTCPeerConnection.prototype; } function supportsAdaptiveStream() { return typeof ResizeObserver !== undefined && typeof IntersectionObserver !== undefined; } function supportsDynacast() { return supportsTransceiver(); } function supportsAV1() { if (!('getCapabilities' in RTCRtpSender)) { return false; } if (isSafari() || isFireFox()) { // Safari 17 on iPhone14 reports AV1 capability, but does not actually support it // Firefox does support AV1, but SVC publishing is not supported return false; } const capabilities = RTCRtpSender.getCapabilities('video'); let hasAV1 = false; if (capabilities) { for (const codec of capabilities.codecs) { if (codec.mimeType.toLowerCase() === 'video/av1') { hasAV1 = true; break; } } } return hasAV1; } function supportsVP9() { if (!('getCapabilities' in RTCRtpSender)) { return false; } if (isFireFox()) { // technically speaking FireFox supports VP9, but SVC publishing is broken // https://bugzilla.mozilla.org/show_bug.cgi?id=1633876 return false; } if (isSafari()) { const browser = getBrowser(); if ((browser === null || browser === void 0 ? void 0 : browser.version) && compareVersions(browser.version, '16') < 0) { // Safari 16 and below does not support VP9 return false; } if ((browser === null || browser === void 0 ? void 0 : browser.os) === 'iOS' && (browser === null || browser === void 0 ? void 0 : browser.osVersion) && compareVersions(browser.osVersion, '16') < 0) { // Safari 16 and below on iOS does not support VP9 we need the iOS check to account for other browsers running webkit under the hood return false; } } const capabilities = RTCRtpSender.getCapabilities('video'); let hasVP9 = false; if (capabilities) { for (const codec of capabilities.codecs) { if (codec.mimeType.toLowerCase() === 'video/vp9') { hasVP9 = true; break; } } } return hasVP9; } function isSVCCodec(codec) { return codec === 'av1' || codec === 'vp9'; } function supportsSetSinkId(elm) { if (!document || isSafariBased()) { return false; } if (!elm) { elm = document.createElement('audio'); } return 'setSinkId' in elm; } /** * Checks whether or not setting an audio output via {@link Room#setActiveDevice} * is supported for the current browser. */ function supportsAudioOutputSelection() { // Note: this is method publicly exported under a user friendly name and currently only proxying `supportsSetSinkId` return supportsSetSinkId(); } function isBrowserSupported() { if (typeof RTCPeerConnection === 'undefined') { return false; } return supportsTransceiver() || supportsAddTrack(); } function isFireFox() { var _a; return ((_a = getBrowser()) === null || _a === void 0 ? void 0 : _a.name) === 'Firefox'; } function isChromiumBased() { const browser = getBrowser(); return !!browser && browser.name === 'Chrome' && browser.os !== 'iOS'; } function isScriptTransformSupportedForWorker() { // Chrome occasionally throws an `InvalidState` error when using script transforms directly after introducing this API in 141. // Disabling it for Chrome based browsers until the API has stabilized. // @ts-ignore return typeof window !== 'undefined' && // @ts-ignore typeof window.RTCRtpScriptTransform !== 'undefined' && !isChromiumBased(); } function isSafari() { var _a; return ((_a = getBrowser()) === null || _a === void 0 ? void 0 : _a.name) === 'Safari'; } function isSafariBased() { const b = getBrowser(); return (b === null || b === void 0 ? void 0 : b.name) === 'Safari' || (b === null || b === void 0 ? void 0 : b.os) === 'iOS'; } function isSafari17Based() { const b = getBrowser(); return (b === null || b === void 0 ? void 0 : b.name) === 'Safari' && b.version.startsWith('17.') || (b === null || b === void 0 ? void 0 : b.os) === 'iOS' && !!(b === null || b === void 0 ? void 0 : b.osVersion) && compareVersions(b.osVersion, '17') >= 0; } function isSafariSvcApi(browser) { if (!browser) { browser = getBrowser(); } // Safari 18.4 requires legacy svc api and scaleResolutionDown to be set return (browser === null || browser === void 0 ? void 0 : browser.name) === 'Safari' && compareVersions(browser.version, '18.3') > 0 || (browser === null || browser === void 0 ? void 0 : browser.os) === 'iOS' && !!(browser === null || browser === void 0 ? void 0 : browser.osVersion) && compareVersions(browser.osVersion, '18.3') > 0; } function isMobile() { var _a, _b; if (!isWeb()) return false; return ( // @ts-expect-error `userAgentData` is not yet part of typescript (_b = (_a = navigator.userAgentData) === null || _a === void 0 ? void 0 : _a.mobile) !== null && _b !== void 0 ? _b : /Tablet|iPad|Mobile|Android|BlackBerry/.test(navigator.userAgent) ); } function isE2EESimulcastSupported() { const browser = getBrowser(); const supportedSafariVersion = '17.2'; // see https://bugs.webkit.org/show_bug.cgi?id=257803 if (browser) { if (browser.name !== 'Safari' && browser.os !== 'iOS') { return true; } else if (browser.os === 'iOS' && browser.osVersion && compareVersions(browser.osVersion, supportedSafariVersion) >= 0) { return true; } else if (browser.name === 'Safari' && compareVersions(browser.version, supportedSafariVersion) >= 0) { return true; } else { return false; } } } function isWeb() { return typeof document !== 'undefined'; } function isReactNative() { // navigator.product is deprecated on browsers, but will be set appropriately for react-native. return navigator.product == 'ReactNative'; } function isCloud(serverUrl) { return serverUrl.hostname.endsWith('.livekit.cloud') || serverUrl.hostname.endsWith('.livekit.run'); } function extractProjectFromUrl(serverUrl) { if (!isCloud(serverUrl)) { return null; } return serverUrl.hostname.split('.')[0]; } function getLKReactNativeInfo() { // global defined only for ReactNative. // @ts-ignore if (global && global.LiveKitReactNativeGlobal) { // @ts-ignore return global.LiveKitReactNativeGlobal; } return undefined; } function getReactNativeOs() { if (!isReactNative()) { return undefined; } let info = getLKReactNativeInfo(); if (info) { return info.platform; } return undefined; } function getDevicePixelRatio() { if (isWeb()) { return window.devicePixelRatio; } if (isReactNative()) { let info = getLKReactNativeInfo(); if (info) { return info.devicePixelRatio; } } return 1; } /** * @param v1 - The first version string to compare. * @param v2 - The second version string to compare. * @returns A number indicating the order of the versions: * - 1 if v1 is greater than v2 * - -1 if v1 is less than v2 * - 0 if v1 and v2 are equal */ function compareVersions(v1, v2) { const parts1 = v1.split('.'); const parts2 = v2.split('.'); const k = Math.min(parts1.length, parts2.length); for (let i = 0; i < k; ++i) { const p1 = parseInt(parts1[i], 10); const p2 = parseInt(parts2[i], 10); if (p1 > p2) return 1; if (p1 < p2) return -1; if (i === k - 1 && p1 === p2) return 0; } if (v1 === '' && v2 !== '') { return -1; } else if (v2 === '') { return 1; } return parts1.length == parts2.length ? 0 : parts1.length < parts2.length ? -1 : 1; } function roDispatchCallback(entries) { for (const entry of entries) { entry.target.handleResize(entry); } } function ioDispatchCallback(entries) { for (const entry of entries) { entry.target.handleVisibilityChanged(entry); } } let resizeObserver = null; const getResizeObserver = () => { if (!resizeObserver) resizeObserver = new ResizeObserver(roDispatchCallback); return resizeObserver; }; let intersectionObserver = null; const getIntersectionObserver = () => { if (!intersectionObserver) { intersectionObserver = new IntersectionObserver(ioDispatchCallback, { root: null, rootMargin: '0px' }); } return intersectionObserver; }; function getClientInfo(capabilities) { var _a; const info = new ClientInfo({ capabilities, sdk: ClientInfo_SDK.JS, protocol: protocolVersion, clientProtocol, version }); if (isReactNative()) { info.os = (_a = getReactNativeOs()) !== null && _a !== void 0 ? _a : ''; } return info; } let emptyVideoStreamTrack; function getEmptyVideoStreamTrack() { if (!emptyVideoStreamTrack) { emptyVideoStreamTrack = createDummyVideoStreamTrack(); } return emptyVideoStreamTrack.clone(); } function createDummyVideoStreamTrack() { let width = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 16; let height = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 16; let enabled = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; let paintContent = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; const canvas = document.createElement('canvas'); // the canvas size is set to 16 by default, because electron apps seem to fail with smaller values canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); ctx === null || ctx === void 0 ? void 0 : ctx.fillRect(0, 0, canvas.width, canvas.height); if (paintContent && ctx) { ctx.beginPath(); ctx.arc(width / 2, height / 2, 50, 0, Math.PI * 2, true); ctx.closePath(); ctx.fillStyle = 'grey'; ctx.fill(); } // @ts-ignore const dummyStream = canvas.captureStream(); const _dummyStream$getTrack = dummyStream.getTracks(), _dummyStream$getTrack2 = _slicedToArray(_dummyStream$getTrack, 1), dummyTrack = _dummyStream$getTrack2[0]; if (!dummyTrack) { throw Error('Could not get empty media stream video track'); } dummyTrack.enabled = enabled; return dummyTrack; } let emptyAudioStreamTrack; function getEmptyAudioStreamTrack() { if (!emptyAudioStreamTrack) { // implementation adapted from https://blog.mozilla.org/webrtc/warm-up-with-replacetrack/ const ctx = new AudioContext(); const oscillator = ctx.createOscillator(); const gain = ctx.createGain(); gain.gain.setValueAtTime(0, 0); const dst = ctx.createMediaStreamDestination(); oscillator.connect(gain); gain.connect(dst); oscillator.start(); var _dst$stream$getAudioT = dst.stream.getAudioTracks(); var _dst$stream$getAudioT2 = _slicedToArray(_dst$stream$getAudioT, 1); emptyAudioStreamTrack = _dst$stream$getAudioT2[0]; if (!emptyAudioStreamTrack) { throw Error('Could not get empty media stream audio track'); } emptyAudioStreamTrack.enabled = false; } return emptyAudioStreamTrack.clone(); } /** An object that represents a serialized version of a `new Promise((resolve, reject) => {})` * constructor. Wait for a promise resolution with `await future.promise` and explicitly resolve or * reject the inner promise with `future.resolve(...)` or `future.reject(...)`. */ class Future { get isResolved() { return this._isResolved; } constructor(futureBase, onFinally) { this._isResolved = false; this.onFinally = onFinally; this.promise = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { this.resolve = resolve; this.reject = reject; if (futureBase) { yield futureBase(resolve, reject); } })).finally(() => { var _a; this._isResolved = true; (_a = this.onFinally) === null || _a === void 0 ? void 0 : _a.call(this); }); } } /** * Creates and returns an analyser web audio node that is attached to the provided track. * Additionally returns a convenience method `calculateVolume` to perform instant volume readings on that track. * Call the returned `cleanup` function to close the audioContext that has been created for the instance of this helper */ function createAudioAnalyser(track, options) { const opts = Object.assign({ cloneTrack: false, fftSize: 2048, smoothingTimeConstant: 0.8, minDecibels: -100, maxDecibels: -80 }, options); const audioContext = getNewAudioContext(); if (!audioContext) { throw new Error('Audio Context not supported on this browser'); } const streamTrack = opts.cloneTrack ? track.mediaStreamTrack.clone() : track.mediaStreamTrack; const mediaStreamSource = audioContext.createMediaStreamSource(new MediaStream([streamTrack])); const analyser = audioContext.createAnalyser(); analyser.minDecibels = opts.minDecibels; analyser.maxDecibels = opts.maxDecibels; analyser.fftSize = opts.fftSize; analyser.smoothingTimeConstant = opts.smoothingTimeConstant; mediaStreamSource.connect(analyser); const dataArray = new Uint8Array(analyser.frequencyBinCount); /** * Calculates the current volume of the track in the range from 0 to 1 */ const calculateVolume = () => { analyser.getByteFrequencyData(dataArray); let sum = 0; for (const amplitude of dataArray) { sum += Math.pow(amplitude / 255, 2); } const volume = Math.sqrt(sum / dataArray.length); return volume; }; const cleanup = () => __awaiter(this, void 0, void 0, function* () { yield audioContext.close(); if (opts.cloneTrack) { streamTrack.stop(); } }); return { calculateVolume, analyser, cleanup }; } function isAudioCodec(maybeCodec) { return audioCodecs.includes(maybeCodec); } function isVideoCodec(maybeCodec) { return videoCodecs.includes(maybeCodec); } function unwrapConstraint(constraint) { if (typeof constraint === 'string' || typeof constraint === 'number') { return constraint; } if (Array.isArray(constraint)) { return constraint[0]; } if (constraint.exact !== undefined) { if (Array.isArray(constraint.exact)) { return constraint.exact[0]; } return constraint.exact; } if (constraint.ideal !== undefined) { if (Array.isArray(constraint.ideal)) { return constraint.ideal[0]; } return constraint.ideal; } throw Error('could not unwrap constraint'); } function toWebsocketUrl(url) { if (url.startsWith('http')) { return url.replace(/^(http)/, 'ws'); } return url; } function toHttpUrl(url) { if (url.startsWith('ws')) { return url.replace(/^(ws)/, 'http'); } return url; } function extractTranscriptionSegments(transcription, firstReceivedTimesMap) { return transcription.segments.map(_ref => { let id = _ref.id, text = _ref.text, language = _ref.language, startTime = _ref.startTime, endTime = _ref.endTime, final = _ref.final; var _a; const firstReceivedTime = (_a = firstReceivedTimesMap.get(id)) !== null && _a !== void 0 ? _a : Date.now(); const lastReceivedTime = Date.now(); if (final) { firstReceivedTimesMap.delete(id); } else { firstReceivedTimesMap.set(id, firstReceivedTime); } return { id, text, startTime: Number.parseInt(startTime.toString()), endTime: Number.parseInt(endTime.toString()), final, language, firstReceivedTime, lastReceivedTime }; }); } function extractChatMessage(msg) { const id = msg.id, timestamp = msg.timestamp, message = msg.message, editTimestamp = msg.editTimestamp; return { id, timestamp: Number.parseInt(timestamp.toString()), editTimestamp: editTimestamp ? Number.parseInt(editTimestamp.toString()) : undefined, message }; } function getDisconnectReasonFromConnectionError(e) { switch (e.reason) { case ConnectionErrorReason.LeaveRequest: return e.context; case ConnectionErrorReason.Cancelled: return DisconnectReason.CLIENT_INITIATED; case ConnectionErrorReason.NotAllowed: return DisconnectReason.USER_REJECTED; case ConnectionErrorReason.ServerUnreachable: return DisconnectReason.JOIN_FAILURE; default: return DisconnectReason.UNKNOWN_REASON; } } /** convert bigints to numbers preserving undefined values */ function bigIntToNumber(value) { return value !== undefined ? Number(value) : undefined; } /** convert numbers to bigints preserving undefined values */ function numberToBigInt(value) { return value !== undefined ? BigInt(value) : undefined; } function isLocalTrack(track) { return !!track && !(track instanceof MediaStreamTrack) && track.isLocal; } function isAudioTrack(track) { return !!track && track.kind == Track.Kind.Audio; } function isVideoTrack(track) { return !!track && track.kind == Track.Kind.Video; } function isLocalVideoTrack(track) { return isLocalTrack(track) && isVideoTrack(track); } function isLocalAudioTrack(track) { return isLocalTrack(track) && isAudioTrack(track); } function isRemoteTrack(track) { return !!track && !track.isLocal; } function isRemotePub(pub) { return !!pub && !pub.isLocal; } function isRemoteVideoTrack(track) { return isRemoteTrack(track) && isVideoTrack(track); } function isLocalParticipant(p) { return p.isLocal; } function isRemoteParticipant(p) { return !p.isLocal; } function splitUtf8(s, n) { // adapted from https://stackoverflow.com/a/6043797 const result = []; let encoded = new TextEncoder().encode(s); while (encoded.length > n) { let k = n; while (k > 0) { const byte = encoded[k]; if (byte !== undefined && (byte & 0xc0) !== 0x80) { break; } k--; } result.push(encoded.slice(0, k)); encoded = encoded.slice(k); } if (encoded.length > 0) { result.push(encoded); } return result; } function extractMaxAgeFromRequestHeaders(headers) { var _a; const cacheControl = headers.get('Cache-Control'); if (cacheControl) { const maxAge = (_a = cacheControl.match(/(?:^|[,\s])max-age=(\d+)/)) === null || _a === void 0 ? void 0 : _a[1]; if (maxAge) { return parseInt(maxAge, 10); } } return undefined; } function isCompressionStreamSupported() { return typeof CompressionStream !== 'undefined'; } function isPublisherOfferWithJoinSupported() { // we have connectivity issue about publisher offer with join on firefox #1919 return isCompressionStreamSupported() && !isFireFox(); }function createRtcUrl(url, searchParams) { let useV0Path = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; const v0Url = createV0RtcUrl(url, searchParams); if (useV0Path) { return v0Url; } else { return appendUrlPath(v0Url, 'v1'); } } function createV0RtcUrl(url, searchParams) { const urlObj = new URL(toWebsocketUrl(url)); searchParams.forEach((value, key) => { urlObj.searchParams.set(key, value); }); return appendUrlPath(urlObj, 'rtc'); } function createValidateUrl(rtcWsUrl) { const urlObj = new URL(toHttpUrl(rtcWsUrl)); return appendUrlPath(urlObj, 'validate'); } function ensureTrailingSlash(path) { return path.endsWith('/') ? path : "".concat(path, "/"); } function appendUrlPath(urlObj, path) { urlObj.pathname = "".concat(ensureTrailingSlash(urlObj.pathname)).concat(path); return urlObj; } function parseSignalResponse(value) { if (typeof value === 'string') { return SignalResponse.fromJson(JSON.parse(value), { ignoreUnknownFields: true }); } else if (value instanceof ArrayBuffer) { return SignalResponse.fromBinary(new Uint8Array(value)); } throw new Error("could not decode websocket message: ".concat(typeof value)); } function getAbortReasonAsString(signal) { let defaultMessage = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'Unknown reason'; if (!(signal instanceof AbortSignal)) { return defaultMessage; } const reason = signal.reason; switch (typeof reason) { case 'string': return reason; case 'object': return reason instanceof Error ? reason.message : defaultMessage; default: return 'toString' in reason ? reason.toString() : defaultMessage; } }const ENCRYPTION_ALGORITHM = 'AES-GCM'; // How many consecutive frames can fail decrypting before a particular key gets marked as invalid const DECRYPTION_FAILURE_TOLERANCE = 10; // flag set to indicate that e2ee has been setup for sender/receiver; const E2EE_FLAG = 'lk_e2ee'; const SALT = 'LKFrameEncryptionKey'; const KEY_PROVIDER_DEFAULTS = { sharedKey: false, ratchetSalt: SALT, ratchetWindowSize: 8, failureTolerance: DECRYPTION_FAILURE_TOLERANCE, keyringSize: 16, keySize: 128 };var KeyProviderEvent; (function (KeyProviderEvent) { KeyProviderEvent["SetKey"] = "setKey"; /** Event for requesting to ratchet the key used to encrypt the stream */ KeyProviderEvent["RatchetRequest"] = "ratchetRequest"; /** Emitted when a key is ratcheted. Could be after auto-ratcheting on decryption failure or * following a `RatchetRequest`, will contain the ratcheted key material */ KeyProviderEvent["KeyRatcheted"] = "keyRatcheted"; })(KeyProviderEvent || (KeyProviderEvent = {})); var KeyHandlerEvent; (function (KeyHandlerEvent) { /** Emitted when a key has been ratcheted. Is emitted when any key has been ratcheted * i.e. when the FrameCryptor tried to ratchet when decryption is failing */ KeyHandlerEvent["KeyRatcheted"] = "keyRatcheted"; })(KeyHandlerEvent || (KeyHandlerEvent = {})); var EncryptionEvent; (function (EncryptionEvent) { EncryptionEvent["ParticipantEncryptionStatusChanged"] = "participantEncryptionStatusChanged"; EncryptionEvent["EncryptionError"] = "encryptionError"; })(EncryptionEvent || (EncryptionEvent = {})); var CryptorEvent; (function (CryptorEvent) { CryptorEvent["Error"] = "cryptorError"; })(CryptorEvent || (CryptorEvent = {}));function isE2EESupported() { return isInsertableStreamSupported() || isScriptTransformSupported(); } function isScriptTransformSupported() { // @ts-ignore return typeof window !== 'undefined' && typeof window.RTCRtpScriptTransform !== 'undefined'; } function isInsertableStreamSupported() { return typeof window !== 'undefined' && typeof window.RTCRtpSender !== 'undefined' && // @ts-ignore typeof window.RTCRtpSender.prototype.createEncodedStreams !== 'undefined'; } function isVideoFrame(frame) { return 'type' in frame; } function importKey(keyBytes_1) { return __awaiter(this, arguments, void 0, function (keyBytes) { let algorithm = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { name: ENCRYPTION_ALGORITHM }; let usage = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'encrypt'; return function* () { // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey return crypto.subtle.importKey('raw', keyBytes, algorithm, false, usage === 'derive' ? ['deriveBits', 'deriveKey'] : ['encrypt', 'decrypt']); }(); }); } function createKeyMaterialFromString(password) { return __awaiter(this, void 0, void 0, function* () { let enc = new TextEncoder(); const keyMaterial = yield crypto.subtle.importKey('raw', enc.encode(password), { name: 'PBKDF2' }, false, ['deriveBits', 'deriveKey']); return keyMaterial; }); } function createKeyMaterialFromBuffer(cryptoBuffer) { return __awaiter(this, void 0, void 0, function* () { const keyMaterial = yield crypto.subtle.importKey('raw', cryptoBuffer, 'HKDF', false, ['deriveBits', 'deriveKey']); return keyMaterial; }); } function getAlgoOptions(algorithmName, salt) { const textEncoder = new TextEncoder(); const encodedSalt = textEncoder.encode(salt); switch (algorithmName) { case 'HKDF': return { name: 'HKDF', salt: encodedSalt, hash: 'SHA-256', info: new ArrayBuffer(128) }; case 'PBKDF2': { return { name: 'PBKDF2', salt: encodedSalt, hash: 'SHA-256', iterations: 100000 }; } default: throw new Error("algorithm ".concat(algorithmName, " is currently unsupported")); } } /** * Derives a set of keys from the master key. * See https://tools.ietf.org/html/draft-omara-sframe-00#section-4.3.1 */ function deriveKeys(material, options) { return __awaiter(this, void 0, void 0, function* () { const algorithmOptions = getAlgoOptions(material.algorithm.name, options.ratchetSalt); // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey#HKDF // https://developer.mozilla.org/en-US/docs/Web/API/HkdfParams const encryptionKey = yield crypto.subtle.deriveKey(algorithmOptions, material, { name: ENCRYPTION_ALGORITHM, length: options.keySize }, false, ['encrypt', 'decrypt']); return { material, encryptionKey }; }); } function createE2EEKey() { return window.crypto.getRandomValues(new Uint8Array(32)); } /** * Ratchets a key. See * https://tools.ietf.org/html/draft-omara-sframe-00#section-4.3.5.1 */ function ratchet(material, salt) { return __awaiter(this, void 0, void 0, function* () { const algorithmOptions = getAlgoOptions(material.algorithm.name, salt); // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveBits return crypto.subtle.deriveBits(algorithmOptions, material, 256); }); } function needsRbspUnescaping(frameData) { for (var i = 0; i < frameData.length - 3; i++) { if (frameData[i] == 0 && frameData[i + 1] == 0 && frameData[i + 2] == 3) return true; } return false; } function parseRbsp(stream) { const dataOut = []; var length = stream.length; for (var i = 0; i < stream.length;) { // Be careful about over/underflow here. byte_length_ - 3 can underflow, and // i + 3 can overflow, but byte_length_ - i can't, because i < byte_length_ // above, and that expression will produce the number of bytes left in // the stream including the byte at i. if (length - i >= 3 && !stream[i] && !stream[i + 1] && stream[i + 2] == 3) { // Two rbsp bytes. dataOut.push(stream[i++]); dataOut.push(stream[i++]); // Skip the emulation byte. i++; } else { // Single rbsp byte. dataOut.push(stream[i++]); } } return new Uint8Array(dataOut); } const kZerosInStartSequence = 2; const kEmulationByte = 3; function writeRbsp(data_in) { const dataOut = []; var numConsecutiveZeros = 0; for (var i = 0; i < data_in.length; ++i) { var byte = data_in[i]; if (byte <= kEmulationByte && numConsecutiveZeros >= kZerosInStartSequence) { // Need to escape. dataOut.push(kEmulationByte); numConsecutiveZeros = 0; } dataOut.push(byte); if (byte == 0) { ++numConsecutiveZeros; } else { numConsecutiveZeros = 0; } } return new Uint8Array(dataOut); } function asEncryptablePacket(packet) { var _a, _b, _c, _d, _e; if (((_a = packet.value) === null || _a === void 0 ? void 0 : _a.case) !== 'sipDtmf' && ((_b = packet.value) === null || _b === void 0 ? void 0 : _b.case) !== 'metrics' && ((_c = packet.value) === null || _c === void 0 ? void 0 : _c.case) !== 'speaker' && ((_d = packet.value) === null || _d === void 0 ? void 0 : _d.case) !== 'transcription' && ((_e = packet.value) === null || _e === void 0 ? void 0 : _e.case) !== 'encryptedPacket') { return new EncryptedPacketPayload({ value: packet.value }); } return undefined; }/** * @experimental */ class BaseKeyProvider extends eventsExports.EventEmitter { constructor() { let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; super(); this.latestManuallySetKeyIndex = 0; /** * Callback being invoked after a key has been ratcheted. * Can happen when: * - A decryption failure occurs and the key is auto-ratcheted * - A ratchet request is sent (see {@link ratchetKey()}) * @param ratchetResult Contains the ratcheted chain key (exportable to other participants) and the derived new key material. * @param participantId * @param keyIndex */ this.onKeyRatcheted = (ratchetResult, participantId, keyIndex) => { livekitLogger.debug('key ratcheted event received', { ratchetResult, participantId, keyIndex }); }; this.keyInfoMap = new Map(); this.options = Object.assign(Object.assign({}, KEY_PROVIDER_DEFAULTS), options); this.on(KeyProviderEvent.KeyRatcheted, this.onKeyRatcheted); } /** * callback to invoke once a key has been set for a participant * @param key * @param participantIdentity * @param keyIndex */ onSetEncryptionKey(key, participantIdentity, keyIndex) { const keyInfo = { key, participantIdentity, keyIndex }; if (!this.options.sharedKey && !participantIdentity) { throw new Error('participant identity needs to be passed for encryption key if sharedKey option is false'); } this.keyInfoMap.set("".concat(participantIdentity !== null && participantIdentity !== void 0 ? participantIdentity : 'shared', "-").concat(keyIndex !== null && keyIndex !== void 0 ? keyIndex : 0), keyInfo); if (keyIndex !== undefined) { this.latestManuallySetKeyIndex = keyIndex; } this.emit(KeyProviderEvent.SetKey, keyInfo, keyIndex !== undefined); } getKeys() { return Array.from(this.keyInfoMap.values()); } getLatestManuallySetKeyIndex() { return this.latestManuallySetKeyIndex; } getOptions() { return this.options; } ratchetKey(participantIdentity, keyIndex) { this.emit(KeyProviderEvent.RatchetRequest, participantIdentity, keyIndex); } } /** * A basic KeyProvider implementation intended for a single shared * passphrase between all participants * @experimental */ class ExternalE2EEKeyProvider extends BaseKeyProvider { constructor() { let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; const opts = Object.assign(Object.assign({}, options), { sharedKey: true, // for a shared key provider failing to decrypt for a specific participant // should not mark the key as invalid, so we accept wrong keys forever // and won't try to auto-ratchet ratchetWindowSize: 0, failureTolerance: -1 }); super(opts); } /** * Accepts a passphrase that's used to create the crypto keys. * When passing in a string, PBKDF2 is used. (recommended for maximum compatibility across SDKs) * When passing in an ArrayBuffer of cryptographically random numbers, HKDF is used. * * Note: Not all client SDKS support HKDF. * @param key */ setKey(key) { return __awaiter(this, void 0, void 0, function* () { const derivedKey = typeof key === 'string' ? yield createKeyMaterialFromString(key) : yield createKeyMaterialFromBuffer(key); this.onSetEncryptionKey(derivedKey); }); } }var CryptorErrorReason; (function (CryptorErrorReason) { CryptorErrorReason[CryptorErrorReason["InvalidKey"] = 0] = "InvalidKey"; CryptorErrorReason[CryptorErrorReason["MissingKey"] = 1] = "MissingKey"; CryptorErrorReason[CryptorErrorReason["InternalError"] = 2] = "InternalError"; })(CryptorErrorReason || (CryptorErrorReason = {})); class CryptorError extends LivekitError { constructor(message) { let reason = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : CryptorErrorReason.InternalError; let participantIdentity = arguments.length > 2 ? arguments[2] : undefined; super(40, message); this.reason = reason; this.participantIdentity = participantIdentity; } }function shouldUseFrameMetadataScriptTransform() { return isScriptTransformSupportedForWorker(); } function isFrameMetadataSupported(options) { return !!(options === null || options === void 0 ? void 0 : options.worker) && (isInsertableStreamSupported() || shouldUseFrameMetadataScriptTransform()); } function hasFrameMetadataPublishOptions(options) { return !!((options === null || options === void 0 ? void 0 : options.timestamp) || (options === null || options === void 0 ? void 0 : options.frameId)); } function getFrameMetadataFeatures(options) { const features = []; if (options === null || options === void 0 ? void 0 : options.timestamp) { features.push(PacketTrailerFeature.PTF_USER_TIMESTAMP); } if (options === null || options === void 0 ? void 0 : options.frameId) { features.push(PacketTrailerFeature.PTF_FRAME_ID); } return features; } function getFrameMetadataPublishOptions(features) { if (!features || features.length === 0) { return undefined; } const options = {}; if (features.includes(PacketTrailerFeature.PTF_USER_TIMESTAMP)) { options.timestamp = true; } if (features.includes(PacketTrailerFeature.PTF_FRAME_ID)) { options.frameId = true; } return options.timestamp || options.frameId ? options : undefined; }/** * Originally from ts-debounce (https://github.com/chodorowicz/ts-debounce) * with the following license: * * MIT License * * Copyright (c) 2017 Jakub Chodorowicz * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * Modified to use CriticalTimers for reliable timer execution. */ /* eslint-disable @typescript-eslint/no-this-alias, @typescript-eslint/no-unused-expressions, @typescript-eslint/no-shadow */ function debounce(func) { let waitMilliseconds = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 50; let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var _a, _b; let timeoutId; const isImmediate = (_a = options.isImmediate) !== null && _a !== void 0 ? _a : false; const callback = (_b = options.callback) !== null && _b !== void 0 ? _b : false; const maxWait = options.maxWait; let lastInvokeTime = Date.now(); let promises = []; function nextInvokeTimeout() { if (maxWait !== undefined) { const timeSinceLastInvocation = Date.now() - lastInvokeTime; if (timeSinceLastInvocation + waitMilliseconds >= maxWait) { return maxWait - timeSinceLastInvocation; } } return waitMilliseconds; } const debouncedFunction = function () { for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } const context = this; return new Promise((resolve, reject) => { const invokeFunction = function () { timeoutId = undefined; lastInvokeTime = Date.now(); if (!isImmediate) { const result = func.apply(context, args); callback && callback(result); // biome-ignore lint/suspicious/useIterableCallbackReturn: vendored code promises.forEach(_ref => { let resolve = _ref.resolve; return resolve(result); }); promises = []; } }; const shouldCallNow = isImmediate && timeoutId === undefined; if (timeoutId !== undefined) { CriticalTimers.clearTimeout(timeoutId); } timeoutId = CriticalTimers.setTimeout(invokeFunction, nextInvokeTimeout()); if (shouldCallNow) { const result = func.apply(context, args); callback && callback(result); return resolve(result); } promises.push({ resolve, reject }); }); }; debouncedFunction.cancel = function (reason) { if (timeoutId !== undefined) { CriticalTimers.clearTimeout(timeoutId); } // biome-ignore lint/suspicious/useIterableCallbackReturn: vendored code promises.forEach(_ref2 => { let reject = _ref2.reject; return reject(reason); }); promises = []; }; return debouncedFunction; }const monitorFrequency = 2000; function computeBitrate(currentStats, prevStats) { if (!prevStats) { return 0; } let bytesNow; let bytesPrev; if ('bytesReceived' in currentStats) { bytesNow = currentStats.bytesReceived; bytesPrev = prevStats.bytesReceived; } else if ('bytesSent' in currentStats) { bytesNow = currentStats.bytesSent; bytesPrev = prevStats.bytesSent; } if (bytesNow === undefined || bytesPrev === undefined || currentStats.timestamp === undefined || prevStats.timestamp === undefined) { return 0; } return (bytesNow - bytesPrev) * 8 * 1000 / (currentStats.timestamp - prevStats.timestamp); }class RemoteTrack extends Track { constructor(mediaTrack, sid, kind, receiver, loggerOptions) { super(mediaTrack, kind, loggerOptions); this.sid = sid; this.receiver = receiver; } get isLocal() { return false; } /** @internal */ setMuted(muted) { if (this.isMuted !== muted) { this.isMuted = muted; this._mediaStreamTrack.enabled = !muted; this.emit(muted ? TrackEvent.Muted : TrackEvent.Unmuted, this); } } /** @internal */ setMediaStream(stream) { // this is needed to determine when the track is finished this.mediaStream = stream; const onRemoveTrack = event => { if (event.track === this._mediaStreamTrack) { stream.removeEventListener('removetrack', onRemoveTrack); if (this.receiver && 'playoutDelayHint' in this.receiver) { this.receiver.playoutDelayHint = undefined; } this.receiver = undefined; this._currentBitrate = 0; this.emit(TrackEvent.Ended, this); } }; stream.addEventListener('removetrack', onRemoveTrack); } start() { this.startMonitor(); // use `enabled` of track to enable re-use of transceiver super.enable(); } stop() { this.stopMonitor(); // use `enabled` of track to enable re-use of transceiver super.disable(); } /** * Gets the RTCStatsReport for the RemoteTrack's underlying RTCRtpReceiver * See https://developer.mozilla.org/en-US/docs/Web/API/RTCStatsReport * * @returns Promise | undefined */ getRTCStatsReport() { return __awaiter(this, void 0, void 0, function* () { var _a; if (!((_a = this.receiver) === null || _a === void 0 ? void 0 : _a.getStats)) { return; } const statsReport = yield this.receiver.getStats(); return statsReport; }); } /** * Allows to set a playout delay (in seconds) for this track. * A higher value allows for more buffering of the track in the browser * and will result in a delay of media being played back of `delayInSeconds` */ setPlayoutDelay(delayInSeconds) { if (this.receiver) { if ('playoutDelayHint' in this.receiver) { this.receiver.playoutDelayHint = delayInSeconds; } else { this.log.warn('Playout delay not supported in this browser'); } } else { this.log.warn('Cannot set playout delay, track already ended'); } } /** * Returns the current playout delay (in seconds) of this track. */ getPlayoutDelay() { if (this.receiver) { if ('playoutDelayHint' in this.receiver) { return this.receiver.playoutDelayHint; } else { this.log.warn('Playout delay not supported in this browser'); } } else { this.log.warn('Cannot get playout delay, track already ended'); } return 0; } /* @internal */ startMonitor() { if (!this.monitorInterval) { this.monitorInterval = setInterval(() => this.monitorReceiver(), monitorFrequency); } if (supportsSynchronizationSources()) { this.registerTimeSyncUpdate(); } } registerTimeSyncUpdate() { const loop = () => { var _a; this.timeSyncHandle = requestAnimationFrame(() => loop()); const sources = (_a = this.receiver) === null || _a === void 0 ? void 0 : _a.getSynchronizationSources()[0]; if (sources) { const timestamp = sources.timestamp, rtpTimestamp = sources.rtpTimestamp; if (rtpTimestamp && this.rtpTimestamp !== rtpTimestamp) { this.emit(TrackEvent.TimeSyncUpdate, { timestamp, rtpTimestamp }); this.rtpTimestamp = rtpTimestamp; } } }; loop(); } }const REACTION_DELAY = 100; class RemoteVideoTrack extends RemoteTrack { constructor(mediaTrack, sid, receiver, adaptiveStreamSettings, loggerOptions) { super(mediaTrack, sid, Track.Kind.Video, receiver, loggerOptions); this.elementInfos = []; this.monitorReceiver = () => __awaiter(this, void 0, void 0, function* () { if (!this.receiver) { this._currentBitrate = 0; return; } const stats = yield this.getReceiverStats(); if (stats && this.prevStats && this.receiver) { this._currentBitrate = computeBitrate(stats, this.prevStats); } this.prevStats = stats; }); this.debouncedHandleResize = debounce(() => { this.updateDimensions(); }, REACTION_DELAY); this.adaptiveStreamSettings = adaptiveStreamSettings; } get isAdaptiveStream() { return this.adaptiveStreamSettings !== undefined; } /** * Look up frame-level metadata for a given RTP timestamp. * Use with the `TrackEvent.TimeSyncUpdate` event to correlate displayed frames * with their capture-time metadata. * * Requires the room to be configured with the `frameMetadata` worker option * and the publishing track to have frame metadata features enabled. * */ lookupFrameMetadata(_ref) { let rtpTimestamp = _ref.rtpTimestamp; var _a; return (_a = this.frameMetadataExtractor) === null || _a === void 0 ? void 0 : _a.lookupMetadata(rtpTimestamp); } setStreamState(value) { super.setStreamState(value); this.log.debug('setStreamState', value); if (this.isAdaptiveStream && value === Track.StreamState.Active) { // update visibility for adaptive stream tracks when stream state received from server is active // this is needed to ensure the track is stopped when there's no element attached to it at all this.updateVisibility(); } } /** * Note: When using adaptiveStream, you need to use remoteVideoTrack.attach() to add the track to a HTMLVideoElement, otherwise your video tracks might never start */ get mediaStreamTrack() { return this._mediaStreamTrack; } /** @internal */ setMuted(muted) { super.setMuted(muted); this.attachedElements.forEach(element => { // detach or attach if (muted) { detachTrack(this._mediaStreamTrack, element); } else { attachToElement(this._mediaStreamTrack, element); } }); } attach(element) { if (!element) { element = super.attach(); } else { super.attach(element); } // It's possible attach is called multiple times on an element. When that's // the case, we'd want to avoid adding duplicate elementInfos if (this.adaptiveStreamSettings && this.elementInfos.find(info => info.element === element) === undefined) { const elementInfo = new HTMLElementInfo(element); this.observeElementInfo(elementInfo); } return element; } /** * Observe an ElementInfo for changes when adaptive streaming. * @param elementInfo * @internal */ observeElementInfo(elementInfo) { if (this.adaptiveStreamSettings && this.elementInfos.find(info => info === elementInfo) === undefined) { elementInfo.handleResize = () => { this.debouncedHandleResize(); }; elementInfo.handleVisibilityChanged = () => { this.updateVisibility(); }; this.elementInfos.push(elementInfo); elementInfo.observe(); // trigger the first resize update cycle // if the tab is backgrounded, the initial resize event does not fire until // the tab comes into focus for the first time. this.debouncedHandleResize(); this.updateVisibility(); } else { this.log.warn('visibility resize observer not triggered', this.logContext); } } /** * Stop observing an ElementInfo for changes. * @param elementInfo * @internal */ stopObservingElementInfo(elementInfo) { if (!this.isAdaptiveStream) { this.log.warn('stopObservingElementInfo ignored', this.logContext); return; } const stopElementInfos = this.elementInfos.filter(info => info === elementInfo); for (const info of stopElementInfos) { info.stopObserving(); } this.elementInfos = this.elementInfos.filter(info => info !== elementInfo); this.updateVisibility(); this.debouncedHandleResize(); } detach(element) { if (element) { this.stopObservingElement(element); return super.detach(element); } const detachedElements = super.detach(); for (const e of detachedElements) { this.stopObservingElement(e); } return detachedElements; } /** @internal */ getDecoderImplementation() { var _a; return (_a = this.prevStats) === null || _a === void 0 ? void 0 : _a.decoderImplementation; } getReceiverStats() { return __awaiter(this, void 0, void 0, function* () { if (!this.receiver || !this.receiver.getStats) { return; } const stats = yield this.receiver.getStats(); let receiverStats; let codecID = ''; let codecs = new Map(); stats.forEach(v => { if (v.type === 'inbound-rtp') { codecID = v.codecId; receiverStats = { type: 'video', streamId: v.id, framesDecoded: v.framesDecoded, framesDropped: v.framesDropped, framesReceived: v.framesReceived, packetsReceived: v.packetsReceived, packetsLost: v.packetsLost, frameWidth: v.frameWidth, frameHeight: v.frameHeight, pliCount: v.pliCount, firCount: v.firCount, nackCount: v.nackCount, jitter: v.jitter, timestamp: v.timestamp, bytesReceived: v.bytesReceived, decoderImplementation: v.decoderImplementation }; } else if (v.type === 'codec') { codecs.set(v.id, v); } }); if (receiverStats && codecID !== '' && codecs.get(codecID)) { receiverStats.mimeType = codecs.get(codecID).mimeType; } return receiverStats; }); } stopObservingElement(element) { const stopElementInfos = this.elementInfos.filter(info => info.element === element); for (const info of stopElementInfos) { this.stopObservingElementInfo(info); } } handleAppVisibilityChanged() { const _super = Object.create(null, { handleAppVisibilityChanged: { get: () => super.handleAppVisibilityChanged } }); return __awaiter(this, void 0, void 0, function* () { yield _super.handleAppVisibilityChanged.call(this); if (!this.isAdaptiveStream) return; this.updateVisibility(); }); } updateVisibility(forceEmit) { var _a, _b; const lastVisibilityChange = this.elementInfos.reduce((prev, info) => Math.max(prev, info.visibilityChangedAt || 0), 0); const backgroundPause = ((_b = (_a = this.adaptiveStreamSettings) === null || _a === void 0 ? void 0 : _a.pauseVideoInBackground) !== null && _b !== void 0 ? _b : true // default to true ) ? this.isInBackground : false; const isPiPMode = this.elementInfos.some(info => info.pictureInPicture); const isVisible = this.elementInfos.some(info => info.visible) && !backgroundPause || isPiPMode; if (this.lastVisible === isVisible && !forceEmit) { return; } if (!isVisible && Date.now() - lastVisibilityChange < REACTION_DELAY) { // delay hidden events CriticalTimers.setTimeout(() => { this.updateVisibility(); }, REACTION_DELAY); return; } this.lastVisible = isVisible; this.emit(TrackEvent.VisibilityChanged, isVisible, this); } updateDimensions() { var _a, _b; let maxWidth = 0; let maxHeight = 0; const pixelDensity = this.getPixelDensity(); for (const info of this.elementInfos) { const currentElementWidth = info.width() * pixelDensity; const currentElementHeight = info.height() * pixelDensity; if (currentElementWidth + currentElementHeight > maxWidth + maxHeight) { maxWidth = currentElementWidth; maxHeight = currentElementHeight; } } if (((_a = this.lastDimensions) === null || _a === void 0 ? void 0 : _a.width) === maxWidth && ((_b = this.lastDimensions) === null || _b === void 0 ? void 0 : _b.height) === maxHeight) { return; } this.lastDimensions = { width: maxWidth, height: maxHeight }; this.emit(TrackEvent.VideoDimensionsChanged, this.lastDimensions, this); } getPixelDensity() { var _a; const pixelDensity = (_a = this.adaptiveStreamSettings) === null || _a === void 0 ? void 0 : _a.pixelDensity; if (pixelDensity === 'screen') { return getDevicePixelRatio(); } else if (!pixelDensity) { // when unset, we'll pick a sane default here. // for higher pixel density devices (mobile phones, etc), we'll use 2 // otherwise it defaults to 1 const devicePixelRatio = getDevicePixelRatio(); if (devicePixelRatio > 2) { return 2; } else { return 1; } } return pixelDensity; } } class HTMLElementInfo { get visible() { return this.isPiP || this.isIntersecting; } get pictureInPicture() { return this.isPiP; } constructor(element, visible) { this.onVisibilityChanged = entry => { var _a; const target = entry.target, isIntersecting = entry.isIntersecting; if (target === this.element) { this.isIntersecting = isIntersecting; this.isPiP = isElementInPiP(this.element); this.visibilityChangedAt = Date.now(); (_a = this.handleVisibilityChanged) === null || _a === void 0 ? void 0 : _a.call(this); } }; this.onEnterPiP = () => { var _a, _b; (_b = (_a = window.documentPictureInPicture) === null || _a === void 0 ? void 0 : _a.window) === null || _b === void 0 ? void 0 : _b.addEventListener('pagehide', this.onLeavePiP); // Document PiP: the browser may fire 'enter' before the app has appended its subtree into // documentPictureInPicture.window. Defer so pipWin.document.contains(video) is reliable. queueMicrotask(() => { requestAnimationFrame(() => { var _a; this.isPiP = isElementInPiP(this.element); (_a = this.handleVisibilityChanged) === null || _a === void 0 ? void 0 : _a.call(this); }); }); }; this.onLeavePiP = () => { var _a; this.isPiP = isElementInPiP(this.element); (_a = this.handleVisibilityChanged) === null || _a === void 0 ? void 0 : _a.call(this); }; this.element = element; this.isIntersecting = visible !== null && visible !== void 0 ? visible : isElementInViewport(element); this.isPiP = isWeb() && isElementInPiP(element); this.visibilityChangedAt = 0; } width() { return this.element.clientWidth; } height() { return this.element.clientHeight; } observe() { var _a, _b, _c; // make sure we update the current visible state once we start to observe this.isIntersecting = isElementInViewport(this.element); this.isPiP = isElementInPiP(this.element); this.element.handleResize = () => { var _a; (_a = this.handleResize) === null || _a === void 0 ? void 0 : _a.call(this); }; this.element.handleVisibilityChanged = this.onVisibilityChanged; getIntersectionObserver().observe(this.element); getResizeObserver().observe(this.element); this.element.addEventListener('enterpictureinpicture', this.onEnterPiP); this.element.addEventListener('leavepictureinpicture', this.onLeavePiP); (_a = window.documentPictureInPicture) === null || _a === void 0 ? void 0 : _a.addEventListener('enter', this.onEnterPiP); (_c = (_b = window.documentPictureInPicture) === null || _b === void 0 ? void 0 : _b.window) === null || _c === void 0 ? void 0 : _c.addEventListener('pagehide', this.onLeavePiP); } stopObserving() { var _a, _b, _c, _d, _e; (_a = getIntersectionObserver()) === null || _a === void 0 ? void 0 : _a.unobserve(this.element); (_b = getResizeObserver()) === null || _b === void 0 ? void 0 : _b.unobserve(this.element); this.element.removeEventListener('enterpictureinpicture', this.onEnterPiP); this.element.removeEventListener('leavepictureinpicture', this.onLeavePiP); (_c = window.documentPictureInPicture) === null || _c === void 0 ? void 0 : _c.removeEventListener('enter', this.onEnterPiP); (_e = (_d = window.documentPictureInPicture) === null || _d === void 0 ? void 0 : _d.window) === null || _e === void 0 ? void 0 : _e.removeEventListener('pagehide', this.onLeavePiP); } } function isElementInPiP(el) { var _a, _b; // Simple video PiP if (document.pictureInPictureElement === el) return true; // Document PiP if ((_a = window.documentPictureInPicture) === null || _a === void 0 ? void 0 : _a.window) return isElementInViewport(el, (_b = window.documentPictureInPicture) === null || _b === void 0 ? void 0 : _b.window); return false; } // does not account for occlusion by other elements or opacity property function isElementInViewport(el, win) { const viewportWindow = win || window; let top = el.offsetTop; let left = el.offsetLeft; const width = el.offsetWidth; const height = el.offsetHeight; const _el = el, hidden = _el.hidden; const _getComputedStyle = getComputedStyle(el), display = _getComputedStyle.display; while (el.offsetParent) { el = el.offsetParent; top += el.offsetTop; left += el.offsetLeft; } return top < viewportWindow.pageYOffset + viewportWindow.innerHeight && left < viewportWindow.pageXOffset + viewportWindow.innerWidth && top + height > viewportWindow.pageYOffset && left + width > viewportWindow.pageXOffset && !hidden && display !== 'none'; }/** * @experimental */ class E2EEManager extends eventsExports.EventEmitter { constructor(options, dcEncryptionEnabled) { super(); this.decryptDataRequests = new Map(); this.encryptDataRequests = new Map(); this.onWorkerMessage = ev => { var _a, _b; const _ev$data = ev.data, kind = _ev$data.kind, data = _ev$data.data; switch (kind) { case 'error': livekitLogger.error(data.error.message); // If error has uuid, it's from an async operation (encrypt/decrypt) // Reject the corresponding future if (data.uuid) { const decryptFuture = this.decryptDataRequests.get(data.uuid); if (decryptFuture === null || decryptFuture === void 0 ? void 0 : decryptFuture.reject) { decryptFuture.reject(data.error); break; // Don't emit general error if it's handled by future } const encryptFuture = this.encryptDataRequests.get(data.uuid); if (encryptFuture === null || encryptFuture === void 0 ? void 0 : encryptFuture.reject) { encryptFuture.reject(data.error); break; // Don't emit general error if it's handled by future } } // Emit general error event for unhandled errors this.emit(EncryptionEvent.EncryptionError, data.error, data.participantIdentity); break; case 'initAck': if (data.enabled) { this.keyProvider.getKeys().forEach(keyInfo => { this.postKey(keyInfo, false); }); } break; case 'enable': if (data.enabled) { this.keyProvider.getKeys().forEach(keyInfo => { this.postKey(keyInfo, false); }); } if (this.encryptionEnabled !== data.enabled && data.participantIdentity === ((_a = this.room) === null || _a === void 0 ? void 0 : _a.localParticipant.identity)) { this.emit(EncryptionEvent.ParticipantEncryptionStatusChanged, data.enabled, this.room.localParticipant); this.encryptionEnabled = data.enabled; } else if (data.participantIdentity) { const participant = (_b = this.room) === null || _b === void 0 ? void 0 : _b.getParticipantByIdentity(data.participantIdentity); if (!participant) { throw TypeError("couldn't set encryption status, participant not found".concat(data.participantIdentity)); } this.emit(EncryptionEvent.ParticipantEncryptionStatusChanged, data.enabled, participant); } break; case 'ratchetKey': this.keyProvider.emit(KeyProviderEvent.KeyRatcheted, data.ratchetResult, data.participantIdentity, data.keyIndex); break; case 'decryptDataResponse': const decryptFuture = this.decryptDataRequests.get(data.uuid); if (decryptFuture === null || decryptFuture === void 0 ? void 0 : decryptFuture.resolve) { decryptFuture.resolve(data); } break; case 'encryptDataResponse': const encryptFuture = this.encryptDataRequests.get(data.uuid); if (encryptFuture === null || encryptFuture === void 0 ? void 0 : encryptFuture.resolve) { encryptFuture.resolve(data); } break; case 'packetTrailerMetadata': this.handleFrameMetadata(data.trackId, data.rtpTimestamp, data.ssrc, data.metadata); break; } }; this.onWorkerError = ev => { livekitLogger.error('e2ee worker encountered an error:', { error: ev.error }); this.emit(EncryptionEvent.EncryptionError, ev.error, undefined); }; this.keyProvider = options.keyProvider; this.worker = options.worker; this.encryptionEnabled = false; this.dataChannelEncryptionEnabled = dcEncryptionEnabled; } get isEnabled() { return this.encryptionEnabled; } get isDataChannelEncryptionEnabled() { return this.isEnabled && this.dataChannelEncryptionEnabled; } /** * @internal */ setup(room) { if (!isE2EESupported()) { throw new DeviceUnsupportedError('tried to setup end-to-end encryption on an unsupported browser'); } livekitLogger.info('setting up e2ee'); if (room !== this.room) { this.room = room; this.setupEventListeners(room, this.keyProvider); // this.worker = new Worker(''); const msg = { kind: 'init', data: { keyProviderOptions: this.keyProvider.getOptions(), loglevel: workerLogger.getLevel() } }; if (this.worker) { livekitLogger.info("initializing worker", { worker: this.worker }); this.worker.onmessage = this.onWorkerMessage; this.worker.onerror = this.onWorkerError; this.worker.postMessage(msg); } } } /** * @internal */ setParticipantCryptorEnabled(enabled, participantIdentity) { livekitLogger.debug("set e2ee to ".concat(enabled, " for participant ").concat(participantIdentity)); this.postEnable(enabled, participantIdentity); } /** * @internal */ setSifTrailer(trailer) { if (!trailer || trailer.length === 0) { livekitLogger.warn("ignoring server sent trailer as it's empty"); } else { this.postSifTrailer(trailer); } } handleFrameMetadata(trackId, rtpTimestamp, ssrc, metadata) { if (!this.room) { return; } for (const participant of [this.room.localParticipant, ...this.room.remoteParticipants.values()]) { for (const pub of participant.trackPublications.values()) { if (pub.track && pub.track.mediaStreamID === trackId && pub.track instanceof RemoteVideoTrack && pub.track.frameMetadataExtractor) { pub.track.frameMetadataExtractor.storeMetadata(rtpTimestamp, ssrc, metadata); return; } } } } setupEngine(engine) { engine.on(EngineEvent.RTPVideoMapUpdate, rtpMap => { this.postRTPMap(rtpMap); }); } setupEventListeners(room, keyProvider) { room.on(RoomEvent.TrackPublished, (pub, participant) => this.setParticipantCryptorEnabled(pub.trackInfo.encryption !== Encryption_Type.NONE, participant.identity)); room.on(RoomEvent.ConnectionStateChanged, state => { if (state === ConnectionState.Connected) { room.remoteParticipants.forEach(participant => { participant.trackPublications.forEach(pub => { this.setParticipantCryptorEnabled(pub.trackInfo.encryption !== Encryption_Type.NONE, participant.identity); }); }); } }).on(RoomEvent.TrackUnsubscribed, (track, _, participant) => { var _a; const msg = { kind: 'removeTransform', data: { participantIdentity: participant.identity, trackId: track.mediaStreamID } }; (_a = this.worker) === null || _a === void 0 ? void 0 : _a.postMessage(msg); }).on(RoomEvent.TrackSubscribed, (track, pub, participant) => { this.setupE2EEReceiver(track, participant.identity, pub.trackInfo); }).on(RoomEvent.SignalConnected, () => { if (!this.room) { throw new TypeError("expected room to be present on signal connect"); } const latestKeyIndex = keyProvider.getLatestManuallySetKeyIndex(); keyProvider.getKeys().forEach(keyInfo => { var _a; this.postKey(keyInfo, latestKeyIndex === ((_a = keyInfo.keyIndex) !== null && _a !== void 0 ? _a : 0)); }); this.setParticipantCryptorEnabled(this.room.localParticipant.isE2EEEnabled, this.room.localParticipant.identity); }); room.localParticipant.on(ParticipantEvent.LocalSenderCreated, (sender, track) => __awaiter(this, void 0, void 0, function* () { this.setupE2EESender(track, sender); })); room.localParticipant.on(ParticipantEvent.LocalTrackPublished, publication => { // Safari doesn't support retrieving payload information on RTCEncodedVideoFrame, so we need to update the codec manually once we have the trackInfo from the server if (!isVideoTrack(publication.track) || !isSafariBased()) { return; } const msg = { kind: 'updateCodec', data: { trackId: publication.track.mediaStreamID, codec: mimeTypeToVideoCodecString(publication.trackInfo.codecs[0].mimeType), participantIdentity: this.room.localParticipant.identity, hasPacketTrailer: false } }; this.worker.postMessage(msg); }); keyProvider.on(KeyProviderEvent.SetKey, (keyInfo, updateCurrentKeyIndex) => this.postKey(keyInfo, updateCurrentKeyIndex !== null && updateCurrentKeyIndex !== void 0 ? updateCurrentKeyIndex : true)).on(KeyProviderEvent.RatchetRequest, (participantId, keyIndex) => this.postRatchetRequest(participantId, keyIndex)); } encryptData(data) { return __awaiter(this, void 0, void 0, function* () { if (!this.worker) { throw Error('could not encrypt data, worker is missing'); } const uuid = crypto.randomUUID(); const msg = { kind: 'encryptDataRequest', data: { uuid, payload: data, participantIdentity: this.room.localParticipant.identity } }; const future = new Future(); future.onFinally = () => { this.encryptDataRequests.delete(uuid); }; this.encryptDataRequests.set(uuid, future); this.worker.postMessage(msg); return future.promise; }); } handleEncryptedData(payload, iv, participantIdentity, keyIndex) { if (!this.worker) { throw Error('could not handle encrypted data, worker is missing'); } const uuid = crypto.randomUUID(); const msg = { kind: 'decryptDataRequest', data: { uuid, payload, iv, participantIdentity, keyIndex } }; const future = new Future(); future.onFinally = () => { this.decryptDataRequests.delete(uuid); }; this.decryptDataRequests.set(uuid, future); this.worker.postMessage(msg); return future.promise; } postRatchetRequest(participantIdentity, keyIndex) { if (!this.worker) { throw Error('could not ratchet key, worker is missing'); } const msg = { kind: 'ratchetRequest', data: { participantIdentity: participantIdentity, keyIndex } }; this.worker.postMessage(msg); } postKey(_ref, updateCurrentKeyIndex) { let key = _ref.key, participantIdentity = _ref.participantIdentity, keyIndex = _ref.keyIndex; var _a; if (!this.worker) { throw Error('could not set key, worker is missing'); } const msg = { kind: 'setKey', data: { participantIdentity: participantIdentity, isPublisher: participantIdentity === ((_a = this.room) === null || _a === void 0 ? void 0 : _a.localParticipant.identity), key, keyIndex, updateCurrentKeyIndex } }; this.worker.postMessage(msg); } postEnable(enabled, participantIdentity) { if (this.worker) { const enableMsg = { kind: 'enable', data: { enabled, participantIdentity } }; this.worker.postMessage(enableMsg); } else { throw new ReferenceError('failed to enable e2ee, worker is not ready'); } } postRTPMap(map) { var _a; if (!this.worker) { throw TypeError('could not post rtp map, worker is missing'); } if (!((_a = this.room) === null || _a === void 0 ? void 0 : _a.localParticipant.identity)) { throw TypeError('could not post rtp map, local participant identity is missing'); } const msg = { kind: 'setRTPMap', data: { map, participantIdentity: this.room.localParticipant.identity } }; this.worker.postMessage(msg); } postSifTrailer(trailer) { if (!this.worker) { throw Error('could not post SIF trailer, worker is missing'); } const msg = { kind: 'setSifTrailer', data: { trailer } }; this.worker.postMessage(msg); } setupE2EEReceiver(track, remoteId, trackInfo) { if (!track.receiver) { return; } if (!(trackInfo === null || trackInfo === void 0 ? void 0 : trackInfo.mimeType) || trackInfo.mimeType === '') { throw new TypeError('MimeType missing from trackInfo, cannot set up E2EE cryptor'); } const hasPacketTrailer = track.kind === 'video' && !!trackInfo.packetTrailerFeatures && trackInfo.packetTrailerFeatures.length > 0; this.handleReceiver(track.receiver, track.mediaStreamID, remoteId, track.kind === 'video' ? mimeTypeToVideoCodecString(trackInfo.mimeType) : undefined, hasPacketTrailer); } setupE2EESender(track, sender) { var _a, _b, _c; if (!isLocalTrack(track) || !sender) { if (!sender) livekitLogger.warn('early return because sender is not ready'); return; } this.handleSender(sender, track.mediaStreamID, undefined, isVideoTrack(track) ? (_b = (_a = track.publishOptions) === null || _a === void 0 ? void 0 : _a.frameMetadata) !== null && _b !== void 0 ? _b : (_c = track.publishOptions) === null || _c === void 0 ? void 0 : _c.packetTrailer : undefined); } /** * Handles the given {@code RTCRtpReceiver} by creating a {@code TransformStream} which will inject * a frame decoder. * */ handleReceiver(receiver, trackId, participantIdentity, codec, hasPacketTrailer) { return __awaiter(this, void 0, void 0, function* () { if (!this.worker) { return; } if (isScriptTransformSupportedForWorker()) { const options = { kind: 'decode', participantIdentity, trackId, codec, hasPacketTrailer }; // @ts-ignore receiver.transform = new RTCRtpScriptTransform(this.worker, options); } else { if (E2EE_FLAG in receiver && codec) { // update track-specific state when the transceiver is reused const msg = { kind: 'updateCodec', data: { trackId, codec, participantIdentity, hasPacketTrailer } }; this.worker.postMessage(msg); return; } // @ts-ignore let writable = receiver.writableStream; // @ts-ignore let readable = receiver.readableStream; if (!writable || !readable) { // @ts-ignore const receiverStreams = receiver.createEncodedStreams(); // @ts-ignore receiver.writableStream = receiverStreams.writable; writable = receiverStreams.writable; // @ts-ignore receiver.readableStream = receiverStreams.readable; readable = receiverStreams.readable; } const msg = { kind: 'decode', data: { readableStream: readable, writableStream: writable, trackId: trackId, codec, participantIdentity: participantIdentity, isReuse: E2EE_FLAG in receiver, hasPacketTrailer } }; this.worker.postMessage(msg, [readable, writable]); } // @ts-ignore receiver[E2EE_FLAG] = true; }); } /** * Handles the given {@code RTCRtpSender} by creating a {@code TransformStream} which will inject * a frame encoder. * */ handleSender(sender, trackId, codec, frameMetadata) { var _a; if (E2EE_FLAG in sender || !this.worker) { return; } if (!((_a = this.room) === null || _a === void 0 ? void 0 : _a.localParticipant.identity) || this.room.localParticipant.identity === '') { throw TypeError('local identity needs to be known in order to set up encrypted sender'); } if (isScriptTransformSupportedForWorker()) { livekitLogger.info('initialize script transform'); const options = { kind: 'encode', participantIdentity: this.room.localParticipant.identity, trackId, codec, hasPacketTrailer: hasFrameMetadataPublishOptions(frameMetadata), packetTrailer: frameMetadata }; // @ts-ignore sender.transform = new RTCRtpScriptTransform(this.worker, options); } else { livekitLogger.info('initialize encoded streams'); // @ts-ignore const senderStreams = sender.createEncodedStreams(); const msg = { kind: 'encode', data: { readableStream: senderStreams.readable, writableStream: senderStreams.writable, codec, trackId, participantIdentity: this.room.localParticipant.identity, isReuse: false, hasPacketTrailer: hasFrameMetadataPublishOptions(frameMetadata), packetTrailer: frameMetadata } }; this.worker.postMessage(msg, [senderStreams.readable, senderStreams.writable]); } // @ts-ignore sender[E2EE_FLAG] = true; } }const MAX_ENTRIES = 300; /** * Caches frame metadata extracted from received video frames, * keyed by RTP timestamp so it can be looked up when the frame is displayed. * * Metadata is populated either by the frame metadata worker managed by * `FrameMetadataManager` (non-E2EE) or by the E2EE FrameCryptor worker * after decryption (E2EE). * * @experimental */ class FrameMetadataExtractor { constructor() { this.metadataMap = new Map(); this.activeSsrc = 0; } storeMetadata(rtpTimestamp, ssrc, metadata) { // Simulcast layer switch: SSRC changed, flush stale entries from old layer. if (this.activeSsrc !== 0 && this.activeSsrc !== ssrc) { this.metadataMap.clear(); } this.activeSsrc = ssrc; while (this.metadataMap.size >= MAX_ENTRIES) { const evicted = this.metadataMap.keys().next().value; this.metadataMap.delete(evicted); } this.metadataMap.set(rtpTimestamp, metadata); } lookupMetadata(rtpTimestamp) { return this.metadataMap.get(rtpTimestamp); } dispose() { this.metadataMap.clear(); this.activeSsrc = 0; } }/** * Manages packet trailer extraction for received video tracks. * * When a track's TrackInfo indicates packet trailer features, the manager * wires up an encoded frame transform to strip the trailer from encoded frames * and cache the metadata for lookup. * * Packet trailer extraction is worker-only. If no worker is configured, the * SDK does not advertise packet trailer support and skips extraction. * * When E2EE is active, the E2EE FrameCryptor worker handles trailer * extraction directly (before decryption), so this manager only creates * the extractor/metadata cache — no separate pipeline is installed. * * @experimental */ class FrameMetadataManager { constructor(options) { this.extractors = new Map(); /** * Tracks the trackId associated with each receiver that has had its * encoded streams handed off to the worker. Used to detect receiver * reuse (transceiver recycling) so we can remap trackIds instead of * re-transferring already-consumed streams. */ this.workerPipelines = new Map(); this.onWorkerMessage = ev => { const msg = ev.data; if (msg.kind === 'metadata') { const extractor = this.extractors.get(msg.data.trackId); if (extractor) { extractor.storeMetadata(msg.data.rtpTimestamp, msg.data.ssrc, msg.data.metadata); } } }; this.onWorkerError = ev => { livekitLogger.error('frame metadata worker encountered an error:', { error: ev.error }); }; this.worker = options === null || options === void 0 ? void 0 : options.worker; } /** @internal */ setup(room) { if (room === this.room) { return; } this.room = room; if (this.worker) { this.worker.onmessage = this.onWorkerMessage; this.worker.onerror = this.onWorkerError; this.worker.postMessage({ kind: 'init' }); } room.on(RoomEvent.TrackSubscribed, (track, pub, _participant) => { if (track.kind !== 'video') { return; } this.setupReceiver(track, pub.trackInfo); }).on(RoomEvent.TrackUnsubscribed, track => { this.teardownTrack(track); }).on(RoomEvent.Disconnected, () => { this.cleanup(); }); } setupReceiver(track, trackInfo) { var _a, _b, _c; const receiver = track.receiver; if (!receiver) { return; } // Only install a pipeline for tracks that actually advertise packet // trailer features. This keeps us out of the way for tracks published by // clients on older protocols or that don't opt into the feature. const hasFeatures = !!(trackInfo === null || trackInfo === void 0 ? void 0 : trackInfo.packetTrailerFeatures) && trackInfo.packetTrailerFeatures.length > 0; if (!hasFeatures) { if (!((_a = this.room) === null || _a === void 0 ? void 0 : _a.hasE2EESetup)) { this.setupPassthroughReceiver(receiver, track.mediaStreamID); } return; } if (!isFrameMetadataSupported(this.worker ? { worker: this.worker } : undefined) && !((_b = this.room) === null || _b === void 0 ? void 0 : _b.hasE2EESetup)) { livekitLogger.warn('frame metadata transform not supported; skipping extraction'); return; } const extractor = new FrameMetadataExtractor(); const trackId = track.mediaStreamID; this.extractors.set(trackId, extractor); track.frameMetadataExtractor = extractor; if ((_c = this.room) === null || _c === void 0 ? void 0 : _c.hasE2EESetup) { // E2EE worker strips the trailer and injects metadata directly into // the extractor via E2eeManager; no pipeline is needed here. return; } this.setupWorkerReceiver(receiver, trackId, true); } setupPassthroughReceiver(receiver, trackId) { if (shouldUseFrameMetadataScriptTransform()) { if ('transform' in receiver) { // @ts-ignore receiver.transform = null; } return; } if (this.worker && isFrameMetadataSupported({ worker: this.worker }) && !this.workerPipelines.has(receiver)) { this.setupWorkerReceiver(receiver, trackId, false); return; } if (this.worker && this.workerPipelines.has(receiver)) { this.setupWorkerReceiver(receiver, trackId, false); } } setupWorkerReceiver(receiver, newTrackId) { let hasPacketTrailer = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; const worker = this.worker; if (!worker) { return; } if (shouldUseFrameMetadataScriptTransform()) { // @ts-ignore receiver.transform = new RTCRtpScriptTransform(worker, { kind: 'decode', trackId: newTrackId }); return; } const existingTrackId = this.workerPipelines.get(receiver); if (existingTrackId) { // Receiver is reused (transceiver recycled). The worker already owns // the encoded streams — just remap the trackId so metadata is keyed // correctly and re-activate processing. const msg = { kind: 'updateTrackId', data: { oldTrackId: existingTrackId, newTrackId, hasPacketTrailer } }; worker.postMessage(msg); this.workerPipelines.set(receiver, newTrackId); return; } if (!('createEncodedStreams' in receiver)) { livekitLogger.warn('createEncodedStreams not supported'); return; } let streams; try { // @ts-ignore — createEncodedStreams is not in standard typings streams = receiver.createEncodedStreams(); } catch (err) { livekitLogger.warn('failed to create encoded streams', { error: err }); return; } const msg = { kind: 'decode', data: { readableStream: streams.readable, writableStream: streams.writable, trackId: newTrackId, hasPacketTrailer } }; worker.postMessage(msg, [streams.readable, streams.writable]); this.workerPipelines.set(receiver, newTrackId); } teardownTrack(track) { const trackId = track.mediaStreamID; const extractor = this.extractors.get(trackId); if (extractor) { extractor.dispose(); this.extractors.delete(trackId); } if (track instanceof RemoteVideoTrack) { track.frameMetadataExtractor = undefined; } // The receiver pipeline is intentionally left running. If the receiver is // reused for a new track, `setupReceiver` will remap it. If the room // disconnects, `cleanup` drops all state. Any metadata produced in the // meantime is harmless — the extractor above has already been disposed and // is no longer reachable from any track. } cleanup() { var _a; for (const extractor of this.extractors.values()) { extractor.dispose(); } this.extractors.clear(); this.workerPipelines.clear(); (_a = this.worker) === null || _a === void 0 ? void 0 : _a.terminate(); } } /** @deprecated Use {@link FrameMetadataManager} instead. */ const PacketTrailerManager = FrameMetadataManager;const CONNECTION_BACKOFF_MIN_MS = 500; const CONNECTION_BACKOFF_MAX_MS = 15000; /** * BackOffStrategy implements exponential backoff for connection failures. * * When severe connection failures occur (e.g., network issues, server unavailability), * this strategy introduces increasing delays between reconnection attempts to avoid * overwhelming the server and to give transient issues time to resolve. * * This strategy is only applied to LiveKit Cloud projects. It identifies * projects by extracting the project name from the connection URL and tracks failures * per project. Self-hosted deployments (URLs without a project identifier) are not * subject to backoff delays. * * The class is implemented as a singleton to maintain consistent backoff state across * the entire application lifecycle instead of room instance lifecycle. */ class BackOffStrategy { // eslint-disable-next-line @typescript-eslint/no-empty-function constructor() { this.failedConnectionAttempts = new Map(); this.backOffPromises = new Map(); } static getInstance() { if (!this._instance) { this._instance = new BackOffStrategy(); } return this._instance; } addFailedConnectionAttempt(urlString) { var _a; const url = new URL(urlString); const projectName = extractProjectFromUrl(url); if (!projectName) { return; } let failureCount = (_a = this.failedConnectionAttempts.get(projectName)) !== null && _a !== void 0 ? _a : 0; this.failedConnectionAttempts.set(projectName, failureCount + 1); this.backOffPromises.set(projectName, sleep(Math.min(CONNECTION_BACKOFF_MIN_MS * Math.pow(2, failureCount), CONNECTION_BACKOFF_MAX_MS))); } getBackOffPromise(urlString) { const url = new URL(urlString); const projectName = url && extractProjectFromUrl(url); const backoffPromise = projectName && this.backOffPromises.get(projectName); return backoffPromise || Promise.resolve(); } resetFailedConnectionAttempts(urlString) { const url = new URL(urlString); const projectName = url && extractProjectFromUrl(url); if (projectName) { this.failedConnectionAttempts.set(projectName, 0); this.backOffPromises.set(projectName, Promise.resolve()); } } resetAll() { this.backOffPromises.clear(); this.failedConnectionAttempts.clear(); } } BackOffStrategy._instance = null;const defaultId = 'default'; class DeviceManager { constructor() { this._previousDevices = []; } static getInstance() { if (this.instance === undefined) { this.instance = new DeviceManager(); } return this.instance; } get previousDevices() { return this._previousDevices; } getDevices(kind_1) { return __awaiter(this, arguments, void 0, function (kind) { var _this = this; let requestPermissions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; return function* () { var _a; if (((_a = DeviceManager.userMediaPromiseMap) === null || _a === void 0 ? void 0 : _a.size) > 0) { livekitLogger.debug('awaiting getUserMedia promise'); try { if (kind) { yield DeviceManager.userMediaPromiseMap.get(kind); } else { yield Promise.all(DeviceManager.userMediaPromiseMap.values()); } } catch (e) { livekitLogger.warn('error waiting for media permissons'); } } let devices = yield navigator.mediaDevices.enumerateDevices(); if (requestPermissions && // for safari we need to skip this check, as otherwise it will re-acquire user media and fail on iOS https://bugs.webkit.org/show_bug.cgi?id=179363 !(isSafari() && _this.hasDeviceInUse(kind))) { const isDummyDeviceOrEmpty = devices.filter(d => d.kind === kind).length === 0 || devices.some(device => { const noLabel = device.label === ''; const isRelevant = kind ? device.kind === kind : true; return noLabel && isRelevant; }); if (isDummyDeviceOrEmpty) { const permissionsToAcquire = { video: kind !== 'audioinput' && kind !== 'audiooutput', audio: kind !== 'videoinput' && { deviceId: { ideal: 'default' } } }; const stream = yield navigator.mediaDevices.getUserMedia(permissionsToAcquire); devices = yield navigator.mediaDevices.enumerateDevices(); stream.getTracks().forEach(track => { track.stop(); }); } } _this._previousDevices = devices; if (kind) { devices = devices.filter(device => device.kind === kind); } return devices; }(); }); } normalizeDeviceId(kind, deviceId, groupId) { return __awaiter(this, void 0, void 0, function* () { if (deviceId !== defaultId) { return deviceId; } // resolve actual device id if it's 'default': Chrome returns it when no // device has been chosen const devices = yield this.getDevices(kind); const defaultDevice = devices.find(d => d.deviceId === defaultId); if (!defaultDevice) { livekitLogger.warn('could not reliably determine default device'); return undefined; } const device = devices.find(d => d.deviceId !== defaultId && d.groupId === (groupId !== null && groupId !== void 0 ? groupId : defaultDevice.groupId)); if (!device) { livekitLogger.warn('could not reliably determine default device'); return undefined; } return device === null || device === void 0 ? void 0 : device.deviceId; }); } hasDeviceInUse(kind) { return kind ? DeviceManager.userMediaPromiseMap.has(kind) : DeviceManager.userMediaPromiseMap.size > 0; } } DeviceManager.mediaDeviceKinds = ['audioinput', 'audiooutput', 'videoinput']; DeviceManager.userMediaPromiseMap = new Map();const U16_MAX_SIZE = 0xffff; const U32_MAX_SIZE = 0xffffffff; /** * A number of fields withing the data tracks packet specification assume wrap around behavior when * an unsigned type is incremented beyond its max size (ie, the packet `sequence` field). This * wrapper type manually reimplements this wrap around behavior given javascript's lack of fixed * size integer types. */ class WrapAroundUnsignedInt { static u16(raw) { return new WrapAroundUnsignedInt(raw, U16_MAX_SIZE); } static u32(raw) { return new WrapAroundUnsignedInt(raw, U32_MAX_SIZE); } constructor(raw, maxSize) { this.value = raw; if (raw < 0) { throw new Error('WrapAroundUnsignedInt: cannot faithfully represent an integer smaller than 0'); } if (maxSize > Number.MAX_SAFE_INTEGER) { throw new Error('WrapAroundUnsignedInt: cannot faithfully represent an integer bigger than MAX_SAFE_INTEGER.'); } this.maxSize = maxSize; this.clamp(); } /** Manually clamp the given containing value according to the wrap around max size bounds. Use * this after out of bounds modification to the contained value by external code. */ clamp() { while (this.value > this.maxSize) { this.value -= this.maxSize + 1; } while (this.value < 0) { this.value += this.maxSize + 1; } } clone() { return new WrapAroundUnsignedInt(this.value, this.maxSize); } /** When called, maps the containing value to a new containing value. After mapping, the wrap * around external max size bounds are applied. Note that this is a mutative operation. */ update(updateFn) { this.value = updateFn(this.value); this.clamp(); } /** Increments the given `n` to the inner value. Note that this is a mutative operation. */ increment() { let n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; this.update(value => value + n); } /** Decrements the given `n` from the inner value. Note that this is a mutative operation. */ decrement() { let n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; this.update(value => value - n); } getThenIncrement() { const previousValue = this.value; this.increment(); return new WrapAroundUnsignedInt(previousValue, this.maxSize); } /** Returns true if {@link this} is before the passed other {@link WrapAroundUnsignedInt}. */ isBefore(other) { const a = this.value >>> 0; const b = other.value >>> 0; const diff = b - a >>> 0; return diff !== 0 && diff < this.maxSize + 1; } } class DataTrackTimestamp { static fromRtpTicks(rtpTicks) { return new DataTrackTimestamp(rtpTicks, 90000); } /** Generates a timestamp initialized to a non cryptographically secure random value, so that * different streams are more difficult to correlate in packet capture. */ static rtpRandom() { const randomValue = Math.round(Math.random() * U32_MAX_SIZE); return DataTrackTimestamp.fromRtpTicks(randomValue); } constructor(raw, rateInHz) { this.timestamp = WrapAroundUnsignedInt.u32(raw); this.rateInHz = rateInHz; } asTicks() { return this.timestamp.value; } clone() { return new DataTrackTimestamp(this.timestamp.value, this.rateInHz); } wrappingAdd(n) { this.timestamp.increment(n); } /** Returns true if {@link this} is before the passed other {@link DataTrackTimestamp}. */ isBefore(other) { return this.timestamp.isBefore(other.timestamp); } } class DataTrackClock { constructor(rateInHz, epoch, base) { this.epoch = epoch; this.base = base; this.previous = base.clone(); this.rateInHz = rateInHz; } static startingNow(base, rateInHz) { return new DataTrackClock(rateInHz, new Date(), base); } static startingAtTime(epoch, base, rateInHz) { return new DataTrackClock(rateInHz, epoch, base); } static rtpStartingNow(base) { return DataTrackClock.startingNow(base, 90000); } static rtpStartingAtTime(epoch, base) { return DataTrackClock.startingAtTime(epoch, base, 90000); } now() { return this.at(new Date()); } at(timestamp) { let elapsedMs = timestamp.getTime() - this.epoch.getTime(); let durationTicks = DataTrackClock.durationInMsToTicks(elapsedMs, this.rateInHz); let result = this.base.clone(); result.wrappingAdd(durationTicks); // Enforce monotonicity in RTP wraparound space if (result.isBefore(this.previous)) { result = this.previous; } this.previous = result.clone(); return result.clone(); } /** Convert a duration since the epoch into clock ticks. */ static durationInMsToTicks(durationMilliseconds, rateInHz) { // round(nanos * rate_hz / 1e9) let durationNanoseconds = durationMilliseconds * 1e6; let ticks = (durationNanoseconds * rateInHz + 500000000) / 1000000000; return Math.round(ticks); } } function coerceToDataView(input) { if (input instanceof DataView) { return input; } else if (input instanceof ArrayBuffer) { return new DataView(input); } else if (input instanceof Uint8Array) { return new DataView(input.buffer, input.byteOffset, input.byteLength); } else { throw new Error("Error coercing ".concat(input, " to DataView - input was not DataView, ArrayBuffer, or Uint8Array.")); } }var DataTrackHandleErrorReason; (function (DataTrackHandleErrorReason) { DataTrackHandleErrorReason[DataTrackHandleErrorReason["Reserved"] = 0] = "Reserved"; DataTrackHandleErrorReason[DataTrackHandleErrorReason["TooLarge"] = 1] = "TooLarge"; })(DataTrackHandleErrorReason || (DataTrackHandleErrorReason = {})); class DataTrackHandleError extends LivekitReasonedError { constructor(message, reason) { super(19, message); this.name = 'DataTrackHandleError'; this.reason = reason; this.reasonName = DataTrackHandleErrorReason[reason]; } isReason(reason) { return this.reason === reason; } static tooLarge() { return new DataTrackHandleError('Value too large to be a valid track handle', DataTrackHandleErrorReason.TooLarge); } static reserved(value) { return new DataTrackHandleError("0x".concat(value.toString(16), " is a reserved value."), DataTrackHandleErrorReason.Reserved); } } const DataTrackHandle = { fromNumber(raw) { if (raw === 0) { throw DataTrackHandleError.reserved(raw); } if (raw > U16_MAX_SIZE) { throw DataTrackHandleError.tooLarge(); } return raw; } }; /** Manage allocating new handles which don't conflict over the lifetime of the client. */ class DataTrackHandleAllocator { constructor() { this.value = 0; } /** Returns a unique track handle for the next publication, if one can be obtained. */ get() { this.value += 1; if (this.value > U16_MAX_SIZE) { return null; } return this.value; } reset() { this.value = 0; } }const DataTrackInfo = { from(protocolInfo) { return { sid: protocolInfo.sid, pubHandle: protocolInfo.pubHandle, name: protocolInfo.name, usesE2ee: protocolInfo.encryption !== Encryption_Type.NONE }; }, toProtobuf(info) { return new DataTrackInfo$1({ sid: info.sid, pubHandle: info.pubHandle, name: info.name, encryption: info.usesE2ee ? Encryption_Type.GCM : Encryption_Type.NONE }); } };var QueueTaskStatus; (function (QueueTaskStatus) { QueueTaskStatus[QueueTaskStatus["WAITING"] = 0] = "WAITING"; QueueTaskStatus[QueueTaskStatus["RUNNING"] = 1] = "RUNNING"; QueueTaskStatus[QueueTaskStatus["COMPLETED"] = 2] = "COMPLETED"; })(QueueTaskStatus || (QueueTaskStatus = {})); class AsyncQueue { constructor() { this.pendingTasks = new Map(); this.taskMutex = new _(); this.nextTaskIndex = 0; } run(task) { return __awaiter(this, void 0, void 0, function* () { const taskInfo = { id: this.nextTaskIndex++, enqueuedAt: Date.now(), status: QueueTaskStatus.WAITING }; this.pendingTasks.set(taskInfo.id, taskInfo); const unlock = yield this.taskMutex.lock(); try { taskInfo.executedAt = Date.now(); taskInfo.status = QueueTaskStatus.RUNNING; return yield task(); } finally { taskInfo.status = QueueTaskStatus.COMPLETED; this.pendingTasks.delete(taskInfo.id); unlock(); } }); } flush() { return __awaiter(this, void 0, void 0, function* () { return this.run(() => __awaiter(this, void 0, void 0, function* () {})); }); } snapshot() { return Array.from(this.pendingTasks.values()); } }/** * [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) with [Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) * * @see https://web.dev/websocketstream/ */ class WebSocketStream { get readyState() { return this.ws.readyState; } constructor(url) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var _a, _b; if ((_a = options.signal) === null || _a === void 0 ? void 0 : _a.aborted) { throw new DOMException('This operation was aborted', 'AbortError'); } this.url = url; const ws = new WebSocket(url, (_b = options.protocols) !== null && _b !== void 0 ? _b : []); ws.binaryType = 'arraybuffer'; this.ws = ws; const closeWithInfo = function () { let _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, code = _ref.closeCode, reason = _ref.reason; return ws.close(code, reason); }; this.opened = new TypedPromise((resolve, reject) => { const rejectHandler = () => { reject(ConnectionError.websocket('Encountered websocket error during connection establishment')); }; ws.onopen = () => { resolve({ readable: new ReadableStream({ start(controller) { ws.onmessage = _ref2 => { let data = _ref2.data; return controller.enqueue(data); }; ws.onerror = e => controller.error(e); }, cancel: closeWithInfo }), writable: new WritableStream({ write(chunk) { ws.send(chunk); }, abort() { ws.close(); }, close: closeWithInfo }), protocol: ws.protocol, extensions: ws.extensions }); ws.removeEventListener('error', rejectHandler); }; ws.addEventListener('error', rejectHandler); }); this.closed = new TypedPromise((resolve, reject) => { const rejectHandler = () => __awaiter(this, void 0, void 0, function* () { const closePromise = new TypedPromise(res => { if (ws.readyState === WebSocket.CLOSED) return;else { ws.addEventListener('close', closeEv => { res(closeEv); }, { once: true }); } }); const reason = yield TypedPromise.race([sleep(250), closePromise]); if (!reason) { reject(ConnectionError.websocket('Encountered unspecified websocket error without a timely close event')); } else { // if we can infer the close reason from the close event then resolve the promise, we don't need to throw resolve(reason); } }); ws.onclose = _ref3 => { let code = _ref3.code, reason = _ref3.reason; resolve({ closeCode: code, reason }); ws.removeEventListener('error', rejectHandler); }; ws.addEventListener('error', rejectHandler); }); if (options.signal) { options.signal.onabort = () => ws.close(); } this.close = closeWithInfo; } }const passThroughQueueSignals = ['syncState', 'trickle', 'offer', 'answer', 'simulate', 'leave']; function canPassThroughQueue(req) { const canPass = passThroughQueueSignals.indexOf(req.case) >= 0; livekitLogger.trace('request allowed to bypass queue:', { canPass, req }); return canPass; } var SignalConnectionState; (function (SignalConnectionState) { SignalConnectionState[SignalConnectionState["CONNECTING"] = 0] = "CONNECTING"; SignalConnectionState[SignalConnectionState["CONNECTED"] = 1] = "CONNECTED"; SignalConnectionState[SignalConnectionState["RECONNECTING"] = 2] = "RECONNECTING"; SignalConnectionState[SignalConnectionState["DISCONNECTING"] = 3] = "DISCONNECTING"; SignalConnectionState[SignalConnectionState["DISCONNECTED"] = 4] = "DISCONNECTED"; })(SignalConnectionState || (SignalConnectionState = {})); /** specifies how much time (in ms) we allow for the ws to close its connection gracefully before continuing */ const MAX_WS_CLOSE_TIME = 250; /** @internal */ class SignalClient { get currentState() { return this.state; } get isDisconnected() { return this.state === SignalConnectionState.DISCONNECTING || this.state === SignalConnectionState.DISCONNECTED; } get isEstablishingConnection() { return this.state === SignalConnectionState.CONNECTING || this.state === SignalConnectionState.RECONNECTING; } getNextRequestId() { this._requestId += 1; return this._requestId; } constructor() { let useJSON = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; let loggerOptions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var _a; /** signal rtt in milliseconds */ this.rtt = 0; this.state = SignalConnectionState.DISCONNECTED; this.log = livekitLogger; this._requestId = 0; this.useV0SignalPath = false; /** @internal */ this.resetCallbacks = () => { this.onAnswer = undefined; this.onLeave = undefined; this.onLocalTrackPublished = undefined; this.onLocalTrackUnpublished = undefined; this.onNegotiateRequested = undefined; this.onOffer = undefined; this.onRemoteMuteChanged = undefined; this.onSubscribedQualityUpdate = undefined; this.onTokenRefresh = undefined; this.onTrickle = undefined; this.onClose = undefined; this.onMediaSectionsRequirement = undefined; }; this.loggerContextCb = loggerOptions.loggerContextCb; this.log = getLogger((_a = loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.Signal, () => this.logContext); this.useJSON = useJSON; this.requestQueue = new AsyncQueue(); this.queuedRequests = []; this.closingLock = new _(); this.connectionLock = new _(); this.state = SignalConnectionState.DISCONNECTED; } get logContext() { var _a, _b; return (_b = (_a = this.loggerContextCb) === null || _a === void 0 ? void 0 : _a.call(this)) !== null && _b !== void 0 ? _b : {}; } join(url_1, token_1, opts_1, abortSignal_1) { return __awaiter(this, arguments, void 0, function (url, token, opts, abortSignal) { var _this = this; let useV0Path = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; let publisherOffer = arguments.length > 5 ? arguments[5] : undefined; return function* () { // during a full reconnect, we'd want to start the sequence even if currently // connected _this.state = SignalConnectionState.CONNECTING; _this.options = opts; const res = yield _this.connect(url, token, opts, abortSignal, useV0Path, publisherOffer); return res; }(); }); } reconnect(url, token, sid, reason) { return __awaiter(this, void 0, void 0, function* () { if (!this.options) { this.log.warn('attempted to reconnect without signal options being set, ignoring'); return; } this.state = SignalConnectionState.RECONNECTING; // clear ping interval and restart it once reconnected this.clearPingInterval(); const res = yield this.connect(url, token, Object.assign(Object.assign({}, this.options), { reconnect: true, sid, reconnectReason: reason }), undefined, this.useV0SignalPath); return res; }); } connect(url_1, token_1, opts_1, abortSignal_1) { return __awaiter(this, arguments, void 0, function (url, token, opts, abortSignal) { var _this2 = this; let useV0Path = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; let publisherOffer = arguments.length > 5 ? arguments[5] : undefined; return function* () { const unlock = yield _this2.connectionLock.lock(); _this2.connectOptions = opts; _this2.useV0SignalPath = useV0Path; const clientInfo = getClientInfo(opts.clientInfoCapabilities); const params = useV0Path ? createConnectionParams(token, clientInfo, opts) : yield createJoinRequestConnectionParams(token, clientInfo, opts, publisherOffer); const rtcUrl = createRtcUrl(url, params, useV0Path).toString(); const validateUrl = createValidateUrl(rtcUrl).toString(); return new Promise((resolve, reject) => __awaiter(_this2, void 0, void 0, function* () { var _a, _b; try { let alreadyAborted = false; const abortHandler = eventOrError => __awaiter(this, void 0, void 0, function* () { if (alreadyAborted) { return; } alreadyAborted = true; const target = eventOrError instanceof Event ? eventOrError.currentTarget : eventOrError; const reason = getAbortReasonAsString(target, 'Abort handler called'); // send leave if we have an active stream writer (connection is open) if (this.streamWriter && !this.isDisconnected) { this.sendLeave().then(() => this.close(reason)).catch(e => { this.log.error(e); this.close(); }); } else { this.close(); } cleanupAbortHandlers(); reject(ConnectionError.cancelled(reason)); }); abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.addEventListener('abort', abortHandler); const cleanupAbortHandlers = () => { clearTimeout(wsTimeout); abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.removeEventListener('abort', abortHandler); }; const wsTimeout = setTimeout(() => { abortHandler(ConnectionError.timeout('room connection has timed out (signal)')); }, opts.websocketTimeout); const handleSignalConnected = (connection, firstMessage) => { this.handleSignalConnected(connection, wsTimeout, firstMessage); }; const redactedUrl = new URL(rtcUrl); if (redactedUrl.searchParams.has('access_token')) { redactedUrl.searchParams.set('access_token', ''); } if (this.ws) { const startClose = performance.now(); yield this.close(false); this.log.debug("closed previous ws connection in ".concat(performance.now() - startClose, "ms")); } this.log.info("signal connecting to ".concat(redactedUrl), { reconnect: opts.reconnect, reconnectReason: opts.reconnectReason }); this.ws = new WebSocketStream(rtcUrl); try { this.ws.closed.then(closeInfo => { var _a; if (this.isEstablishingConnection) { reject(ConnectionError.internal("Websocket got closed during a (re)connection attempt: ".concat(closeInfo.reason))); } if (closeInfo.closeCode !== 1000) { this.log.warn("websocket closed", { reason: closeInfo.reason, code: closeInfo.closeCode, wasClean: closeInfo.closeCode === 1000, state: this.state }); if (this.state === SignalConnectionState.CONNECTED) { this.handleOnClose((_a = closeInfo.reason) !== null && _a !== void 0 ? _a : 'Unexpected WS error'); } } return; }).catch(reason => { if (this.isEstablishingConnection) { reject(ConnectionError.internal("Websocket error during a (re)connection attempt: ".concat(reason))); } }); const connection = yield this.ws.opened.catch(reason => __awaiter(this, void 0, void 0, function* () { if (this.state !== SignalConnectionState.CONNECTED) { this.state = SignalConnectionState.DISCONNECTED; clearTimeout(wsTimeout); const error = yield this.handleConnectionError(reason, validateUrl); reject(error); return; } // other errors, handle this.handleWSError(reason); reject(reason); return; })); clearTimeout(wsTimeout); if (!connection) { return; } const signalReader = connection.readable.getReader(); this.streamWriter = connection.writable.getWriter(); const firstMessage = yield signalReader.read(); signalReader.releaseLock(); if (!firstMessage.value) { throw ConnectionError.internal('no message received as first message'); } const firstSignalResponse = parseSignalResponse(firstMessage.value); // Validate the first message const validation = this.validateFirstMessage(firstSignalResponse, (_a = opts.reconnect) !== null && _a !== void 0 ? _a : false); if (!validation.isValid) { reject(validation.error); return; } // Handle join response if (((_b = firstSignalResponse.message) === null || _b === void 0 ? void 0 : _b.case) === 'join') { // Set up ping configuration this.pingTimeoutDuration = firstSignalResponse.message.value.pingTimeout; this.pingIntervalDuration = firstSignalResponse.message.value.pingInterval; if (this.pingTimeoutDuration && this.pingTimeoutDuration > 0) { this.log.debug('ping config', { timeout: this.pingTimeoutDuration, interval: this.pingIntervalDuration }); } if (this.onJoined) { this.onJoined(firstSignalResponse.message.value); } } // Handle successful connection const firstMessageToProcess = validation.shouldProcessFirstMessage ? firstSignalResponse : undefined; handleSignalConnected(connection, firstMessageToProcess); resolve(validation.response); } catch (e) { reject(e); } finally { cleanupAbortHandlers(); } } finally { unlock(); } })); }(); }); } startReadingLoop(signalReader, firstMessage) { return __awaiter(this, void 0, void 0, function* () { if (firstMessage) { this.handleSignalResponse(firstMessage); } while (true) { if (this.signalLatency) { yield sleep(this.signalLatency); } const _yield$signalReader$r = yield signalReader.read(), done = _yield$signalReader$r.done, value = _yield$signalReader$r.value; if (done) { break; } const resp = parseSignalResponse(value); this.handleSignalResponse(resp); } }); } close() { return __awaiter(this, arguments, void 0, function () { var _this3 = this; let updateState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; let reason = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'Close method called on signal client'; return function* () { if ([SignalConnectionState.DISCONNECTING || SignalConnectionState.DISCONNECTED].includes(_this3.state)) { _this3.log.debug("ignoring signal close as it's already in disconnecting state"); return; } const unlock = yield _this3.closingLock.lock(); try { _this3.clearPingInterval(); if (updateState) { _this3.state = SignalConnectionState.DISCONNECTING; } if (_this3.ws) { _this3.ws.close({ closeCode: 1000, reason }); // calling `ws.close()` only starts the closing handshake (CLOSING state), prefer to wait until state is actually CLOSED const closePromise = _this3.ws.closed; _this3.ws = undefined; _this3.streamWriter = undefined; yield Promise.race([closePromise, sleep(MAX_WS_CLOSE_TIME)]); } } catch (e) { _this3.log.debug('websocket error while closing', { error: e }); } finally { if (updateState) { _this3.state = SignalConnectionState.DISCONNECTED; } unlock(); } }(); }); } // initial offer after joining sendOffer(offer, offerId) { this.log.debug('sending offer', { offerSdp: offer.sdp }); this.sendRequest({ case: 'offer', value: toProtoSessionDescription(offer, offerId) }); } // answer a server-initiated offer sendAnswer(answer, offerId) { this.log.debug('sending answer', { answerSdp: answer.sdp }); return this.sendRequest({ case: 'answer', value: toProtoSessionDescription(answer, offerId) }); } sendIceCandidate(candidate, target) { this.log.debug('sending ice candidate', { candidate }); return this.sendRequest({ case: 'trickle', value: new TrickleRequest({ candidateInit: JSON.stringify(candidate), target }) }); } sendMuteTrack(trackSid, muted) { return this.sendRequest({ case: 'mute', value: new MuteTrackRequest({ sid: trackSid, muted }) }); } sendAddTrack(req) { return this.sendRequest({ case: 'addTrack', value: req }); } sendUpdateLocalMetadata(metadata_1, name_1) { return __awaiter(this, arguments, void 0, function (metadata, name) { var _this4 = this; let attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; return function* () { const requestId = _this4.getNextRequestId(); yield _this4.sendRequest({ case: 'updateMetadata', value: new UpdateParticipantMetadata({ requestId, metadata, name, attributes }) }); return requestId; }(); }); } sendUpdateTrackSettings(settings) { this.sendRequest({ case: 'trackSetting', value: settings }); } sendUpdateSubscription(sub) { return this.sendRequest({ case: 'subscription', value: sub }); } sendSyncState(sync) { return this.sendRequest({ case: 'syncState', value: sync }); } sendUpdateVideoLayers(trackSid, layers) { return this.sendRequest({ case: 'updateLayers', value: new UpdateVideoLayers({ trackSid, layers }) }); } sendUpdateSubscriptionPermissions(allParticipants, trackPermissions) { return this.sendRequest({ case: 'subscriptionPermission', value: new SubscriptionPermission({ allParticipants, trackPermissions }) }); } sendSimulateScenario(scenario) { return this.sendRequest({ case: 'simulate', value: scenario }); } sendPing() { /** send both of ping and pingReq for compatibility to old and new server */ return Promise.all([this.sendRequest({ case: 'ping', value: protoInt64.parse(Date.now()) }), this.sendRequest({ case: 'pingReq', value: new Ping({ timestamp: protoInt64.parse(Date.now()), rtt: protoInt64.parse(this.rtt) }) })]); } sendUpdateLocalAudioTrack(trackSid, features) { return this.sendRequest({ case: 'updateAudioTrack', value: new UpdateLocalAudioTrack({ trackSid, features }) }); } sendLeave() { return this.sendRequest({ case: 'leave', value: new LeaveRequest({ reason: DisconnectReason.CLIENT_INITIATED, // server doesn't process this field, keeping it here to indicate the intent of a full disconnect action: LeaveRequest_Action.DISCONNECT }) }); } sendPublishDataTrackRequest(handle, name, usesE2ee) { return this.sendRequest({ case: 'publishDataTrackRequest', value: new PublishDataTrackRequest({ pubHandle: handle, name: name, encryption: usesE2ee ? Encryption_Type.GCM : Encryption_Type.NONE }) }); } sendUnPublishDataTrackRequest(handle) { return this.sendRequest({ case: 'unpublishDataTrackRequest', value: new UnpublishDataTrackRequest({ pubHandle: handle }) }); } sendUpdateDataSubscription(sid, subscribe) { return this.sendRequest({ case: 'updateDataSubscription', value: new UpdateDataSubscription({ // FIXME: consider refactoring to allow caller to pass an array of events through updates: [new UpdateDataSubscription_Update({ trackSid: sid, subscribe })] }) }); } sendRequest(message_1) { return __awaiter(this, arguments, void 0, function (message) { var _this5 = this; let fromQueue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; return function* () { // capture all requests while reconnecting and put them in a queue // unless the request originates from the queue, then don't enqueue again const canQueue = !fromQueue && !canPassThroughQueue(message); if (canQueue && _this5.state === SignalConnectionState.RECONNECTING) { _this5.queuedRequests.push(() => __awaiter(_this5, void 0, void 0, function* () { yield this.sendRequest(message, true); })); return; } // make sure previously queued requests are being sent first if (!fromQueue) { yield _this5.requestQueue.flush(); } if (_this5.signalLatency) { yield sleep(_this5.signalLatency); } if (_this5.isDisconnected) { // Skip requests if the signal layer is disconnected // This can happen if an event is sent in the mist of room.connect() initializing _this5.log.debug("skipping signal request (type: ".concat(message.case, ") - SignalClient disconnected")); return; } if (!_this5.streamWriter) { _this5.log.error("cannot send signal request before connected, type: ".concat(message === null || message === void 0 ? void 0 : message.case)); return; } const req = new SignalRequest({ message }); try { if (_this5.useJSON) { yield _this5.streamWriter.write(req.toJsonString()); } else { yield _this5.streamWriter.write(req.toBinary().buffer); } } catch (e) { _this5.log.error('error sending signal message', { error: e }); } }(); }); } handleSignalResponse(res) { var _a, _b; const msg = res.message; if (msg == undefined) { this.log.debug('received unsupported message'); return; } let pingHandled = false; if (msg.case === 'answer') { const sd = fromProtoSessionDescription(msg.value); if (this.onAnswer) { this.onAnswer(sd, msg.value.id, msg.value.midToTrackId); } } else if (msg.case === 'offer') { const sd = fromProtoSessionDescription(msg.value); if (this.onOffer) { this.onOffer(sd, msg.value.id, msg.value.midToTrackId); } } else if (msg.case === 'trickle') { const candidate = JSON.parse(msg.value.candidateInit); if (this.onTrickle) { this.onTrickle(candidate, msg.value.target); } } else if (msg.case === 'update') { if (this.onParticipantUpdate) { this.onParticipantUpdate((_a = msg.value.participants) !== null && _a !== void 0 ? _a : []); } } else if (msg.case === 'trackPublished') { if (this.onLocalTrackPublished) { this.onLocalTrackPublished(msg.value); } } else if (msg.case === 'speakersChanged') { if (this.onSpeakersChanged) { this.onSpeakersChanged((_b = msg.value.speakers) !== null && _b !== void 0 ? _b : []); } } else if (msg.case === 'leave') { if (this.onLeave) { this.onLeave(msg.value); } } else if (msg.case === 'mute') { if (this.onRemoteMuteChanged) { this.onRemoteMuteChanged(msg.value.sid, msg.value.muted); } } else if (msg.case === 'roomUpdate') { if (this.onRoomUpdate && msg.value.room) { this.onRoomUpdate(msg.value.room); } } else if (msg.case === 'connectionQuality') { if (this.onConnectionQuality) { this.onConnectionQuality(msg.value); } } else if (msg.case === 'streamStateUpdate') { if (this.onStreamStateUpdate) { this.onStreamStateUpdate(msg.value); } } else if (msg.case === 'subscribedQualityUpdate') { if (this.onSubscribedQualityUpdate) { this.onSubscribedQualityUpdate(msg.value); } } else if (msg.case === 'subscriptionPermissionUpdate') { if (this.onSubscriptionPermissionUpdate) { this.onSubscriptionPermissionUpdate(msg.value); } } else if (msg.case === 'refreshToken') { if (this.onTokenRefresh) { this.onTokenRefresh(msg.value); } } else if (msg.case === 'trackUnpublished') { if (this.onLocalTrackUnpublished) { this.onLocalTrackUnpublished(msg.value); } } else if (msg.case === 'subscriptionResponse') { if (this.onSubscriptionError) { this.onSubscriptionError(msg.value); } } else if (msg.case === 'pong') ; else if (msg.case === 'pongResp') { this.rtt = Date.now() - Number.parseInt(msg.value.lastPingTimestamp.toString()); this.resetPingTimeout(); pingHandled = true; } else if (msg.case === 'requestResponse') { if (this.onRequestResponse) { this.onRequestResponse(msg.value); } } else if (msg.case === 'trackSubscribed') { if (this.onLocalTrackSubscribed) { this.onLocalTrackSubscribed(msg.value.trackSid); } } else if (msg.case === 'roomMoved') { if (this.onTokenRefresh) { this.onTokenRefresh(msg.value.token); } if (this.onRoomMoved) { this.onRoomMoved(msg.value); } } else if (msg.case === 'mediaSectionsRequirement') { if (this.onMediaSectionsRequirement) { this.onMediaSectionsRequirement(msg.value); } } else if (msg.case === 'publishDataTrackResponse') { if (this.onPublishDataTrackResponse) { this.onPublishDataTrackResponse(msg.value); } } else if (msg.case === 'unpublishDataTrackResponse') { if (this.onUnPublishDataTrackResponse) { this.onUnPublishDataTrackResponse(msg.value); } } else if (msg.case === 'dataTrackSubscriberHandles') { if (this.onDataTrackSubscriberHandles) { this.onDataTrackSubscriberHandles(msg.value); } } else { this.log.debug('unsupported message', { msgCase: msg.case }); } if (!pingHandled) { this.resetPingTimeout(); } } setReconnected() { while (this.queuedRequests.length > 0) { const req = this.queuedRequests.shift(); if (req) { this.requestQueue.run(req); } } } handleOnClose(reason) { return __awaiter(this, void 0, void 0, function* () { if (this.state === SignalConnectionState.DISCONNECTED) return; const onCloseCallback = this.onClose; yield this.close(undefined, reason); this.log.info("websocket connection closed: ".concat(reason), { reason }); if (onCloseCallback) { onCloseCallback(reason); } }); } handleWSError(error) { this.log.error('websocket error', { error }); } /** * Resets the ping timeout and starts a new timeout. * Call this after receiving a pong message */ resetPingTimeout() { this.clearPingTimeout(); if (!this.pingTimeoutDuration) { this.log.warn('ping timeout duration not set'); return; } this.pingTimeout = CriticalTimers.setTimeout(() => { this.log.warn("ping timeout triggered. last pong received at: ".concat(new Date(Date.now() - this.pingTimeoutDuration * 1000).toUTCString())); this.handleOnClose('ping timeout'); }, this.pingTimeoutDuration * 1000); } /** * Clears ping timeout (does not start a new timeout) */ clearPingTimeout() { if (this.pingTimeout) { CriticalTimers.clearTimeout(this.pingTimeout); } } startPingInterval() { this.clearPingInterval(); this.resetPingTimeout(); if (!this.pingIntervalDuration) { this.log.warn('ping interval duration not set'); return; } this.log.debug('start ping interval'); this.pingInterval = CriticalTimers.setInterval(() => { this.sendPing(); }, this.pingIntervalDuration * 1000); } clearPingInterval() { this.log.debug('clearing ping interval'); this.clearPingTimeout(); if (this.pingInterval) { CriticalTimers.clearInterval(this.pingInterval); } } /** * Handles the successful connection to the signal server * @param connection The WebSocket connection * @param timeoutHandle The timeout handle to clear * @param firstMessage Optional first message to process * @internal */ handleSignalConnected(connection, timeoutHandle, firstMessage) { this.state = SignalConnectionState.CONNECTED; this.log.info('signal connected'); clearTimeout(timeoutHandle); this.startPingInterval(); this.startReadingLoop(connection.readable.getReader(), firstMessage); } /** * Validates the first message received from the signal server * @param firstSignalResponse The first signal response received * @param isReconnect Whether this is a reconnection attempt * @returns Validation result with response or error * @internal */ validateFirstMessage(firstSignalResponse, isReconnect) { var _a, _b, _c, _d, _e; if (((_a = firstSignalResponse.message) === null || _a === void 0 ? void 0 : _a.case) === 'join') { return { isValid: true, response: firstSignalResponse.message.value }; } else if (this.state === SignalConnectionState.RECONNECTING && ((_b = firstSignalResponse.message) === null || _b === void 0 ? void 0 : _b.case) !== 'leave') { if (((_c = firstSignalResponse.message) === null || _c === void 0 ? void 0 : _c.case) === 'reconnect') { return { isValid: true, response: firstSignalResponse.message.value }; } else { // in reconnecting, any message received means signal reconnected and we still need to process it this.log.debug('declaring signal reconnected without reconnect response received'); return { isValid: true, response: undefined, shouldProcessFirstMessage: true }; } } else if (this.isEstablishingConnection && ((_d = firstSignalResponse.message) === null || _d === void 0 ? void 0 : _d.case) === 'leave') { return { isValid: false, error: ConnectionError.leaveRequest('Received leave request while trying to (re)connect', firstSignalResponse.message.value.reason) }; } else if (!isReconnect) { // non-reconnect case, should receive join response first return { isValid: false, error: ConnectionError.internal("did not receive join response, got ".concat((_e = firstSignalResponse.message) === null || _e === void 0 ? void 0 : _e.case, " instead")) }; } return { isValid: false, error: ConnectionError.internal('Unexpected first message') }; } /** * Handles WebSocket connection errors by validating with the server * @param reason The error that occurred * @param validateUrl The URL to validate the connection with * @returns A ConnectionError with appropriate reason and status * @internal */ handleConnectionError(reason, validateUrl) { return __awaiter(this, void 0, void 0, function* () { try { const resp = yield fetch(validateUrl); switch (resp.status) { case 404: const errorMsg = yield resp.text(); if (errorMsg.includes('requested room does not exist')) { return ConnectionError.notAllowed(errorMsg, resp.status); } return ConnectionError.serviceNotFound('v1 RTC path not found. Consider upgrading your LiveKit server version', 'v0-rtc'); case 401: case 403: const msg = yield resp.text(); return ConnectionError.notAllowed(msg, resp.status); default: break; } if (reason instanceof ConnectionError) { return reason; } else { return ConnectionError.internal("Encountered unknown websocket error during connection: ".concat(reason), { status: resp.status, statusText: resp.statusText }); } } catch (e) { return e instanceof ConnectionError ? e : ConnectionError.serverUnreachable(e instanceof Error ? e.message : 'server was not reachable'); } }); } } function fromProtoSessionDescription(sd) { const rsd = { type: 'offer', sdp: sd.sdp }; switch (sd.type) { case 'answer': case 'offer': case 'pranswer': case 'rollback': rsd.type = sd.type; break; } return rsd; } function toProtoSessionDescription(rsd, id) { const sd = new SessionDescription({ sdp: rsd.sdp, type: rsd.type, id }); return sd; } function createConnectionParams(token, info, opts) { var _a; const params = new URLSearchParams(); params.set('access_token', token); // opts if (opts.reconnect) { params.set('reconnect', '1'); if (opts.sid) { params.set('sid', opts.sid); } } params.set('auto_subscribe', opts.autoSubscribe ? '1' : '0'); // ClientInfo params.set('sdk', isReactNative() ? 'reactnative' : 'js'); params.set('version', info.version); params.set('protocol', info.protocol.toString()); params.set('client_protocol', info.clientProtocol.toString()); if (info.deviceModel) { params.set('device_model', info.deviceModel); } if (info.os) { params.set('os', info.os); } if (info.osVersion) { params.set('os_version', info.osVersion); } if (info.browser) { params.set('browser', info.browser); } if (info.browserVersion) { params.set('browser_version', info.browserVersion); } if (opts.adaptiveStream) { params.set('adaptive_stream', '1'); } if (opts.reconnectReason) { params.set('reconnect_reason', opts.reconnectReason.toString()); } // @ts-ignore if ((_a = navigator.connection) === null || _a === void 0 ? void 0 : _a.type) { // @ts-ignore params.set('network', navigator.connection.type); } return params; } function createJoinRequestConnectionParams(token, info, opts, publisherOffer) { return __awaiter(this, void 0, void 0, function* () { const params = new URLSearchParams(); params.set('access_token', token); const joinRequest = new JoinRequest({ clientInfo: info, connectionSettings: new ConnectionSettings({ autoSubscribe: !!opts.autoSubscribe, adaptiveStream: !!opts.adaptiveStream }), reconnect: !!opts.reconnect, participantSid: opts.sid ? opts.sid : undefined, publisherOffer: publisherOffer }); if (opts.reconnectReason) { joinRequest.reconnectReason = opts.reconnectReason; } const joinRequestBytes = joinRequest.toBinary(); let requestBytes; let compression; if (isCompressionStreamSupported()) { const stream = new CompressionStream('gzip'); const writer = stream.writable.getWriter(); writer.write(new Uint8Array(joinRequestBytes)); writer.close(); const chunks = []; const reader = stream.readable.getReader(); while (true) { const _yield$reader$read = yield reader.read(), done = _yield$reader$read.done, value = _yield$reader$read.value; if (done) break; chunks.push(value); } const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0); const result = new Uint8Array(totalLength); let offset = 0; for (const chunk of chunks) { result.set(chunk, offset); offset += chunk.length; } requestBytes = result; compression = WrappedJoinRequest_Compression.GZIP; } else { requestBytes = joinRequestBytes; compression = WrappedJoinRequest_Compression.NONE; } const wrappedJoinRequest = new WrappedJoinRequest({ joinRequest: requestBytes, compression }); const wrappedBytes = wrappedJoinRequest.toBinary(); const bytesToBase64 = bytes => { const binString = Array.from(bytes, byte => String.fromCodePoint(byte)).join(''); return btoa(binString); }; params.set('join_request', bytesToBase64(wrappedBytes).replace(/\+/g, '-').replace(/\//g, '_')); return params; }); }class DataPacketBuffer { constructor() { this.buffer = []; this._totalSize = 0; } push(item) { this.buffer.push(item); this._totalSize += item.data.byteLength; } pop() { const item = this.buffer.shift(); if (item) { this._totalSize -= item.data.byteLength; } return item; } getAll() { return this.buffer.slice(); } popToSequence(sequence) { while (this.buffer.length > 0) { const first = this.buffer[0]; if (first.sequence <= sequence) { this.pop(); } else { break; } } } alignBufferedAmount(bufferedAmount) { while (this.buffer.length > 0) { const first = this.buffer[0]; if (this._totalSize - first.data.byteLength <= bufferedAmount) { break; } this.pop(); } } get length() { return this.buffer.length; } }class TTLMap { /** * @param ttl ttl of the key (ms) */ constructor(ttl) { this._map = new Map(); this._lastCleanup = 0; this.ttl = ttl; } set(key, value) { const now = Date.now(); if (now - this._lastCleanup > this.ttl / 2) { this.cleanup(); } const expiresAt = now + this.ttl; this._map.set(key, { value, expiresAt }); return this; } get(key) { const entry = this._map.get(key); if (!entry) return undefined; if (entry.expiresAt < Date.now()) { this._map.delete(key); return undefined; } return entry.value; } has(key) { const entry = this._map.get(key); if (!entry) return false; if (entry.expiresAt < Date.now()) { this._map.delete(key); return false; } return true; } delete(key) { return this._map.delete(key); } clear() { this._map.clear(); } cleanup() { const now = Date.now(); for (const _ref of this._map.entries()) { var _ref2 = _slicedToArray(_ref, 2); const key = _ref2[0]; const entry = _ref2[1]; if (entry.expiresAt < now) { this._map.delete(key); } } this._lastCleanup = now; } get size() { this.cleanup(); return this._map.size; } forEach(callback) { this.cleanup(); for (const _ref3 of this._map.entries()) { var _ref4 = _slicedToArray(_ref3, 2); const key = _ref4[0]; const entry = _ref4[1]; if (entry.expiresAt >= Date.now()) { callback(entry.value, key, this.asValueMap()); } } } map(callback) { this.cleanup(); const result = []; const valueMap = this.asValueMap(); for (const _ref5 of valueMap.entries()) { var _ref6 = _slicedToArray(_ref5, 2); const key = _ref6[0]; const value = _ref6[1]; result.push(callback(value, key, valueMap)); } return result; } asValueMap() { const result = new Map(); for (const _ref7 of this._map.entries()) { var _ref8 = _slicedToArray(_ref7, 2); const key = _ref8[0]; const entry = _ref8[1]; if (entry.expiresAt >= Date.now()) { result.set(key, entry.value); } } return result; } }var lib = {};var parser = {};var grammar = {exports: {}};var hasRequiredGrammar; function requireGrammar() { if (hasRequiredGrammar) return grammar.exports; hasRequiredGrammar = 1; var grammar$1 = grammar.exports = { v: [{ name: 'version', reg: /^(\d*)$/ }], o: [{ // o=- 20518 0 IN IP4 203.0.113.1 // NB: sessionId will be a String in most cases because it is huge name: 'origin', reg: /^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/, names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'], format: '%s %s %d %s IP%d %s' }], // default parsing of these only (though some of these feel outdated) s: [{ name: 'name' }], i: [{ name: 'description' }], u: [{ name: 'uri' }], e: [{ name: 'email' }], p: [{ name: 'phone' }], z: [{ name: 'timezones' }], // TODO: this one can actually be parsed properly... r: [{ name: 'repeats' }], // TODO: this one can also be parsed properly // k: [{}], // outdated thing ignored t: [{ // t=0 0 name: 'timing', reg: /^(\d*) (\d*)/, names: ['start', 'stop'], format: '%d %d' }], c: [{ // c=IN IP4 10.47.197.26 name: 'connection', reg: /^IN IP(\d) (\S*)/, names: ['version', 'ip'], format: 'IN IP%d %s' }], b: [{ // b=AS:4000 push: 'bandwidth', reg: /^(TIAS|AS|CT|RR|RS):(\d*)/, names: ['type', 'limit'], format: '%s:%s' }], m: [{ // m=video 51744 RTP/AVP 126 97 98 34 31 // NB: special - pushes to session // TODO: rtp/fmtp should be filtered by the payloads found here? reg: /^(\w*) (\d*) ([\w/]*)(?: (.*))?/, names: ['type', 'port', 'protocol', 'payloads'], format: '%s %d %s %s' }], a: [{ // a=rtpmap:110 opus/48000/2 push: 'rtp', reg: /^rtpmap:(\d*) ([\w\-.]*)(?:\s*\/(\d*)(?:\s*\/(\S*))?)?/, names: ['payload', 'codec', 'rate', 'encoding'], format: function (o) { return o.encoding ? 'rtpmap:%d %s/%s/%s' : o.rate ? 'rtpmap:%d %s/%s' : 'rtpmap:%d %s'; } }, { // a=fmtp:108 profile-level-id=24;object=23;bitrate=64000 // a=fmtp:111 minptime=10; useinbandfec=1 push: 'fmtp', reg: /^fmtp:(\d*) ([\S| ]*)/, names: ['payload', 'config'], format: 'fmtp:%d %s' }, { // a=control:streamid=0 name: 'control', reg: /^control:(.*)/, format: 'control:%s' }, { // a=rtcp:65179 IN IP4 193.84.77.194 name: 'rtcp', reg: /^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/, names: ['port', 'netType', 'ipVer', 'address'], format: function (o) { return o.address != null ? 'rtcp:%d %s IP%d %s' : 'rtcp:%d'; } }, { // a=rtcp-fb:98 trr-int 100 push: 'rtcpFbTrrInt', reg: /^rtcp-fb:(\*|\d*) trr-int (\d*)/, names: ['payload', 'value'], format: 'rtcp-fb:%s trr-int %d' }, { // a=rtcp-fb:98 nack rpsi push: 'rtcpFb', reg: /^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/, names: ['payload', 'type', 'subtype'], format: function (o) { return o.subtype != null ? 'rtcp-fb:%s %s %s' : 'rtcp-fb:%s %s'; } }, { // a=extmap:2 urn:ietf:params:rtp-hdrext:toffset // a=extmap:1/recvonly URI-gps-string // a=extmap:3 urn:ietf:params:rtp-hdrext:encrypt urn:ietf:params:rtp-hdrext:smpte-tc 25@600/24 push: 'ext', reg: /^extmap:(\d+)(?:\/(\w+))?(?: (urn:ietf:params:rtp-hdrext:encrypt))? (\S*)(?: (\S*))?/, names: ['value', 'direction', 'encrypt-uri', 'uri', 'config'], format: function (o) { return 'extmap:%d' + (o.direction ? '/%s' : '%v') + (o['encrypt-uri'] ? ' %s' : '%v') + ' %s' + (o.config ? ' %s' : ''); } }, { // a=extmap-allow-mixed name: 'extmapAllowMixed', reg: /^(extmap-allow-mixed)/ }, { // a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32 push: 'crypto', reg: /^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/, names: ['id', 'suite', 'config', 'sessionConfig'], format: function (o) { return o.sessionConfig != null ? 'crypto:%d %s %s %s' : 'crypto:%d %s %s'; } }, { // a=setup:actpass name: 'setup', reg: /^setup:(\w*)/, format: 'setup:%s' }, { // a=connection:new name: 'connectionType', reg: /^connection:(new|existing)/, format: 'connection:%s' }, { // a=mid:1 name: 'mid', reg: /^mid:([^\s]*)/, format: 'mid:%s' }, { // a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a name: 'msid', reg: /^msid:(.*)/, format: 'msid:%s' }, { // a=ptime:20 name: 'ptime', reg: /^ptime:(\d*(?:\.\d*)*)/, format: 'ptime:%d' }, { // a=maxptime:60 name: 'maxptime', reg: /^maxptime:(\d*(?:\.\d*)*)/, format: 'maxptime:%d' }, { // a=sendrecv name: 'direction', reg: /^(sendrecv|recvonly|sendonly|inactive)/ }, { // a=ice-lite name: 'icelite', reg: /^(ice-lite)/ }, { // a=ice-ufrag:F7gI name: 'iceUfrag', reg: /^ice-ufrag:(\S*)/, format: 'ice-ufrag:%s' }, { // a=ice-pwd:x9cml/YzichV2+XlhiMu8g name: 'icePwd', reg: /^ice-pwd:(\S*)/, format: 'ice-pwd:%s' }, { // a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33 name: 'fingerprint', reg: /^fingerprint:(\S*) (\S*)/, names: ['type', 'hash'], format: 'fingerprint:%s %s' }, { // a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host // a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0 network-id 3 network-cost 10 // a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0 network-id 3 network-cost 10 // a=candidate:229815620 1 tcp 1518280447 192.168.150.19 60017 typ host tcptype active generation 0 network-id 3 network-cost 10 // a=candidate:3289912957 2 tcp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 tcptype passive generation 0 network-id 3 network-cost 10 push: 'candidates', reg: /^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: tcptype (\S*))?(?: generation (\d*))?(?: network-id (\d*))?(?: network-cost (\d*))?/, names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'tcptype', 'generation', 'network-id', 'network-cost'], format: function (o) { var str = 'candidate:%s %d %s %d %s %d typ %s'; str += o.raddr != null ? ' raddr %s rport %d' : '%v%v'; // NB: candidate has three optional chunks, so %void middles one if it's missing str += o.tcptype != null ? ' tcptype %s' : '%v'; if (o.generation != null) { str += ' generation %d'; } str += o['network-id'] != null ? ' network-id %d' : '%v'; str += o['network-cost'] != null ? ' network-cost %d' : '%v'; return str; } }, { // a=end-of-candidates (keep after the candidates line for readability) name: 'endOfCandidates', reg: /^(end-of-candidates)/ }, { // a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ... name: 'remoteCandidates', reg: /^remote-candidates:(.*)/, format: 'remote-candidates:%s' }, { // a=ice-options:google-ice name: 'iceOptions', reg: /^ice-options:(\S*)/, format: 'ice-options:%s' }, { // a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1 push: 'ssrcs', reg: /^ssrc:(\d*) ([\w_-]*)(?::(.*))?/, names: ['id', 'attribute', 'value'], format: function (o) { var str = 'ssrc:%d'; if (o.attribute != null) { str += ' %s'; if (o.value != null) { str += ':%s'; } } return str; } }, { // a=ssrc-group:FEC 1 2 // a=ssrc-group:FEC-FR 3004364195 1080772241 push: 'ssrcGroups', // token-char = %x21 / %x23-27 / %x2A-2B / %x2D-2E / %x30-39 / %x41-5A / %x5E-7E reg: /^ssrc-group:([\x21\x23\x24\x25\x26\x27\x2A\x2B\x2D\x2E\w]*) (.*)/, names: ['semantics', 'ssrcs'], format: 'ssrc-group:%s %s' }, { // a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV name: 'msidSemantic', reg: /^msid-semantic:\s?(\w*) (\S*)/, names: ['semantic', 'token'], format: 'msid-semantic: %s %s' // space after ':' is not accidental }, { // a=group:BUNDLE audio video push: 'groups', reg: /^group:(\w*) (.*)/, names: ['type', 'mids'], format: 'group:%s %s' }, { // a=rtcp-mux name: 'rtcpMux', reg: /^(rtcp-mux)/ }, { // a=rtcp-rsize name: 'rtcpRsize', reg: /^(rtcp-rsize)/ }, { // a=sctpmap:5000 webrtc-datachannel 1024 name: 'sctpmap', reg: /^sctpmap:([\w_/]*) (\S*)(?: (\S*))?/, names: ['sctpmapNumber', 'app', 'maxMessageSize'], format: function (o) { return o.maxMessageSize != null ? 'sctpmap:%s %s %s' : 'sctpmap:%s %s'; } }, { // a=x-google-flag:conference name: 'xGoogleFlag', reg: /^x-google-flag:([^\s]*)/, format: 'x-google-flag:%s' }, { // a=rid:1 send max-width=1280;max-height=720;max-fps=30;depend=0 push: 'rids', reg: /^rid:([\d\w]+) (\w+)(?: ([\S| ]*))?/, names: ['id', 'direction', 'params'], format: function (o) { return o.params ? 'rid:%s %s %s' : 'rid:%s %s'; } }, { // a=imageattr:97 send [x=800,y=640,sar=1.1,q=0.6] [x=480,y=320] recv [x=330,y=250] // a=imageattr:* send [x=800,y=640] recv * // a=imageattr:100 recv [x=320,y=240] push: 'imageattrs', reg: new RegExp( // a=imageattr:97 '^imageattr:(\\d+|\\*)' + // send [x=800,y=640,sar=1.1,q=0.6] [x=480,y=320] '[\\s\\t]+(send|recv)[\\s\\t]+(\\*|\\[\\S+\\](?:[\\s\\t]+\\[\\S+\\])*)' + // recv [x=330,y=250] '(?:[\\s\\t]+(recv|send)[\\s\\t]+(\\*|\\[\\S+\\](?:[\\s\\t]+\\[\\S+\\])*))?'), names: ['pt', 'dir1', 'attrs1', 'dir2', 'attrs2'], format: function (o) { return 'imageattr:%s %s %s' + (o.dir2 ? ' %s %s' : ''); } }, { // a=simulcast:send 1,2,3;~4,~5 recv 6;~7,~8 // a=simulcast:recv 1;4,5 send 6;7 name: 'simulcast', reg: new RegExp( // a=simulcast: '^simulcast:' + // send 1,2,3;~4,~5 '(send|recv) ([a-zA-Z0-9\\-_~;,]+)' + // space + recv 6;~7,~8 '(?:\\s?(send|recv) ([a-zA-Z0-9\\-_~;,]+))?' + // end '$'), names: ['dir1', 'list1', 'dir2', 'list2'], format: function (o) { return 'simulcast:%s %s' + (o.dir2 ? ' %s %s' : ''); } }, { // old simulcast draft 03 (implemented by Firefox) // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-03 // a=simulcast: recv pt=97;98 send pt=97 // a=simulcast: send rid=5;6;7 paused=6,7 name: 'simulcast_03', reg: /^simulcast:[\s\t]+([\S+\s\t]+)$/, names: ['value'], format: 'simulcast: %s' }, { // a=framerate:25 // a=framerate:29.97 name: 'framerate', reg: /^framerate:(\d+(?:$|\.\d+))/, format: 'framerate:%s' }, { // RFC4570 // a=source-filter: incl IN IP4 239.5.2.31 10.1.15.5 name: 'sourceFilter', reg: /^source-filter: *(excl|incl) (\S*) (IP4|IP6|\*) (\S*) (.*)/, names: ['filterMode', 'netType', 'addressTypes', 'destAddress', 'srcList'], format: 'source-filter: %s %s %s %s %s' }, { // a=bundle-only name: 'bundleOnly', reg: /^(bundle-only)/ }, { // a=label:1 name: 'label', reg: /^label:(.+)/, format: 'label:%s' }, { // RFC version 26 for SCTP over DTLS // https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-26#section-5 name: 'sctpPort', reg: /^sctp-port:(\d+)$/, format: 'sctp-port:%s' }, { // RFC version 26 for SCTP over DTLS // https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-26#section-6 name: 'maxMessageSize', reg: /^max-message-size:(\d+)$/, format: 'max-message-size:%s' }, { // RFC7273 // a=ts-refclk:ptp=IEEE1588-2008:39-A7-94-FF-FE-07-CB-D0:37 push: 'tsRefClocks', reg: /^ts-refclk:([^\s=]*)(?:=(\S*))?/, names: ['clksrc', 'clksrcExt'], format: function (o) { return 'ts-refclk:%s' + (o.clksrcExt != null ? '=%s' : ''); } }, { // RFC7273 // a=mediaclk:direct=963214424 name: 'mediaClk', reg: /^mediaclk:(?:id=(\S*))? *([^\s=]*)(?:=(\S*))?(?: *rate=(\d+)\/(\d+))?/, names: ['id', 'mediaClockName', 'mediaClockValue', 'rateNumerator', 'rateDenominator'], format: function (o) { var str = 'mediaclk:'; str += o.id != null ? 'id=%s %s' : '%v%s'; str += o.mediaClockValue != null ? '=%s' : ''; str += o.rateNumerator != null ? ' rate=%s' : ''; str += o.rateDenominator != null ? '/%s' : ''; return str; } }, { // a=keywds:keywords name: 'keywords', reg: /^keywds:(.+)$/, format: 'keywds:%s' }, { // a=content:main name: 'content', reg: /^content:(.+)/, format: 'content:%s' }, // BFCP https://tools.ietf.org/html/rfc4583 { // a=floorctrl:c-s name: 'bfcpFloorCtrl', reg: /^floorctrl:(c-only|s-only|c-s)/, format: 'floorctrl:%s' }, { // a=confid:1 name: 'bfcpConfId', reg: /^confid:(\d+)/, format: 'confid:%s' }, { // a=userid:1 name: 'bfcpUserId', reg: /^userid:(\d+)/, format: 'userid:%s' }, { // a=floorid:1 name: 'bfcpFloorId', reg: /^floorid:(.+) (?:m-stream|mstrm):(.+)/, names: ['id', 'mStream'], format: 'floorid:%s mstrm:%s' }, { // any a= that we don't understand is kept verbatim on media.invalid push: 'invalid', names: ['value'] }] }; // set sensible defaults to avoid polluting the grammar with boring details Object.keys(grammar$1).forEach(function (key) { var objs = grammar$1[key]; objs.forEach(function (obj) { if (!obj.reg) { obj.reg = /(.*)/; } if (!obj.format) { obj.format = '%s'; } }); }); return grammar.exports; }var hasRequiredParser; function requireParser() { if (hasRequiredParser) return parser; hasRequiredParser = 1; (function (exports) { var toIntIfInt = function (v) { return String(Number(v)) === v ? Number(v) : v; }; var attachProperties = function (match, location, names, rawName) { if (rawName && !names) { location[rawName] = toIntIfInt(match[1]); } else { for (var i = 0; i < names.length; i += 1) { if (match[i + 1] != null) { location[names[i]] = toIntIfInt(match[i + 1]); } } } }; var parseReg = function (obj, location, content) { var needsBlank = obj.name && obj.names; if (obj.push && !location[obj.push]) { location[obj.push] = []; } else if (needsBlank && !location[obj.name]) { location[obj.name] = {}; } var keyLocation = obj.push ? {} : // blank object that will be pushed needsBlank ? location[obj.name] : location; // otherwise, named location or root attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name); if (obj.push) { location[obj.push].push(keyLocation); } }; var grammar = requireGrammar(); var validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/); exports.parse = function (sdp) { var session = {}, media = [], location = session; // points at where properties go under (one of the above) // parse lines we understand sdp.split(/(\r\n|\r|\n)/).filter(validLine).forEach(function (l) { var type = l[0]; var content = l.slice(2); if (type === 'm') { media.push({ rtp: [], fmtp: [] }); location = media[media.length - 1]; // point at latest media line } for (var j = 0; j < (grammar[type] || []).length; j += 1) { var obj = grammar[type][j]; if (obj.reg.test(content)) { return parseReg(obj, location, content); } } }); session.media = media; // link it up return session; }; var paramReducer = function (acc, expr) { var s = expr.split(/=(.+)/, 2); if (s.length === 2) { acc[s[0]] = toIntIfInt(s[1]); } else if (s.length === 1 && expr.length > 1) { acc[s[0]] = undefined; } return acc; }; exports.parseParams = function (str) { return str.split(/;\s?/).reduce(paramReducer, {}); }; // For backward compatibility - alias will be removed in 3.0.0 exports.parseFmtpConfig = exports.parseParams; exports.parsePayloads = function (str) { return str.toString().split(' ').map(Number); }; exports.parseRemoteCandidates = function (str) { var candidates = []; var parts = str.split(' ').map(toIntIfInt); for (var i = 0; i < parts.length; i += 3) { candidates.push({ component: parts[i], ip: parts[i + 1], port: parts[i + 2] }); } return candidates; }; exports.parseImageAttributes = function (str) { return str.split(' ').map(function (item) { return item.substring(1, item.length - 1).split(',').reduce(paramReducer, {}); }); }; exports.parseSimulcastStreamList = function (str) { return str.split(';').map(function (stream) { return stream.split(',').map(function (format) { var scid, paused = false; if (format[0] !== '~') { scid = toIntIfInt(format); } else { scid = toIntIfInt(format.substring(1, format.length)); paused = true; } return { scid: scid, paused: paused }; }); }); }; })(parser); return parser; }var writer; var hasRequiredWriter; function requireWriter() { if (hasRequiredWriter) return writer; hasRequiredWriter = 1; var grammar = requireGrammar(); // customized util.format - discards excess arguments and can void middle ones var formatRegExp = /%[sdv%]/g; var format = function (formatStr) { var i = 1; var args = arguments; var len = args.length; return formatStr.replace(formatRegExp, function (x) { if (i >= len) { return x; // missing argument } var arg = args[i]; i += 1; switch (x) { case '%%': return '%'; case '%s': return String(arg); case '%d': return Number(arg); case '%v': return ''; } }); // NB: we discard excess arguments - they are typically undefined from makeLine }; var makeLine = function (type, obj, location) { var str = obj.format instanceof Function ? obj.format(obj.push ? location : location[obj.name]) : obj.format; var args = [type + '=' + str]; if (obj.names) { for (var i = 0; i < obj.names.length; i += 1) { var n = obj.names[i]; if (obj.name) { args.push(location[obj.name][n]); } else { // for mLine and push attributes args.push(location[obj.names[i]]); } } } else { args.push(location[obj.name]); } return format.apply(null, args); }; // RFC specified order // TODO: extend this with all the rest var defaultOuterOrder = ['v', 'o', 's', 'i', 'u', 'e', 'p', 'c', 'b', 't', 'r', 'z', 'a']; var defaultInnerOrder = ['i', 'c', 'b', 'a']; writer = function (session, opts) { opts = opts || {}; // ensure certain properties exist if (session.version == null) { session.version = 0; // 'v=0' must be there (only defined version atm) } if (session.name == null) { session.name = ' '; // 's= ' must be there if no meaningful name set } session.media.forEach(function (mLine) { if (mLine.payloads == null) { mLine.payloads = ''; } }); var outerOrder = opts.outerOrder || defaultOuterOrder; var innerOrder = opts.innerOrder || defaultInnerOrder; var sdp = []; // loop through outerOrder for matching properties on session outerOrder.forEach(function (type) { grammar[type].forEach(function (obj) { if (obj.name in session && session[obj.name] != null) { sdp.push(makeLine(type, obj, session)); } else if (obj.push in session && session[obj.push] != null) { session[obj.push].forEach(function (el) { sdp.push(makeLine(type, obj, el)); }); } }); }); // then for each media line, follow the innerOrder session.media.forEach(function (mLine) { sdp.push(makeLine('m', grammar.m[0], mLine)); innerOrder.forEach(function (type) { grammar[type].forEach(function (obj) { if (obj.name in mLine && mLine[obj.name] != null) { sdp.push(makeLine(type, obj, mLine)); } else if (obj.push in mLine && mLine[obj.push] != null) { mLine[obj.push].forEach(function (el) { sdp.push(makeLine(type, obj, el)); }); } }); }); }); return sdp.join('\r\n') + '\r\n'; }; return writer; }var hasRequiredLib; function requireLib() { if (hasRequiredLib) return lib; hasRequiredLib = 1; var parser = requireParser(); var writer = requireWriter(); var grammar = requireGrammar(); lib.grammar = grammar; lib.write = writer; lib.parse = parser.parse; lib.parseParams = parser.parseParams; lib.parseFmtpConfig = parser.parseFmtpConfig; // Alias of parseParams(). lib.parsePayloads = parser.parsePayloads; lib.parseRemoteCandidates = parser.parseRemoteCandidates; lib.parseImageAttributes = parser.parseImageAttributes; lib.parseSimulcastStreamList = parser.parseSimulcastStreamList; return lib; }var libExports = requireLib();/* The svc codec (av1/vp9) would use a very low bitrate at the begining and increase slowly by the bandwidth estimator until it reach the target bitrate. The process commonly cost more than 10 seconds cause subscriber will get blur video at the first few seconds. So we use a 70% of target bitrate here as the start bitrate to eliminate this issue. */ const startBitrateForSVC = 0.7; const debounceInterval = 20; const PCEvents = { NegotiationStarted: 'negotiationStarted', NegotiationComplete: 'negotiationComplete', // Fired with the offerId for every successful publisher answer application, // including answers that immediately recurse into another offer via // `renegotiate`. Use this rather than NegotiationComplete to know that a // specific offer has been negotiated end-to-end. OfferAnswered: 'offerAnswered', RTPVideoPayloadTypes: 'rtpVideoPayloadTypes' }; /** @internal */ class PCTransport extends eventsExports.EventEmitter { get pc() { if (!this._pc) { this._pc = this.createPC(); } return this._pc; } constructor(config) { let loggerOptions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var _a; super(); this.log = livekitLogger; this.ddExtID = 0; this.latestOfferId = 0; this.latestAcknowledgedOfferId = 0; this.pendingCandidates = []; this.restartingIce = false; this.renegotiate = false; this.trackBitrates = []; this.remoteStereoMids = []; this.remoteNackMids = []; // debounced negotiate interface this.negotiate = debounce(onError => __awaiter(this, void 0, void 0, function* () { this.emit(PCEvents.NegotiationStarted); try { yield this.createAndSendOffer(); } catch (e) { if (onError) { onError(e); } else { throw e; } } }), debounceInterval); this.close = () => { if (!this._pc) { return; } this.pendingInitialOffer = undefined; this._pc.close(); this._pc.onconnectionstatechange = null; this._pc.oniceconnectionstatechange = null; this._pc.onicegatheringstatechange = null; this._pc.ondatachannel = null; this._pc.onnegotiationneeded = null; this._pc.onsignalingstatechange = null; this._pc.onicecandidate = null; this._pc.ondatachannel = null; this._pc.ontrack = null; this._pc.onconnectionstatechange = null; this._pc.oniceconnectionstatechange = null; this._pc = null; }; this.log = getLogger((_a = loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.PCTransport); this.loggerOptions = loggerOptions; this.config = config; this._pc = this.createPC(); this.offerLock = new _(); } createPC() { const pc = new RTCPeerConnection(this.config); pc.onicecandidate = ev => { var _a; if (!ev.candidate) return; (_a = this.onIceCandidate) === null || _a === void 0 ? void 0 : _a.call(this, ev.candidate); }; pc.onicecandidateerror = ev => { var _a; (_a = this.onIceCandidateError) === null || _a === void 0 ? void 0 : _a.call(this, ev); }; pc.oniceconnectionstatechange = () => { var _a; (_a = this.onIceConnectionStateChange) === null || _a === void 0 ? void 0 : _a.call(this, pc.iceConnectionState); }; pc.onsignalingstatechange = () => { var _a; (_a = this.onSignalingStatechange) === null || _a === void 0 ? void 0 : _a.call(this, pc.signalingState); }; pc.onconnectionstatechange = () => { var _a; (_a = this.onConnectionStateChange) === null || _a === void 0 ? void 0 : _a.call(this, pc.connectionState); }; pc.ondatachannel = ev => { var _a; (_a = this.onDataChannel) === null || _a === void 0 ? void 0 : _a.call(this, ev); }; pc.ontrack = ev => { var _a; (_a = this.onTrack) === null || _a === void 0 ? void 0 : _a.call(this, ev); }; return pc; } get logContext() { var _a, _b; return Object.assign({}, (_b = (_a = this.loggerOptions).loggerContextCb) === null || _b === void 0 ? void 0 : _b.call(_a)); } get isICEConnected() { return this._pc !== null && (this.pc.iceConnectionState === 'connected' || this.pc.iceConnectionState === 'completed'); } addIceCandidate(candidate) { return __awaiter(this, void 0, void 0, function* () { if (this.pc.remoteDescription && !this.restartingIce) { return this.pc.addIceCandidate(candidate); } this.pendingCandidates.push(candidate); }); } setRemoteDescription(sd, offerId) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; if (sd.type === 'answer' && this.latestOfferId > 0 && offerId > 0 && offerId !== this.latestOfferId) { this.log.warn('ignoring answer for old offer', Object.assign(Object.assign({}, this.logContext), { offerId, latestOfferId: this.latestOfferId })); return false; } let mungedSDP = undefined; if (sd.type === 'offer') { let _extractStereoAndNack = extractStereoAndNackAudioFromOffer(sd), stereoMids = _extractStereoAndNack.stereoMids, nackMids = _extractStereoAndNack.nackMids; this.remoteStereoMids = stereoMids; this.remoteNackMids = nackMids; } else if (sd.type === 'answer') { if (this.pendingInitialOffer && this._pc) { const initialOffer = this.pendingInitialOffer; this.pendingInitialOffer = undefined; const sdpParsed = libExports.parse((_a = initialOffer.sdp) !== null && _a !== void 0 ? _a : ''); sdpParsed.media.forEach(media => { ensureIPAddrMatchVersion(media); }); this.log.debug('setting pending initial offer before processing answer', this.logContext); yield this.setMungedSDP(initialOffer, libExports.write(sdpParsed)); } const sdpParsed = libExports.parse((_b = sd.sdp) !== null && _b !== void 0 ? _b : ''); sdpParsed.media.forEach(media => { const mid = getMidString(media.mid); if (media.type === 'audio') { // munge sdp for opus bitrate settings this.trackBitrates.some(trackbr => { if (!trackbr.transceiver || mid != trackbr.transceiver.mid) { return false; } let codecPayload = 0; media.rtp.some(rtp => { if (rtp.codec.toUpperCase() === trackbr.codec.toUpperCase()) { codecPayload = rtp.payload; return true; } return false; }); if (codecPayload === 0) { return true; } let fmtpFound = false; for (const fmtp of media.fmtp) { if (fmtp.payload === codecPayload) { fmtp.config = fmtp.config.split(';').filter(attr => !attr.includes('maxaveragebitrate')).join(';'); if (trackbr.maxbr > 0) { fmtp.config += ";maxaveragebitrate=".concat(trackbr.maxbr * 1000); } fmtpFound = true; break; } } if (!fmtpFound) { if (trackbr.maxbr > 0) { media.fmtp.push({ payload: codecPayload, config: "maxaveragebitrate=".concat(trackbr.maxbr * 1000) }); } } return true; }); } }); mungedSDP = libExports.write(sdpParsed); } yield this.setMungedSDP(sd, mungedSDP, true); this.pendingCandidates.forEach(candidate => { this.pc.addIceCandidate(candidate); }); this.pendingCandidates = []; this.restartingIce = false; // Fire OfferAnswered for every successfully applied answer, including the // ones that recurse into another offer via `renegotiate`. Callers waiting // on a specific offerId can resolve as soon as their offer's answer is in. if (sd.type === 'answer') { this.latestAcknowledgedOfferId = offerId; this.emit(PCEvents.OfferAnswered, offerId); } if (this.renegotiate) { this.renegotiate = false; yield this.createAndSendOffer(); } else if (sd.type === 'answer') { this.emit(PCEvents.NegotiationComplete); if (sd.sdp) { const sdpParsed = libExports.parse(sd.sdp); sdpParsed.media.forEach(media => { if (media.type === 'video') { this.emit(PCEvents.RTPVideoPayloadTypes, media.rtp); } }); } } return true; }); } createInitialOffer() { return __awaiter(this, void 0, void 0, function* () { var _a; const unlock = yield this.offerLock.lock(); try { if (this.pc.signalingState !== 'stable') { this.log.warn('signaling state is not stable, cannot create initial offer', this.logContext); return; } const offerId = this.latestOfferId + 1; this.latestOfferId = offerId; const offer = yield this.pc.createOffer(); this.pendingInitialOffer = { sdp: offer.sdp, type: offer.type }; const sdpParsed = libExports.parse((_a = offer.sdp) !== null && _a !== void 0 ? _a : ''); sdpParsed.media.forEach(media => { ensureIPAddrMatchVersion(media); }); offer.sdp = libExports.write(sdpParsed); return { offer, offerId }; } finally { unlock(); } }); } createAndSendOffer(options) { return __awaiter(this, void 0, void 0, function* () { var _a; const unlock = yield this.offerLock.lock(); try { if (this.onOffer === undefined) { return; } if (options === null || options === void 0 ? void 0 : options.iceRestart) { this.log.debug('restarting ICE', this.logContext); this.restartingIce = true; } if (this._pc && (this._pc.signalingState === 'have-local-offer' || this.pendingInitialOffer)) { // we're waiting for the peer to accept our offer, so we'll just wait // the only exception to this is when ICE restart is needed const currentSD = this._pc.remoteDescription; if ((options === null || options === void 0 ? void 0 : options.iceRestart) && currentSD) { // TODO: handle when ICE restart is needed but we don't have a remote description // the best thing to do is to recreate the peerconnection yield this._pc.setRemoteDescription(currentSD); } else { this.renegotiate = true; this.log.debug('requesting renegotiation', Object.assign({}, this.logContext)); return; } } else if (!this._pc || this._pc.signalingState === 'closed') { this.log.warn('could not createOffer with closed peer connection', this.logContext); return; } // actually negotiate this.log.debug('starting to negotiate', this.logContext); // increase the offer id at the start to ensure the offer is always > 0 so that we can use 0 as a default value for legacy behavior const offerId = this.latestOfferId + 1; this.latestOfferId = offerId; const offer = yield this.pc.createOffer(options); this.log.debug('original offer', Object.assign({ sdp: offer.sdp }, this.logContext)); const sdpParsed = libExports.parse((_a = offer.sdp) !== null && _a !== void 0 ? _a : ''); sdpParsed.media.forEach(media => { ensureIPAddrMatchVersion(media); if (media.type === 'audio') { ensureAudioNackAndStereo(media, ['all'], []); } else if (media.type === 'video') { this.trackBitrates.some(trackbr => { if (!media.msid || !trackbr.cid || !media.msid.includes(trackbr.cid)) { return false; } let codecPayload = 0; media.rtp.some(rtp => { if (rtp.codec.toUpperCase() === trackbr.codec.toUpperCase()) { codecPayload = rtp.payload; return true; } return false; }); if (codecPayload === 0) { return true; } if (isSVCCodec(trackbr.codec) && !isSafari()) { this.ensureVideoDDExtensionForSVC(media, sdpParsed); } // mung sdp for bitrate setting that can't apply by sendEncoding if (!isSVCCodec(trackbr.codec)) { return true; } const startBitrate = Math.round(trackbr.maxbr * startBitrateForSVC); for (const fmtp of media.fmtp) { if (fmtp.payload === codecPayload) { // if another track's fmtp already is set, we cannot override the bitrate // this has the unfortunate consequence of being forced to use the // initial track's bitrate for all tracks if (!fmtp.config.includes('x-google-start-bitrate')) { fmtp.config += ";x-google-start-bitrate=".concat(startBitrate); } break; } } return true; }); } }); if (this.latestOfferId > offerId) { this.log.warn('latestOfferId mismatch', Object.assign(Object.assign({}, this.logContext), { latestOfferId: this.latestOfferId, offerId })); return; } yield this.setMungedSDP(offer, libExports.write(sdpParsed)); this.onOffer(offer, this.latestOfferId); } finally { unlock(); } }); } createAndSetAnswer() { return __awaiter(this, void 0, void 0, function* () { var _a; const answer = yield this.pc.createAnswer(); const sdpParsed = libExports.parse((_a = answer.sdp) !== null && _a !== void 0 ? _a : ''); sdpParsed.media.forEach(media => { ensureIPAddrMatchVersion(media); if (media.type === 'audio') { ensureAudioNackAndStereo(media, this.remoteStereoMids, this.remoteNackMids); } }); yield this.setMungedSDP(answer, libExports.write(sdpParsed)); return answer; }); } createDataChannel(label, dataChannelDict) { return this.pc.createDataChannel(label, dataChannelDict); } addTransceiver(mediaStreamTrack, transceiverInit) { return this.pc.addTransceiver(mediaStreamTrack, transceiverInit); } addTransceiverOfKind(kind, transceiverInit) { return this.pc.addTransceiver(kind, transceiverInit); } addTrack(track) { if (!this._pc) { throw new UnexpectedConnectionState('PC closed, cannot add track'); } return this._pc.addTrack(track); } setTrackCodecBitrate(info) { this.trackBitrates.push(info); } setConfiguration(rtcConfig) { var _a; if (!this._pc) { throw new UnexpectedConnectionState('PC closed, cannot configure'); } return (_a = this._pc) === null || _a === void 0 ? void 0 : _a.setConfiguration(rtcConfig); } canRemoveTrack() { var _a; return !!((_a = this._pc) === null || _a === void 0 ? void 0 : _a.removeTrack); } removeTrack(sender) { var _a; return (_a = this._pc) === null || _a === void 0 ? void 0 : _a.removeTrack(sender); } getConnectionState() { var _a, _b; return (_b = (_a = this._pc) === null || _a === void 0 ? void 0 : _a.connectionState) !== null && _b !== void 0 ? _b : 'closed'; } getICEConnectionState() { var _a, _b; return (_b = (_a = this._pc) === null || _a === void 0 ? void 0 : _a.iceConnectionState) !== null && _b !== void 0 ? _b : 'closed'; } getSignallingState() { var _a, _b; return (_b = (_a = this._pc) === null || _a === void 0 ? void 0 : _a.signalingState) !== null && _b !== void 0 ? _b : 'closed'; } getTransceivers() { var _a, _b; return (_b = (_a = this._pc) === null || _a === void 0 ? void 0 : _a.getTransceivers()) !== null && _b !== void 0 ? _b : []; } getSenders() { var _a, _b; return (_b = (_a = this._pc) === null || _a === void 0 ? void 0 : _a.getSenders()) !== null && _b !== void 0 ? _b : []; } getLocalDescription() { var _a; return (_a = this._pc) === null || _a === void 0 ? void 0 : _a.localDescription; } getRemoteDescription() { var _a; return (_a = this.pc) === null || _a === void 0 ? void 0 : _a.remoteDescription; } getStats() { return this.pc.getStats(); } getMaxMessageSize() { var _a, _b; return (_b = (_a = this._pc) === null || _a === void 0 ? void 0 : _a.sctp) === null || _b === void 0 ? void 0 : _b.maxMessageSize; } getConnectedAddress() { return __awaiter(this, void 0, void 0, function* () { var _a; if (!this._pc) { return; } let selectedCandidatePairId = ''; const candidatePairs = new Map(); // id -> candidate ip const candidates = new Map(); const stats = yield this._pc.getStats(); stats.forEach(v => { switch (v.type) { case 'transport': selectedCandidatePairId = v.selectedCandidatePairId; break; case 'candidate-pair': if (selectedCandidatePairId === '' && v.selected) { selectedCandidatePairId = v.id; } candidatePairs.set(v.id, v); break; case 'remote-candidate': candidates.set(v.id, "".concat(v.address, ":").concat(v.port)); break; } }); if (selectedCandidatePairId === '') { return undefined; } const selectedID = (_a = candidatePairs.get(selectedCandidatePairId)) === null || _a === void 0 ? void 0 : _a.remoteCandidateId; if (selectedID === undefined) { return undefined; } return candidates.get(selectedID); }); } setMungedSDP(sd, munged, remote) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; const originalSdp = sd.sdp; if (munged) { sd.sdp = munged; try { this.log.debug("setting munged ".concat(remote ? 'remote' : 'local', " description"), this.logContext); if (remote) { yield this.pc.setRemoteDescription(sd); } else { yield this.pc.setLocalDescription(sd); } return; } catch (e) { this.log.warn("not able to set ".concat(sd.type, ", falling back to unmodified sdp"), Object.assign(Object.assign({}, this.logContext), { error: e, mungedSdp: munged, originalSdp })); sd.sdp = originalSdp; } } try { if (remote) { yield (_a = this._pc) === null || _a === void 0 ? void 0 : _a.setRemoteDescription(sd); } else { yield (_b = this._pc) === null || _b === void 0 ? void 0 : _b.setLocalDescription(sd); } } catch (e) { let msg = 'unknown error'; if (e instanceof Error) { msg = e.message; } else if (typeof e === 'string') { msg = e; } const fields = { error: msg, sdp: sd.sdp }; if (munged && munged !== originalSdp) { fields.mungedSdp = munged; } if (!remote && this.pc.remoteDescription) { fields.remoteSdp = this.pc.remoteDescription; } this.log.error("unable to set ".concat(sd.type), Object.assign(Object.assign({}, this.logContext), { fields })); throw new NegotiationError(msg); } }); } ensureVideoDDExtensionForSVC(media, sdp) { var _a, _b; const ddFound = (_a = media.ext) === null || _a === void 0 ? void 0 : _a.some(ext => { if (ext.uri === ddExtensionURI) { return true; } return false; }); if (!ddFound) { if (this.ddExtID === 0) { let maxID = 0; sdp.media.forEach(m => { var _a; (_a = m.ext) === null || _a === void 0 ? void 0 : _a.forEach(ext => { if (ext.value > maxID) { maxID = ext.value; } }); }); this.ddExtID = maxID + 1; } (_b = media.ext) === null || _b === void 0 ? void 0 : _b.push({ value: this.ddExtID, uri: ddExtensionURI }); } } } function ensureAudioNackAndStereo(media, stereoMids, nackMids) { // sdp-transform types don't include number however the parser outputs mids as numbers in some cases const mid = getMidString(media.mid); // found opus codec to add nack fb let opusPayload = 0; media.rtp.some(rtp => { if (rtp.codec === 'opus') { opusPayload = rtp.payload; return true; } return false; }); // add nack rtcpfb if not exist if (opusPayload > 0) { if (!media.rtcpFb) { media.rtcpFb = []; } if (nackMids.includes(mid) && !media.rtcpFb.some(fb => fb.payload === opusPayload && fb.type === 'nack')) { media.rtcpFb.push({ payload: opusPayload, type: 'nack' }); } if (stereoMids.includes(mid) || stereoMids.length === 1 && stereoMids[0] === 'all') { media.fmtp.some(fmtp => { if (fmtp.payload === opusPayload) { if (!fmtp.config.includes('stereo=1')) { fmtp.config += ';stereo=1'; } return true; } return false; }); } } } function extractStereoAndNackAudioFromOffer(offer) { var _a; const stereoMids = []; const nackMids = []; const sdpParsed = libExports.parse((_a = offer.sdp) !== null && _a !== void 0 ? _a : ''); let opusPayload = 0; sdpParsed.media.forEach(media => { var _a; const mid = getMidString(media.mid); if (media.type === 'audio') { media.rtp.some(rtp => { if (rtp.codec === 'opus') { opusPayload = rtp.payload; return true; } return false; }); if ((_a = media.rtcpFb) === null || _a === void 0 ? void 0 : _a.some(fb => fb.payload === opusPayload && fb.type === 'nack')) { nackMids.push(mid); } media.fmtp.some(fmtp => { if (fmtp.payload === opusPayload) { if (fmtp.config.includes('sprop-stereo=1')) { stereoMids.push(mid); } return true; } return false; }); } }); return { stereoMids, nackMids }; } function ensureIPAddrMatchVersion(media) { // Chrome could generate sdp with c = IN IP4 // in edge case and return error when set sdp.This is not a // sdk error but correct it if the issue detected. if (media.connection) { const isV6 = media.connection.ip.indexOf(':') >= 0; if (media.connection.version === 4 && isV6 || media.connection.version === 6 && !isV6) { // fallback to dummy address media.connection.ip = '0.0.0.0'; media.connection.version = 4; } } } function getMidString(mid) { return typeof mid === 'number' ? mid.toFixed(0) : mid; }const defaultVideoCodec = 'vp8'; const publishDefaults = { audioPreset: AudioPresets.music, dtx: true, red: true, forceStereo: false, simulcast: true, screenShareEncoding: ScreenSharePresets.h1080fps15.encoding, stopMicTrackOnMute: false, videoCodec: defaultVideoCodec, backupCodec: true, preConnectBuffer: false }; const audioDefaults = { deviceId: { ideal: 'default' }, autoGainControl: true, echoCancellation: true, noiseSuppression: true, voiceIsolation: true }; const videoDefaults = { deviceId: { ideal: 'default' }, resolution: VideoPresets.h720.resolution }; const roomOptionDefaults = { adaptiveStream: false, dynacast: false, stopLocalTrackOnUnpublish: true, reconnectPolicy: new DefaultReconnectPolicy(), disconnectOnPageLeave: true, webAudioMix: false, singlePeerConnection: true }; const roomConnectOptionDefaults = { autoSubscribe: true, maxRetries: 1, peerConnectionTimeout: 15000, websocketTimeout: 15000 };var PCTransportState; (function (PCTransportState) { PCTransportState[PCTransportState["NEW"] = 0] = "NEW"; PCTransportState[PCTransportState["CONNECTING"] = 1] = "CONNECTING"; PCTransportState[PCTransportState["CONNECTED"] = 2] = "CONNECTED"; PCTransportState[PCTransportState["FAILED"] = 3] = "FAILED"; PCTransportState[PCTransportState["CLOSING"] = 4] = "CLOSING"; PCTransportState[PCTransportState["CLOSED"] = 5] = "CLOSED"; })(PCTransportState || (PCTransportState = {})); class PCTransportManager { get needsPublisher() { return this.isPublisherConnectionRequired; } get needsSubscriber() { return this.isSubscriberConnectionRequired; } get currentState() { return this.state; } get mode() { return this._mode; } constructor(mode, loggerOptions, rtcConfig) { var _a; this.peerConnectionTimeout = roomConnectOptionDefaults.peerConnectionTimeout; this.log = livekitLogger; this.updateState = () => { var _a, _b; const previousState = this.state; const connectionStates = this.requiredTransports.map(tr => tr.getConnectionState()); if (connectionStates.every(st => st === 'connected')) { this.state = PCTransportState.CONNECTED; } else if (connectionStates.some(st => st === 'failed')) { this.state = PCTransportState.FAILED; } else if (connectionStates.some(st => st === 'connecting')) { this.state = PCTransportState.CONNECTING; } else if (connectionStates.every(st => st === 'closed')) { this.state = PCTransportState.CLOSED; } else if (connectionStates.some(st => st === 'closed')) { this.state = PCTransportState.CLOSING; } else if (connectionStates.every(st => st === 'new')) { this.state = PCTransportState.NEW; } if (previousState !== this.state) { this.log.debug("pc state change: from ".concat(PCTransportState[previousState], " to ").concat(PCTransportState[this.state]), this.logContext); (_a = this.onStateChange) === null || _a === void 0 ? void 0 : _a.call(this, this.state, this.publisher.getConnectionState(), (_b = this.subscriber) === null || _b === void 0 ? void 0 : _b.getConnectionState()); } }; this.log = getLogger((_a = loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.PCManager); this.loggerOptions = loggerOptions; this.isPublisherConnectionRequired = mode !== 'subscriber-primary'; this.isSubscriberConnectionRequired = mode === 'subscriber-primary'; this.publisher = new PCTransport(rtcConfig, loggerOptions); this._mode = mode; if (mode !== 'publisher-only') { this.subscriber = new PCTransport(rtcConfig, loggerOptions); this.subscriber.onConnectionStateChange = this.updateState; this.subscriber.onIceConnectionStateChange = this.updateState; this.subscriber.onSignalingStatechange = this.updateState; this.subscriber.onIceCandidate = candidate => { var _a; (_a = this.onIceCandidate) === null || _a === void 0 ? void 0 : _a.call(this, candidate, SignalTarget.SUBSCRIBER); }; // in subscriber primary mode, server side opens sub data channels. this.subscriber.onDataChannel = ev => { var _a; (_a = this.onDataChannel) === null || _a === void 0 ? void 0 : _a.call(this, ev); }; this.subscriber.onTrack = ev => { var _a; (_a = this.onTrack) === null || _a === void 0 ? void 0 : _a.call(this, ev); }; } this.publisher.onConnectionStateChange = this.updateState; this.publisher.onIceConnectionStateChange = this.updateState; this.publisher.onSignalingStatechange = this.updateState; this.publisher.onIceCandidate = candidate => { var _a; (_a = this.onIceCandidate) === null || _a === void 0 ? void 0 : _a.call(this, candidate, SignalTarget.PUBLISHER); }; this.publisher.onTrack = ev => { var _a; (_a = this.onTrack) === null || _a === void 0 ? void 0 : _a.call(this, ev); }; this.publisher.onOffer = (offer, offerId) => { var _a; (_a = this.onPublisherOffer) === null || _a === void 0 ? void 0 : _a.call(this, offer, offerId); }; this.state = PCTransportState.NEW; this.connectionLock = new _(); this.remoteOfferLock = new _(); } get logContext() { var _a, _b; return Object.assign({}, (_b = (_a = this.loggerOptions).loggerContextCb) === null || _b === void 0 ? void 0 : _b.call(_a)); } requirePublisher() { let require = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; this.isPublisherConnectionRequired = require; this.updateState(); } createAndSendPublisherOffer(options) { return this.publisher.createAndSendOffer(options); } setPublisherAnswer(sd, offerId) { return this.publisher.setRemoteDescription(sd, offerId); } removeTrack(sender) { return this.publisher.removeTrack(sender); } close() { return __awaiter(this, void 0, void 0, function* () { var _a; if (this.publisher && this.publisher.getSignallingState() !== 'closed') { const publisher = this.publisher; for (const sender of publisher.getSenders()) { try { // TODO: react-native-webrtc doesn't have removeTrack yet. if (publisher.canRemoveTrack()) { publisher.removeTrack(sender); } } catch (e) { this.log.warn('could not removeTrack', Object.assign(Object.assign({}, this.logContext), { error: e })); } } } yield Promise.all([this.publisher.close(), (_a = this.subscriber) === null || _a === void 0 ? void 0 : _a.close()]); this.updateState(); }); } triggerIceRestart() { return __awaiter(this, void 0, void 0, function* () { if (this.subscriber) { this.subscriber.restartingIce = true; } // only restart publisher if it's needed if (this.needsPublisher) { yield this.createAndSendPublisherOffer({ iceRestart: true }); } }); } addIceCandidate(candidate, target) { return __awaiter(this, void 0, void 0, function* () { var _a; if (target === SignalTarget.PUBLISHER) { yield this.publisher.addIceCandidate(candidate); } else { yield (_a = this.subscriber) === null || _a === void 0 ? void 0 : _a.addIceCandidate(candidate); } }); } createSubscriberAnswerFromOffer(sd, offerId) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; this.log.debug('received server offer', Object.assign(Object.assign({}, this.logContext), { RTCSdpType: sd.type, sdp: sd.sdp, signalingState: (_a = this.subscriber) === null || _a === void 0 ? void 0 : _a.getSignallingState().toString() })); const unlock = yield this.remoteOfferLock.lock(); try { const success = yield (_b = this.subscriber) === null || _b === void 0 ? void 0 : _b.setRemoteDescription(sd, offerId); if (!success) { return undefined; } // answer the offer const answer = yield (_c = this.subscriber) === null || _c === void 0 ? void 0 : _c.createAndSetAnswer(); return answer; } finally { unlock(); } }); } updateConfiguration(config, iceRestart) { var _a; this.publisher.setConfiguration(config); (_a = this.subscriber) === null || _a === void 0 ? void 0 : _a.setConfiguration(config); if (iceRestart) { this.triggerIceRestart(); } } ensurePCTransportConnection(abortController, timeout) { return __awaiter(this, void 0, void 0, function* () { var _a; const unlock = yield this.connectionLock.lock(); try { if (this.isPublisherConnectionRequired && this.publisher.getConnectionState() !== 'connected' && this.publisher.getConnectionState() !== 'connecting') { this.log.debug('negotiation required, start negotiating', this.logContext); this.publisher.negotiate(); } yield Promise.all((_a = this.requiredTransports) === null || _a === void 0 ? void 0 : _a.map(transport => this.ensureTransportConnected(transport, abortController, timeout))); } finally { unlock(); } }); } negotiate(abortController) { return __awaiter(this, void 0, void 0, function* () { return new TypedPromise((resolve, reject) => { // Capture the publisher's latest offer id at request time. We are done // when an offer with a higher id has had its answer successfully // applied — that offer is the one that includes any transceiver/SDP // changes that motivated this negotiate call. Concurrent callers each // get their own checkpoint and resolve independently. const checkpoint = this.publisher.latestOfferId; // Race: an answer past our checkpoint already arrived before we had a // chance to subscribe. if (this.publisher.latestAcknowledgedOfferId > checkpoint) { this.log.debug("negotiation already handled in more recent acknowledged offer", this.logContext); resolve(); return; } let cleanedUp = false; const cleanup = () => { if (cleanedUp) return; cleanedUp = true; clearTimeout(deadlineTimer); this.publisher.off(PCEvents.OfferAnswered, onAnswered); abortController.signal.removeEventListener('abort', onAbort); }; const onAnswered = offerId => { if (offerId > checkpoint) { cleanup(); resolve(); } }; const onAbort = () => { cleanup(); reject(new NegotiationError('negotiation aborted')); }; // Single hard deadline as a backstop. Not reset on cycle progress — // progress is tracked via OfferAnswered, not NegotiationStarted. const deadlineTimer = setTimeout(() => { cleanup(); reject(new NegotiationError('negotiation timed out')); }, this.peerConnectionTimeout); abortController.signal.addEventListener('abort', onAbort); this.publisher.on(PCEvents.OfferAnswered, onAnswered); this.publisher.negotiate(e => { cleanup(); if (e instanceof Error) { reject(e); } else { reject(new Error(String(e))); } }); }); }); } addPublisherTransceiver(track, transceiverInit) { return this.publisher.addTransceiver(track, transceiverInit); } addPublisherTransceiverOfKind(kind, transceiverInit) { return this.publisher.addTransceiverOfKind(kind, transceiverInit); } getMidForReceiver(receiver) { const transceivers = this.subscriber ? this.subscriber.getTransceivers() : this.publisher.getTransceivers(); const matchingTransceiver = transceivers.find(transceiver => transceiver.receiver === receiver); return matchingTransceiver === null || matchingTransceiver === void 0 ? void 0 : matchingTransceiver.mid; } getMaxPublisherMessageSize() { return this.publisher.getMaxMessageSize(); } addPublisherTrack(track) { return this.publisher.addTrack(track); } createPublisherDataChannel(label, dataChannelDict) { return this.publisher.createDataChannel(label, dataChannelDict); } /** * Returns the first required transport's address if no explicit target is specified */ getConnectedAddress(target) { if (target === SignalTarget.PUBLISHER) { return this.publisher.getConnectedAddress(); } else if (target === SignalTarget.SUBSCRIBER) { return this.publisher.getConnectedAddress(); } return this.requiredTransports[0].getConnectedAddress(); } get requiredTransports() { const transports = []; if (this.isPublisherConnectionRequired) { transports.push(this.publisher); } if (this.isSubscriberConnectionRequired && this.subscriber) { transports.push(this.subscriber); } return transports; } ensureTransportConnected(pcTransport_1, abortController_1) { return __awaiter(this, arguments, void 0, function (pcTransport, abortController) { var _this = this; let timeout = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.peerConnectionTimeout; return function* () { const connectionState = pcTransport.getConnectionState(); if (connectionState === 'connected') { return; } return new Promise((resolve, reject) => __awaiter(_this, void 0, void 0, function* () { const abortHandler = () => { this.log.warn('abort transport connection', this.logContext); CriticalTimers.clearTimeout(connectTimeout); reject(ConnectionError.cancelled('room connection has been cancelled')); }; if (abortController === null || abortController === void 0 ? void 0 : abortController.signal.aborted) { abortHandler(); } abortController === null || abortController === void 0 ? void 0 : abortController.signal.addEventListener('abort', abortHandler); const connectTimeout = CriticalTimers.setTimeout(() => { abortController === null || abortController === void 0 ? void 0 : abortController.signal.removeEventListener('abort', abortHandler); reject(ConnectionError.internal('could not establish pc connection')); }, timeout); while (this.state !== PCTransportState.CONNECTED) { yield sleep(50); // FIXME we shouldn't rely on `sleep` in the connection paths, as it invokes `setTimeout` which can be drastically throttled by browser implementations if (abortController === null || abortController === void 0 ? void 0 : abortController.signal.aborted) { reject(ConnectionError.cancelled('room connection has been cancelled')); return; } } CriticalTimers.clearTimeout(connectTimeout); abortController === null || abortController === void 0 ? void 0 : abortController.signal.removeEventListener('abort', abortHandler); resolve(); })); }(); }); } }// Check if MediaRecorder is available const isMediaRecorderAvailable = typeof MediaRecorder !== 'undefined'; // Fallback class for environments without MediaRecorder class FallbackRecorder { constructor() { throw new Error('MediaRecorder is not available in this environment'); } } // Use conditional inheritance to avoid parse-time errors const RecorderBase = isMediaRecorderAvailable ? MediaRecorder : FallbackRecorder; class LocalTrackRecorder extends RecorderBase { constructor(track, options) { if (!isMediaRecorderAvailable) { throw new Error('MediaRecorder is not available in this environment'); } super(new MediaStream([track.mediaStreamTrack]), options); let dataListener; let streamController; const isClosed = () => streamController === undefined; const onStop = () => { this.removeEventListener('dataavailable', dataListener); this.removeEventListener('stop', onStop); this.removeEventListener('error', onError); streamController === null || streamController === void 0 ? void 0 : streamController.close(); streamController = undefined; }; const onError = event => { streamController === null || streamController === void 0 ? void 0 : streamController.error(event); this.removeEventListener('dataavailable', dataListener); this.removeEventListener('stop', onStop); this.removeEventListener('error', onError); streamController = undefined; }; this.byteStream = new ReadableStream({ start: controller => { streamController = controller; dataListener = event => __awaiter(this, void 0, void 0, function* () { let data; if (event.data.arrayBuffer) { const arrayBuffer = yield event.data.arrayBuffer(); data = new Uint8Array(arrayBuffer); // @ts-expect-error react-native passes over Uint8Arrays directly } else if (event.data.byteArray) { // @ts-expect-error data = event.data.byteArray; } else { throw new Error('no data available!'); } if (isClosed()) { return; } controller.enqueue(data); }); this.addEventListener('dataavailable', dataListener); }, cancel: () => { onStop(); } }); this.addEventListener('stop', onStop); this.addEventListener('error', onError); } } // Helper function to check if recording is supported function isRecordingSupported() { return isMediaRecorderAvailable; }const DEFAULT_DIMENSIONS_TIMEOUT = 1000; const PRE_CONNECT_BUFFER_TIMEOUT = 10000; class LocalTrack extends Track { /** @internal */ get sender() { return this._sender; } /** @internal */ set sender(sender) { this._sender = sender; } get constraints() { return this._constraints; } get hasPreConnectBuffer() { return !!this.localTrackRecorder; } /** * * @param mediaTrack * @param kind * @param constraints MediaTrackConstraints that are being used when restarting or reacquiring tracks * @param userProvidedTrack Signals to the SDK whether or not the mediaTrack should be managed (i.e. released and reacquired) internally by the SDK */ constructor(mediaTrack, kind, constraints) { let userProvidedTrack = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; let loggerOptions = arguments.length > 4 ? arguments[4] : undefined; super(mediaTrack, kind, loggerOptions); this.manuallyStopped = false; this.pendingDeviceChange = false; this._isUpstreamPaused = false; this.handleTrackMuteEvent = () => this.debouncedTrackMuteHandler().catch(() => this.log.debug('track mute bounce got cancelled by an unmute event', this.logContext)); this.debouncedTrackMuteHandler = debounce(() => __awaiter(this, void 0, void 0, function* () { yield this.pauseUpstream(); }), 5000); this.handleTrackUnmuteEvent = () => __awaiter(this, void 0, void 0, function* () { this.debouncedTrackMuteHandler.cancel('unmute'); yield this.resumeUpstream(); }); this.handleEnded = () => { if (this.isInBackground) { this.reacquireTrack = true; } this._mediaStreamTrack.removeEventListener('mute', this.handleTrackMuteEvent); this._mediaStreamTrack.removeEventListener('unmute', this.handleTrackUnmuteEvent); this.emit(TrackEvent.Ended, this); }; this.reacquireTrack = false; this.providedByUser = userProvidedTrack; this.muteLock = new _(); this.pauseUpstreamLock = new _(); this.trackChangeLock = new _(); this.trackChangeLock.lock().then(unlock => __awaiter(this, void 0, void 0, function* () { try { yield this.setMediaStreamTrack(mediaTrack, true); } finally { unlock(); } })); // added to satisfy TS compiler, constraints are synced with MediaStreamTrack this._constraints = mediaTrack.getConstraints(); if (constraints) { this._constraints = constraints; } } get id() { return this._mediaStreamTrack.id; } get dimensions() { if (this.kind !== Track.Kind.Video) { return undefined; } const _this$_mediaStreamTra = this._mediaStreamTrack.getSettings(), width = _this$_mediaStreamTra.width, height = _this$_mediaStreamTra.height; if (width && height) { return { width, height }; } return undefined; } get isUpstreamPaused() { return this._isUpstreamPaused; } get isUserProvided() { return this.providedByUser; } get mediaStreamTrack() { var _a, _b; return (_b = (_a = this.processor) === null || _a === void 0 ? void 0 : _a.processedTrack) !== null && _b !== void 0 ? _b : this._mediaStreamTrack; } get isLocal() { return true; } /** * @internal * returns mediaStreamTrack settings of the capturing mediastreamtrack source - ignoring processors */ getSourceTrackSettings() { return this._mediaStreamTrack.getSettings(); } setMediaStreamTrack(newTrack, force, isUnmuting) { return __awaiter(this, void 0, void 0, function* () { var _a; if (newTrack === this._mediaStreamTrack && !force) { return; } if (this._mediaStreamTrack) { // detach this.attachedElements.forEach(el => { detachTrack(this._mediaStreamTrack, el); }); this.debouncedTrackMuteHandler.cancel('new-track'); this._mediaStreamTrack.removeEventListener('ended', this.handleEnded); this._mediaStreamTrack.removeEventListener('mute', this.handleTrackMuteEvent); this._mediaStreamTrack.removeEventListener('unmute', this.handleTrackUnmuteEvent); } this.mediaStream = new MediaStream([newTrack]); if (newTrack) { newTrack.addEventListener('ended', this.handleEnded); // when underlying track emits mute, it indicates that the device is unable // to produce media. In this case we'll need to signal with remote that // the track is "muted" // note this is different from LocalTrack.mute because we do not want to // touch MediaStreamTrack.enabled newTrack.addEventListener('mute', this.handleTrackMuteEvent); newTrack.addEventListener('unmute', this.handleTrackUnmuteEvent); this._constraints = newTrack.getConstraints(); } let processedTrack; if (this.processor && newTrack) { this.log.debug('restarting processor', this.logContext); if (this.kind === 'unknown') { throw TypeError('cannot set processor on track of unknown kind'); } if (this.processorElement) { attachToElement(newTrack, this.processorElement); // ensure the processorElement itself stays muted this.processorElement.muted = true; } yield this.processor.restart({ track: newTrack, kind: this.kind, element: this.processorElement, localTrack: this }); processedTrack = this.processor.processedTrack; } if (this.sender && ((_a = this.sender.transport) === null || _a === void 0 ? void 0 : _a.state) !== 'closed') { yield this.sender.replaceTrack(processedTrack !== null && processedTrack !== void 0 ? processedTrack : newTrack); } // if `newTrack` is different from the existing track, stop the // older track just before replacing it if (!this.providedByUser && this._mediaStreamTrack !== newTrack) { this._mediaStreamTrack.stop(); } this._mediaStreamTrack = newTrack; if (newTrack) { // sync muted state with the enabled state of the newly provided track // if restarting as part of an unmute, set enabled to true directly to avoid mute cycling this._mediaStreamTrack.enabled = isUnmuting ? true : !this.isMuted; // when a valid track is replace, we'd want to start producing yield this.resumeUpstream(); this.attachedElements.forEach(el => { attachToElement(processedTrack !== null && processedTrack !== void 0 ? processedTrack : newTrack, el); }); } }); } waitForDimensions() { return __awaiter(this, arguments, void 0, function () { var _this = this; let timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_DIMENSIONS_TIMEOUT; return function* () { var _a; if (_this.kind === Track.Kind.Audio) { throw new Error('cannot get dimensions for audio tracks'); } if (((_a = getBrowser()) === null || _a === void 0 ? void 0 : _a.os) === 'iOS') { // browsers report wrong initial resolution on iOS. // when slightly delaying the call to .getSettings(), the correct resolution is being reported yield sleep(10); } const started = Date.now(); while (Date.now() - started < timeout) { const dims = _this.dimensions; if (dims) { return dims; } yield sleep(50); } throw new TrackInvalidError('unable to get track dimensions after timeout'); }(); }); } setDeviceId(deviceId) { return __awaiter(this, void 0, void 0, function* () { if (this._constraints.deviceId === deviceId && this._mediaStreamTrack.getSettings().deviceId === unwrapConstraint(deviceId)) { return true; } this._constraints.deviceId = deviceId; // when track is muted, underlying media stream track is stopped and // will be restarted later if (this.isMuted) { this.pendingDeviceChange = true; return true; } yield this.restartTrack(); return unwrapConstraint(deviceId) === this._mediaStreamTrack.getSettings().deviceId; }); } /** * @returns DeviceID of the device that is currently being used for this track */ getDeviceId() { return __awaiter(this, arguments, void 0, function () { var _this2 = this; let normalize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; return function* () { // screen share doesn't have a usable device id if (_this2.source === Track.Source.ScreenShare) { return; } const _this2$_mediaStreamTr = _this2._mediaStreamTrack.getSettings(), deviceId = _this2$_mediaStreamTr.deviceId, groupId = _this2$_mediaStreamTr.groupId; const kind = _this2.kind === Track.Kind.Audio ? 'audioinput' : 'videoinput'; return normalize ? DeviceManager.getInstance().normalizeDeviceId(kind, deviceId, groupId) : deviceId; }(); }); } mute() { return __awaiter(this, void 0, void 0, function* () { this.setTrackMuted(true); return this; }); } unmute() { return __awaiter(this, void 0, void 0, function* () { this.setTrackMuted(false); return this; }); } replaceTrack(track, userProvidedOrOptions) { return __awaiter(this, void 0, void 0, function* () { const unlock = yield this.trackChangeLock.lock(); try { if (!this.sender) { throw new TrackInvalidError('unable to replace an unpublished track'); } let userProvidedTrack; let stopProcessor; if (typeof userProvidedOrOptions === 'boolean') { userProvidedTrack = userProvidedOrOptions; } else if (userProvidedOrOptions !== undefined) { userProvidedTrack = userProvidedOrOptions.userProvidedTrack; stopProcessor = userProvidedOrOptions.stopProcessor; } this.providedByUser = userProvidedTrack !== null && userProvidedTrack !== void 0 ? userProvidedTrack : true; this.log.debug('replace MediaStreamTrack', this.logContext); yield this.setMediaStreamTrack(track); // this must be synced *after* setting mediaStreamTrack above, since it relies // on the previous state in order to cleanup if (stopProcessor && this.processor) { yield this.internalStopProcessor(); } } finally { unlock(); } yield this.onSenderTrackSwapped(); return this; }); } /** * Hook invoked after the MediaStreamTrack on the sender has been swapped * (via replaceTrack, setProcessor, or stopProcessor). Fires outside the * trackChangeLock so subclasses can do asynchronous work such as polling * for new dimensions without blocking other track operations. */ onSenderTrackSwapped() { return __awaiter(this, void 0, void 0, function* () { // base implementation is a no-op; LocalVideoTrack overrides this to // recompute sender encoding parameters. }); } restart(constraints, isUnmuting) { return __awaiter(this, void 0, void 0, function* () { this.manuallyStopped = false; const unlock = yield this.trackChangeLock.lock(); try { if (!constraints) { constraints = this._constraints; } const _constraints = constraints, deviceId = _constraints.deviceId, facingMode = _constraints.facingMode, otherConstraints = __rest(constraints, ["deviceId", "facingMode"]); this.log.debug('restarting track with constraints', Object.assign(Object.assign({}, this.logContext), { constraints })); const streamConstraints = { audio: false, video: false }; if (this.kind === Track.Kind.Video) { streamConstraints.video = deviceId || facingMode ? { deviceId, facingMode } : true; } else { streamConstraints.audio = deviceId ? Object.assign({ deviceId }, otherConstraints) : true; } // these steps are duplicated from setMediaStreamTrack because we must stop // the previous tracks before new tracks can be acquired this.attachedElements.forEach(el => { detachTrack(this.mediaStreamTrack, el); }); this._mediaStreamTrack.removeEventListener('ended', this.handleEnded); // on Safari, the old audio track must be stopped before attempting to acquire // the new track, otherwise the new track will stop with // 'A MediaStreamTrack ended due to a capture failure` this._mediaStreamTrack.stop(); // create new track and attach const mediaStream = yield navigator.mediaDevices.getUserMedia(streamConstraints); const newTrack = mediaStream.getTracks()[0]; if (this.kind === Track.Kind.Video) { // we already captured the audio track with the constraints, so we only need to apply the video constraints yield newTrack.applyConstraints(otherConstraints); } newTrack.addEventListener('ended', this.handleEnded); this.log.debug('re-acquired MediaStreamTrack', this.logContext); yield this.setMediaStreamTrack(newTrack, false, isUnmuting); this._constraints = constraints; this.pendingDeviceChange = false; this.emit(TrackEvent.Restarted, this); if (this.manuallyStopped) { this.log.warn('track was stopped during a restart, stopping restarted track', this.logContext); this.stop(); } return this; } finally { unlock(); } }); } setTrackMuted(muted) { this.log.debug("setting ".concat(this.kind, " track ").concat(muted ? 'muted' : 'unmuted'), this.logContext); if (this.isMuted === muted && this._mediaStreamTrack.enabled !== muted) { return; } this.isMuted = muted; this._mediaStreamTrack.enabled = !muted; this.emit(muted ? TrackEvent.Muted : TrackEvent.Unmuted, this); } get needsReAcquisition() { return this._mediaStreamTrack.readyState !== 'live' || this._mediaStreamTrack.muted || !this._mediaStreamTrack.enabled || this.reacquireTrack; } handleAppVisibilityChanged() { const _super = Object.create(null, { handleAppVisibilityChanged: { get: () => super.handleAppVisibilityChanged } }); return __awaiter(this, void 0, void 0, function* () { yield _super.handleAppVisibilityChanged.call(this); if (!isMobile()) return; this.log.debug("visibility changed, is in Background: ".concat(this.isInBackground), this.logContext); if (!this.isInBackground && this.needsReAcquisition && !this.isUserProvided && !this.isMuted) { this.log.debug("track needs to be reacquired, restarting ".concat(this.source), this.logContext); yield this.restart(); this.reacquireTrack = false; } }); } stop() { var _a; this.manuallyStopped = true; super.stop(); this._mediaStreamTrack.removeEventListener('ended', this.handleEnded); this._mediaStreamTrack.removeEventListener('mute', this.handleTrackMuteEvent); this._mediaStreamTrack.removeEventListener('unmute', this.handleTrackUnmuteEvent); (_a = this.processor) === null || _a === void 0 ? void 0 : _a.destroy(); this.processor = undefined; } /** * pauses publishing to the server without disabling the local MediaStreamTrack * this is used to display a user's own video locally while pausing publishing to * the server. * this API is unsupported on Safari < 12 due to a bug **/ pauseUpstream() { return __awaiter(this, void 0, void 0, function* () { var _a; const unlock = yield this.pauseUpstreamLock.lock(); try { if (this._isUpstreamPaused === true) { return; } if (!this.sender) { this.log.warn('unable to pause upstream for an unpublished track', this.logContext); return; } this._isUpstreamPaused = true; this.emit(TrackEvent.UpstreamPaused, this); const browser = getBrowser(); if ((browser === null || browser === void 0 ? void 0 : browser.name) === 'Safari' && compareVersions(browser.version, '12.0') < 0) { // https://bugs.webkit.org/show_bug.cgi?id=184911 throw new DeviceUnsupportedError('pauseUpstream is not supported on Safari < 12.'); } if (((_a = this.sender.transport) === null || _a === void 0 ? void 0 : _a.state) !== 'closed') { yield this.sender.replaceTrack(null); } } finally { unlock(); } }); } resumeUpstream() { return __awaiter(this, void 0, void 0, function* () { var _a; const unlock = yield this.pauseUpstreamLock.lock(); try { if (this._isUpstreamPaused === false) { return; } if (!this.sender) { this.log.warn('unable to resume upstream for an unpublished track', this.logContext); return; } this._isUpstreamPaused = false; this.emit(TrackEvent.UpstreamResumed, this); if (((_a = this.sender.transport) === null || _a === void 0 ? void 0 : _a.state) !== 'closed') { // this operation is noop if mediastreamtrack is already being sent yield this.sender.replaceTrack(this.mediaStreamTrack); } } finally { unlock(); } }); } /** * Gets the RTCStatsReport for the LocalTrack's underlying RTCRtpSender * See https://developer.mozilla.org/en-US/docs/Web/API/RTCStatsReport * * @returns Promise | undefined */ getRTCStatsReport() { return __awaiter(this, void 0, void 0, function* () { var _a; if (!((_a = this.sender) === null || _a === void 0 ? void 0 : _a.getStats)) { return; } const statsReport = yield this.sender.getStats(); return statsReport; }); } /** * Sets a processor on this track. * See https://github.com/livekit/track-processors-js for example usage * * @param processor * @param showProcessedStreamLocally * @returns */ setProcessor(processor_1) { return __awaiter(this, arguments, void 0, function (processor) { var _this3 = this; let showProcessedStreamLocally = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; return function* () { var _a; const unlock = yield _this3.trackChangeLock.lock(); try { _this3.log.debug('setting up processor', _this3.logContext); const processorElement = document.createElement(_this3.kind); const processorOptions = { kind: _this3.kind, track: _this3._mediaStreamTrack, element: processorElement, audioContext: _this3.audioContext, localTrack: _this3 }; yield processor.init(processorOptions); _this3.log.debug('processor initialized', _this3.logContext); if (_this3.processor) { yield _this3.internalStopProcessor(); } if (_this3.kind === 'unknown') { throw TypeError('cannot set processor on track of unknown kind'); } attachToElement(_this3._mediaStreamTrack, processorElement); processorElement.muted = true; processorElement.play().catch(error => { if (error instanceof DOMException && error.name === 'AbortError') { // This happens on Safari when the processor is restarted, try again after a delay _this3.log.warn('failed to play processor element, retrying', Object.assign(Object.assign({}, _this3.logContext), { error })); setTimeout(() => { processorElement.play().catch(err => { _this3.log.error('failed to play processor element', Object.assign(Object.assign({}, _this3.logContext), { err })); }); }, 100); } else { _this3.log.error('failed to play processor element', Object.assign(Object.assign({}, _this3.logContext), { error })); } }); _this3.processor = processor; _this3.processorElement = processorElement; if (_this3.processor.processedTrack) { for (const el of _this3.attachedElements) { if (el !== _this3.processorElement && showProcessedStreamLocally) { detachTrack(_this3._mediaStreamTrack, el); attachToElement(_this3.processor.processedTrack, el); } } yield (_a = _this3.sender) === null || _a === void 0 ? void 0 : _a.replaceTrack(_this3.processor.processedTrack); } _this3.emit(TrackEvent.TrackProcessorUpdate, _this3.processor); } finally { unlock(); } yield _this3.onSenderTrackSwapped(); }(); }); } getProcessor() { return this.processor; } /** * Stops the track processor * See https://github.com/livekit/track-processors-js for example usage * */ stopProcessor() { return __awaiter(this, arguments, void 0, function () { var _this4 = this; let keepElement = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; return function* () { const unlock = yield _this4.trackChangeLock.lock(); try { yield _this4.internalStopProcessor(keepElement); } finally { unlock(); } yield _this4.onSenderTrackSwapped(); }(); }); } /** * @internal * This method assumes the caller has acquired a trackChangeLock already. * The public facing method for stopping the processor is `stopProcessor` and it wraps this method in the trackChangeLock. */ internalStopProcessor() { return __awaiter(this, arguments, void 0, function () { var _this5 = this; let keepElement = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; return function* () { var _a, _b; if (!_this5.processor) return; _this5.log.debug('stopping processor', _this5.logContext); (_a = _this5.processor.processedTrack) === null || _a === void 0 ? void 0 : _a.stop(); yield _this5.processor.destroy(); _this5.processor = undefined; if (!keepElement) { (_b = _this5.processorElement) === null || _b === void 0 ? void 0 : _b.remove(); _this5.processorElement = undefined; } // apply original track constraints in case the processor changed them yield _this5._mediaStreamTrack.applyConstraints(_this5._constraints); // force re-setting of the mediaStreamTrack on the sender yield _this5.setMediaStreamTrack(_this5._mediaStreamTrack, true); _this5.emit(TrackEvent.TrackProcessorUpdate); }(); }); } /** @internal */ startPreConnectBuffer() { let timeslice = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 100; if (!isRecordingSupported()) { this.log.warn('MediaRecorder is not available, cannot start preconnect buffer', this.logContext); return; } if (!this.localTrackRecorder) { let mimeType = 'audio/webm;codecs=opus'; if (!MediaRecorder.isTypeSupported(mimeType)) { // iOS currently only supports video/mp4 as a mime type - even for audio. mimeType = 'video/mp4'; } this.localTrackRecorder = new LocalTrackRecorder(this, { mimeType }); } else { this.log.warn('preconnect buffer already started'); return; } this.localTrackRecorder.start(timeslice); this.autoStopPreConnectBuffer = setTimeout(() => { this.log.warn('preconnect buffer timed out, stopping recording automatically', this.logContext); this.stopPreConnectBuffer(); }, PRE_CONNECT_BUFFER_TIMEOUT); } /** @internal */ stopPreConnectBuffer() { clearTimeout(this.autoStopPreConnectBuffer); if (this.localTrackRecorder) { this.localTrackRecorder.stop(); this.localTrackRecorder = undefined; } } /** @internal */ getPreConnectBuffer() { var _a; return (_a = this.localTrackRecorder) === null || _a === void 0 ? void 0 : _a.byteStream; } getPreConnectBufferMimeType() { var _a; return (_a = this.localTrackRecorder) === null || _a === void 0 ? void 0 : _a.mimeType; } }class LocalAudioTrack extends LocalTrack { /** * boolean indicating whether enhanced noise cancellation is currently being used on this track */ get enhancedNoiseCancellation() { return this.isKrispNoiseFilterEnabled; } /** * * @param mediaTrack * @param constraints MediaTrackConstraints that are being used when restarting or reacquiring tracks * @param userProvidedTrack Signals to the SDK whether or not the mediaTrack should be managed (i.e. released and reacquired) internally by the SDK */ constructor(mediaTrack, constraints) { let userProvidedTrack = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; let audioContext = arguments.length > 3 ? arguments[3] : undefined; let loggerOptions = arguments.length > 4 ? arguments[4] : undefined; super(mediaTrack, Track.Kind.Audio, constraints, userProvidedTrack, loggerOptions); /** @internal */ this.stopOnMute = false; this.isKrispNoiseFilterEnabled = false; this.monitorSender = () => __awaiter(this, void 0, void 0, function* () { if (!this.sender) { this._currentBitrate = 0; return; } let stats; try { stats = yield this.getSenderStats(); } catch (e) { this.log.error('could not get audio sender stats', Object.assign(Object.assign({}, this.logContext), { error: e })); return; } if (stats && this.prevStats) { this._currentBitrate = computeBitrate(stats, this.prevStats); } this.prevStats = stats; }); this.handleKrispNoiseFilterEnable = () => { this.isKrispNoiseFilterEnabled = true; this.log.debug("Krisp noise filter enabled", this.logContext); this.emit(TrackEvent.AudioTrackFeatureUpdate, this, AudioTrackFeature.TF_ENHANCED_NOISE_CANCELLATION, true); }; this.handleKrispNoiseFilterDisable = () => { this.isKrispNoiseFilterEnabled = false; this.log.debug("Krisp noise filter disabled", this.logContext); this.emit(TrackEvent.AudioTrackFeatureUpdate, this, AudioTrackFeature.TF_ENHANCED_NOISE_CANCELLATION, false); }; this.audioContext = audioContext; this.checkForSilence(); } mute() { const _super = Object.create(null, { mute: { get: () => super.mute } }); return __awaiter(this, void 0, void 0, function* () { const unlock = yield this.muteLock.lock(); try { if (this.isMuted) { this.log.debug('Track already muted', this.logContext); return this; } // disabled special handling as it will cause BT headsets to switch communication modes if (this.source === Track.Source.Microphone && this.stopOnMute && !this.isUserProvided) { this.log.debug('stopping mic track', this.logContext); // also stop the track, so that microphone indicator is turned off this._mediaStreamTrack.stop(); } yield _super.mute.call(this); return this; } finally { unlock(); } }); } unmute() { const _super = Object.create(null, { unmute: { get: () => super.unmute } }); return __awaiter(this, void 0, void 0, function* () { const unlock = yield this.muteLock.lock(); try { if (!this.isMuted) { this.log.debug('Track already unmuted', this.logContext); return this; } if (this.source === Track.Source.Microphone && (this.stopOnMute || this._mediaStreamTrack.readyState === 'ended' || this.pendingDeviceChange) && !this.isUserProvided) { this.log.debug('reacquiring mic track', this.logContext); yield this.restart(undefined, true); } yield _super.unmute.call(this); return this; } finally { unlock(); } }); } restartTrack(options) { return __awaiter(this, void 0, void 0, function* () { let constraints; if (options) { const streamConstraints = constraintsForOptions({ audio: options }); if (typeof streamConstraints.audio !== 'boolean') { constraints = streamConstraints.audio; } } yield this.restart(constraints); }); } restart(constraints, isUnmuting) { const _super = Object.create(null, { restart: { get: () => super.restart } }); return __awaiter(this, void 0, void 0, function* () { const track = yield _super.restart.call(this, constraints, isUnmuting); this.checkForSilence(); return track; }); } /* @internal */ startMonitor() { if (!isWeb()) { return; } if (this.monitorInterval) { return; } this.monitorInterval = setInterval(() => { this.monitorSender(); }, monitorFrequency); } setProcessor(processor) { return __awaiter(this, void 0, void 0, function* () { var _a; const unlock = yield this.trackChangeLock.lock(); try { if (!isReactNative() && !this.audioContext) { throw Error('Audio context needs to be set on LocalAudioTrack in order to enable processors'); } if (this.processor) { yield this.internalStopProcessor(); } const processorOptions = { kind: this.kind, track: this._mediaStreamTrack, // RN won't have or use AudioContext audioContext: this.audioContext, localTrack: this }; this.log.debug("setting up audio processor ".concat(processor.name), this.logContext); yield processor.init(processorOptions); this.processor = processor; if (this.processor.processedTrack) { yield (_a = this.sender) === null || _a === void 0 ? void 0 : _a.replaceTrack(this.processor.processedTrack); this.processor.processedTrack.addEventListener('enable-lk-krisp-noise-filter', this.handleKrispNoiseFilterEnable); this.processor.processedTrack.addEventListener('disable-lk-krisp-noise-filter', this.handleKrispNoiseFilterDisable); } this.emit(TrackEvent.TrackProcessorUpdate, this.processor); } finally { unlock(); } }); } /** * @internal * @experimental */ setAudioContext(audioContext) { this.audioContext = audioContext; } getSenderStats() { return __awaiter(this, void 0, void 0, function* () { var _a; if (!((_a = this.sender) === null || _a === void 0 ? void 0 : _a.getStats)) { return undefined; } const stats = yield this.sender.getStats(); let audioStats; stats.forEach(v => { if (v.type === 'outbound-rtp') { audioStats = { type: 'audio', streamId: v.id, packetsSent: v.packetsSent, packetsLost: v.packetsLost, bytesSent: v.bytesSent, timestamp: v.timestamp, roundTripTime: v.roundTripTime, jitter: v.jitter }; } }); return audioStats; }); } checkForSilence() { return __awaiter(this, void 0, void 0, function* () { const trackIsSilent = yield detectSilence(this); if (trackIsSilent) { if (!this.isMuted) { this.log.debug('silence detected on local audio track', this.logContext); } this.emit(TrackEvent.AudioSilenceDetected); } return trackIsSilent; }); } }/** @internal */ function mediaTrackToLocalTrack(mediaStreamTrack, constraints, loggerOptions) { switch (mediaStreamTrack.kind) { case 'audio': return new LocalAudioTrack(mediaStreamTrack, constraints, false, undefined, loggerOptions); case 'video': return new LocalVideoTrack(mediaStreamTrack, constraints, false, loggerOptions); default: throw new TrackInvalidError("unsupported track type: ".concat(mediaStreamTrack.kind)); } } /* @internal */ const presets169 = Object.values(VideoPresets); /* @internal */ const presets43 = Object.values(VideoPresets43); /* @internal */ const presetsScreenShare = Object.values(ScreenSharePresets); /* @internal */ const defaultSimulcastPresets169 = [VideoPresets.h180, VideoPresets.h360]; /* @internal */ const defaultSimulcastPresets43 = [VideoPresets43.h180, VideoPresets43.h360]; /* @internal */ const computeDefaultScreenShareSimulcastPresets = fromPreset => { const layers = [{ scaleResolutionDownBy: 2, fps: fromPreset.encoding.maxFramerate }]; return layers.map(t => { var _a, _b; return new VideoPreset(Math.floor(fromPreset.width / t.scaleResolutionDownBy), Math.floor(fromPreset.height / t.scaleResolutionDownBy), Math.max(150000, Math.floor(fromPreset.encoding.maxBitrate / (Math.pow(t.scaleResolutionDownBy, 2) * (((_a = fromPreset.encoding.maxFramerate) !== null && _a !== void 0 ? _a : 30) / ((_b = t.fps) !== null && _b !== void 0 ? _b : 30))))), t.fps, fromPreset.encoding.priority); }); }; // /** // * // * @internal // * @experimental // */ // const computeDefaultMultiCodecSimulcastEncodings = (width: number, height: number) => { // // use vp8 as a default // const vp8 = determineAppropriateEncoding(false, width, height); // const vp9 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.9 }; // const h264 = { ...vp8, maxBitrate: vp8.maxBitrate * 1.1 }; // const av1 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.7 }; // return { // vp8, // vp9, // h264, // av1, // }; // }; const videoRids = ['q', 'h', 'f']; /* @internal */ function computeVideoEncodings(isScreenShare, width, height, options) { var _a, _b; let videoEncoding = options === null || options === void 0 ? void 0 : options.videoEncoding; if (isScreenShare) { videoEncoding = options === null || options === void 0 ? void 0 : options.screenShareEncoding; } const useSimulcast = options === null || options === void 0 ? void 0 : options.simulcast; const scalabilityMode = options === null || options === void 0 ? void 0 : options.scalabilityMode; const videoCodec = options === null || options === void 0 ? void 0 : options.videoCodec; if (!videoEncoding && !useSimulcast && !scalabilityMode || !width || !height) { // when we aren't simulcasting or svc, will need to return a single encoding without // capping bandwidth. we always require a encoding for dynacast return [{}]; } if (!videoEncoding) { // find the right encoding based on width/height videoEncoding = determineAppropriateEncoding(isScreenShare, width, height, videoCodec); livekitLogger.debug('using video encoding', videoEncoding); } const sourceFramerate = videoEncoding.maxFramerate; const original = new VideoPreset(width, height, videoEncoding.maxBitrate, videoEncoding.maxFramerate, videoEncoding.priority); if (scalabilityMode && isSVCCodec(videoCodec)) { const sm = new ScalabilityMode(scalabilityMode); const encodings = []; if (sm.spatial > 3) { throw new Error("unsupported scalabilityMode: ".concat(scalabilityMode)); } // Before M113 in Chrome, defining multiple encodings with an SVC codec indicated // that SVC mode should be used. Safari still works this way. // This is a bit confusing but is due to how libwebrtc interpreted the encodings field // before M113. // Announced here: https://groups.google.com/g/discuss-webrtc/c/-QQ3pxrl-fw?pli=1 const browser = getBrowser(); if (isSafariBased() || // Even tho RN runs M114, it does not produce SVC layers when a single encoding // is provided. So we'll use the legacy SVC specification for now. // TODO: when we upstream libwebrtc, this will need additional verification isReactNative() || (browser === null || browser === void 0 ? void 0 : browser.name) === 'Chrome' && compareVersions(browser === null || browser === void 0 ? void 0 : browser.version, '113') < 0) { const bitratesRatio = sm.suffix == 'h' ? 2 : 3; // safari 18.4 uses a different svc API that requires scaleResolutionDownBy to be set. const requireScale = isSafariSvcApi(browser); for (let i = 0; i < sm.spatial; i += 1) { // in legacy SVC, scaleResolutionDownBy cannot be set encodings.push({ rid: videoRids[2 - i], maxBitrate: videoEncoding.maxBitrate / Math.pow(bitratesRatio, i), maxFramerate: original.encoding.maxFramerate, scaleResolutionDownBy: requireScale ? Math.pow(2, i) : undefined }); } // legacy SVC, scalabilityMode is set only on the first encoding /* @ts-ignore */ encodings[0].scalabilityMode = scalabilityMode; } else { encodings.push({ maxBitrate: videoEncoding.maxBitrate, maxFramerate: original.encoding.maxFramerate, /* @ts-ignore */ scalabilityMode: scalabilityMode }); } if (original.encoding.priority) { encodings[0].priority = original.encoding.priority; encodings[0].networkPriority = original.encoding.priority; } livekitLogger.debug("using svc encoding", { encodings }); return encodings; } if (!useSimulcast) { return [videoEncoding]; } let presets; if (isScreenShare) { presets = (_a = sortPresets(options === null || options === void 0 ? void 0 : options.screenShareSimulcastLayers)) !== null && _a !== void 0 ? _a : defaultSimulcastLayers(isScreenShare, original); } else { presets = (_b = sortPresets(options === null || options === void 0 ? void 0 : options.videoSimulcastLayers)) !== null && _b !== void 0 ? _b : defaultSimulcastLayers(isScreenShare, original); } let midPreset; if (presets.length > 0) { const lowPreset = presets[0]; if (presets.length > 1) { var _presets = presets; var _presets2 = _slicedToArray(_presets, 2); midPreset = _presets2[1]; } // NOTE: // 1. Ordering of these encodings is important. Chrome seems // to use the index into encodings to decide which layer // to disable when CPU constrained. // So encodings should be ordered in increasing spatial // resolution order. // 2. livekit-server translates rids into layers. So, all encodings // should have the base layer `q` and then more added // based on other conditions. const size = Math.max(width, height); if (size >= 960 && midPreset) { return encodingsFromPresets(width, height, [lowPreset, midPreset, original], sourceFramerate); } if (size >= 480) { return encodingsFromPresets(width, height, [lowPreset, original], sourceFramerate); } } return encodingsFromPresets(width, height, [original]); } function computeTrackBackupEncodings(track, videoCodec, opts) { var _a, _b, _c, _d; // backupCodec should not be true anymore, default codec is set in LocalParticipant.publish if (!opts.backupCodec || opts.backupCodec === true || opts.backupCodec.codec === opts.videoCodec) { // backup codec publishing is disabled return; } if (videoCodec !== opts.backupCodec.codec) { livekitLogger.warn('requested a different codec than specified as backup', { serverRequested: videoCodec, backup: opts.backupCodec.codec }); } opts.videoCodec = videoCodec; // use backup encoding setting as videoEncoding for backup codec publishing opts.videoEncoding = opts.backupCodec.encoding; const settings = track.mediaStreamTrack.getSettings(); const width = (_a = settings.width) !== null && _a !== void 0 ? _a : (_b = track.dimensions) === null || _b === void 0 ? void 0 : _b.width; const height = (_c = settings.height) !== null && _c !== void 0 ? _c : (_d = track.dimensions) === null || _d === void 0 ? void 0 : _d.height; // disable simulcast for screenshare backup codec since L1Tx is used by primary codec if (track.source === Track.Source.ScreenShare && opts.simulcast) { opts.simulcast = false; } const encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, width, height, opts); return encodings; } /* @internal */ function determineAppropriateEncoding(isScreenShare, width, height, codec) { const presets = presetsForResolution(isScreenShare, width, height); let encoding = presets[0].encoding; // handle portrait by swapping dimensions const size = Math.max(width, height); for (let i = 0; i < presets.length; i += 1) { const preset = presets[i]; encoding = preset.encoding; if (preset.width >= size) { break; } } // presets are based on the assumption of vp8 as a codec // for other codecs we adjust the maxBitrate if no specific videoEncoding has been provided // users should override these with ones that are optimized for their use case // NOTE: SVC codec bitrates are inclusive of all scalability layers. while // bitrate for non-SVC codecs does not include other simulcast layers. if (codec) { switch (codec) { case 'av1': case 'h265': encoding = Object.assign({}, encoding); encoding.maxBitrate = encoding.maxBitrate * 0.7; break; case 'vp9': encoding = Object.assign({}, encoding); encoding.maxBitrate = encoding.maxBitrate * 0.85; break; } } return encoding; } /* @internal */ function presetsForResolution(isScreenShare, width, height) { if (isScreenShare) { return presetsScreenShare; } const aspect = width > height ? width / height : height / width; if (Math.abs(aspect - 16.0 / 9) < Math.abs(aspect - 4.0 / 3)) { return presets169; } return presets43; } /* @internal */ function defaultSimulcastLayers(isScreenShare, original) { if (isScreenShare) { return computeDefaultScreenShareSimulcastPresets(original); } const width = original.width, height = original.height; const aspect = width > height ? width / height : height / width; if (Math.abs(aspect - 16.0 / 9) < Math.abs(aspect - 4.0 / 3)) { return defaultSimulcastPresets169; } return defaultSimulcastPresets43; } // presets should be ordered by low, medium, high function encodingsFromPresets(width, height, presets, sourceFramerate) { const encodings = []; presets.forEach((preset, idx) => { if (idx >= videoRids.length) { return; } const size = Math.min(width, height); const rid = videoRids[idx]; const encoding = { rid, scaleResolutionDownBy: Math.max(1, size / Math.min(preset.width, preset.height)), maxBitrate: preset.encoding.maxBitrate }; // ensure that the sourceFramerate is the highest framerate applied across all layers so that the // original encoding doesn't get bumped unintentionally by any of the other layers const maxFramerate = sourceFramerate && preset.encoding.maxFramerate ? Math.min(sourceFramerate, preset.encoding.maxFramerate) : preset.encoding.maxFramerate; if (maxFramerate) { encoding.maxFramerate = maxFramerate; } const browser = getBrowser(); const canSetPriority = (browser === null || browser === void 0 ? void 0 : browser.name) === 'Firefox' && browser.os !== 'iOS' || idx === 0; if (preset.encoding.priority && canSetPriority) { encoding.priority = preset.encoding.priority; encoding.networkPriority = preset.encoding.priority; } encodings.push(encoding); }); // RN ios simulcast requires all same framerates. if (isReactNative() && getReactNativeOs() === 'ios') { let topFramerate = undefined; encodings.forEach(encoding => { if (!topFramerate) { topFramerate = encoding.maxFramerate; } else if (encoding.maxFramerate && encoding.maxFramerate > topFramerate) { topFramerate = encoding.maxFramerate; } }); let notifyOnce = true; encodings.forEach(encoding => { var _a; if (encoding.maxFramerate != topFramerate) { if (notifyOnce) { notifyOnce = false; livekitLogger.info("Simulcast on iOS React-Native requires all encodings to share the same framerate."); } livekitLogger.info("Setting framerate of encoding \"".concat((_a = encoding.rid) !== null && _a !== void 0 ? _a : '', "\" to ").concat(topFramerate)); encoding.maxFramerate = topFramerate; } }); } return encodings; } /** @internal */ function sortPresets(presets) { if (!presets) return; // Sort a copy so we don't mutate the caller's array in place. Mutating the // passed-in simulcast layers can cause consumers that compare options by value // (e.g. components-react's useLiveKitRoom) to detect a spurious change. return presets.slice().sort((a, b) => { const aEnc = a.encoding; const bEnc = b.encoding; if (aEnc.maxBitrate > bEnc.maxBitrate) { return 1; } if (aEnc.maxBitrate < bEnc.maxBitrate) return -1; if (aEnc.maxBitrate === bEnc.maxBitrate && aEnc.maxFramerate && bEnc.maxFramerate) { return aEnc.maxFramerate > bEnc.maxFramerate ? 1 : -1; } return 0; }); } /** @internal */ class ScalabilityMode { constructor(scalabilityMode) { const results = scalabilityMode.match(/^L(\d)T(\d)(h|_KEY|_KEY_SHIFT){0,1}$/); if (!results) { throw new Error('invalid scalability mode'); } this.spatial = parseInt(results[1]); this.temporal = parseInt(results[2]); if (results.length > 3) { switch (results[3]) { case 'h': case '_KEY': case '_KEY_SHIFT': this.suffix = results[3]; } } } toString() { var _a; return "L".concat(this.spatial, "T").concat(this.temporal).concat((_a = this.suffix) !== null && _a !== void 0 ? _a : ''); } } function getDefaultDegradationPreference(track) { // a few of reasons we have different default paths: // 1. without this, Chrome seems to aggressively resize the SVC video stating `quality-limitation: bandwidth` even when BW isn't an issue // 2. since we are overriding contentHint to motion (to workaround L1T3 publishing), it overrides the default degradationPreference to `balanced` if (track.source === Track.Source.ScreenShare || track.constraints.height && unwrapConstraint(track.constraints.height) >= 1080) { return 'maintain-resolution'; } else { return 'balanced'; } }const refreshSubscribedCodecAfterNewCodec = 5000; class LocalVideoTrack extends LocalTrack { get sender() { return this._sender; } set sender(sender) { this._sender = sender; if (this.degradationPreference) { this.setDegradationPreference(this.degradationPreference); } } /** * * @param mediaTrack * @param constraints MediaTrackConstraints that are being used when restarting or reacquiring tracks * @param userProvidedTrack Signals to the SDK whether or not the mediaTrack should be managed (i.e. released and reacquired) internally by the SDK */ constructor(mediaTrack, constraints) { let userProvidedTrack = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; let loggerOptions = arguments.length > 3 ? arguments[3] : undefined; super(mediaTrack, Track.Kind.Video, constraints, userProvidedTrack, loggerOptions); /* @internal */ this.simulcastCodecs = new Map(); this.degradationPreference = 'balanced'; this.isCpuConstrained = false; this.optimizeForPerformance = false; this.monitorSender = () => __awaiter(this, void 0, void 0, function* () { if (!this.sender) { this._currentBitrate = 0; return; } let stats; try { stats = yield this.getSenderStats(); } catch (e) { this.log.error('could not get video sender stats', Object.assign(Object.assign({}, this.logContext), { error: e })); return; } const statsMap = new Map(stats.map(s => [s.rid, s])); const isCpuConstrained = stats.some(s => s.qualityLimitationReason === 'cpu'); if (isCpuConstrained !== this.isCpuConstrained) { this.isCpuConstrained = isCpuConstrained; if (this.isCpuConstrained) { this.emit(TrackEvent.CpuConstrained); } } if (this.prevStats) { let totalBitrate = 0; statsMap.forEach((s, key) => { var _a; const prev = (_a = this.prevStats) === null || _a === void 0 ? void 0 : _a.get(key); totalBitrate += computeBitrate(s, prev); }); this._currentBitrate = totalBitrate; } this.prevStats = statsMap; }); this.senderLock = new _(); } get isSimulcast() { if (this.sender && this.sender.getParameters().encodings.length > 1) { return true; } return false; } /* @internal */ startMonitor(signalClient) { var _a; this.signalClient = signalClient; if (!isWeb()) { return; } // save original encodings // TODO : merge simulcast tracks stats const params = (_a = this.sender) === null || _a === void 0 ? void 0 : _a.getParameters(); if (params) { this.encodings = params.encodings; } if (this.monitorInterval) { return; } this.monitorInterval = setInterval(() => { this.monitorSender(); }, monitorFrequency); } stop() { this._mediaStreamTrack.getConstraints(); this.simulcastCodecs.forEach(trackInfo => { trackInfo.mediaStreamTrack.stop(); }); super.stop(); } pauseUpstream() { const _super = Object.create(null, { pauseUpstream: { get: () => super.pauseUpstream } }); return __awaiter(this, void 0, void 0, function* () { var _a, e_1, _b, _c; var _d; yield _super.pauseUpstream.call(this); try { for (var _e = true, _f = __asyncValues(this.simulcastCodecs.values()), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) { _c = _g.value; _e = false; const sc = _c; yield (_d = sc.sender) === null || _d === void 0 ? void 0 : _d.replaceTrack(null); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (!_e && !_a && (_b = _f.return)) yield _b.call(_f); } finally { if (e_1) throw e_1.error; } } }); } resumeUpstream() { const _super = Object.create(null, { resumeUpstream: { get: () => super.resumeUpstream } }); return __awaiter(this, void 0, void 0, function* () { var _a, e_2, _b, _c; var _d; yield _super.resumeUpstream.call(this); try { for (var _e = true, _f = __asyncValues(this.simulcastCodecs.values()), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) { _c = _g.value; _e = false; const sc = _c; yield (_d = sc.sender) === null || _d === void 0 ? void 0 : _d.replaceTrack(sc.mediaStreamTrack); } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (!_e && !_a && (_b = _f.return)) yield _b.call(_f); } finally { if (e_2) throw e_2.error; } } }); } mute() { const _super = Object.create(null, { mute: { get: () => super.mute } }); return __awaiter(this, void 0, void 0, function* () { const unlock = yield this.muteLock.lock(); try { if (this.isMuted) { this.log.debug('Track already muted', this.logContext); return this; } if (this.source === Track.Source.Camera && !this.isUserProvided) { this.log.debug('stopping camera track', this.logContext); // also stop the track, so that camera indicator is turned off this._mediaStreamTrack.stop(); } yield _super.mute.call(this); return this; } finally { unlock(); } }); } unmute() { const _super = Object.create(null, { unmute: { get: () => super.unmute } }); return __awaiter(this, void 0, void 0, function* () { const unlock = yield this.muteLock.lock(); try { if (!this.isMuted) { this.log.debug('Track already unmuted', this.logContext); return this; } if (this.source === Track.Source.Camera && !this.isUserProvided) { this.log.debug('reacquiring camera track', this.logContext); yield this.restart(undefined, true); } yield _super.unmute.call(this); return this; } finally { unlock(); } }); } setTrackMuted(muted) { super.setTrackMuted(muted); for (const sc of this.simulcastCodecs.values()) { sc.mediaStreamTrack.enabled = !muted; } } getSenderStats() { return __awaiter(this, void 0, void 0, function* () { var _a; if (!((_a = this.sender) === null || _a === void 0 ? void 0 : _a.getStats)) { return []; } const items = []; const stats = yield this.sender.getStats(); stats.forEach(v => { var _a; if (v.type === 'outbound-rtp') { const vs = { type: 'video', streamId: v.id, frameHeight: v.frameHeight, frameWidth: v.frameWidth, framesPerSecond: v.framesPerSecond, framesSent: v.framesSent, firCount: v.firCount, pliCount: v.pliCount, nackCount: v.nackCount, packetsSent: v.packetsSent, bytesSent: v.bytesSent, qualityLimitationReason: v.qualityLimitationReason, qualityLimitationDurations: v.qualityLimitationDurations, qualityLimitationResolutionChanges: v.qualityLimitationResolutionChanges, rid: (_a = v.rid) !== null && _a !== void 0 ? _a : v.id, retransmittedPacketsSent: v.retransmittedPacketsSent, targetBitrate: v.targetBitrate, timestamp: v.timestamp }; // locate the appropriate remote-inbound-rtp item const r = stats.get(v.remoteId); if (r) { vs.jitter = r.jitter; vs.packetsLost = r.packetsLost; vs.roundTripTime = r.roundTripTime; } items.push(vs); } }); // make sure highest res layer is always first items.sort((a, b) => { var _a, _b; return ((_a = b.frameWidth) !== null && _a !== void 0 ? _a : 0) - ((_b = a.frameWidth) !== null && _b !== void 0 ? _b : 0); }); return items; }); } setPublishingQuality(maxQuality) { const qualities = []; for (let q = VideoQuality.LOW; q <= VideoQuality.HIGH; q += 1) { qualities.push(new SubscribedQuality({ quality: q, enabled: q <= maxQuality })); } this.log.debug("setting publishing quality. max quality ".concat(maxQuality), this.logContext); this.setPublishingLayers(isSVCCodec(this.codec), qualities); } restartTrack(options) { return __awaiter(this, void 0, void 0, function* () { var _a, e_3, _b, _c; var _d; let constraints; if (options) { const streamConstraints = constraintsForOptions({ video: options }); if (typeof streamConstraints.video !== 'boolean') { constraints = streamConstraints.video; } } yield this.restart(constraints); // reset cpu constrained state after track is restarted this.isCpuConstrained = false; try { for (var _e = true, _f = __asyncValues(this.simulcastCodecs.values()), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) { _c = _g.value; _e = false; const sc = _c; if (sc.sender && ((_d = sc.sender.transport) === null || _d === void 0 ? void 0 : _d.state) !== 'closed') { sc.mediaStreamTrack = this.mediaStreamTrack.clone(); yield sc.sender.replaceTrack(sc.mediaStreamTrack); } } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (!_e && !_a && (_b = _f.return)) yield _b.call(_f); } finally { if (e_3) throw e_3.error; } } // The new MediaStreamTrack may have different dimensions than the previous one // (e.g. switching between cameras with different native resolutions), which would // leave the sender's encoding parameters (scaleResolutionDownBy, maxBitrate, etc.) // based on the old dimensions. Recompute them so the encoded output matches the // new source. yield this.onSenderTrackSwapped(); }); } onSenderTrackSwapped() { return __awaiter(this, void 0, void 0, function* () { yield this.refreshSenderEncodings(); }); } /** * Recomputes encoding parameters for this track's senders based on the current * MediaStreamTrack dimensions and reapplies them via setParameters. This is a no-op * if the track hasn't been published yet or if the track is in performance-optimized * mode (which manages its own encodings). */ refreshSenderEncodings() { return __awaiter(this, void 0, void 0, function* () { var _a; if (!this.sender || !this.publishOptions || this.optimizeForPerformance) { return; } const unlock = yield this.senderLock.lock(); try { let dims; try { dims = yield this.waitForDimensions(); } catch (e) { this.log.warn('could not determine new track dimensions, skipping encoding recompute', Object.assign(Object.assign({}, this.logContext), { error: e })); return; } if (this.lastEncodedDimensions && this.lastEncodedDimensions.width === dims.width && this.lastEncodedDimensions.height === dims.height) { return; } const isScreenShare = this.source === Track.Source.ScreenShare; const newEncodings = computeVideoEncodings(isScreenShare, dims.width, dims.height, Object.assign({}, this.publishOptions)); yield this.applyEncodingsToSender(this.sender, newEncodings); this.encodings = newEncodings; this.lastEncodedDimensions = dims; for (const _ref of this.simulcastCodecs) { var _ref2 = _slicedToArray(_ref, 2); const codec = _ref2[0]; const sc = _ref2[1]; if (!sc.sender || ((_a = sc.sender.transport) === null || _a === void 0 ? void 0 : _a.state) === 'closed') { continue; } if (!isBackupVideoCodec(codec)) { continue; } const backupOpts = Object.assign({}, this.publishOptions); const backupEncodings = computeTrackBackupEncodings(this, codec, backupOpts); if (!backupEncodings) { continue; } yield this.applyEncodingsToSender(sc.sender, backupEncodings); sc.encodings = backupEncodings; } } catch (e) { this.log.warn('failed to apply recomputed encodings', Object.assign(Object.assign({}, this.logContext), { error: e })); } finally { unlock(); } }); } applyEncodingsToSender(sender, encodings) { return __awaiter(this, void 0, void 0, function* () { const params = sender.getParameters(); if (!params.encodings || params.encodings.length !== encodings.length) { return; } params.encodings.forEach((existing, idx) => { // preserve disabled layers (dynacast / Firefox workaround in // setPublishingLayersForSender set scaleResolutionDownBy/maxBitrate to sentinel // values for disabled layers — don't clobber those). if (existing.active === false) { return; } const next = encodings[idx]; if (next.scaleResolutionDownBy !== undefined) { existing.scaleResolutionDownBy = next.scaleResolutionDownBy; } if (next.maxBitrate !== undefined) { existing.maxBitrate = next.maxBitrate; } if (next.maxFramerate !== undefined) { existing.maxFramerate = next.maxFramerate; } if (next.priority !== undefined) { existing.priority = next.priority; existing.networkPriority = next.priority; } }); this.log.debug('updating sender encodings after track restart', Object.assign(Object.assign({}, this.logContext), { encodings: params.encodings })); yield sender.setParameters(params); }); } setProcessor(processor_1) { const _super = Object.create(null, { setProcessor: { get: () => super.setProcessor } }); return __awaiter(this, arguments, void 0, function (processor) { var _this = this; let showProcessedStreamLocally = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; return function* () { var _a, e_4, _b, _c; var _d, _e; yield _super.setProcessor.call(_this, processor, showProcessedStreamLocally); if ((_d = _this.processor) === null || _d === void 0 ? void 0 : _d.processedTrack) { try { for (var _f = true, _g = __asyncValues(_this.simulcastCodecs.values()), _h; _h = yield _g.next(), _a = _h.done, !_a; _f = true) { _c = _h.value; _f = false; const sc = _c; yield (_e = sc.sender) === null || _e === void 0 ? void 0 : _e.replaceTrack(_this.processor.processedTrack); } } catch (e_4_1) { e_4 = { error: e_4_1 }; } finally { try { if (!_f && !_a && (_b = _g.return)) yield _b.call(_g); } finally { if (e_4) throw e_4.error; } } } }(); }); } setDegradationPreference(preference) { return __awaiter(this, void 0, void 0, function* () { this.degradationPreference = preference; if (this.sender) { try { this.log.debug("setting degradationPreference to ".concat(preference), this.logContext); const params = this.sender.getParameters(); params.degradationPreference = preference; this.sender.setParameters(params); } catch (e) { this.log.warn("failed to set degradationPreference", Object.assign({ error: e }, this.logContext)); } } }); } addSimulcastTrack(codec, encodings) { if (this.simulcastCodecs.has(codec)) { this.log.error("".concat(codec, " already added, skipping adding simulcast codec"), this.logContext); return; } const simulcastCodecInfo = { codec, mediaStreamTrack: this.mediaStreamTrack.clone(), sender: undefined, encodings }; this.simulcastCodecs.set(codec, simulcastCodecInfo); return simulcastCodecInfo; } setSimulcastTrackSender(codec, sender) { const simulcastCodecInfo = this.simulcastCodecs.get(codec); if (!simulcastCodecInfo) { return; } simulcastCodecInfo.sender = sender; // browser will reenable disabled codec/layers after new codec has been published, // so refresh subscribedCodecs after publish a new codec setTimeout(() => { if (this.subscribedCodecs) { this.setPublishingCodecs(this.subscribedCodecs); } }, refreshSubscribedCodecAfterNewCodec); } /** * @internal * Sets codecs that should be publishing, returns new codecs that have not yet * been published */ setPublishingCodecs(codecs) { return __awaiter(this, void 0, void 0, function* () { var _a, codecs_1, codecs_1_1; var _b, e_5, _c, _d; this.log.debug('setting publishing codecs', Object.assign(Object.assign({}, this.logContext), { codecs, currentCodec: this.codec })); // only enable simulcast codec for preference codec setted if (!this.codec && codecs.length > 0) { yield this.setPublishingLayers(isSVCCodec(codecs[0].codec), codecs[0].qualities); return []; } this.subscribedCodecs = codecs; const newCodecs = []; try { for (_a = true, codecs_1 = __asyncValues(codecs); codecs_1_1 = yield codecs_1.next(), _b = codecs_1_1.done, !_b; _a = true) { _d = codecs_1_1.value; _a = false; const codec = _d; if (!this.codec || this.codec === codec.codec) { yield this.setPublishingLayers(isSVCCodec(codec.codec), codec.qualities); } else { const simulcastCodecInfo = this.simulcastCodecs.get(codec.codec); this.log.debug("try setPublishingCodec for ".concat(codec.codec), Object.assign(Object.assign({}, this.logContext), { simulcastCodecInfo })); if (!simulcastCodecInfo || !simulcastCodecInfo.sender) { for (const q of codec.qualities) { if (q.enabled) { newCodecs.push(codec.codec); break; } } } else if (simulcastCodecInfo.encodings) { this.log.debug("try setPublishingLayersForSender ".concat(codec.codec), this.logContext); yield setPublishingLayersForSender(simulcastCodecInfo.sender, simulcastCodecInfo.encodings, codec.qualities, this.senderLock, isSVCCodec(codec.codec), this.log, this.logContext); } } } } catch (e_5_1) { e_5 = { error: e_5_1 }; } finally { try { if (!_a && !_b && (_c = codecs_1.return)) yield _c.call(codecs_1); } finally { if (e_5) throw e_5.error; } } return newCodecs; }); } /** * @internal * Sets layers that should be publishing */ setPublishingLayers(isSvc, qualities) { return __awaiter(this, void 0, void 0, function* () { if (this.optimizeForPerformance) { this.log.info('skipping setPublishingLayers due to optimized publishing performance', Object.assign(Object.assign({}, this.logContext), { qualities })); return; } this.log.debug('setting publishing layers', Object.assign(Object.assign({}, this.logContext), { qualities })); if (!this.sender || !this.encodings) { return; } yield setPublishingLayersForSender(this.sender, this.encodings, qualities, this.senderLock, isSvc, this.log, this.logContext); }); } /** * Designed for lower powered devices, reduces video publishing quality and disables simulcast. * @experimental */ prioritizePerformance() { return __awaiter(this, void 0, void 0, function* () { if (!this.sender) { throw new Error('sender not found'); } const unlock = yield this.senderLock.lock(); try { this.optimizeForPerformance = true; const params = this.sender.getParameters(); params.encodings = params.encodings.map((e, idx) => { var _a; return Object.assign(Object.assign({}, e), { active: idx === 0, scaleResolutionDownBy: Math.max(1, Math.ceil(((_a = this.mediaStreamTrack.getSettings().height) !== null && _a !== void 0 ? _a : 360) / 360)), scalabilityMode: idx === 0 && isSVCCodec(this.codec) ? 'L1T3' : undefined, maxFramerate: idx === 0 ? 15 : 0, maxBitrate: idx === 0 ? e.maxBitrate : 0 }); }); this.log.debug('setting performance optimised encodings', Object.assign(Object.assign({}, this.logContext), { encodings: params.encodings })); this.encodings = params.encodings; yield this.sender.setParameters(params); } catch (e) { this.log.error('failed to set performance optimised encodings', Object.assign(Object.assign({}, this.logContext), { error: e })); this.optimizeForPerformance = false; } finally { unlock(); } }); } handleAppVisibilityChanged() { const _super = Object.create(null, { handleAppVisibilityChanged: { get: () => super.handleAppVisibilityChanged } }); return __awaiter(this, void 0, void 0, function* () { yield _super.handleAppVisibilityChanged.call(this); if (!isMobile()) return; if (this.isInBackground && this.source === Track.Source.Camera) { this._mediaStreamTrack.enabled = false; } }); } } function setPublishingLayersForSender(sender, senderEncodings, qualities, senderLock, isSVC, log, logContext) { return __awaiter(this, void 0, void 0, function* () { const unlock = yield senderLock.lock(); log.debug('setPublishingLayersForSender', Object.assign(Object.assign({}, logContext), { sender, qualities, senderEncodings })); try { const params = sender.getParameters(); const encodings = params.encodings; if (!encodings) { return; } if (encodings.length !== senderEncodings.length) { log.warn('cannot set publishing layers, encodings mismatch', Object.assign(Object.assign({}, logContext), { encodings, senderEncodings })); return; } let hasChanged = false; /* disable closable spatial layer as it has video blur / frozen issue with current server / client 1. chrome 113: when switching to up layer with scalability Mode change, it will generate a low resolution frame and recover very quickly, but noticable 2. livekit sfu: additional pli request cause video frozen for a few frames, also noticable */ const closableSpatial = false; /* @ts-ignore */ if (closableSpatial && encodings[0].scalabilityMode) ; else { if (isSVC) { const hasEnabledEncoding = qualities.some(q => q.enabled); if (hasEnabledEncoding) { qualities.forEach(q => q.enabled = true); } } // simulcast dynacast encodings encodings.forEach((encoding, idx) => { var _a; let rid = (_a = encoding.rid) !== null && _a !== void 0 ? _a : ''; if (rid === '') { rid = 'q'; } const quality = videoQualityForRid(rid); const subscribedQuality = qualities.find(q => q.quality === quality); if (!subscribedQuality) { return; } if (encoding.active !== subscribedQuality.enabled) { hasChanged = true; encoding.active = subscribedQuality.enabled; log.debug("setting layer ".concat(subscribedQuality.quality, " to ").concat(encoding.active ? 'enabled' : 'disabled'), logContext); // FireFox does not support setting encoding.active to false, so we // have a workaround of lowering its bitrate and resolution to the min. if (isFireFox()) { if (subscribedQuality.enabled) { encoding.scaleResolutionDownBy = senderEncodings[idx].scaleResolutionDownBy; encoding.maxBitrate = senderEncodings[idx].maxBitrate; /* @ts-ignore */ encoding.maxFrameRate = senderEncodings[idx].maxFrameRate; } else { encoding.scaleResolutionDownBy = 4; encoding.maxBitrate = 10; /* @ts-ignore */ encoding.maxFrameRate = 2; } } } }); } if (hasChanged) { params.encodings = encodings; log.debug("setting encodings", Object.assign(Object.assign({}, logContext), { encodings: params.encodings })); yield sender.setParameters(params); } } finally { unlock(); } }); } function videoQualityForRid(rid) { switch (rid) { case 'f': return VideoQuality.HIGH; case 'h': return VideoQuality.MEDIUM; case 'q': return VideoQuality.LOW; default: return VideoQuality.HIGH; } } function videoLayersFromEncodings(width, height, encodings, svc) { // default to a single layer, HQ if (!encodings) { return [new VideoLayer({ quality: VideoQuality.HIGH, width, height, bitrate: 0, ssrc: 0 })]; } if (svc) { // svc layers /* @ts-ignore */ const encodingSM = encodings[0].scalabilityMode; const sm = new ScalabilityMode(encodingSM); const layers = []; const resRatio = sm.suffix == 'h' ? 1.5 : 2; const bitratesRatio = sm.suffix == 'h' ? 2 : 3; for (let i = 0; i < sm.spatial; i += 1) { layers.push(new VideoLayer({ quality: Math.min(VideoQuality.HIGH, sm.spatial - 1) - i, width: Math.ceil(width / Math.pow(resRatio, i)), height: Math.ceil(height / Math.pow(resRatio, i)), bitrate: encodings[0].maxBitrate ? Math.ceil(encodings[0].maxBitrate / Math.pow(bitratesRatio, i)) : 0, ssrc: 0 })); } return layers; } return encodings.map(encoding => { var _a, _b, _c; const scale = (_a = encoding.scaleResolutionDownBy) !== null && _a !== void 0 ? _a : 1; let quality = videoQualityForRid((_b = encoding.rid) !== null && _b !== void 0 ? _b : ''); return new VideoLayer({ quality, width: Math.ceil(width / scale), height: Math.ceil(height / scale), bitrate: (_c = encoding.maxBitrate) !== null && _c !== void 0 ? _c : 0, ssrc: 0 }); }); }const lossyDataChannel = '_lossy'; const reliableDataChannel = '_reliable'; const dataTrackDataChannel = '_data_track'; const minReconnectWait = 2 * 1000; const leaveReconnect = 'leave-reconnect'; const reliabeReceiveStateTTL = 30000; const lossyDataChannelBufferThresholdMin = 8 * 1024; const lossyDataChannelBufferThresholdMax = 256 * 1024; const initialMediaSectionsAudio = 3; const initialMediaSectionsVideo = 3; var PCState; (function (PCState) { PCState[PCState["New"] = 0] = "New"; PCState[PCState["Connected"] = 1] = "Connected"; PCState[PCState["Disconnected"] = 2] = "Disconnected"; PCState[PCState["Reconnecting"] = 3] = "Reconnecting"; PCState[PCState["Closed"] = 4] = "Closed"; })(PCState || (PCState = {})); var DataChannelKind; (function (DataChannelKind) { DataChannelKind[DataChannelKind["RELIABLE"] = 0] = "RELIABLE"; DataChannelKind[DataChannelKind["LOSSY"] = 1] = "LOSSY"; DataChannelKind[DataChannelKind["DATA_TRACK_LOSSY"] = 2] = "DATA_TRACK_LOSSY"; })(DataChannelKind || (DataChannelKind = {})); // Default data-channel max message size (bytes), used when the remote SDP // answer does not advertise an `a=max-message-size` attribute (RFC 8841). // `0` means "no limit". const DEFAULT_MAX_MESSAGE_SIZE = 64000; /** @internal */ class RTCEngine extends eventsExports.EventEmitter { get isClosed() { return this._isClosed; } get isNewlyCreated() { return this._isNewlyCreated; } get pendingReconnect() { return !!this.reconnectTimeout; } constructor(options) { var _a; super(); this.options = options; this.rtcConfig = {}; this.peerConnectionTimeout = roomConnectOptionDefaults.peerConnectionTimeout; this.fullReconnectOnNext = false; /** * @internal */ this.latestRemoteOfferId = 0; this.subscriberPrimary = false; this.pcState = PCState.New; this._isClosed = true; this._isNewlyCreated = true; this.pendingTrackResolvers = {}; this.reconnectAttempts = 0; this.reconnectStart = 0; this.attemptingReconnect = false; /** keeps track of how often an initial join connection has been tried */ this.joinAttempts = 0; /** specifies how often an initial join connection is allowed to retry */ this.maxJoinAttempts = 1; this.shouldFailNext = false; this.shouldFailOnV1Path = false; this.log = livekitLogger; this.reliableDataSequence = 1; this.reliableMessageBuffer = new DataPacketBuffer(); this.reliableReceivedState = new TTLMap(reliabeReceiveStateTTL); this.lossyDataStatCurrentBytes = 0; this.lossyDataStatByterate = 0; this.lossyDataDropCount = 0; this.midToTrackId = {}; /** used to indicate whether the browser is currently waiting to reconnect */ this.isWaitingForNetworkReconnect = false; this.bufferStatusLowClosingFuture = new Future(); this.handleDataChannel = _a => __awaiter(this, [_a], void 0, function (_ref) { var _this = this; let channel = _ref.channel; return function* () { if (!channel) { return; } let handler; if (channel.label === reliableDataChannel) { _this.reliableDCSub = channel; handler = _this.handleDataMessage; } else if (channel.label === lossyDataChannel) { _this.lossyDCSub = channel; handler = _this.handleDataMessage; } else if (channel.label === dataTrackDataChannel) { _this.dataTrackDCSub = channel; handler = _this.handleDataTrackMessage; } else { return; } _this.log.debug("on data channel ".concat(channel.id, ", ").concat(channel.label)); channel.onmessage = handler; }(); }); this.handleDataMessage = message => __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e; // make sure to respect incoming data message order by processing message events one after the other const unlock = yield this.dataProcessLock.lock(); try { // decode let buffer; if (message.data instanceof ArrayBuffer) { buffer = message.data; } else if (message.data instanceof Blob) { buffer = yield message.data.arrayBuffer(); } else { this.log.error('unsupported data type', { data: message.data }); return; } const dp = DataPacket.fromBinary(new Uint8Array(buffer)); if (dp.sequence > 0 && dp.participantSid !== '') { const lastSeq = this.reliableReceivedState.get(dp.participantSid); if (lastSeq && dp.sequence <= lastSeq) { // ignore duplicate or out-of-order packets in reliable channel return; } this.reliableReceivedState.set(dp.participantSid, dp.sequence); } if (((_a = dp.value) === null || _a === void 0 ? void 0 : _a.case) === 'speaker') { // dispatch speaker updates this.emit(EngineEvent.ActiveSpeakersUpdate, dp.value.value.speakers); } else if (((_b = dp.value) === null || _b === void 0 ? void 0 : _b.case) === 'encryptedPacket') { if (!this.e2eeManager) { this.log.error('Received encrypted packet but E2EE not set up'); return; } const decryptedData = yield (_c = this.e2eeManager) === null || _c === void 0 ? void 0 : _c.handleEncryptedData(dp.value.value.encryptedValue, dp.value.value.iv, dp.participantIdentity, dp.value.value.keyIndex); const decryptedPacket = EncryptedPacketPayload.fromBinary(decryptedData.payload); const newDp = new DataPacket({ value: decryptedPacket.value, participantIdentity: dp.participantIdentity, participantSid: dp.participantSid }); if (((_d = newDp.value) === null || _d === void 0 ? void 0 : _d.case) === 'user') { // compatibility applyUserDataCompat(newDp, newDp.value.value); } this.emit(EngineEvent.DataPacketReceived, newDp, dp.value.value.encryptionType); } else { if (((_e = dp.value) === null || _e === void 0 ? void 0 : _e.case) === 'user') { // compatibility applyUserDataCompat(dp, dp.value.value); } this.emit(EngineEvent.DataPacketReceived, dp, Encryption_Type.NONE); } } finally { unlock(); } }); this.handleDataTrackMessage = message => __awaiter(this, void 0, void 0, function* () { // Decode / normalize into a common format let buffer; if (message.data instanceof ArrayBuffer) { buffer = message.data; } else if (message.data instanceof Blob) { buffer = yield message.data.arrayBuffer(); } else { this.log.error('unsupported data type', { data: message.data }); return; } this.emit('dataTrackPacketReceived', new Uint8Array(buffer)); }); this.handleDataError = event => { // Errors fired while we're tearing the connection down (e.g. the SCTP transport aborting as // the peer connection closes) carry no actionable information — the channel is going away // regardless. Suppress them so a graceful disconnect doesn't surface spurious errors. // See livekit/client-sdk-js#1953. if (this._isClosed) { return; } const channel = event.currentTarget; const channelKind = channel.maxRetransmits === 0 ? 'lossy' : 'reliable'; if (typeof RTCErrorEvent !== 'undefined' && event instanceof RTCErrorEvent && event.error) { const error = event.error; this.log.error("DataChannel error on ".concat(channelKind, ": ").concat(error.message), { error, errorDetail: error.errorDetail, sctpCauseCode: error.sctpCauseCode }); } else { this.log.error("Unknown DataChannel error on ".concat(channelKind), { event }); } }; this.handleDataChannelClose = kind => () => { var _a; // A publisher DC closing while the session is up and the publisher PC is still // connected is the signature of an oversized message having aborted the channel // (see livekit/rust-sdks#1137). Surface it; do not attempt renegotiation. if (!this._isClosed && ((_a = this.pcManager) === null || _a === void 0 ? void 0 : _a.publisher.getConnectionState()) === 'connected') { this.log.error("publisher data channel '".concat(DataChannelKind[kind], "' closed unexpectedly"), this.logContext); } }; this.handleBufferedAmountLow = channelKind => { this.updateAndEmitDCBufferStatus(channelKind); }; // websocket reconnect behavior. if websocket is interrupted, and the PeerConnection // continues to work, we can reconnect to websocket to continue the session // after a number of retries, we'll close and give up permanently this.handleDisconnect = (connection, disconnectReason) => { if (this._isClosed) { return; } this.log.warn("".concat(connection, " disconnected")); if (this.reconnectAttempts === 0) { // only reset start time on the first try this.reconnectStart = Date.now(); } const disconnect = duration => { this.log.warn("could not recover connection after ".concat(this.reconnectAttempts, " attempts, ").concat(duration, "ms. giving up")); this.emit(EngineEvent.Disconnected); this.close(); }; const duration = Date.now() - this.reconnectStart; let delay = this.getNextRetryDelay({ elapsedMs: duration, retryCount: this.reconnectAttempts }); if (delay === null) { disconnect(duration); return; } if (connection === leaveReconnect) { delay = 0; } this.log.debug("reconnecting in ".concat(delay, "ms")); this.clearReconnectTimeout(); if (this.token) { // token may have been refreshed, we do not want to recreate the regionUrlProvider // since the current engine may have inherited a regional url this.emit(EngineEvent.TokenRefreshed, this.token); } this.reconnectTimeout = CriticalTimers.setTimeout(() => this.attemptReconnect(disconnectReason).finally(() => this.reconnectTimeout = undefined), delay); }; this.waitForRestarted = () => { return new Promise((resolve, reject) => { if (this.pcState === PCState.Connected) { resolve(); } const onRestarted = () => { this.off(EngineEvent.Disconnected, onDisconnected); resolve(); }; const onDisconnected = () => { this.off(EngineEvent.Restarted, onRestarted); reject(); }; this.once(EngineEvent.Restarted, onRestarted); this.once(EngineEvent.Disconnected, onDisconnected); }); }; this.updateAndEmitDCBufferStatus = kind => { if (kind === DataChannelKind.RELIABLE) { const dc = this.dataChannelForKind(kind); if (dc) { this.reliableMessageBuffer.alignBufferedAmount(dc.bufferedAmount); } } const status = this.isBufferStatusLow(kind); if (typeof status !== 'undefined' && status !== this.dcBufferStatus.get(kind)) { this.dcBufferStatus.set(kind, status); this.emit(EngineEvent.DCBufferStatusChanged, status, kind); } }; this.isBufferStatusLow = kind => { const dc = this.dataChannelForKind(kind); if (dc) { return dc.bufferedAmount <= dc.bufferedAmountLowThreshold; } }; this.onRtpMapAvailable = rtpTypes => { const rtpMap = new Map(); rtpTypes.forEach(rtp => { const codec = rtp.codec.toLowerCase(); if (isVideoCodec(codec)) { rtpMap.set(rtp.payload, codec); } }); this.emit(EngineEvent.RTPVideoMapUpdate, rtpMap); }; this.handleBrowserOnLine = () => __awaiter(this, void 0, void 0, function* () { if (!this.url) { return; } const hasNetworkConnection = yield fetch(toHttpUrl(this.url), { method: 'HEAD' }).then(resp => resp.ok).catch(() => false); if (!hasNetworkConnection) { return; } this.log.info('detected network reconnected'); if ( // in case the engine is currently reconnecting, attempt a reconnect immediately after the browser state has changed to 'onLine' this.client.currentState === SignalConnectionState.RECONNECTING || // also if the browser went offline before and the engine still thinks it's in a connected state, treat it as a network interruption that we haven't noticed yet this.isWaitingForNetworkReconnect && this.client.currentState === SignalConnectionState.CONNECTED) { this.clearReconnectTimeout(); this.attemptReconnect(ReconnectReason.RR_SIGNAL_DISCONNECTED); this.isWaitingForNetworkReconnect = false; } }); this.handleBrowserOffline = () => __awaiter(this, void 0, void 0, function* () { if (!this.url) { return; } try { yield Promise.race([fetch(toHttpUrl(this.url), { method: 'HEAD' }), // if there's no internet connection the fetch rejects immediately, so we only use a short timeout here sleep(4000).then(() => Promise.reject())]); } catch (e) { // only set if the browser still thinks it's offline after the request failed if (window.navigator.onLine === false) { this.log.info('detected network interruption'); this.isWaitingForNetworkReconnect = true; } } }); this.log = getLogger((_a = options.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.Engine, () => this.logContext); this.loggerOptions = { loggerName: options.loggerName, loggerContextCb: () => this.logContext }; this.client = new SignalClient(undefined, this.loggerOptions); this.client.signalLatency = this.options.expSignalLatency; this.reconnectPolicy = this.options.reconnectPolicy; this.closingLock = new _(); this.dataProcessLock = new _(); this.dcBufferStatus = new Map([[DataChannelKind.RELIABLE, true], [DataChannelKind.LOSSY, true], [DataChannelKind.DATA_TRACK_LOSSY, true]]); this.client.onParticipantUpdate = updates => this.emit(EngineEvent.ParticipantUpdate, updates); this.client.onConnectionQuality = update => this.emit(EngineEvent.ConnectionQualityUpdate, update); this.client.onRoomUpdate = update => this.emit(EngineEvent.RoomUpdate, update); this.client.onSubscriptionError = resp => this.emit(EngineEvent.SubscriptionError, resp); this.client.onSubscriptionPermissionUpdate = update => this.emit(EngineEvent.SubscriptionPermissionUpdate, update); this.client.onSpeakersChanged = update => this.emit(EngineEvent.SpeakersChanged, update); this.client.onStreamStateUpdate = update => this.emit(EngineEvent.StreamStateChanged, update); this.client.onRequestResponse = response => this.emit(EngineEvent.SignalRequestResponse, response); this.client.onParticipantUpdate = updates => this.emit(EngineEvent.ParticipantUpdate, updates); this.client.onJoined = joinResponse => this.emit(EngineEvent.Joined, joinResponse); this.on(EngineEvent.Closing, () => { var _a, _b; (_b = (_a = this.bufferStatusLowClosingFuture).reject) === null || _b === void 0 ? void 0 : _b.call(_a, new UnexpectedConnectionState('engine closed')); }); // Swallow the rejection at the source so it doesn't surface as an unhandled promise rejection // when no waitForBufferStatusLow callers are attached. this.bufferStatusLowClosingFuture.promise.catch(() => {}); } /** @internal */ get logContext() { var _a, _b, _c, _d, _e, _f; return { room: (_b = (_a = this.latestJoinResponse) === null || _a === void 0 ? void 0 : _a.room) === null || _b === void 0 ? void 0 : _b.name, roomID: (_d = (_c = this.latestJoinResponse) === null || _c === void 0 ? void 0 : _c.room) === null || _d === void 0 ? void 0 : _d.sid, participant: (_f = (_e = this.latestJoinResponse) === null || _e === void 0 ? void 0 : _e.participant) === null || _f === void 0 ? void 0 : _f.identity, participantID: this.participantSid }; } join(url_1, token_1, opts_1, abortSignal_1) { return __awaiter(this, arguments, void 0, function (url, token, opts, abortSignal) { var _this2 = this; let useV0Path = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; return function* () { var _a, _b, _c; _this2._isNewlyCreated = false; _this2.url = url; _this2.token = token; _this2.signalOpts = opts; _this2.maxJoinAttempts = opts.maxRetries; try { _this2.joinAttempts += 1; _this2.setupSignalClientCallbacks(); // Whether the initial publisher offer is bundled with the join request. Computed once and // reused after the join below. Only the (non-Firefox) offer-with-join path does this. const sendOfferWithJoin = !useV0Path && isPublisherOfferWithJoinSupported(); let offerProto; if (sendOfferWithJoin) { if (!_this2.pcManager) { // Firefox is excluded from offer-with-join (see isPublisherOfferWithJoinSupported): // customers reported ICE connectivity problems for FF on this path (#1919) that we were // never able to reproduce, so out of caution FF stays on the deferred path below. The // exact cause is unknown — note that ICE gathering does not actually start here, since // createInitialOffer() defers setLocalDescription (via pendingInitialOffer) until the // answer is applied, after updateConfiguration() has set the server's TURN servers. yield _this2.configure(); _this2.applyInitialPublisherLayout(); } const offer = yield (_a = _this2.pcManager) === null || _a === void 0 ? void 0 : _a.publisher.createInitialOffer(); if (offer) { offerProto = toProtoSessionDescription(offer.offer, offer.offerId); } } if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) { throw ConnectionError.cancelled('Connection aborted'); } if (!useV0Path && _this2.shouldFailOnV1Path) { _this2.shouldFailOnV1Path = false; throw ConnectionError.serviceNotFound('Simulated v1 path failure', 'v0-rtc'); } const joinResponse = yield _this2.client.join(url, token, opts, abortSignal, useV0Path, offerProto); _this2._isClosed = false; _this2.latestJoinResponse = joinResponse; _this2.participantSid = (_b = joinResponse.participant) === null || _b === void 0 ? void 0 : _b.sid; _this2.subscriberPrimary = joinResponse.subscriberPrimary; if (sendOfferWithJoin) { (_c = _this2.pcManager) === null || _c === void 0 ? void 0 : _c.updateConfiguration(_this2.makeRTCConfiguration(joinResponse)); } else { if (!_this2.pcManager) { // Deferred path (Firefox, and V0): configure with the join response so the PC picks up // the server's ICE servers and topology, then negotiate separately rather than bundling // the offer with the join. yield _this2.configure(joinResponse, !useV0Path); if (!useV0Path) { // The V1 first offer must carry the media layout so Firefox binds receive decoders for // subscribed tracks — without it, subscribed audio/video arrive as RTP but // never decode. V0 (legacy dual-PC) keeps its original lazy behavior. _this2.applyInitialPublisherLayout(); } } // create offer if (!_this2.subscriberPrimary || joinResponse.fastPublish) { _this2.negotiate().catch(err => { _this2.log.error(err); }); } } _this2.registerOnLineListener(); _this2.clientConfiguration = joinResponse.clientConfiguration; _this2.emit(EngineEvent.SignalConnected, joinResponse); let serverInfo = joinResponse.serverInfo; if (!serverInfo) { serverInfo = { version: joinResponse.serverVersion, region: joinResponse.serverRegion }; } _this2.log.info("connected to Livekit Server ".concat(Object.entries(serverInfo).map(_ref2 => { let _ref3 = _slicedToArray(_ref2, 2), key = _ref3[0], value = _ref3[1]; return "".concat(key, ": ").concat(value); }).join(', '))); return { joinResponse, serverInfo }; } catch (e) { if (e instanceof ConnectionError) { if (e.reason === ConnectionErrorReason.ServerUnreachable) { _this2.log.warn("Couldn't connect to server, attempt ".concat(_this2.joinAttempts, " of ").concat(_this2.maxJoinAttempts)); if (_this2.joinAttempts < _this2.maxJoinAttempts) { return _this2.join(url, token, opts, abortSignal, useV0Path); } } else if (e.reason === ConnectionErrorReason.ServiceNotFound) { _this2.log.warn("Initial connection failed: ".concat(e.message, " \u2013 Retrying")); if (_this2.pcManager) { _this2.pcManager.onStateChange = undefined; yield _this2.cleanupPeerConnections(); } return _this2.join(url, token, opts, abortSignal, true); } } throw e; } }(); }); } close() { return __awaiter(this, void 0, void 0, function* () { const unlock = yield this.closingLock.lock(); if (this.isClosed) { unlock(); return; } try { this._isClosed = true; this.joinAttempts = 0; this.emit(EngineEvent.Closing); this.removeAllListeners(); this.deregisterOnLineListener(); this.clearPendingReconnect(); this.cleanupLossyDataStats(); yield this.cleanupPeerConnections(); yield this.cleanupClient(); } finally { unlock(); } }); } cleanupPeerConnections() { return __awaiter(this, void 0, void 0, function* () { var _a; const dcCleanup = dc => { if (!dc) { return; } // Detach the data channel handlers before closing anything. Closing a peer connection tears // down the SCTP transport, which can dispatch `error`/`close` events on the still-open data // channels; if our handlers are still attached at that point, handleDataError logs a spurious // "Unknown DataChannel error" during an otherwise graceful disconnect. Removing the handlers // before dc.close()/pcManager.close() makes this deterministic regardless of how/when the // browser dispatches those teardown events. See livekit/client-sdk-js#1953. dc.onbufferedamountlow = null; dc.onclose = null; dc.onclosing = null; dc.onerror = null; dc.onmessage = null; dc.onopen = null; dc.close(); }; dcCleanup(this.lossyDC); dcCleanup(this.lossyDCSub); dcCleanup(this.reliableDC); dcCleanup(this.reliableDCSub); dcCleanup(this.dataTrackDC); dcCleanup(this.dataTrackDCSub); yield (_a = this.pcManager) === null || _a === void 0 ? void 0 : _a.close(); this.pcManager = undefined; this.lossyDC = undefined; this.lossyDCSub = undefined; this.reliableDC = undefined; this.reliableDCSub = undefined; this.dataTrackDC = undefined; this.dataTrackDCSub = undefined; this.reliableMessageBuffer = new DataPacketBuffer(); this.reliableDataSequence = 1; this.reliableReceivedState.clear(); }); } cleanupLossyDataStats() { this.lossyDataStatByterate = 0; this.lossyDataStatCurrentBytes = 0; if (this.lossyDataStatInterval) { clearInterval(this.lossyDataStatInterval); this.lossyDataStatInterval = undefined; } this.lossyDataDropCount = 0; } cleanupClient() { return __awaiter(this, void 0, void 0, function* () { yield this.client.close(); this.client.resetCallbacks(); // Any in-flight addTrack requests are orphaned by the signal reconnect — the new session // won't deliver `trackPublishedResponse` for them, so reject the pending resolvers and // clear the map. Otherwise a subsequent `addTrack` call with the same client id (e.g. a // publish retry after a `NegotiationError`) throws `TrackInvalidError`. for (const cid of Object.keys(this.pendingTrackResolvers)) { this.pendingTrackResolvers[cid].reject(); } this.pendingTrackResolvers = {}; }); } addTrack(req) { if (this.pendingTrackResolvers[req.cid]) { throw new TrackInvalidError('a track with the same ID has already been published'); } return new Promise((resolve, reject) => { const publicationTimeout = setTimeout(() => { delete this.pendingTrackResolvers[req.cid]; reject(ConnectionError.timeout('publication of local track timed out, no response from server')); }, 10000); this.pendingTrackResolvers[req.cid] = { resolve: info => { clearTimeout(publicationTimeout); resolve(info); }, reject: () => { clearTimeout(publicationTimeout); reject(new Error('Cancelled publication by calling unpublish')); } }; this.client.sendAddTrack(req); }); } /** * Removes sender from PeerConnection, returning true if it was removed successfully * and a negotiation is necessary * @param sender * @returns */ removeTrack(sender) { if (sender.track && this.pendingTrackResolvers[sender.track.id]) { const reject = this.pendingTrackResolvers[sender.track.id].reject; if (reject) { reject(); } delete this.pendingTrackResolvers[sender.track.id]; } try { this.pcManager.removeTrack(sender); return true; } catch (e) { this.log.warn('failed to remove track', { error: e }); } return false; } updateMuteStatus(trackSid, muted) { this.client.sendMuteTrack(trackSid, muted); } get dataSubscriberReadyState() { var _a; return (_a = this.reliableDCSub) === null || _a === void 0 ? void 0 : _a.readyState; } getConnectedServerAddress() { return __awaiter(this, void 0, void 0, function* () { var _a; return (_a = this.pcManager) === null || _a === void 0 ? void 0 : _a.getConnectedAddress(); }); } /* @internal */ setRegionStrategy(strategy) { this.regionStrategy = strategy; } configure(joinResponse, useSinglePeerConnection) { return __awaiter(this, void 0, void 0, function* () { var _a; // already configured if (this.pcManager && this.pcManager.currentState !== PCTransportState.NEW) { return; } if (!joinResponse) { const rtcConfig = this.makeRTCConfiguration(); this.pcManager = new PCTransportManager('publisher-only', this.loggerOptions, rtcConfig); } else { this.participantSid = (_a = joinResponse.participant) === null || _a === void 0 ? void 0 : _a.sid; const rtcConfig = this.makeRTCConfiguration(joinResponse); this.pcManager = new PCTransportManager(useSinglePeerConnection ? 'publisher-only' : joinResponse.subscriberPrimary ? 'subscriber-primary' : 'publisher-primary', this.loggerOptions, rtcConfig); } this.emit(EngineEvent.TransportsCreated, this.pcManager.publisher, this.pcManager.subscriber); this.pcManager.onIceCandidate = (candidate, target) => { this.client.sendIceCandidate(candidate, target); }; this.pcManager.onPublisherOffer = (offer, offerId) => { this.client.sendOffer(offer, offerId); }; this.pcManager.onDataChannel = this.handleDataChannel; this.pcManager.onStateChange = (connectionState, publisherState, subscriberState) => __awaiter(this, void 0, void 0, function* () { this.log.debug("primary PC state changed ".concat(connectionState)); if (['closed', 'disconnected', 'failed'].includes(publisherState)) { // reset publisher connection promise this.publisherConnectionPromise = undefined; } if (connectionState === PCTransportState.CONNECTED) { const shouldEmit = this.pcState === PCState.New; this.pcState = PCState.Connected; if (shouldEmit) { this.emit(EngineEvent.Connected, this.latestJoinResponse); } } else if (connectionState === PCTransportState.FAILED) { // on Safari, PeerConnection will switch to 'disconnected' during renegotiation if (this.pcState === PCState.Connected || this.pcState === PCState.Reconnecting) { this.pcState = PCState.Disconnected; this.handleDisconnect('peerconnection failed', subscriberState === 'failed' ? ReconnectReason.RR_SUBSCRIBER_FAILED : ReconnectReason.RR_PUBLISHER_FAILED); } } // detect cases where both signal client and peer connection are severed and assume that user has lost network connection const isSignalSevered = this.client.isDisconnected || this.client.currentState === SignalConnectionState.RECONNECTING; const isPCSevered = [PCTransportState.FAILED, PCTransportState.CLOSING, PCTransportState.CLOSED].includes(connectionState); if (isSignalSevered && isPCSevered && !this._isClosed) { this.emit(EngineEvent.Offline); } }); this.pcManager.onTrack = ev => { // this fires after the underlying transceiver is stopped and potentially // peer connection closed, so do not bubble up if there are no streams if (ev.streams.length === 0) return; this.emit(EngineEvent.MediaTrackAdded, ev.track, ev.streams[0], ev.receiver); }; }); } setupSignalClientCallbacks() { // configure signaling client this.client.onAnswer = (sd, offerId, midToTrackId) => __awaiter(this, void 0, void 0, function* () { if (!this.pcManager) { return; } this.log.debug('received server answer', { RTCSdpType: sd.type, sdp: sd.sdp, midToTrackId }); this.midToTrackId = midToTrackId; yield this.pcManager.setPublisherAnswer(sd, offerId); }); // add candidate on trickle this.client.onTrickle = (candidate, target) => { if (!this.pcManager) { return; } this.log.debug('got ICE candidate from peer', { candidate, target }); this.pcManager.addIceCandidate(candidate, target); }; // when server creates an offer for the client this.client.onOffer = (sd, offerId, midToTrackId) => __awaiter(this, void 0, void 0, function* () { this.latestRemoteOfferId = offerId; if (!this.pcManager) { return; } this.midToTrackId = midToTrackId; const answer = yield this.pcManager.createSubscriberAnswerFromOffer(sd, offerId); if (answer) { this.client.sendAnswer(answer, offerId); } }); this.client.onLocalTrackPublished = res => { var _a; this.log.debug('received trackPublishedResponse', { cid: res.cid, track: (_a = res.track) === null || _a === void 0 ? void 0 : _a.sid }); if (!this.pendingTrackResolvers[res.cid]) { this.log.error("missing track resolver for ".concat(res.cid), { cid: res.cid }); return; } const resolve = this.pendingTrackResolvers[res.cid].resolve; delete this.pendingTrackResolvers[res.cid]; resolve(res.track); }; this.client.onLocalTrackUnpublished = response => { this.emit(EngineEvent.LocalTrackUnpublished, response); }; this.client.onLocalTrackSubscribed = trackSid => { this.emit(EngineEvent.LocalTrackSubscribed, trackSid); }; this.client.onTokenRefresh = token => { this.token = token; this.emit(EngineEvent.TokenRefreshed, token); }; this.client.onRemoteMuteChanged = (trackSid, muted) => { this.emit(EngineEvent.RemoteMute, trackSid, muted); }; this.client.onSubscribedQualityUpdate = update => { this.emit(EngineEvent.SubscribedQualityUpdate, update); }; this.client.onRoomMoved = res => { var _a; this.participantSid = (_a = res.participant) === null || _a === void 0 ? void 0 : _a.sid; if (this.latestJoinResponse) { this.latestJoinResponse.room = res.room; } this.emit(EngineEvent.RoomMoved, res); }; this.client.onMediaSectionsRequirement = requirement => { this.addMediaSections(requirement.numAudios, requirement.numVideos); this.negotiate(); }; this.client.onPublishDataTrackResponse = event => { this.emit(EngineEvent.PublishDataTrackResponse, event); }; this.client.onUnPublishDataTrackResponse = event => { this.emit(EngineEvent.UnPublishDataTrackResponse, event); }; this.client.onDataTrackSubscriberHandles = event => { this.emit(EngineEvent.DataTrackSubscriberHandles, event); }; this.client.onClose = () => { this.handleDisconnect('signal', ReconnectReason.RR_SIGNAL_DISCONNECTED); }; this.client.onLeave = leave => { this.log.info("client leave request received (action=".concat(leave === null || leave === void 0 ? void 0 : leave.action, ")"), { reason: leave === null || leave === void 0 ? void 0 : leave.reason }); if (leave.regions) { this.log.debug('updating regions'); this.emit(EngineEvent.ServerRegionsReported, leave.regions); } switch (leave.action) { case LeaveRequest_Action.DISCONNECT: this.emit(EngineEvent.Disconnected, leave === null || leave === void 0 ? void 0 : leave.reason); this.close(); break; case LeaveRequest_Action.RECONNECT: this.fullReconnectOnNext = true; // reconnect immediately instead of waiting for next attempt this.handleDisconnect(leaveReconnect); break; case LeaveRequest_Action.RESUME: // reconnect immediately instead of waiting for next attempt this.handleDisconnect(leaveReconnect); } }; } makeRTCConfiguration(serverResponse) { var _a; const rtcConfig = Object.assign({}, this.rtcConfig); // E2EE and packet trailer extraction both rely on encoded frame transforms. // Only opt into the createEncodedStreams flavor when that path will be // used; RTCRtpScriptTransform does not need the PeerConnection flag. const needsInsertableStreams = ((_a = this.signalOpts) === null || _a === void 0 ? void 0 : _a.e2eeEnabled) || this.frameMetadataWorker && !shouldUseFrameMetadataScriptTransform(); if (needsInsertableStreams && isInsertableStreamSupported()) { this.log.debug('E2EE - setting up transports with insertable streams'); // this makes sure that no data is sent before the transforms are ready // @ts-ignore rtcConfig.encodedInsertableStreams = true; } // @ts-ignore rtcConfig.sdpSemantics = 'unified-plan'; // @ts-ignore rtcConfig.continualGatheringPolicy = 'gather_continually'; if (!serverResponse) { return rtcConfig; } // update ICE servers before creating PeerConnection if (serverResponse.iceServers && !rtcConfig.iceServers) { const rtcIceServers = []; serverResponse.iceServers.forEach(iceServer => { const rtcIceServer = { urls: iceServer.urls }; if (iceServer.username) rtcIceServer.username = iceServer.username; if (iceServer.credential) { rtcIceServer.credential = iceServer.credential; } rtcIceServers.push(rtcIceServer); }); rtcConfig.iceServers = rtcIceServers; } if (serverResponse.clientConfiguration && serverResponse.clientConfiguration.forceRelay === ClientConfigSetting.ENABLED) { rtcConfig.iceTransportPolicy = 'relay'; } return rtcConfig; } /** * Populate the publisher PC so its first offer carries the data channels + recvonly media * sections. Required for every V1 connection: Firefox only binds receive decoders for media * present in that first offer, and the offer-with-join path needs the sections to * build a meaningful initial offer. Must be called on a configured pcManager. */ applyInitialPublisherLayout() { this.createDataChannels(); /** * Native libwebrtc does not support pre-populating the media sections, * so we skip it for React Native. * * Related: https://github.com/livekit/rust-sdks/pull/1151 */ if (!isReactNative()) { this.addMediaSections(initialMediaSectionsAudio, initialMediaSectionsVideo); } } addMediaSections(numAudios, numVideos) { var _a, _b; const transceiverInit = { direction: 'recvonly' }; for (let i = 0; i < numAudios; i++) { (_a = this.pcManager) === null || _a === void 0 ? void 0 : _a.addPublisherTransceiverOfKind('audio', transceiverInit); } for (let i = 0; i < numVideos; i++) { (_b = this.pcManager) === null || _b === void 0 ? void 0 : _b.addPublisherTransceiverOfKind('video', transceiverInit); } } createDataChannels() { if (!this.pcManager) { return; } // clear old data channel callbacks if recreate if (this.lossyDC) { this.lossyDC.onmessage = null; this.lossyDC.onerror = null; this.lossyDC.onclose = null; } if (this.reliableDC) { this.reliableDC.onmessage = null; this.reliableDC.onerror = null; this.reliableDC.onclose = null; } if (this.dataTrackDC) { this.dataTrackDC.onmessage = null; this.dataTrackDC.onerror = null; this.dataTrackDC.onclose = null; } // create data channels this.lossyDC = this.pcManager.createPublisherDataChannel(lossyDataChannel, { ordered: false, maxRetransmits: 0 }); this.reliableDC = this.pcManager.createPublisherDataChannel(reliableDataChannel, { ordered: true }); this.dataTrackDC = this.pcManager.createPublisherDataChannel(dataTrackDataChannel, { ordered: false, maxRetransmits: 0 }); // also handle messages over the pub channel, for backwards compatibility this.lossyDC.onmessage = this.handleDataMessage; this.reliableDC.onmessage = this.handleDataMessage; this.dataTrackDC.onmessage = this.handleDataTrackMessage; // handle datachannel errors this.lossyDC.onerror = this.handleDataError; this.reliableDC.onerror = this.handleDataError; this.dataTrackDC.onerror = this.handleDataError; // detect unexpected publisher data channel closes this.lossyDC.onclose = this.handleDataChannelClose(DataChannelKind.LOSSY); this.reliableDC.onclose = this.handleDataChannelClose(DataChannelKind.RELIABLE); this.dataTrackDC.onclose = this.handleDataChannelClose(DataChannelKind.DATA_TRACK_LOSSY); // set up dc buffer threshold, set to 64kB (otherwise 0 by default) this.lossyDC.bufferedAmountLowThreshold = 65535; this.reliableDC.bufferedAmountLowThreshold = 65535; this.dataTrackDC.bufferedAmountLowThreshold = 65535; // handle buffer amount low events this.lossyDC.onbufferedamountlow = () => this.handleBufferedAmountLow(DataChannelKind.LOSSY); this.reliableDC.onbufferedamountlow = () => this.handleBufferedAmountLow(DataChannelKind.RELIABLE); this.dataTrackDC.onbufferedamountlow = () => this.handleBufferedAmountLow(DataChannelKind.DATA_TRACK_LOSSY); this.cleanupLossyDataStats(); this.lossyDataStatInterval = setInterval(() => { this.lossyDataStatByterate = this.lossyDataStatCurrentBytes; this.lossyDataStatCurrentBytes = 0; const dc = this.dataChannelForKind(DataChannelKind.LOSSY); if (dc) { // control buffered latency to ~100ms const threshold = this.lossyDataStatByterate / 10; dc.bufferedAmountLowThreshold = Math.min(Math.max(threshold, lossyDataChannelBufferThresholdMin), lossyDataChannelBufferThresholdMax); } }, 1000); } createSender(track, opts, encodings) { return __awaiter(this, void 0, void 0, function* () { let sender; if (supportsTransceiver()) { sender = yield this.createTransceiverRTCRtpSender(track, opts, encodings); } else if (supportsAddTrack()) { this.log.warn('using add-track fallback'); sender = yield this.createRTCRtpSender(track.mediaStreamTrack); } else { throw new UnexpectedConnectionState('Required webRTC APIs not supported on this device'); } this.setupFrameMetadataSender(sender, opts); return sender; }); } createSimulcastSender(track, simulcastTrack, opts, encodings) { return __awaiter(this, void 0, void 0, function* () { let sender; if (supportsTransceiver()) { sender = yield this.createSimulcastTransceiverSender(track, simulcastTrack, opts, encodings); } else if (supportsAddTrack()) { this.log.debug('using add-track fallback'); sender = yield this.createRTCRtpSender(track.mediaStreamTrack); } else { throw new UnexpectedConnectionState('Cannot stream on this device'); } if (sender) { this.setupFrameMetadataSender(sender, opts); } return sender; }); } get frameMetadataWorker() { var _a, _b; return (_b = (_a = this.options.frameMetadata) !== null && _a !== void 0 ? _a : this.options.packetTrailer) === null || _b === void 0 ? void 0 : _b.worker; } setupFrameMetadataSender(sender) { let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var _a, _b, _c; const worker = this.frameMetadataWorker; if (!worker || ((_a = this.signalOpts) === null || _a === void 0 ? void 0 : _a.e2eeEnabled)) { return; } const frameMetadata = (_b = opts.frameMetadata) !== null && _b !== void 0 ? _b : opts.packetTrailer; const hasMetadata = hasFrameMetadataPublishOptions(frameMetadata); if (shouldUseFrameMetadataScriptTransform()) { if (hasMetadata) { // @ts-ignore sender.transform = new RTCRtpScriptTransform(worker, { kind: 'encode', packetTrailer: frameMetadata }); } return; } if (!isFrameMetadataSupported((_c = this.options.frameMetadata) !== null && _c !== void 0 ? _c : this.options.packetTrailer) || !('createEncodedStreams' in sender)) { if (hasMetadata) { this.log.warn('frame metadata transform not supported; skipping write', this.logContext); } return; } // @ts-ignore const _sender$createEncoded = sender.createEncodedStreams(), readable = _sender$createEncoded.readable, writable = _sender$createEncoded.writable; if (hasMetadata) { worker.postMessage({ kind: 'encode', data: { readableStream: readable, writableStream: writable, packetTrailer: frameMetadata } }, [readable, writable]); } else { readable.pipeTo(writable); } } createTransceiverRTCRtpSender(track, opts, encodings) { return __awaiter(this, void 0, void 0, function* () { if (!this.pcManager) { throw new UnexpectedConnectionState('publisher is closed'); } const streams = []; if (track.mediaStream) { streams.push(track.mediaStream); } if (isVideoTrack(track)) { track.codec = opts.videoCodec; } const transceiverInit = { direction: 'sendonly', streams }; if (encodings) { transceiverInit.sendEncodings = encodings; } // addTransceiver for react-native is async. web is synchronous, but await won't effect it. const transceiver = yield this.pcManager.addPublisherTransceiver(track.mediaStreamTrack, transceiverInit); return transceiver.sender; }); } createSimulcastTransceiverSender(track, simulcastTrack, opts, encodings) { return __awaiter(this, void 0, void 0, function* () { if (!this.pcManager) { throw new UnexpectedConnectionState('publisher is closed'); } const transceiverInit = { direction: 'sendonly' }; if (encodings) { transceiverInit.sendEncodings = encodings; } // addTransceiver for react-native is async. web is synchronous, but await won't effect it. const transceiver = yield this.pcManager.addPublisherTransceiver(simulcastTrack.mediaStreamTrack, transceiverInit); if (!opts.videoCodec) { return; } track.setSimulcastTrackSender(opts.videoCodec, transceiver.sender); return transceiver.sender; }); } createRTCRtpSender(track) { return __awaiter(this, void 0, void 0, function* () { if (!this.pcManager) { throw new UnexpectedConnectionState('publisher is closed'); } return this.pcManager.addPublisherTrack(track); }); } attemptReconnect(reason) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; if (this._isClosed) { return; } // guard for attempting reconnection multiple times while one attempt is still not finished if (this.attemptingReconnect) { this.log.warn('already attempting reconnect, returning early'); return; } if (((_a = this.clientConfiguration) === null || _a === void 0 ? void 0 : _a.resumeConnection) === ClientConfigSetting.DISABLED || // signaling state could change to closed due to hardware sleep // those connections cannot be resumed ((_c = (_b = this.pcManager) === null || _b === void 0 ? void 0 : _b.currentState) !== null && _c !== void 0 ? _c : PCTransportState.NEW) === PCTransportState.NEW) { this.fullReconnectOnNext = true; } try { this.attemptingReconnect = true; if (this.fullReconnectOnNext) { yield this.restartConnection(); } else { yield this.resumeConnection(reason); } this.clearPendingReconnect(); this.fullReconnectOnNext = false; } catch (e) { this.reconnectAttempts += 1; let recoverable = true; if (e instanceof UnexpectedConnectionState) { this.log.debug('received unrecoverable error', { error: e }); // unrecoverable recoverable = false; } else if (!(e instanceof SignalReconnectError)) { // cannot resume this.fullReconnectOnNext = true; } if (recoverable) { this.handleDisconnect('reconnect', ReconnectReason.RR_UNKNOWN); } else { this.log.info("could not recover connection after ".concat(this.reconnectAttempts, " attempts, ").concat(Date.now() - this.reconnectStart, "ms. giving up")); this.emit(EngineEvent.Disconnected); yield this.close(); } } finally { this.attemptingReconnect = false; } }); } getNextRetryDelay(context) { try { return this.reconnectPolicy.nextRetryDelayInMs(context); } catch (e) { this.log.warn('encountered error in reconnect policy', { error: e }); } // error in user code with provided reconnect policy, stop reconnecting return null; } restartConnection(regionUrl) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; try { if (!this.url || !this.token) { // permanent failure, don't attempt reconnection throw new UnexpectedConnectionState('could not reconnect, url or token not saved'); } this.log.info("reconnecting, attempt: ".concat(this.reconnectAttempts)); this.emit(EngineEvent.Restarting); if (!this.client.isDisconnected) { yield this.client.sendLeave(); } yield this.cleanupPeerConnections(); yield this.cleanupClient(); let joinResponse; try { if (!this.signalOpts) { this.log.warn('attempted connection restart, without signal options present'); throw new SignalReconnectError(); } // in case a regionUrl is passed, the region URL takes precedence joinResponse = (yield this.join(regionUrl !== null && regionUrl !== void 0 ? regionUrl : this.url, this.token, this.signalOpts, undefined, !this.options.singlePeerConnection)).joinResponse; } catch (e) { if (e instanceof ConnectionError && e.reason === ConnectionErrorReason.NotAllowed) { throw new UnexpectedConnectionState('could not reconnect, token might be expired'); } throw new SignalReconnectError(); } if (this.shouldFailNext) { this.shouldFailNext = false; throw new Error('simulated failure'); } this.client.setReconnected(); this.emit(EngineEvent.SignalRestarted, joinResponse); yield this.waitForPCReconnected(); // re-check signal connection state before setting engine as resumed if (this.client.currentState !== SignalConnectionState.CONNECTED) { throw new SignalReconnectError('Signal connection got severed during reconnect'); } (_a = this.regionStrategy) === null || _a === void 0 ? void 0 : _a.resetAttempts(); // reconnect success this.emit(EngineEvent.Restarted); } catch (error) { const nextRegionUrl = yield (_b = this.regionStrategy) === null || _b === void 0 ? void 0 : _b.getNextUrl(); if (nextRegionUrl) { yield this.restartConnection(nextRegionUrl); return; } else { // no more regions to try (or we're not on cloud) (_c = this.regionStrategy) === null || _c === void 0 ? void 0 : _c.resetAttempts(); throw error; } } }); } resumeConnection(reason) { return __awaiter(this, void 0, void 0, function* () { var _a; if (!this.url || !this.token) { // permanent failure, don't attempt reconnection throw new UnexpectedConnectionState('could not reconnect, url or token not saved'); } // trigger publisher reconnect if (!this.pcManager) { throw new UnexpectedConnectionState('publisher and subscriber connections unset'); } this.log.info("resuming signal connection, attempt ".concat(this.reconnectAttempts)); this.emit(EngineEvent.Resuming); let res; try { this.setupSignalClientCallbacks(); res = yield this.client.reconnect(this.url, this.token, this.participantSid, reason); } catch (error) { let message = ''; if (error instanceof Error) { message = error.message; this.log.error(error.message, { error }); } if (error instanceof ConnectionError && error.reason === ConnectionErrorReason.NotAllowed) { throw new UnexpectedConnectionState('could not reconnect, token might be expired'); } if (error instanceof ConnectionError && error.reason === ConnectionErrorReason.LeaveRequest) { throw error; } throw new SignalReconnectError(message); } this.emit(EngineEvent.SignalResumed); if (res) { const rtcConfig = this.makeRTCConfiguration(res); this.pcManager.updateConfiguration(rtcConfig); if (this.latestJoinResponse) { this.latestJoinResponse.serverInfo = res.serverInfo; } } else { this.log.warn('Did not receive reconnect response'); } if (this.shouldFailNext) { this.shouldFailNext = false; throw new Error('simulated failure'); } yield this.pcManager.triggerIceRestart(); yield this.waitForPCReconnected(); // re-check signal connection state before setting engine as resumed if (this.client.currentState !== SignalConnectionState.CONNECTED) { throw new SignalReconnectError('Signal connection got severed during reconnect'); } this.client.setReconnected(); // recreate publish datachannel if it's id is null // (for safari https://bugs.webkit.org/show_bug.cgi?id=184688) if (((_a = this.reliableDC) === null || _a === void 0 ? void 0 : _a.readyState) === 'open' && this.reliableDC.id === null) { this.createDataChannels(); } if (res === null || res === void 0 ? void 0 : res.lastMessageSeq) { this.resendReliableMessagesForResume(res.lastMessageSeq); } // resume success this.emit(EngineEvent.Resumed); }); } waitForPCInitialConnection(timeout, abortController) { return __awaiter(this, void 0, void 0, function* () { if (!this.pcManager) { throw new UnexpectedConnectionState('PC manager is closed'); } yield this.pcManager.ensurePCTransportConnection(abortController, timeout); }); } waitForPCReconnected() { return __awaiter(this, void 0, void 0, function* () { this.pcState = PCState.Reconnecting; this.log.debug('waiting for peer connection to reconnect'); try { yield sleep(minReconnectWait); // FIXME setTimeout again not ideal for a connection critical path if (!this.pcManager) { throw new UnexpectedConnectionState('PC manager is closed'); } yield this.pcManager.ensurePCTransportConnection(undefined, this.peerConnectionTimeout); this.pcState = PCState.Connected; } catch (e) { // TODO do we need a `failed` state here for the PC? this.pcState = PCState.Disconnected; throw ConnectionError.internal("could not establish PC connection, ".concat(e.message)); } }); } /** @internal */ publishRpcAck(destinationIdentity, requestId) { return __awaiter(this, void 0, void 0, function* () { const packet = new DataPacket({ destinationIdentities: [destinationIdentity], kind: DataPacket_Kind.RELIABLE, value: { case: 'rpcAck', value: new RpcAck({ requestId }) } }); yield this.sendDataPacket(packet, DataChannelKind.RELIABLE); }); } /* @internal */ sendDataPacket(packet, kind) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; // make sure we do have a data connection yield this.ensurePublisherConnected(kind); if (this.e2eeManager && this.e2eeManager.isDataChannelEncryptionEnabled) { const encryptablePacket = asEncryptablePacket(packet); if (encryptablePacket) { const encryptedData = yield this.e2eeManager.encryptData(encryptablePacket.toBinary()); packet.value = { case: 'encryptedPacket', value: new EncryptedPacket({ encryptedValue: encryptedData.payload, iv: encryptedData.iv, keyIndex: encryptedData.keyIndex }) }; } } if (kind === DataChannelKind.RELIABLE) { packet.sequence = this.reliableDataSequence; this.reliableDataSequence += 1; } const msg = packet.toBinary(); // Clamp to the SDK default - libwebrtc advertises larger (~256 KiB) // than LiveKit/pion can deliver end-to-end (~64 KiB), so we trust // the answer up untilthe built in ceiling. const maxPublisherMessageSizeBytes = Math.min((_b = (_a = this.pcManager) === null || _a === void 0 ? void 0 : _a.getMaxPublisherMessageSize()) !== null && _b !== void 0 ? _b : DEFAULT_MAX_MESSAGE_SIZE, DEFAULT_MAX_MESSAGE_SIZE); if (typeof maxPublisherMessageSizeBytes !== 'undefined' && maxPublisherMessageSizeBytes !== 0 /* 0 means "no limit" */ && msg.byteLength > maxPublisherMessageSizeBytes) { throw new PublishDataError("cannot publish data packet larger than ".concat(maxPublisherMessageSizeBytes, " bytes (got ").concat(msg.byteLength, ")")); } switch (kind) { case DataChannelKind.LOSSY: case DataChannelKind.DATA_TRACK_LOSSY: return this.sendLossyBytes(msg, kind); case DataChannelKind.RELIABLE: const dc = this.dataChannelForKind(kind); if (dc) { yield this.waitForBufferStatusLow(kind); this.reliableMessageBuffer.push({ data: msg, sequence: packet.sequence }); if (this.attemptingReconnect) { return; } dc.send(msg); } this.updateAndEmitDCBufferStatus(kind); break; } }); } /* @internal */ sendLossyBytes(bytes_1, kind_1) { return __awaiter(this, arguments, void 0, function (bytes, kind) { var _this3 = this; let bufferStatusLowBehavior = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'drop'; return function* () { // make sure we do have a data connection yield _this3.ensurePublisherConnected(kind); const dc = _this3.dataChannelForKind(kind); if (dc) { if (!_this3.isBufferStatusLow(kind)) { // Depending on the exact circumstance that data is being sent, either drop or wait for the // buffer status to not be low before continuing. switch (bufferStatusLowBehavior) { case 'wait': yield _this3.waitForBufferStatusLow(kind); break; case 'drop': // this.log.warn(`dropping lossy data channel message`, this.logContext); // Drop messages to reduce latency _this3.lossyDataDropCount += 1; if (_this3.lossyDataDropCount % 100 === 0) { _this3.log.warn("dropping lossy data channel messages, total dropped: ".concat(_this3.lossyDataDropCount)); } return; } } _this3.lossyDataStatCurrentBytes += bytes.byteLength; if (_this3.attemptingReconnect) { return; } dc.send(bytes); } _this3.updateAndEmitDCBufferStatus(kind); }(); }); } resendReliableMessagesForResume(lastMessageSeq) { return __awaiter(this, void 0, void 0, function* () { yield this.ensurePublisherConnected(DataChannelKind.RELIABLE); const dc = this.dataChannelForKind(DataChannelKind.RELIABLE); if (dc) { this.reliableMessageBuffer.popToSequence(lastMessageSeq); this.reliableMessageBuffer.getAll().forEach(msg => { dc.send(msg.data); }); } this.updateAndEmitDCBufferStatus(DataChannelKind.RELIABLE); }); } waitForBufferStatusLow(kind) { return __awaiter(this, void 0, void 0, function* () { return new TypedPromise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { if (this.isClosed) { reject(new UnexpectedConnectionState('engine closed')); } if (this.isBufferStatusLow(kind)) { resolve(); } else { const dc = this.dataChannelForKind(kind); if (!dc) { reject(new UnexpectedConnectionState("DataChannel not found, kind: ".concat(kind))); return; } this.bufferStatusLowClosingFuture.promise.catch(e => reject(e)); dc.addEventListener('bufferedamountlow', () => resolve(), { once: true }); } })); }); } /** * @internal */ ensureDataTransportConnected(kind_1) { return __awaiter(this, arguments, void 0, function (kind) { var _this4 = this; let subscriber = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.subscriberPrimary; return function* () { var _a; if (!_this4.pcManager) { throw new UnexpectedConnectionState('PC manager is closed'); } const transport = subscriber ? _this4.pcManager.subscriber : _this4.pcManager.publisher; const transportName = subscriber ? 'Subscriber' : 'Publisher'; if (!transport) { throw ConnectionError.internal("".concat(transportName, " connection not set")); } let needNegotiation = false; if (!subscriber && !_this4.dataChannelForKind(kind, subscriber)) { _this4.createDataChannels(); needNegotiation = true; } if (!needNegotiation && !subscriber && !_this4.pcManager.publisher.isICEConnected && _this4.pcManager.publisher.getICEConnectionState() !== 'checking') { needNegotiation = true; } if (needNegotiation) { // start negotiation _this4.negotiate().catch(err => { _this4.log.error(err); }); } const targetChannel = _this4.dataChannelForKind(kind, subscriber); if ((targetChannel === null || targetChannel === void 0 ? void 0 : targetChannel.readyState) === 'open') { return; } // wait until ICE connected const endTime = new Date().getTime() + _this4.peerConnectionTimeout; while (new Date().getTime() < endTime) { if (transport.isICEConnected && ((_a = _this4.dataChannelForKind(kind, subscriber)) === null || _a === void 0 ? void 0 : _a.readyState) === 'open') { return; } yield sleep(50); } throw ConnectionError.internal("could not establish ".concat(transportName, " connection, state: ").concat(transport.getICEConnectionState())); }(); }); } ensurePublisherConnected(kind) { return __awaiter(this, void 0, void 0, function* () { if (!this.publisherConnectionPromise) { this.publisherConnectionPromise = this.ensureDataTransportConnected(kind, false); } yield this.publisherConnectionPromise; }); } /* @internal */ verifyTransport() { if (!this.pcManager) { return false; } const allowedConnectionStates = [PCTransportState.CONNECTING, PCTransportState.CONNECTED]; if (!allowedConnectionStates.includes(this.pcManager.currentState)) { return false; } // ensure signal is connected if (!this.client.ws || this.client.ws.readyState === WebSocket.CLOSED) { return false; } return true; } /** @internal */ negotiate() { return __awaiter(this, void 0, void 0, function* () { // observe signal state return new TypedPromise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { if (!this.pcManager) { reject(new NegotiationError('PC manager is closed')); return; } this.pcManager.requirePublisher(); // don't negotiate without any transceivers or data channel, it will generate sdp without ice frag then negotiate failed if (this.pcManager.publisher.getTransceivers().length == 0 && !this.lossyDC && !this.reliableDC && !this.dataTrackDC) { this.createDataChannels(); } const abortController = new AbortController(); const handleClosed = () => { abortController.abort(); this.log.debug('engine disconnected while negotiation was ongoing'); resolve(); return; }; if (this.isClosed) { reject(new NegotiationError('cannot negotiate on closed engine')); } this.on(EngineEvent.Closing, handleClosed); this.on(EngineEvent.Restarting, handleClosed); this.pcManager.publisher.off(PCEvents.RTPVideoPayloadTypes, this.onRtpMapAvailable); this.pcManager.publisher.once(PCEvents.RTPVideoPayloadTypes, this.onRtpMapAvailable); try { yield this.pcManager.negotiate(abortController); resolve(); } catch (e) { if (abortController.signal.aborted) { // negotiation was aborted due to engine close or restart, resolve // cleanly to avoid triggering a cascading reconnect loop resolve(); return; } if (e instanceof NegotiationError) { this.fullReconnectOnNext = true; } this.handleDisconnect('negotiation', ReconnectReason.RR_UNKNOWN); if (e instanceof Error) { reject(e); } else { reject(new Error(String(e))); } } finally { this.off(EngineEvent.Closing, handleClosed); this.off(EngineEvent.Restarting, handleClosed); } })); }); } dataChannelForKind(kind, sub) { switch (kind) { case DataChannelKind.RELIABLE: if (!sub) { return this.reliableDC; } else { return this.reliableDCSub; } case DataChannelKind.LOSSY: if (!sub) { return this.lossyDC; } else { return this.lossyDCSub; } case DataChannelKind.DATA_TRACK_LOSSY: if (!sub) { return this.dataTrackDC; } else { return this.dataTrackDCSub; } } } /** @internal */ sendSyncState(remoteTracks, localTracks, localDataTrackInfos) { var _a, _b, _c, _d; if (!this.pcManager) { this.log.warn('sync state cannot be sent without peer connection setup'); return; } const previousPublisherOffer = this.pcManager.publisher.getLocalDescription(); const previousPublisherAnswer = this.pcManager.publisher.getRemoteDescription(); const previousSubscriberOffer = (_a = this.pcManager.subscriber) === null || _a === void 0 ? void 0 : _a.getRemoteDescription(); const previousSubscriberAnswer = (_b = this.pcManager.subscriber) === null || _b === void 0 ? void 0 : _b.getLocalDescription(); /* 1. autosubscribe on, so subscribed tracks = all tracks - unsub tracks, in this case, we send unsub tracks, so server add all tracks to this subscribe pc and unsub special tracks from it. 2. autosubscribe off, we send subscribed tracks. */ const autoSubscribe = (_d = (_c = this.signalOpts) === null || _c === void 0 ? void 0 : _c.autoSubscribe) !== null && _d !== void 0 ? _d : true; const trackSids = new Array(); const trackSidsDisabled = new Array(); remoteTracks.forEach(track => { if (track.isDesired !== autoSubscribe) { trackSids.push(track.trackSid); } if (!track.isEnabled) { trackSidsDisabled.push(track.trackSid); } }); this.client.sendSyncState(new SyncState({ answer: this.pcManager.mode === 'publisher-only' ? previousPublisherAnswer ? toProtoSessionDescription({ sdp: previousPublisherAnswer.sdp, type: previousPublisherAnswer.type }) : undefined : previousSubscriberAnswer ? toProtoSessionDescription({ sdp: previousSubscriberAnswer.sdp, type: previousSubscriberAnswer.type }) : undefined, offer: this.pcManager.mode === 'publisher-only' ? previousPublisherOffer ? toProtoSessionDescription({ sdp: previousPublisherOffer.sdp, type: previousPublisherOffer.type }) : undefined : previousSubscriberOffer ? toProtoSessionDescription({ sdp: previousSubscriberOffer.sdp, type: previousSubscriberOffer.type }) : undefined, subscription: new UpdateSubscription({ trackSids, subscribe: !autoSubscribe, participantTracks: [] }), publishTracks: getTrackPublicationInfo(localTracks), dataChannels: this.dataChannelsInfo(), trackSidsDisabled, datachannelReceiveStates: this.reliableReceivedState.map((seq, sid) => { return new DataChannelReceiveState({ publisherSid: sid, lastSeq: seq }); }), publishDataTracks: localDataTrackInfos.map(info => { return new PublishDataTrackResponse({ info: DataTrackInfo.toProtobuf(info) }); }) })); } /* @internal */ failNext() { // debugging method to fail the next reconnect/resume attempt this.shouldFailNext = true; } /* @internal */ failNextV1Path() { // debugging method to fail the next connection attempt for /rtc/v1 to trigger the fallback version this.shouldFailOnV1Path = true; } dataChannelsInfo() { const infos = []; const getInfo = (dc, target) => { if ((dc === null || dc === void 0 ? void 0 : dc.id) !== undefined && dc.id !== null) { infos.push(new DataChannelInfo({ label: dc.label, id: dc.id, target })); } }; getInfo(this.dataChannelForKind(DataChannelKind.LOSSY), SignalTarget.PUBLISHER); getInfo(this.dataChannelForKind(DataChannelKind.RELIABLE), SignalTarget.PUBLISHER); getInfo(this.dataChannelForKind(DataChannelKind.LOSSY, true), SignalTarget.SUBSCRIBER); getInfo(this.dataChannelForKind(DataChannelKind.RELIABLE, true), SignalTarget.SUBSCRIBER); return infos; } clearReconnectTimeout() { if (this.reconnectTimeout) { CriticalTimers.clearTimeout(this.reconnectTimeout); } } clearPendingReconnect() { this.clearReconnectTimeout(); this.reconnectAttempts = 0; } registerOnLineListener() { if (isWeb()) { window.addEventListener('online', this.handleBrowserOnLine); window.addEventListener('offline', this.handleBrowserOffline); } } deregisterOnLineListener() { if (isWeb()) { window.removeEventListener('online', this.handleBrowserOnLine); window.removeEventListener('offline', this.handleBrowserOffline); } } getTrackIdForReceiver(receiver) { var _a; const mid = (_a = this.pcManager) === null || _a === void 0 ? void 0 : _a.getMidForReceiver(receiver); if (mid) { const match = Object.entries(this.midToTrackId).find(_ref4 => { let _ref5 = _slicedToArray(_ref4, 1), key = _ref5[0]; return key === mid; }); if (match) { return match[1]; } } } } function applyUserDataCompat(newObj, oldObj) { const participantIdentity = newObj.participantIdentity ? newObj.participantIdentity : oldObj.participantIdentity; newObj.participantIdentity = participantIdentity; oldObj.participantIdentity = participantIdentity; const destinationIdentities = newObj.destinationIdentities.length !== 0 ? newObj.destinationIdentities : oldObj.destinationIdentities; newObj.destinationIdentities = destinationIdentities; oldObj.destinationIdentities = destinationIdentities; }const DEFAULT_MAX_AGE_MS = 5000; const STOP_REFETCH_DELAY_MS = 30000; class RegionUrlProvider { static fetchRegionSettings(serverUrl, token, signal) { return __awaiter(this, void 0, void 0, function* () { const unlock = yield RegionUrlProvider.fetchLock.lock(); try { const regionSettingsResponse = yield fetch("".concat(getCloudConfigUrl(serverUrl), "/regions"), { headers: { authorization: "Bearer ".concat(token) }, signal }); if (regionSettingsResponse.ok) { const maxAge = extractMaxAgeFromRequestHeaders(regionSettingsResponse.headers); const maxAgeInMs = maxAge ? maxAge * 1000 : DEFAULT_MAX_AGE_MS; const regionSettings = yield regionSettingsResponse.json(); return { regionSettings, updatedAtInMs: Date.now(), maxAgeInMs }; } else { if (regionSettingsResponse.status === 401) { throw ConnectionError.notAllowed("Could not fetch region settings: ".concat(regionSettingsResponse.statusText), regionSettingsResponse.status); } else { throw ConnectionError.internal("Could not fetch region settings: ".concat(regionSettingsResponse.statusText)); } } } catch (e) { if (e instanceof ConnectionError) { // rethrow connection errors throw e; } else if (signal === null || signal === void 0 ? void 0 : signal.aborted) { throw ConnectionError.cancelled("Region fetching was aborted"); } else { // wrap other errors as connection errors throw ConnectionError.serverUnreachable("Could not fetch region settings, ".concat(e instanceof Error ? "".concat(e.name, ": ").concat(e.message) : e)); } } finally { unlock(); } }); } static scheduleRefetch(url, token, maxAgeInMs) { return __awaiter(this, void 0, void 0, function* () { const timeout = RegionUrlProvider.settingsTimeouts.get(url.hostname); clearTimeout(timeout); RegionUrlProvider.settingsTimeouts.set(url.hostname, setTimeout(() => __awaiter(this, void 0, void 0, function* () { try { const newSettings = yield RegionUrlProvider.fetchRegionSettings(url, token); RegionUrlProvider.updateCachedRegionSettings(url, token, newSettings); } catch (error) { if (error instanceof ConnectionError && error.reason === ConnectionErrorReason.NotAllowed) { livekitLogger.debug('token is not valid, cancelling auto region refresh'); return; } livekitLogger.debug('auto refetching of region settings failed', { error }); // continue retrying with the same max age RegionUrlProvider.scheduleRefetch(url, token, maxAgeInMs); } }), maxAgeInMs)); }); } static updateCachedRegionSettings(url, token, settings) { RegionUrlProvider.cache.set(url.hostname, settings); RegionUrlProvider.scheduleRefetch(url, token, settings.maxAgeInMs); } static stopRefetch(hostname) { const timeout = RegionUrlProvider.settingsTimeouts.get(hostname); if (timeout) { clearTimeout(timeout); RegionUrlProvider.settingsTimeouts.delete(hostname); } } static scheduleCleanup(hostname) { let tracker = RegionUrlProvider.connectionTrackers.get(hostname); if (!tracker) { return; } // Cancel any existing cleanup timeout if (tracker.cleanupTimeout) { clearTimeout(tracker.cleanupTimeout); } // Schedule cleanup to stop refetch after delay tracker.cleanupTimeout = setTimeout(() => { const currentTracker = RegionUrlProvider.connectionTrackers.get(hostname); if (currentTracker && currentTracker.connectionCount === 0) { livekitLogger.debug('stopping region refetch after disconnect delay', { hostname }); RegionUrlProvider.stopRefetch(hostname); } if (currentTracker) { currentTracker.cleanupTimeout = undefined; } }, STOP_REFETCH_DELAY_MS); } static cancelCleanup(hostname) { const tracker = RegionUrlProvider.connectionTrackers.get(hostname); if (tracker === null || tracker === void 0 ? void 0 : tracker.cleanupTimeout) { clearTimeout(tracker.cleanupTimeout); tracker.cleanupTimeout = undefined; } } notifyConnected() { const hostname = this.serverUrl.hostname; let tracker = RegionUrlProvider.connectionTrackers.get(hostname); if (!tracker) { tracker = { connectionCount: 0 }; RegionUrlProvider.connectionTrackers.set(hostname, tracker); } tracker.connectionCount++; // Cancel any scheduled cleanup since we have an active connection RegionUrlProvider.cancelCleanup(hostname); } notifyDisconnected() { const hostname = this.serverUrl.hostname; const tracker = RegionUrlProvider.connectionTrackers.get(hostname); if (!tracker) { return; } tracker.connectionCount = Math.max(0, tracker.connectionCount - 1); // If no more connections, schedule cleanup if (tracker.connectionCount === 0) { RegionUrlProvider.scheduleCleanup(hostname); } } constructor(url, token) { this.attemptedRegions = []; this.serverUrl = new URL(url); this.token = token; } updateToken(token) { var _a; this.token = token; const url = this.getServerUrl(); const settings = RegionUrlProvider.cache.get(url.hostname); RegionUrlProvider.scheduleRefetch(this.serverUrl, this.token, (_a = settings === null || settings === void 0 ? void 0 : settings.maxAgeInMs) !== null && _a !== void 0 ? _a : DEFAULT_MAX_AGE_MS); } isCloud() { return isCloud(this.serverUrl); } getServerUrl() { return this.serverUrl; } /** @internal */ fetchRegionSettings(abortSignal) { return __awaiter(this, void 0, void 0, function* () { return RegionUrlProvider.fetchRegionSettings(this.serverUrl, this.token, abortSignal); }); } getNextBestRegionUrl(abortSignal) { return __awaiter(this, void 0, void 0, function* () { if (!this.isCloud()) { throw Error('region availability is only supported for LiveKit Cloud domains'); } let cachedSettings = RegionUrlProvider.cache.get(this.serverUrl.hostname); if (!cachedSettings || Date.now() - cachedSettings.updatedAtInMs > cachedSettings.maxAgeInMs) { cachedSettings = yield this.fetchRegionSettings(abortSignal); RegionUrlProvider.updateCachedRegionSettings(this.serverUrl, this.token, cachedSettings); } const regionsLeft = cachedSettings.regionSettings.regions.filter(region => !this.attemptedRegions.find(attempted => attempted.url === region.url)); if (regionsLeft.length > 0) { const nextRegion = regionsLeft[0]; this.attemptedRegions.push(nextRegion); livekitLogger.debug("next region: ".concat(nextRegion.region)); return nextRegion.url; } else { return null; } }); } resetAttempts() { this.attemptedRegions = []; } setServerReportedRegions(settings) { RegionUrlProvider.updateCachedRegionSettings(this.serverUrl, this.token, settings); } } RegionUrlProvider.cache = new Map(); RegionUrlProvider.settingsTimeouts = new Map(); RegionUrlProvider.connectionTrackers = new Map(); RegionUrlProvider.fetchLock = new _(); function getCloudConfigUrl(serverUrl) { return "".concat(serverUrl.protocol.replace('ws', 'http'), "//").concat(serverUrl.host, "/settings"); }class BaseStreamReader { get info() { return this._info; } /** @internal */ validateBytesReceived() { let doneReceiving = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; if (typeof this.totalByteSize !== 'number' || this.totalByteSize === 0) { return; } if (doneReceiving && this.bytesReceived < this.totalByteSize) { throw new DataStreamError("Not enough chunk(s) received - expected ".concat(this.totalByteSize, " bytes of data total, only received ").concat(this.bytesReceived, " bytes"), DataStreamErrorReason.Incomplete); } else if (this.bytesReceived > this.totalByteSize) { throw new DataStreamError("Extra chunk(s) received - expected ".concat(this.totalByteSize, " bytes of data total, received ").concat(this.bytesReceived, " bytes"), DataStreamErrorReason.LengthExceeded); } } constructor(info, stream, totalByteSize) { this.reader = stream; this.totalByteSize = totalByteSize; this._info = info; this.bytesReceived = 0; } } class ByteStreamReader extends BaseStreamReader { handleChunkReceived(chunk) { var _a; this.bytesReceived += chunk.content.byteLength; this.validateBytesReceived(); const currentProgress = this.totalByteSize ? this.bytesReceived / this.totalByteSize : undefined; (_a = this.onProgress) === null || _a === void 0 ? void 0 : _a.call(this, currentProgress); } [Symbol.asyncIterator]() { const reader = this.reader.getReader(); // Suppress unhandled rejection on reader.closed — errors are // already propagated through reader.read() to the consumer. reader.closed.catch(() => {}); const cleanup = () => { reader.releaseLock(); this.signal = undefined; }; return { next: () => __awaiter(this, void 0, void 0, function* () { try { const signal = this.signal; if (signal === null || signal === void 0 ? void 0 : signal.aborted) { throw signal.reason; } const result = yield new Promise((resolve, reject) => { if (signal) { const onAbort = () => reject(signal.reason); signal.addEventListener('abort', onAbort, { once: true }); reader.read().then(resolve, reject).finally(() => { signal.removeEventListener('abort', onAbort); }); } else { reader.read().then(resolve, reject); } }); if (result.done) { this.validateBytesReceived(true); return { done: true, value: undefined }; } else { this.handleChunkReceived(result.value); return { done: false, value: result.value.content }; } } catch (err) { cleanup(); throw err; } }), // note: `return` runs only for premature exits, see: // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#errors_during_iteration return() { return __awaiter(this, void 0, void 0, function* () { cleanup(); return { done: true, value: undefined }; }); } }; } /** * Injects an AbortSignal, which if aborted, will terminate the currently active * stream iteration operation. * * Note that when using AbortSignal.timeout(...), the timeout applies across * the whole iteration operation, not just one individual chunk read. */ withAbortSignal(signal) { this.signal = signal; return this; } readAll() { return __awaiter(this, arguments, void 0, function () { var _this = this; let opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; return function* () { var _a, e_1, _b, _c; let chunks = new Set(); const iterator = opts.signal ? _this.withAbortSignal(opts.signal) : _this; try { for (var _d = true, iterator_1 = __asyncValues(iterator), iterator_1_1; iterator_1_1 = yield iterator_1.next(), _a = iterator_1_1.done, !_a; _d = true) { _c = iterator_1_1.value; _d = false; const chunk = _c; chunks.add(chunk); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (!_d && !_a && (_b = iterator_1.return)) yield _b.call(iterator_1); } finally { if (e_1) throw e_1.error; } } return Array.from(chunks); }(); }); } } /** * A class to read chunks from a ReadableStream and provide them in a structured format. */ class TextStreamReader extends BaseStreamReader { /** * A TextStreamReader instance can be used as an AsyncIterator that returns the entire string * that has been received up to the current point in time. */ constructor(info, stream, totalChunkCount) { super(info, stream, totalChunkCount); this.receivedChunks = new Map(); } handleChunkReceived(chunk) { var _a; const index = bigIntToNumber(chunk.chunkIndex); const previousChunkAtIndex = this.receivedChunks.get(index); if (previousChunkAtIndex && previousChunkAtIndex.version > chunk.version) { // we have a newer version already, dropping the old one return; } this.receivedChunks.set(index, chunk); this.bytesReceived += chunk.content.byteLength; this.validateBytesReceived(); const currentProgress = this.totalByteSize ? this.bytesReceived / this.totalByteSize : undefined; (_a = this.onProgress) === null || _a === void 0 ? void 0 : _a.call(this, currentProgress); } /** * Async iterator implementation to allow usage of `for await...of` syntax. * Yields structured chunks from the stream. * */ [Symbol.asyncIterator]() { const reader = this.reader.getReader(); // Suppress unhandled rejection on reader.closed — errors are // already propagated through reader.read() to the consumer. reader.closed.catch(() => {}); const decoder = new TextDecoder('utf-8', { fatal: true }); const signal = this.signal; const cleanup = () => { reader.releaseLock(); this.signal = undefined; }; return { next: () => __awaiter(this, void 0, void 0, function* () { try { if (signal === null || signal === void 0 ? void 0 : signal.aborted) { throw signal.reason; } const result = yield new Promise((resolve, reject) => { if (signal) { const onAbort = () => reject(signal.reason); signal.addEventListener('abort', onAbort, { once: true }); reader.read().then(resolve, reject).finally(() => { signal.removeEventListener('abort', onAbort); }); } else { reader.read().then(resolve, reject); } }); if (result.done) { this.validateBytesReceived(true); return { done: true, value: undefined }; } else { this.handleChunkReceived(result.value); let decodedResult; try { decodedResult = decoder.decode(result.value.content); } catch (err) { throw new DataStreamError("Cannot decode datastream chunk ".concat(result.value.chunkIndex, " as text: ").concat(err), DataStreamErrorReason.DecodeFailed); } return { done: false, value: decodedResult }; } } catch (err) { cleanup(); throw err; } }), // note: `return` runs only for premature exits, see: // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#errors_during_iteration return() { return __awaiter(this, void 0, void 0, function* () { cleanup(); return { done: true, value: undefined }; }); } }; } /** * Injects an AbortSignal, which if aborted, will terminate the currently active * stream iteration operation. * * Note that when using AbortSignal.timeout(...), the timeout applies across * the whole iteration operation, not just one individual chunk read. */ withAbortSignal(signal) { this.signal = signal; return this; } readAll() { return __awaiter(this, arguments, void 0, function () { var _this2 = this; let opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; return function* () { var _a, e_2, _b, _c; let finalString = ''; const iterator = opts.signal ? _this2.withAbortSignal(opts.signal) : _this2; try { for (var _d = true, iterator_2 = __asyncValues(iterator), iterator_2_1; iterator_2_1 = yield iterator_2.next(), _a = iterator_2_1.done, !_a; _d = true) { _c = iterator_2_1.value; _d = false; const chunk = _c; finalString += chunk; } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (!_d && !_a && (_b = iterator_2.return)) yield _b.call(iterator_2); } finally { if (e_2) throw e_2.error; } } return finalString; }(); }); } }class IncomingDataStreamManager { constructor() { this.log = livekitLogger; this.byteStreamControllers = new Map(); this.textStreamControllers = new Map(); this.byteStreamHandlers = new Map(); this.textStreamHandlers = new Map(); this.isConnected = false; this.bufferedPackets = []; } setConnected(connected) { this.isConnected = connected; if (connected) { this.flushBufferedPackets(); } } flushBufferedPackets() { const packets = this.bufferedPackets; this.bufferedPackets = []; for (const _ref of packets) { const packet = _ref.packet; const encryptionType = _ref.encryptionType; this.handleDataStreamPacket(packet, encryptionType); } } registerTextStreamHandler(topic, callback) { if (this.textStreamHandlers.has(topic)) { throw new DataStreamError("A text stream handler for topic \"".concat(topic, "\" has already been set."), DataStreamErrorReason.HandlerAlreadyRegistered); } this.textStreamHandlers.set(topic, callback); } unregisterTextStreamHandler(topic) { this.textStreamHandlers.delete(topic); } registerByteStreamHandler(topic, callback) { if (this.byteStreamHandlers.has(topic)) { throw new DataStreamError("A byte stream handler for topic \"".concat(topic, "\" has already been set."), DataStreamErrorReason.HandlerAlreadyRegistered); } this.byteStreamHandlers.set(topic, callback); } unregisterByteStreamHandler(topic) { this.byteStreamHandlers.delete(topic); } clearControllers() { this.byteStreamControllers.clear(); this.textStreamControllers.clear(); this.bufferedPackets = []; } validateParticipantHasNoActiveDataStreams(participantIdentity) { // Terminate any in flight data stream receives from the given participant const textStreamsBeingSentByDisconnectingParticipant = Array.from(this.textStreamControllers.entries()).filter(entry => entry[1].sendingParticipantIdentity === participantIdentity); const byteStreamsBeingSentByDisconnectingParticipant = Array.from(this.byteStreamControllers.entries()).filter(entry => entry[1].sendingParticipantIdentity === participantIdentity); if (textStreamsBeingSentByDisconnectingParticipant.length > 0 || byteStreamsBeingSentByDisconnectingParticipant.length > 0) { const abnormalEndError = new DataStreamError("Participant ".concat(participantIdentity, " unexpectedly disconnected in the middle of sending data"), DataStreamErrorReason.AbnormalEnd); for (const _ref2 of byteStreamsBeingSentByDisconnectingParticipant) { var _ref3 = _slicedToArray(_ref2, 2); const id = _ref3[0]; const controller = _ref3[1]; controller.controller.error(abnormalEndError); this.byteStreamControllers.delete(id); } for (const _ref4 of textStreamsBeingSentByDisconnectingParticipant) { var _ref5 = _slicedToArray(_ref4, 2); const id = _ref5[0]; const controller = _ref5[1]; controller.controller.error(abnormalEndError); this.textStreamControllers.delete(id); } } } handleDataStreamPacket(packet, encryptionType) { if (!this.isConnected) { this.bufferedPackets.push({ packet, encryptionType }); return; } switch (packet.value.case) { case 'streamHeader': return this.handleStreamHeader(packet.value.value, packet.participantIdentity, encryptionType); case 'streamChunk': return this.handleStreamChunk(packet.value.value, encryptionType); case 'streamTrailer': return this.handleStreamTrailer(packet.value.value, encryptionType); default: throw new Error("DataPacket of value \"".concat(packet.value.case, "\" is not data stream related!")); } } handleStreamHeader(streamHeader, participantIdentity, encryptionType) { var _a; if (streamHeader.contentHeader.case === 'byteHeader') { const streamHandlerCallback = this.byteStreamHandlers.get(streamHeader.topic); if (!streamHandlerCallback) { this.log.debug('ignoring incoming byte stream due to no handler for topic', streamHeader.topic); return; } let streamController; const info = { id: streamHeader.streamId, name: (_a = streamHeader.contentHeader.value.name) !== null && _a !== void 0 ? _a : 'unknown', mimeType: streamHeader.mimeType, size: streamHeader.totalLength ? Number(streamHeader.totalLength) : undefined, topic: streamHeader.topic, timestamp: bigIntToNumber(streamHeader.timestamp), attributes: streamHeader.attributes, encryptionType }; const stream = new ReadableStream({ start: controller => { streamController = controller; if (this.textStreamControllers.has(streamHeader.streamId)) { throw new DataStreamError("A data stream read is already in progress for a stream with id ".concat(streamHeader.streamId, "."), DataStreamErrorReason.AlreadyOpened); } this.byteStreamControllers.set(streamHeader.streamId, { info, controller: streamController, startTime: Date.now(), sendingParticipantIdentity: participantIdentity }); } }); streamHandlerCallback(new ByteStreamReader(info, stream, bigIntToNumber(streamHeader.totalLength)), { identity: participantIdentity }); } else if (streamHeader.contentHeader.case === 'textHeader') { const streamHandlerCallback = this.textStreamHandlers.get(streamHeader.topic); if (!streamHandlerCallback) { this.log.debug('ignoring incoming text stream due to no handler for topic', streamHeader.topic); return; } let streamController; const info = { id: streamHeader.streamId, mimeType: streamHeader.mimeType, size: streamHeader.totalLength ? Number(streamHeader.totalLength) : undefined, topic: streamHeader.topic, timestamp: Number(streamHeader.timestamp), attributes: streamHeader.attributes, encryptionType, attachedStreamIds: streamHeader.contentHeader.value.attachedStreamIds }; const stream = new ReadableStream({ start: controller => { streamController = controller; if (this.textStreamControllers.has(streamHeader.streamId)) { throw new DataStreamError("A data stream read is already in progress for a stream with id ".concat(streamHeader.streamId, "."), DataStreamErrorReason.AlreadyOpened); } this.textStreamControllers.set(streamHeader.streamId, { info, controller: streamController, startTime: Date.now(), sendingParticipantIdentity: participantIdentity }); } }); streamHandlerCallback(new TextStreamReader(info, stream, bigIntToNumber(streamHeader.totalLength)), { identity: participantIdentity }); } } handleStreamChunk(chunk, encryptionType) { const fileBuffer = this.byteStreamControllers.get(chunk.streamId); if (fileBuffer) { if (fileBuffer.info.encryptionType !== encryptionType) { fileBuffer.controller.error(new DataStreamError("Encryption type mismatch for stream ".concat(chunk.streamId, ". Expected ").concat(encryptionType, ", got ").concat(fileBuffer.info.encryptionType), DataStreamErrorReason.EncryptionTypeMismatch)); this.byteStreamControllers.delete(chunk.streamId); } else if (chunk.content.length > 0) { fileBuffer.controller.enqueue(chunk); } } const textBuffer = this.textStreamControllers.get(chunk.streamId); if (textBuffer) { if (textBuffer.info.encryptionType !== encryptionType) { textBuffer.controller.error(new DataStreamError("Encryption type mismatch for stream ".concat(chunk.streamId, ". Expected ").concat(encryptionType, ", got ").concat(textBuffer.info.encryptionType), DataStreamErrorReason.EncryptionTypeMismatch)); this.textStreamControllers.delete(chunk.streamId); } else if (chunk.content.length > 0) { textBuffer.controller.enqueue(chunk); } } } handleStreamTrailer(trailer, encryptionType) { const textBuffer = this.textStreamControllers.get(trailer.streamId); if (textBuffer) { if (textBuffer.info.encryptionType !== encryptionType) { textBuffer.controller.error(new DataStreamError("Encryption type mismatch for stream ".concat(trailer.streamId, ". Expected ").concat(encryptionType, ", got ").concat(textBuffer.info.encryptionType), DataStreamErrorReason.EncryptionTypeMismatch)); } else { textBuffer.info.attributes = Object.assign(Object.assign({}, textBuffer.info.attributes), trailer.attributes); textBuffer.controller.close(); this.textStreamControllers.delete(trailer.streamId); } } const fileBuffer = this.byteStreamControllers.get(trailer.streamId); if (fileBuffer) { if (fileBuffer.info.encryptionType !== encryptionType) { fileBuffer.controller.error(new DataStreamError("Encryption type mismatch for stream ".concat(trailer.streamId, ". Expected ").concat(encryptionType, ", got ").concat(fileBuffer.info.encryptionType), DataStreamErrorReason.EncryptionTypeMismatch)); } else { fileBuffer.info.attributes = Object.assign(Object.assign({}, fileBuffer.info.attributes), trailer.attributes); fileBuffer.controller.close(); } this.byteStreamControllers.delete(trailer.streamId); } } }class BaseStreamWriter { constructor(writableStream, info, onClose) { this.writableStream = writableStream; this.defaultWriter = writableStream.getWriter(); this.onClose = onClose; this.info = info; } write(chunk) { return this.defaultWriter.write(chunk); } close() { return __awaiter(this, void 0, void 0, function* () { var _a; yield this.defaultWriter.close(); this.defaultWriter.releaseLock(); (_a = this.onClose) === null || _a === void 0 ? void 0 : _a.call(this); }); } } class TextStreamWriter extends BaseStreamWriter {} class ByteStreamWriter extends BaseStreamWriter {}const STREAM_CHUNK_SIZE = 15000; /** * Manages sending custom user data via data channels. * @internal */ class OutgoingDataStreamManager { constructor(engine, log) { this.engine = engine; this.log = log; } setupEngine(engine) { this.engine = engine; } /** {@inheritDoc LocalParticipant.sendText} */ sendText(text, options) { return __awaiter(this, void 0, void 0, function* () { var _a; const streamId = crypto.randomUUID(); const textInBytes = new TextEncoder().encode(text); const totalTextLength = textInBytes.byteLength; const fileIds = (_a = options === null || options === void 0 ? void 0 : options.attachments) === null || _a === void 0 ? void 0 : _a.map(() => crypto.randomUUID()); const progresses = new Array(fileIds ? fileIds.length + 1 : 1).fill(0); const handleProgress = (progress, idx) => { var _a; progresses[idx] = progress; const totalProgress = progresses.reduce((acc, val) => acc + val, 0); (_a = options === null || options === void 0 ? void 0 : options.onProgress) === null || _a === void 0 ? void 0 : _a.call(options, totalProgress); }; const writer = yield this.streamText({ streamId, totalSize: totalTextLength, destinationIdentities: options === null || options === void 0 ? void 0 : options.destinationIdentities, topic: options === null || options === void 0 ? void 0 : options.topic, attachedStreamIds: fileIds, attributes: options === null || options === void 0 ? void 0 : options.attributes }); yield writer.write(text); // set text part of progress to 1 handleProgress(1, 0); yield writer.close(); if ((options === null || options === void 0 ? void 0 : options.attachments) && fileIds) { yield Promise.all(options.attachments.map((file, idx) => __awaiter(this, void 0, void 0, function* () { return this._sendFile(fileIds[idx], file, { topic: options.topic, mimeType: file.type, onProgress: progress => { handleProgress(progress, idx + 1); } }); }))); } return writer.info; }); } /** * @internal */ streamText(options) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; const streamId = (_a = options === null || options === void 0 ? void 0 : options.streamId) !== null && _a !== void 0 ? _a : crypto.randomUUID(); const info = { id: streamId, mimeType: 'text/plain', timestamp: Date.now(), topic: (_b = options === null || options === void 0 ? void 0 : options.topic) !== null && _b !== void 0 ? _b : '', size: options === null || options === void 0 ? void 0 : options.totalSize, attributes: options === null || options === void 0 ? void 0 : options.attributes, encryptionType: ((_c = this.engine.e2eeManager) === null || _c === void 0 ? void 0 : _c.isDataChannelEncryptionEnabled) ? Encryption_Type.GCM : Encryption_Type.NONE, attachedStreamIds: options === null || options === void 0 ? void 0 : options.attachedStreamIds }; const header = new DataStream_Header({ streamId, mimeType: info.mimeType, topic: info.topic, timestamp: numberToBigInt(info.timestamp), totalLength: numberToBigInt(info.size), attributes: info.attributes, contentHeader: { case: 'textHeader', value: new DataStream_TextHeader({ version: options === null || options === void 0 ? void 0 : options.version, attachedStreamIds: info.attachedStreamIds, replyToStreamId: options === null || options === void 0 ? void 0 : options.replyToStreamId, operationType: (options === null || options === void 0 ? void 0 : options.type) === 'update' ? DataStream_OperationType.UPDATE : DataStream_OperationType.CREATE }) } }); const destinationIdentities = options === null || options === void 0 ? void 0 : options.destinationIdentities; const packet = new DataPacket({ destinationIdentities, value: { case: 'streamHeader', value: header } }); yield this.engine.sendDataPacket(packet, DataChannelKind.RELIABLE); let chunkId = 0; const engine = this.engine; const writableStream = new WritableStream({ // Implement the sink write(text) { return __awaiter(this, void 0, void 0, function* () { for (const textByteChunk of splitUtf8(text, STREAM_CHUNK_SIZE)) { const chunk = new DataStream_Chunk({ content: textByteChunk, streamId, chunkIndex: numberToBigInt(chunkId) }); const chunkPacket = new DataPacket({ destinationIdentities, value: { case: 'streamChunk', value: chunk } }); yield engine.sendDataPacket(chunkPacket, DataChannelKind.RELIABLE); chunkId += 1; } }); }, close() { return __awaiter(this, void 0, void 0, function* () { const trailer = new DataStream_Trailer({ streamId }); const trailerPacket = new DataPacket({ destinationIdentities, value: { case: 'streamTrailer', value: trailer } }); yield engine.sendDataPacket(trailerPacket, DataChannelKind.RELIABLE); }); }, abort(err) { console.log('Sink error:', err); // TODO handle aborts to signal something to receiver side } }); let onEngineClose = () => __awaiter(this, void 0, void 0, function* () { yield writer.close(); }); engine.once(EngineEvent.Closing, onEngineClose); const writer = new TextStreamWriter(writableStream, info, () => this.engine.off(EngineEvent.Closing, onEngineClose)); return writer; }); } sendFile(file, options) { return __awaiter(this, void 0, void 0, function* () { const streamId = crypto.randomUUID(); yield this._sendFile(streamId, file, options); return { id: streamId }; }); } _sendFile(streamId, file, options) { return __awaiter(this, void 0, void 0, function* () { var _a; const writer = yield this.streamBytes({ streamId, totalSize: file.size, name: file.name, mimeType: (_a = options === null || options === void 0 ? void 0 : options.mimeType) !== null && _a !== void 0 ? _a : file.type, topic: options === null || options === void 0 ? void 0 : options.topic, destinationIdentities: options === null || options === void 0 ? void 0 : options.destinationIdentities }); const reader = file.stream().getReader(); while (true) { const _yield$reader$read = yield reader.read(), done = _yield$reader$read.done, value = _yield$reader$read.value; if (done) { break; } yield writer.write(value); } yield writer.close(); return writer.info; }); } streamBytes(options) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e; const streamId = (_a = options === null || options === void 0 ? void 0 : options.streamId) !== null && _a !== void 0 ? _a : crypto.randomUUID(); const destinationIdentities = options === null || options === void 0 ? void 0 : options.destinationIdentities; const info = { id: streamId, mimeType: (_b = options === null || options === void 0 ? void 0 : options.mimeType) !== null && _b !== void 0 ? _b : 'application/octet-stream', topic: (_c = options === null || options === void 0 ? void 0 : options.topic) !== null && _c !== void 0 ? _c : '', timestamp: Date.now(), attributes: options === null || options === void 0 ? void 0 : options.attributes, size: options === null || options === void 0 ? void 0 : options.totalSize, name: (_d = options === null || options === void 0 ? void 0 : options.name) !== null && _d !== void 0 ? _d : 'unknown', encryptionType: ((_e = this.engine.e2eeManager) === null || _e === void 0 ? void 0 : _e.isDataChannelEncryptionEnabled) ? Encryption_Type.GCM : Encryption_Type.NONE }; const header = new DataStream_Header({ totalLength: numberToBigInt(info.size), mimeType: info.mimeType, streamId, topic: info.topic, timestamp: numberToBigInt(Date.now()), attributes: info.attributes, contentHeader: { case: 'byteHeader', value: new DataStream_ByteHeader({ name: info.name }) } }); const packet = new DataPacket({ destinationIdentities, value: { case: 'streamHeader', value: header } }); yield this.engine.sendDataPacket(packet, DataChannelKind.RELIABLE); let chunkId = 0; const writeMutex = new _(); const engine = this.engine; const logLocal = this.log; const writableStream = new WritableStream({ write(chunk) { return __awaiter(this, void 0, void 0, function* () { const unlock = yield writeMutex.lock(); let byteOffset = 0; try { while (byteOffset < chunk.byteLength) { const subChunk = chunk.slice(byteOffset, byteOffset + STREAM_CHUNK_SIZE); const chunkPacket = new DataPacket({ destinationIdentities, value: { case: 'streamChunk', value: new DataStream_Chunk({ content: subChunk, streamId, chunkIndex: numberToBigInt(chunkId) }) } }); yield engine.sendDataPacket(chunkPacket, DataChannelKind.RELIABLE); chunkId += 1; byteOffset += subChunk.byteLength; } } finally { unlock(); } }); }, close() { return __awaiter(this, void 0, void 0, function* () { const trailer = new DataStream_Trailer({ streamId }); const trailerPacket = new DataPacket({ destinationIdentities, value: { case: 'streamTrailer', value: trailer } }); yield engine.sendDataPacket(trailerPacket, DataChannelKind.RELIABLE); }); }, abort(err) { logLocal.error('Sink error:', err); } }); const byteWriter = new ByteStreamWriter(writableStream, info); return byteWriter; }); } }/** * Implementation of AbortSignal.any * Creates a signal that will be aborted when any of the given signals is aborted. * @link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any */ function abortSignalAny(signals) { // Handle empty signals array if (signals.length === 0) { const controller = new AbortController(); return controller.signal; } // Fast path for single signal if (signals.length === 1) { return signals[0]; } // Check if any signal is already aborted for (const signal of signals) { if (signal.aborted) { return signal; } } // Create a new controller for the combined signal const controller = new AbortController(); const unlisteners = Array(signals.length); // Function to clean up all event listeners const cleanup = () => { for (const unsubscribe of unlisteners) { unsubscribe(); } }; // Add event listeners to each signal signals.forEach((signal, index) => { const handler = () => { controller.abort(signal.reason); cleanup(); }; signal.addEventListener('abort', handler); unlisteners[index] = () => signal.removeEventListener('abort', handler); }); return controller.signal; } /** * Implementation of AbortSignal.timeout * Creates a signal that will be aborted after the specified timeout. * @link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout */ function abortSignalTimeout(ms) { const controller = new AbortController(); setTimeout(() => { controller.abort(new DOMException("signal timed out after ".concat(ms, " ms"), 'TimeoutError')); }, ms); return controller.signal; }// Number type sizes const U8_LENGTH_BYTES = 1; const U16_LENGTH_BYTES = 2; const U32_LENGTH_BYTES = 4; const U64_LENGTH_BYTES = 8; /// Constants used for serialization and deserialization. const SUPPORTED_VERSION = 0; const BASE_HEADER_LEN = 12; // Bitfield shifts and masks for header flags const VERSION_SHIFT = 5; const VERSION_MASK = 0x07; const FRAME_MARKER_SHIFT = 3; const FRAME_MARKER_MASK = 0x3; const FRAME_MARKER_START = 0x2; const FRAME_MARKER_FINAL = 0x1; const FRAME_MARKER_INTER = 0x0; const FRAME_MARKER_SINGLE = 0x3; const EXT_WORDS_INDICATOR_SIZE = 2; const EXT_FLAG_SHIFT = 0x2; const EXT_FLAG_MASK = 0x1; const EXT_TAG_PADDING = 0;var DataTrackDeserializeErrorReason; (function (DataTrackDeserializeErrorReason) { DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["TooShort"] = 0] = "TooShort"; DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["HeaderOverrun"] = 1] = "HeaderOverrun"; DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["MissingExtWords"] = 2] = "MissingExtWords"; DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["UnsupportedVersion"] = 3] = "UnsupportedVersion"; DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["InvalidHandle"] = 4] = "InvalidHandle"; DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["MalformedExt"] = 5] = "MalformedExt"; })(DataTrackDeserializeErrorReason || (DataTrackDeserializeErrorReason = {})); class DataTrackDeserializeError extends LivekitReasonedError { constructor(message, reason, options) { super(19, message, options); this.name = 'DataTrackDeserializeError'; this.reason = reason; this.reasonName = DataTrackDeserializeErrorReason[reason]; } static tooShort() { return new DataTrackDeserializeError('Too short to contain a valid header', DataTrackDeserializeErrorReason.TooShort); } static headerOverrun() { return new DataTrackDeserializeError('Header exceeds total packet length', DataTrackDeserializeErrorReason.HeaderOverrun); } static missingExtWords() { return new DataTrackDeserializeError('Extension word indicator is missing', DataTrackDeserializeErrorReason.MissingExtWords); } static unsupportedVersion(version) { return new DataTrackDeserializeError("Unsupported version ".concat(version), DataTrackDeserializeErrorReason.UnsupportedVersion); } static invalidHandle(cause) { return new DataTrackDeserializeError("invalid track handle: ".concat(cause.message), DataTrackDeserializeErrorReason.InvalidHandle, { cause }); } static malformedExt(tag) { return new DataTrackDeserializeError("Extension with tag ".concat(tag, " is malformed"), DataTrackDeserializeErrorReason.MalformedExt); } } var DataTrackSerializeErrorReason; (function (DataTrackSerializeErrorReason) { DataTrackSerializeErrorReason[DataTrackSerializeErrorReason["TooSmallForHeader"] = 0] = "TooSmallForHeader"; DataTrackSerializeErrorReason[DataTrackSerializeErrorReason["TooSmallForPayload"] = 1] = "TooSmallForPayload"; })(DataTrackSerializeErrorReason || (DataTrackSerializeErrorReason = {})); class DataTrackSerializeError extends LivekitReasonedError { constructor(message, reason, options) { super(19, message, options); this.name = 'DataTrackSerializeError'; this.reason = reason; this.reasonName = DataTrackSerializeErrorReason[reason]; } static tooSmallForHeader() { return new DataTrackSerializeError('Buffer cannot fit header', DataTrackSerializeErrorReason.TooSmallForHeader); } static tooSmallForPayload() { return new DataTrackSerializeError('Buffer cannot fit payload', DataTrackSerializeErrorReason.TooSmallForPayload); } }/** An abstract class implementing common behavior related to data track binary serialization. */ class Serializable { /** Encodes the instance as binary and returns the data as a Uint8Array. */ toBinary() { const lengthBytes = this.toBinaryLengthBytes(); const output = new ArrayBuffer(lengthBytes); const view = new DataView(output); const writtenBytes = this.toBinaryInto(view); if (lengthBytes !== writtenBytes) { // @throws-transformer ignore - this should be treated as a "panic" and not be caught throw new Error("".concat(this.constructor.name, ".toBinary: written bytes (").concat(writtenBytes, " bytes) not equal to allocated array buffer length (").concat(lengthBytes, " bytes).")); } return new Uint8Array(output); } }var DataTrackExtensionTag; (function (DataTrackExtensionTag) { DataTrackExtensionTag[DataTrackExtensionTag["UserTimestamp"] = 2] = "UserTimestamp"; DataTrackExtensionTag[DataTrackExtensionTag["E2ee"] = 1] = "E2ee"; })(DataTrackExtensionTag || (DataTrackExtensionTag = {})); class DataTrackExtension extends Serializable {} class DataTrackUserTimestampExtension extends DataTrackExtension { constructor(timestamp) { super(); this.timestamp = timestamp; } toBinaryLengthBytes() { return U8_LENGTH_BYTES /* tag */ + U8_LENGTH_BYTES /* length */ + DataTrackUserTimestampExtension.lengthBytes; } toBinaryInto(dataView) { let byteIndex = 0; dataView.setUint8(byteIndex, DataTrackUserTimestampExtension.tag); byteIndex += U8_LENGTH_BYTES; dataView.setUint8(byteIndex, DataTrackUserTimestampExtension.lengthBytes); byteIndex += U8_LENGTH_BYTES; dataView.setBigUint64(byteIndex, this.timestamp); byteIndex += U64_LENGTH_BYTES; const totalLengthBytes = this.toBinaryLengthBytes(); if (byteIndex !== totalLengthBytes) { // @throws-transformer ignore - this should be treated as a "panic" and not be caught throw new Error("DataTrackUserTimestampExtension.toBinaryInto: Wrote ".concat(byteIndex, " bytes but expected length was ").concat(totalLengthBytes, " bytes")); } return byteIndex; } toJSON() { return { tag: DataTrackUserTimestampExtension.tag, lengthBytes: DataTrackUserTimestampExtension.lengthBytes, timestamp: this.timestamp }; } } DataTrackUserTimestampExtension.tag = DataTrackExtensionTag.UserTimestamp; DataTrackUserTimestampExtension.lengthBytes = 8; class DataTrackE2eeExtension extends DataTrackExtension { constructor(keyIndex, iv) { super(); this.keyIndex = keyIndex; this.iv = iv; } toBinaryLengthBytes() { return U8_LENGTH_BYTES /* tag */ + U8_LENGTH_BYTES /* length */ + DataTrackE2eeExtension.lengthBytes; } toBinaryInto(dataView) { let byteIndex = 0; dataView.setUint8(byteIndex, DataTrackE2eeExtension.tag); byteIndex += U8_LENGTH_BYTES; dataView.setUint8(byteIndex, DataTrackE2eeExtension.lengthBytes); byteIndex += U8_LENGTH_BYTES; dataView.setUint8(byteIndex, this.keyIndex); byteIndex += U8_LENGTH_BYTES; for (let i = 0; i < this.iv.length; i += 1) { dataView.setUint8(byteIndex, this.iv[i]); byteIndex += U8_LENGTH_BYTES; } const totalLengthBytes = this.toBinaryLengthBytes(); if (byteIndex !== totalLengthBytes) { // @throws-transformer ignore - this should be treated as a "panic" and not be caught throw new Error("DataTrackE2eeExtension.toBinaryInto: Wrote ".concat(byteIndex, " bytes but expected length was ").concat(totalLengthBytes, " bytes")); } return byteIndex; } toJSON() { return { tag: DataTrackE2eeExtension.tag, lengthBytes: DataTrackE2eeExtension.lengthBytes, keyIndex: this.keyIndex, iv: this.iv }; } } DataTrackE2eeExtension.tag = DataTrackExtensionTag.E2ee; DataTrackE2eeExtension.lengthBytes = 13; class DataTrackExtensions extends Serializable { constructor() { let opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; super(); this.userTimestamp = opts.userTimestamp; this.e2ee = opts.e2ee; } toBinaryLengthBytes() { let lengthBytes = 0; if (this.userTimestamp) { lengthBytes += this.userTimestamp.toBinaryLengthBytes(); } if (this.e2ee) { lengthBytes += this.e2ee.toBinaryLengthBytes(); } return lengthBytes; } toBinaryInto(dataView) { let byteIndex = 0; if (this.e2ee) { const e2eeBytes = this.e2ee.toBinaryInto(dataView); byteIndex += e2eeBytes; } if (this.userTimestamp) { const userTimestampBytes = this.userTimestamp.toBinaryInto(new DataView(dataView.buffer, dataView.byteOffset + byteIndex)); byteIndex += userTimestampBytes; } const totalLengthBytes = this.toBinaryLengthBytes(); if (byteIndex !== totalLengthBytes) { // @throws-transformer ignore - this should be treated as a "panic" and not be caught throw new Error("DataTrackExtensions.toBinaryInto: Wrote ".concat(byteIndex, " bytes but expected length was ").concat(totalLengthBytes, " bytes")); } return byteIndex; } static fromBinary(input) { const dataView = coerceToDataView(input); let userTimestamp; let e2ee; let byteIndex = 0; while (dataView.byteLength - byteIndex >= U8_LENGTH_BYTES + U8_LENGTH_BYTES) { const tag = dataView.getUint8(byteIndex); byteIndex += U8_LENGTH_BYTES; const lengthBytes = dataView.getUint8(byteIndex); byteIndex += U8_LENGTH_BYTES; if (tag === EXT_TAG_PADDING) { // Skip padding continue; } switch (tag) { case DataTrackExtensionTag.UserTimestamp: if (dataView.byteLength - byteIndex < DataTrackUserTimestampExtension.lengthBytes) { throw DataTrackDeserializeError.malformedExt(tag); } userTimestamp = new DataTrackUserTimestampExtension(dataView.getBigUint64(byteIndex)); byteIndex += lengthBytes; break; case DataTrackExtensionTag.E2ee: if (dataView.byteLength - byteIndex < DataTrackE2eeExtension.lengthBytes) { throw DataTrackDeserializeError.malformedExt(tag); } const keyIndex = dataView.getUint8(byteIndex); const iv = new Uint8Array(12); for (let i = 0; i < iv.length; i += 1) { let byteIndexForIv = byteIndex; byteIndexForIv += U8_LENGTH_BYTES; // key index byteIndexForIv += i * U8_LENGTH_BYTES; // Index into iv array iv[i] = dataView.getUint8(byteIndexForIv); } e2ee = new DataTrackE2eeExtension(keyIndex, iv); byteIndex += lengthBytes; break; default: // Skip over unknown extensions (forward compatible). if (dataView.byteLength - byteIndex < lengthBytes) { throw DataTrackDeserializeError.malformedExt(tag); } byteIndex += lengthBytes; break; } } // NOTE: padding bytes after extension data is intentionally not being processed return [new DataTrackExtensions({ userTimestamp, e2ee }), dataView.byteLength]; } toJSON() { var _a, _b, _c, _d; return { userTimestamp: (_b = (_a = this.userTimestamp) === null || _a === void 0 ? void 0 : _a.toJSON()) !== null && _b !== void 0 ? _b : null, e2ee: (_d = (_c = this.e2ee) === null || _c === void 0 ? void 0 : _c.toJSON()) !== null && _d !== void 0 ? _d : null }; } }const DataTrackFrameInternal = { from(frame) { return { payload: frame.payload, extensions: new DataTrackExtensions({ userTimestamp: frame.userTimestamp ? new DataTrackUserTimestampExtension(frame.userTimestamp) : undefined }) }; }, /** Converts from a DataTrackFrameInternal -> DataTrackFrame. Some internal information is * discarded like e2ee encrption extension data. */ lossyIntoFrame(frame) { var _a; return { payload: frame.payload, userTimestamp: (_a = frame.extensions.userTimestamp) === null || _a === void 0 ? void 0 : _a.timestamp }; } };const TrackSymbol = Symbol.for('lk.track'); const DataTrackSymbol = Symbol.for('lk.data-track');class RemoteDataTrack { /** @internal */ constructor(info, manager, options) { this.trackSymbol = TrackSymbol; this.isLocal = false; this.typeSymbol = DataTrackSymbol; this.info = info; this.manager = manager; this.publisherIdentity = options.publisherIdentity; } /** Subscribes to the data track to receive frames. * * # Returns * * A stream that yields {@link DataTrackFrame}s as they arrive. * * # Multiple Subscriptions * * An application may call `subscribe` more than once to process frames in * multiple places. For example, one async task might plot values on a graph * while another writes them to a file. * * Internally, only the first call to `subscribe` communicates with the SFU and * allocates the resources required to receive frames. Additional subscriptions * reuse the same underlying pipeline and do not trigger additional signaling. * * Note that newly created subscriptions only receive frames published after * the initial subscription is established. */ subscribe(options) { try { const _this$manager$openSub = this.manager.openSubscriptionStream(this.info.sid, options === null || options === void 0 ? void 0 : options.signal, options === null || options === void 0 ? void 0 : options.bufferSize), _this$manager$openSub2 = _slicedToArray(_this$manager$openSub, 2), stream = _this$manager$openSub2[0], sfuSubscriptionComplete = _this$manager$openSub2[1]; // Prevent uncaught promise rejections from bubbling up if rejections occur after the // readable stream is discarded. sfuSubscriptionComplete.catch(() => {}); return stream; } catch (err) { // NOTE: Rethrow errors to break Throws<...> type boundary throw err; } } /** Configure how incoming frames for this track are processed before they are handed out to * subscribers (the "pipeline"). These options apply to all current and future subscriptions * of this track, and may be set at any time. */ setPipelineOptions(options) { this.manager.setPipelineOptions(this.info.sid, options); } }/** A class for serializing / deserializing data track packet header sections. */ class DataTrackPacketHeader extends Serializable { constructor(opts) { var _a; super(); this.marker = opts.marker; this.trackHandle = opts.trackHandle; this.sequence = opts.sequence; this.frameNumber = opts.frameNumber; this.timestamp = opts.timestamp; this.extensions = (_a = opts.extensions) !== null && _a !== void 0 ? _a : new DataTrackExtensions(); } extensionsMetrics() { const lengthBytes = this.extensions.toBinaryLengthBytes(); const lengthWords = Math.ceil((EXT_WORDS_INDICATOR_SIZE + lengthBytes) / 4); const paddingLengthBytes = lengthWords * 4 - EXT_WORDS_INDICATOR_SIZE - lengthBytes; return { lengthBytes, lengthWords, paddingLengthBytes }; } toBinaryLengthBytes() { const _this$extensionsMetri = this.extensionsMetrics(), extLengthBytes = _this$extensionsMetri.lengthBytes, extPaddingLengthBytes = _this$extensionsMetri.paddingLengthBytes; let totalLengthBytes = BASE_HEADER_LEN; if (extLengthBytes > 0) { totalLengthBytes += EXT_WORDS_INDICATOR_SIZE + extLengthBytes + extPaddingLengthBytes; } return totalLengthBytes; } toBinaryInto(dataView) { if (dataView.byteLength < this.toBinaryLengthBytes()) { throw DataTrackSerializeError.tooSmallForHeader(); } let initial = SUPPORTED_VERSION << VERSION_SHIFT; let marker; switch (this.marker) { case FrameMarker.Inter: marker = FRAME_MARKER_INTER; break; case FrameMarker.Final: marker = FRAME_MARKER_FINAL; break; case FrameMarker.Start: marker = FRAME_MARKER_START; break; case FrameMarker.Single: marker = FRAME_MARKER_SINGLE; break; } initial |= marker << FRAME_MARKER_SHIFT; const _this$extensionsMetri2 = this.extensionsMetrics(), extensionsLengthBytes = _this$extensionsMetri2.lengthBytes, extensionsLengthWords = _this$extensionsMetri2.lengthWords, extensionsPaddingLengthBytes = _this$extensionsMetri2.paddingLengthBytes; if (extensionsLengthBytes > 0) { initial |= 1 << EXT_FLAG_SHIFT; } let byteIndex = 0; dataView.setUint8(byteIndex, initial); byteIndex += U8_LENGTH_BYTES; dataView.setUint8(byteIndex, 0); // Reserved byteIndex += U8_LENGTH_BYTES; dataView.setUint16(byteIndex, this.trackHandle); byteIndex += U16_LENGTH_BYTES; dataView.setUint16(byteIndex, this.sequence.value); byteIndex += U16_LENGTH_BYTES; dataView.setUint16(byteIndex, this.frameNumber.value); byteIndex += U16_LENGTH_BYTES; dataView.setUint32(byteIndex, this.timestamp.asTicks()); byteIndex += U32_LENGTH_BYTES; if (extensionsLengthBytes > 0) { // NOTE: The protocol is implemented in a way where if the extension bit is set, any // deserializer assumes the extensions section is at least one byte long, and the "length" // field represents the "number of additional bytes" long the extensions section is. This is // potentially unintuitive so I wanted to call it out. const rtpOrientedExtensionLengthWords = extensionsLengthWords - 1; dataView.setUint16(byteIndex, rtpOrientedExtensionLengthWords); byteIndex += U16_LENGTH_BYTES; const extensionBytes = this.extensions.toBinaryInto(new DataView(dataView.buffer, dataView.byteOffset + byteIndex)); byteIndex += extensionBytes; for (let i = 0; i < extensionsPaddingLengthBytes; i += 1) { dataView.setUint8(byteIndex, 0); byteIndex += U8_LENGTH_BYTES; } } const totalLengthBytes = this.toBinaryLengthBytes(); if (byteIndex !== totalLengthBytes) { // @throws-transformer ignore - this should be treated as a "panic" and not be caught throw new Error("DataTrackPacketHeader.toBinaryInto: Wrote ".concat(byteIndex, " bytes but expected length was ").concat(totalLengthBytes, " bytes")); } return totalLengthBytes; } static fromBinary(input) { const dataView = coerceToDataView(input); if (dataView.byteLength < BASE_HEADER_LEN) { throw DataTrackDeserializeError.tooShort(); } let byteIndex = 0; const initial = dataView.getUint8(byteIndex); byteIndex += U8_LENGTH_BYTES; const version = initial >> VERSION_SHIFT & VERSION_MASK; if (version > SUPPORTED_VERSION) { throw DataTrackDeserializeError.unsupportedVersion(version); } let marker; switch (initial >> FRAME_MARKER_SHIFT & FRAME_MARKER_MASK) { case FRAME_MARKER_START: marker = FrameMarker.Start; break; case FRAME_MARKER_FINAL: marker = FrameMarker.Final; break; case FRAME_MARKER_SINGLE: marker = FrameMarker.Single; break; case FRAME_MARKER_INTER: default: marker = FrameMarker.Inter; break; } const extensionsFlag = (initial >> EXT_FLAG_SHIFT & EXT_FLAG_MASK) > 0; byteIndex += U8_LENGTH_BYTES; // Reserved let trackHandle; try { trackHandle = DataTrackHandle.fromNumber(dataView.getUint16(byteIndex)); } catch (e) { if (e instanceof DataTrackHandleError && (e.isReason(DataTrackHandleErrorReason.Reserved) || e.isReason(DataTrackHandleErrorReason.TooLarge))) { throw DataTrackDeserializeError.invalidHandle(e); } else { throw e; } } byteIndex += U16_LENGTH_BYTES; const sequence = WrapAroundUnsignedInt.u16(dataView.getUint16(byteIndex)); byteIndex += U16_LENGTH_BYTES; const frameNumber = WrapAroundUnsignedInt.u16(dataView.getUint16(byteIndex)); byteIndex += U16_LENGTH_BYTES; const timestamp = DataTrackTimestamp.fromRtpTicks(dataView.getUint32(byteIndex)); byteIndex += U32_LENGTH_BYTES; let extensions = new DataTrackExtensions(); if (extensionsFlag) { if (dataView.byteLength - byteIndex < U16_LENGTH_BYTES) { throw DataTrackDeserializeError.missingExtWords(); } let rtpOrientedExtensionWords = dataView.getUint16(byteIndex); byteIndex += U16_LENGTH_BYTES; // NOTE: The protocol is implemented in a way where if the extension bit is set, any // deserializer assumes the extensions section is at least one byte long, and the "length" // field represents the "number of additional bytes" long the extensions section is. This is // potentially unintuitive so I wanted to call it out. const extensionWords = rtpOrientedExtensionWords + 1; let extensionLengthBytes = 4 * extensionWords - EXT_WORDS_INDICATOR_SIZE; if (byteIndex + extensionLengthBytes > dataView.byteLength) { throw DataTrackDeserializeError.headerOverrun(); } let extensionDataView = new DataView(dataView.buffer, dataView.byteOffset + byteIndex, extensionLengthBytes); const _DataTrackExtensions$ = DataTrackExtensions.fromBinary(extensionDataView), _DataTrackExtensions$2 = _slicedToArray(_DataTrackExtensions$, 2), result = _DataTrackExtensions$2[0], readBytes = _DataTrackExtensions$2[1]; extensions = result; byteIndex += readBytes; } return [new DataTrackPacketHeader({ marker, trackHandle: trackHandle, sequence, frameNumber, timestamp, extensions }), byteIndex]; } toJSON() { return { marker: this.marker, trackHandle: this.trackHandle, sequence: this.sequence.value, frameNumber: this.frameNumber.value, timestamp: this.timestamp.asTicks(), extensions: this.extensions.toJSON() }; } } /** Marker indicating a packet's position in relation to a frame. */ var FrameMarker; (function (FrameMarker) { /** Packet is the first in a frame. */ FrameMarker[FrameMarker["Start"] = 0] = "Start"; /** Packet is within a frame. */ FrameMarker[FrameMarker["Inter"] = 1] = "Inter"; /** Packet is the last in a frame. */ FrameMarker[FrameMarker["Final"] = 2] = "Final"; /** Packet is the only one in a frame. */ FrameMarker[FrameMarker["Single"] = 3] = "Single"; })(FrameMarker || (FrameMarker = {})); /** A class for serializing / deserializing data track packets. */ class DataTrackPacket extends Serializable { constructor(header, payload) { super(); this.header = header; this.payload = payload; } toBinaryLengthBytes() { return this.header.toBinaryLengthBytes() + this.payload.byteLength; } toBinaryInto(dataView) { let byteIndex = 0; const headerLengthBytes = this.header.toBinaryInto(dataView); byteIndex += headerLengthBytes; if (dataView.byteLength - byteIndex < this.payload.byteLength) { throw DataTrackSerializeError.tooSmallForPayload(); } for (let index = 0; index < this.payload.length; index += 1) { dataView.setUint8(byteIndex, this.payload[index]); byteIndex += U8_LENGTH_BYTES; } const totalLengthBytes = this.toBinaryLengthBytes(); if (byteIndex !== totalLengthBytes) { // @throws-transformer ignore - this should be treated as a "panic" and not be caught throw new Error("DataTrackPacket.toBinaryInto: Wrote ".concat(byteIndex, " bytes but expected length was ").concat(totalLengthBytes, " bytes")); } return totalLengthBytes; } static fromBinary(input) { const dataView = coerceToDataView(input); const _DataTrackPacketHeade = DataTrackPacketHeader.fromBinary(dataView), _DataTrackPacketHeade2 = _slicedToArray(_DataTrackPacketHeade, 2), header = _DataTrackPacketHeade2[0], headerByteLength = _DataTrackPacketHeade2[1]; const payload = dataView.buffer.slice(dataView.byteOffset + headerByteLength, dataView.byteOffset + dataView.byteLength); return [new DataTrackPacket(header, new Uint8Array(payload)), dataView.byteLength]; } toJSON() { return { header: this.header.toJSON(), payload: this.payload }; } }const log$3 = getLogger(LoggerNames.DataTracks); /** An error indicating a frame was dropped. */ class DataTrackDepacketizerDropError extends LivekitReasonedError { constructor(message, reason, frameNumber, options) { super(19, "Frame ".concat(frameNumber, " dropped: ").concat(message), options); this.name = 'DataTrackDepacketizerDropError'; this.reason = reason; this.reasonName = DataTrackDepacketizerDropReason[reason]; this.frameNumber = frameNumber; } static interrupted(frameNumber, newFrameNumber) { return new DataTrackDepacketizerDropError("Interrupted by the start of a new frame ".concat(newFrameNumber), DataTrackDepacketizerDropReason.Interrupted, frameNumber); } static unknownFrame(frameNumber) { return new DataTrackDepacketizerDropError('Initial packet was never received.', DataTrackDepacketizerDropReason.UnknownFrame, frameNumber); } static bufferFull(frameNumber) { return new DataTrackDepacketizerDropError('Reorder buffer is full.', DataTrackDepacketizerDropReason.BufferFull, frameNumber); } static incomplete(frameNumber, receivedPackets, expectedPackets) { return new DataTrackDepacketizerDropError("Not all packets received before final packet. Received ".concat(receivedPackets, " packets, expected ").concat(expectedPackets, " packets."), DataTrackDepacketizerDropReason.Incomplete, frameNumber); } } /** Reason why a frame was dropped. */ var DataTrackDepacketizerDropReason; (function (DataTrackDepacketizerDropReason) { DataTrackDepacketizerDropReason[DataTrackDepacketizerDropReason["Interrupted"] = 0] = "Interrupted"; DataTrackDepacketizerDropReason[DataTrackDepacketizerDropReason["UnknownFrame"] = 1] = "UnknownFrame"; DataTrackDepacketizerDropReason[DataTrackDepacketizerDropReason["BufferFull"] = 2] = "BufferFull"; DataTrackDepacketizerDropReason[DataTrackDepacketizerDropReason["Incomplete"] = 3] = "Incomplete"; })(DataTrackDepacketizerDropReason || (DataTrackDepacketizerDropReason = {})); class DataTrackDepacketizer { constructor() { /** Partial frames currently being assembled, keyed by frame number. `Map` preserves insertion * order, so the oldest entry is the first key. */ this.partials = new Map(); } /** Should be repeatedly called with received {@link DataTrackPacket}s - intermediate calls * aggregate the packet's state internally, and return null. * * Once this method is called with the final packet to form a frame, a new {@link DataTrackFrameInternal} * is returned.*/ push(packet, options) { switch (packet.header.marker) { case FrameMarker.Single: return this.frameFromSingle(packet, options); case FrameMarker.Start: return this.beginPartial(packet, options); case FrameMarker.Inter: case FrameMarker.Final: return this.pushToPartial(packet); } } reset() { this.partials.clear(); } peekOldestPartialFrameNumber() { const first = this.partials.keys().next(); return first.done ? null : first.value; } frameFromSingle(packet, options) { var _a; if (packet.header.marker !== FrameMarker.Single) { // @throws-transformer ignore - this should be treated as a "panic" and not be caught throw new Error("Depacketizer.frameFromSingle: packet.header.marker was not FrameMarker.Single, found ".concat(packet.header.marker, ".")); } // A `Single` packet is a self-contained frame and doesn't reserve a partials slot, but if // the partials map is at capacity, treat it as a signal that the oldest in-flight partial // is stale and evict it (matches `main`'s behavior when `maxPartialFrames` // defaults to 1). const maxPartialFrames = (_a = options === null || options === void 0 ? void 0 : options.maxPartialFrames) !== null && _a !== void 0 ? _a : 1; if (this.partials.size >= maxPartialFrames) { const oldestPartialFrameNumber = this.peekOldestPartialFrameNumber(); if (typeof oldestPartialFrameNumber !== 'number') { // @throws-transformer ignore - this should be treated as a "panic" and not be caught throw new Error("Depacketizer.frameFromSingle: no oldest frame number found, but partials.size is ".concat(this.partials.size, ".")); } this.partials.delete(oldestPartialFrameNumber); if (options === null || options === void 0 ? void 0 : options.throwOnInterruption) { throw DataTrackDepacketizerDropError.interrupted(oldestPartialFrameNumber, packet.header.frameNumber.value); } log$3.warn("Data track frame ".concat(oldestPartialFrameNumber, " was interrupted by single-packet frame ").concat(packet.header.frameNumber.value, ", dropping.")); } return { payload: packet.payload, extensions: packet.header.extensions }; } /** Begin assembling a new packet. */ beginPartial(packet, options) { var _a; if (packet.header.marker !== FrameMarker.Start) { // @throws-transformer ignore - this should be treated as a "panic" and not be caught throw new Error("Depacketizer.beginPartial: packet.header.marker was not FrameMarker.Start, found ".concat(packet.header.marker, ".")); } const startSequence = packet.header.sequence; const frameNumber = packet.header.frameNumber.value; const partial = { startSequence, extensions: packet.header.extensions, payloads: new Map([[startSequence.value, packet.payload]]) }; // Loop in case `maxPartialFrames` shrunk relative to a previous push call - evict the // oldest partials until there is room for the new one. With `throwOnInterruption` set the // throw inside the loop short-circuits on the first eviction, matching the single-eviction // behavior callers expect when they ask to be told about interruptions. const maxPartialFrames = (_a = options === null || options === void 0 ? void 0 : options.maxPartialFrames) !== null && _a !== void 0 ? _a : 1; while (this.partials.size >= maxPartialFrames) { const oldestPartialFrameNumber = this.peekOldestPartialFrameNumber(); if (typeof oldestPartialFrameNumber !== 'number') { // partials map is empty - nothing more to evict break; } this.partials.delete(oldestPartialFrameNumber); if (options === null || options === void 0 ? void 0 : options.throwOnInterruption) { throw DataTrackDepacketizerDropError.interrupted(oldestPartialFrameNumber, frameNumber); } log$3.warn("Data track partials full (max ".concat(maxPartialFrames, "), evicted oldest frame ").concat(oldestPartialFrameNumber, " to make room for new frame ").concat(frameNumber, ".")); } this.partials.set(frameNumber, partial); return null; } /** Push to the existing partial frame. */ pushToPartial(packet) { if (packet.header.marker !== FrameMarker.Inter && packet.header.marker !== FrameMarker.Final) { // @throws-transformer ignore - this should be treated as a "panic" and not be caught throw new Error("Depacketizer.pushToPartial: packet.header.marker was not FrameMarker.Inter or FrameMarker.Final, found ".concat(packet.header.marker, ".")); } const packetFrameNumber = packet.header.frameNumber.value; const matchingPartial = this.partials.get(packetFrameNumber); if (!matchingPartial) { this.partials.delete(packetFrameNumber); throw DataTrackDepacketizerDropError.unknownFrame(packetFrameNumber); } // NOTE: this check will block reprocessing packets with duplicate sequence values if the // buffer is full already, which could maybe be problematic for very large frames. if (matchingPartial.payloads.size >= DataTrackDepacketizer.MAX_BUFFER_PACKETS) { this.partials.delete(packetFrameNumber); throw DataTrackDepacketizerDropError.bufferFull(packetFrameNumber); } // Note: receiving a packet with a duplicate `sequence` value is something that likely won't // happen in actual use, but even if it does (maybe a low level network retransmission?) the // last packet with a given sequence received should always win. if (matchingPartial.payloads.has(packet.header.sequence.value)) { log$3.warn("Data track frame ".concat(packetFrameNumber, " received duplicate packet for sequence ").concat(packet.header.sequence.value, ", so replacing with newly received packet.")); } matchingPartial.payloads.set(packet.header.sequence.value, packet.payload); if (packet.header.marker === FrameMarker.Final) { return this.finalize(packetFrameNumber, matchingPartial, packet.header.sequence.value); } return null; } /** Try to reassemble the complete frame. */ finalize(partialFrameNumber, partial, endSequence) { const received = partial.payloads.size; let payloadLengthBytes = 0; for (const p of partial.payloads.values()) { payloadLengthBytes += p.length; } const payload = new Uint8Array(payloadLengthBytes); let sequencePointer = partial.startSequence.clone(); let payloadOffsetPointerBytes = 0; while (true) { const partialPayload = partial.payloads.get(sequencePointer.value); if (!partialPayload) { break; } partial.payloads.delete(sequencePointer.value); const payloadRemainingBytes = payload.length - payloadOffsetPointerBytes; if (partialPayload.length > payloadRemainingBytes) { // @throws-transformer ignore - this should be treated as a "panic" and not be caught throw new Error("Depacketizer.finalize: Expected at least ".concat(partialPayload.length, " more bytes left in the payload buffer, only got ").concat(payloadRemainingBytes, " bytes.")); } payload.set(partialPayload, payloadOffsetPointerBytes); payloadOffsetPointerBytes += partialPayload.length; // NOTE: sequencePointer could wrap around, which is why this isn't a "<" if (sequencePointer.value != endSequence) { sequencePointer.increment(); continue; } // The packet is done processing, reset the state so another frame can be processed next. this.partials.delete(partialFrameNumber); return { payload, extensions: partial.extensions }; } this.partials.delete(partialFrameNumber); throw DataTrackDepacketizerDropError.incomplete(partialFrameNumber, received, endSequence - partial.startSequence.value + 1); } } /** Maximum number of packets to buffer per frame before dropping. */ DataTrackDepacketizer.MAX_BUFFER_PACKETS = 128;var DataTrackSubscribeErrorReason; (function (DataTrackSubscribeErrorReason) { /** The track has been unpublished and is no longer available */ DataTrackSubscribeErrorReason[DataTrackSubscribeErrorReason["Unpublished"] = 0] = "Unpublished"; /** Request to subscribe to data track timed-out */ DataTrackSubscribeErrorReason[DataTrackSubscribeErrorReason["Timeout"] = 1] = "Timeout"; /** Cannot subscribe to data track when disconnected */ DataTrackSubscribeErrorReason[DataTrackSubscribeErrorReason["Disconnected"] = 2] = "Disconnected"; /** Subscription to data track cancelled by caller */ DataTrackSubscribeErrorReason[DataTrackSubscribeErrorReason["Cancelled"] = 4] = "Cancelled"; })(DataTrackSubscribeErrorReason || (DataTrackSubscribeErrorReason = {})); class DataTrackSubscribeError extends LivekitReasonedError { constructor(message, reason, options) { super(22, message, options); this.name = 'DataTrackSubscribeError'; this.reason = reason; this.reasonName = DataTrackSubscribeErrorReason[reason]; } static unpublished() { return new DataTrackSubscribeError('The track has been unpublished and is no longer available', DataTrackSubscribeErrorReason.Unpublished); } static timeout() { return new DataTrackSubscribeError('Request to subscribe to data track timed-out', DataTrackSubscribeErrorReason.Timeout); } static disconnected() { return new DataTrackSubscribeError('Cannot subscribe to data track when disconnected', DataTrackSubscribeErrorReason.Disconnected); } // NOTE: this was introduced by web / there isn't a corresponding case in the rust version. static cancelled() { return new DataTrackSubscribeError('Subscription to data track cancelled by caller', DataTrackSubscribeErrorReason.Cancelled); } }const log$2 = getLogger(LoggerNames.DataTracks); /** * Pipeline for an individual data track subscription. */ class IncomingDataTrackPipeline { /** * Creates a new pipeline with the given options. */ constructor(options) { var _a, _b; const hasProvider = options.e2eeManager !== null; if (options.info.usesE2ee !== hasProvider) { // @throws-transformer ignore - this should be treated as a "panic" and not be caught throw new Error('IncomingDataTrackPipeline: DataTrackInfo.usesE2ee must match presence of decryptionProvider'); } const depacketizer = new DataTrackDepacketizer(); this.publisherIdentity = options.publisherIdentity; this.e2eeManager = (_a = options.e2eeManager) !== null && _a !== void 0 ? _a : null; this.depacketizer = depacketizer; this.options = (_b = options.pipelineOptions) !== null && _b !== void 0 ? _b : {}; } updateE2eeManager(e2eeManager) { this.e2eeManager = e2eeManager; } setOptions(options) { this.options = options; } processPacket(packet) { return __awaiter(this, void 0, void 0, function* () { const frame = this.depacketize(packet); if (!frame) { return null; } const decrypted = yield this.decryptIfNeeded(frame); if (!decrypted) { return null; } return decrypted; }); } /** * Depacketize the given frame, log if a drop occurs. */ depacketize(packet) { let frame; try { frame = this.depacketizer.push(packet, { throwOnInterruption: false, maxPartialFrames: this.options.maxPartialFrames }); } catch (err) { // In a future version, use this to maintain drop statistics. // FIXME: is this a good idea? log$2.warn("Data frame depacketize error: ".concat(err)); return null; } return frame; } /** * Decrypt the frame's payload if E2EE is enabled for this track. */ decryptIfNeeded(frame) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; const e2eeManager = this.e2eeManager; if (!e2eeManager) { return frame; } const e2ee = (_b = (_a = frame.extensions) === null || _a === void 0 ? void 0 : _a.e2ee) !== null && _b !== void 0 ? _b : null; if (!e2ee) { log$2.error('Missing E2EE meta'); return null; } let result; try { result = yield e2eeManager.handleEncryptedData(frame.payload, e2ee.iv, this.publisherIdentity, e2ee.keyIndex); } catch (err) { log$2.error("Error decrypting packet: ".concat(err)); return null; } frame.payload = result.payload; return frame; }); } }const log$1 = getLogger(LoggerNames.DataTracks); /** How long to wait when attempting to subscribe before timing out. */ const SUBSCRIBE_TIMEOUT_MILLISECONDS = 10000; /** Maximum number of {@link DataTrackFrame}s that are cached for each ReadableStream subscription. * If data comes in too fast and saturates this threshold, backpressure will be applied. */ const READABLE_STREAM_DEFAULT_BUFFER_SIZE = 16; class IncomingDataTrackManager extends eventsExports.EventEmitter { constructor(options) { var _a; super(); /** Mapping between track SID and descriptor. */ this.descriptors = new Map(); /** Mapping between subscriber handle and track SID. * * This is an index that allows track descriptors to be looked up * by subscriber handle in O(1) time, to make routing incoming packets * (a hot code path) faster. */ this.subscriptionHandles = new Map(); this.e2eeManager = (_a = options === null || options === void 0 ? void 0 : options.e2eeManager) !== null && _a !== void 0 ? _a : null; } /** @internal */ updateE2eeManager(e2eeManager) { this.e2eeManager = e2eeManager; // Propegate downwards to all pre-existing pipelines for (const descriptor of this.descriptors.values()) { if (descriptor.subscription.type === 'active') { descriptor.subscription.pipeline.updateE2eeManager(e2eeManager); } } } /** @internal */ setPipelineOptions(sid, options) { const descriptor = this.descriptors.get(sid); if (!descriptor) { log$1.warn("Unknown track ".concat(sid, ", cannot set pipeline options.")); return; } descriptor.pipelineOptions = options; if (descriptor.subscription.type === 'active') { descriptor.subscription.pipeline.setOptions(options); } } /** Allocates a ReadableStream which emits when a new {@link DataTrackFrame} is received from the * SFU. The SFU subscription is initiated lazily when the stream is created. * * @returns A tuple of the ReadableStream and a Promise that resolves once the SFU subscription * is fully established / the stream is ready to receive frames. * * @internal **/ openSubscriptionStream(sid, signal) { let bufferSize = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : READABLE_STREAM_DEFAULT_BUFFER_SIZE; let streamController = null; const sfuSubscriptionComplete = new Future(); const detachSignal = () => { signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort); }; const cleanup = () => { detachSignal(); if (!streamController) { log$1.warn("ReadableStream subscribed to ".concat(sid, " was not started.")); return; } const descriptor = this.descriptors.get(sid); if (!descriptor) { log$1.warn("Unknown track ".concat(sid, ", skipping cancel...")); return; } if (descriptor.subscription.type !== 'active') { log$1.warn("Subscription for track ".concat(sid, " is not active, skipping cancel...")); return; } descriptor.subscription.streamControllers.delete(streamController); // If no active stream controllers are left, also unsubscribe on the SFU end. if (descriptor.subscription.streamControllers.size === 0) { this.unSubscribeRequest(descriptor.info.sid); } }; const onAbort = () => { var _a; if (!streamController) { return; } const currentDescriptor = this.descriptors.get(sid); if ((currentDescriptor === null || currentDescriptor === void 0 ? void 0 : currentDescriptor.subscription.type) === 'active') { currentDescriptor.subscription.streamControllers.delete(streamController); } streamController.error(DataTrackSubscribeError.cancelled()); (_a = sfuSubscriptionComplete.reject) === null || _a === void 0 ? void 0 : _a.call(sfuSubscriptionComplete, DataTrackSubscribeError.cancelled()); cleanup(); }; const stream = new ReadableStream({ start: controller => { streamController = controller; this.subscribeRequest(sid, signal).then(() => __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; const descriptor = this.descriptors.get(sid); if (!descriptor) { log$1.error("Unknown track ".concat(sid)); const err = DataTrackSubscribeError.disconnected(); controller.error(err); (_a = sfuSubscriptionComplete.reject) === null || _a === void 0 ? void 0 : _a.call(sfuSubscriptionComplete, err); return; } if (descriptor.subscription.type !== 'active') { log$1.error("Subscription for track ".concat(sid, " is not active")); const err = DataTrackSubscribeError.disconnected(); controller.error(err); (_b = sfuSubscriptionComplete.reject) === null || _b === void 0 ? void 0 : _b.call(sfuSubscriptionComplete, err); return; } // Attach the abort signal, aborting immediately if the abort signal was fired while // subscribeRequest was in flight. if (signal === null || signal === void 0 ? void 0 : signal.aborted) { onAbort(); return; } signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', onAbort); descriptor.subscription.streamControllers.set(controller, detachSignal); (_c = sfuSubscriptionComplete.resolve) === null || _c === void 0 ? void 0 : _c.call(sfuSubscriptionComplete); })).catch(err => { var _a; // subscribeRequest rejected (cancelled, timed out, disconnected). The signal // listener was never attached in this path, so nothing to detach. controller.error(err); (_a = sfuSubscriptionComplete.reject) === null || _a === void 0 ? void 0 : _a.call(sfuSubscriptionComplete, err); }); }, cancel: () => { cleanup(); } }, new CountQueuingStrategy({ highWaterMark: bufferSize })); return [stream, sfuSubscriptionComplete.promise]; } /** Client requested to subscribe to a data track. * * This is sent when the user calls {@link RemoteDataTrack.subscribe}. * * Only the first request to subscribe to a given track incurs meaningful overhead; subsequent * requests simply attach an additional receiver to the broadcast channel, allowing them to consume * frames from the existing subscription pipeline. */ subscribeRequest(sid, signal) { return __awaiter(this, void 0, void 0, function* () { const descriptor = this.descriptors.get(sid); if (!descriptor) { // @throws-transformer ignore - this should be treated as a "panic" and not be caught throw new Error('Cannot subscribe to unknown track'); } const waitForCompletionFuture = (currentDescriptor, userProvidedSignal, timeoutSignal) => __awaiter(this, void 0, void 0, function* () { if (currentDescriptor.subscription.type === 'active') { // Subscription has already become active! So bail out early, there is nothing to wait for. return; } if (currentDescriptor.subscription.type !== 'pending') { // @throws-transformer ignore - this should be treated as a "panic" and not be caught throw new Error("Descriptor for track ".concat(sid, " is not pending, found ").concat(currentDescriptor.subscription.type)); } const combinedSignal = abortSignalAny([userProvidedSignal, timeoutSignal].filter(s => typeof s !== 'undefined')); const proxiedCompletionFuture = new Future(); currentDescriptor.subscription.completionFuture.promise.then(() => { var _a; return (_a = proxiedCompletionFuture.resolve) === null || _a === void 0 ? void 0 : _a.call(proxiedCompletionFuture); }).catch(err => { var _a; return (_a = proxiedCompletionFuture.reject) === null || _a === void 0 ? void 0 : _a.call(proxiedCompletionFuture, err); }); const onAbort = () => { var _a; if (currentDescriptor.subscription.type !== 'pending') { return; } currentDescriptor.subscription.pendingRequestCount -= 1; if (timeoutSignal === null || timeoutSignal === void 0 ? void 0 : timeoutSignal.aborted) { // A timeout should apply to the underlying SFU subscription and cancel all user // subscriptions. currentDescriptor.subscription.cancel(); return; } if (currentDescriptor.subscription.pendingRequestCount <= 0) { // No user subscriptions are still pending, so cancel the underlying pending `sfuUpdateSubscription` currentDescriptor.subscription.cancel(); return; } // Other subscriptions are still pending for this data track, so just cancel this one // active user subscription, and leave the rest of the user subscriptions alone. (_a = proxiedCompletionFuture.reject) === null || _a === void 0 ? void 0 : _a.call(proxiedCompletionFuture, DataTrackSubscribeError.cancelled()); }; if (combinedSignal.aborted) { onAbort(); } combinedSignal.addEventListener('abort', onAbort); yield proxiedCompletionFuture.promise; combinedSignal.removeEventListener('abort', onAbort); }); switch (descriptor.subscription.type) { case 'none': { descriptor.subscription = { type: 'pending', completionFuture: new Future(), pendingRequestCount: 1, cancel: () => { var _a, _b; const previousDescriptorSubscription = descriptor.subscription; descriptor.subscription = { type: 'none' }; // Let the SFU know that the subscribe has been cancelled this.emit('sfuUpdateSubscription', { sid, subscribe: false }); if (previousDescriptorSubscription.type === 'pending') { (_b = (_a = previousDescriptorSubscription.completionFuture).reject) === null || _b === void 0 ? void 0 : _b.call(_a, timeoutSignal.aborted ? DataTrackSubscribeError.timeout() : // NOTE: the below cancelled case was introduced by web / there isn't a corresponding case in the rust version. DataTrackSubscribeError.cancelled()); } } }; this.emit('sfuUpdateSubscription', { sid, subscribe: true }); const timeoutSignal = abortSignalTimeout(SUBSCRIBE_TIMEOUT_MILLISECONDS); // Wait for the subscription to complete, or time out if it takes too long yield waitForCompletionFuture(descriptor, signal, timeoutSignal); return; } case 'pending': { descriptor.subscription.pendingRequestCount += 1; // Wait for the subscription to complete yield waitForCompletionFuture(descriptor, signal); return; } case 'active': { return; } } }); } /** * Get information about all currently subscribed tracks. * @internal */ querySubscribed() { return __awaiter(this, void 0, void 0, function* () { const descriptorInfos = Array.from(this.descriptors.values()).filter(descriptor => descriptor.subscription.type === 'active').map(descriptor => [descriptor.info, descriptor.publisherIdentity]); return descriptorInfos; }); } /** Client requested to unsubscribe from a data track. */ unSubscribeRequest(sid) { var _a; const descriptor = this.descriptors.get(sid); if (!descriptor) { // FIXME: rust implementation returns here, not throws // @throws-transformer ignore - this should be treated as a "panic" and not be caught throw new Error('Cannot subscribe to unknown track'); } if (descriptor.subscription.type !== 'active') { log$1.warn("Unexpected descriptor state in unSubscribeRequest, expected active, found ".concat((_a = descriptor.subscription) === null || _a === void 0 ? void 0 : _a.type)); return; } this.closeStreamControllers(descriptor.subscription.streamControllers, sid); // FIXME: this might be wrong? Shouldn't this only occur if it is the last subscription to // terminate? const previousDescriptorSubscription = descriptor.subscription; descriptor.subscription = { type: 'none' }; this.subscriptionHandles.delete(previousDescriptorSubscription.subcriptionHandle); this.emit('sfuUpdateSubscription', { sid, subscribe: false }); } /** Detach abort-signal listeners and close all downstream stream controllers for an active * subscription. Used when the subscription is being torn down by the manager (unsubscribe, * unpublish, or shutdown). */ closeStreamControllers(streamControllers, sid) { for (const _ref of streamControllers) { var _ref2 = _slicedToArray(_ref, 2); const controller = _ref2[0]; const detachSignal = _ref2[1]; // Detach before close so we don't leak a listener on the user's AbortSignal. detachSignal(); try { controller.close(); } catch (err) { // Defensive: if the controller has already been errored (e.g. by a racing abort whose // listener removed itself before we got here), close() throws. There's nothing // meaningful to do other than log — the stream is already terminal. log$1.warn("Failed to close readable stream for track ".concat(sid, ": ").concat(err)); } } } /** SFU notification that track publications have changed. * * This event is produced from both {@link JoinResponse} and {@link ParticipantUpdate} * to provide a complete view of remote participants' track publications: * * - From a `JoinResponse`, it captures the initial set of tracks published when a participant joins. * - From a `ParticipantUpdate`, it captures subsequent changes (i.e., new tracks being * published and existing tracks unpublished). */ receiveSfuPublicationUpdates(updates) { return __awaiter(this, void 0, void 0, function* () { if (updates.size === 0) { return; } // Detect published track const publisherParticipantToSidsInUpdate = new Map(); for (const _ref3 of updates.entries()) { var _ref4 = _slicedToArray(_ref3, 2); const publisherIdentity = _ref4[0]; const infos = _ref4[1]; const sidsInUpdate = new Set(); for (const info of infos) { sidsInUpdate.add(info.sid); if (this.descriptors.has(info.sid)) { continue; } yield this.handleTrackPublished(publisherIdentity, info); } publisherParticipantToSidsInUpdate.set(publisherIdentity, sidsInUpdate); } // Detect unpublished tracks for (const _ref5 of publisherParticipantToSidsInUpdate.entries()) { var _ref6 = _slicedToArray(_ref5, 2); const publisherIdentity = _ref6[0]; const sidsInUpdate = _ref6[1]; const descriptorsForPublisher = Array.from(this.descriptors.entries()).filter(_ref7 => { let _ref8 = _slicedToArray(_ref7, 2); _ref8[0]; let descriptor = _ref8[1]; return descriptor.publisherIdentity === publisherIdentity; }).map(_ref9 => { let _ref0 = _slicedToArray(_ref9, 1), sid = _ref0[0]; return sid; }); let unpublishedSids = descriptorsForPublisher.filter(sid => !sidsInUpdate.has(sid)); for (const sid of unpublishedSids) { this.handleTrackUnpublished(sid); } } }); } /** * Get information about all currently remotely published tracks which could be subscribed to. * @internal */ queryPublications() { return __awaiter(this, void 0, void 0, function* () { return Array.from(this.descriptors.values()).map(descriptor => descriptor.info); }); } handleTrackPublished(publisherIdentity, info) { return __awaiter(this, void 0, void 0, function* () { if (this.descriptors.has(info.sid)) { log$1.error("Existing descriptor for track ".concat(info.sid)); return; } let descriptor = { info, publisherIdentity, subscription: { type: 'none' }, pipelineOptions: {} }; this.descriptors.set(descriptor.info.sid, descriptor); const track = new RemoteDataTrack(descriptor.info, this, { publisherIdentity }); this.emit('trackPublished', { track }); }); } handleTrackUnpublished(sid) { const descriptor = this.descriptors.get(sid); if (!descriptor) { log$1.error("Unknown track ".concat(sid)); return; } this.descriptors.delete(sid); if (descriptor.subscription.type === 'active') { this.closeStreamControllers(descriptor.subscription.streamControllers, sid); this.subscriptionHandles.delete(descriptor.subscription.subcriptionHandle); } this.emit('trackUnpublished', { sid, publisherIdentity: descriptor.publisherIdentity }); } /** SFU notification that handles have been assigned for requested subscriptions. */ receivedSfuSubscriberHandles( /** Mapping between track handles attached to incoming packets to the * track SIDs they belong to. */ mapping) { for (const _ref1 of mapping.entries()) { var _ref10 = _slicedToArray(_ref1, 2); const handle = _ref10[0]; const sid = _ref10[1]; this.registerSubscriberHandle(handle, sid); } } registerSubscriberHandle(assignedHandle, sid) { var _a, _b; const descriptor = this.descriptors.get(sid); if (!descriptor) { log$1.error("Unknown track ".concat(sid)); return; } switch (descriptor.subscription.type) { case 'none': { // Handle assigned when there is no pending or active subscription is unexpected. log$1.warn("No subscription for ".concat(sid)); return; } case 'active': { // Update handle for an active subscription. This can occur following a full reconnect. descriptor.subscription.subcriptionHandle = assignedHandle; this.subscriptionHandles.set(assignedHandle, sid); return; } case 'pending': { const pipeline = new IncomingDataTrackPipeline({ info: descriptor.info, publisherIdentity: descriptor.publisherIdentity, e2eeManager: this.e2eeManager, pipelineOptions: descriptor.pipelineOptions }); const previousDescriptorSubscription = descriptor.subscription; descriptor.subscription = { type: 'active', subcriptionHandle: assignedHandle, pipeline, streamControllers: new Map() }; this.subscriptionHandles.set(assignedHandle, sid); (_b = (_a = previousDescriptorSubscription.completionFuture).resolve) === null || _b === void 0 ? void 0 : _b.call(_a); } } } /** Packet has been received over the transport. */ packetReceived(bytes) { return __awaiter(this, void 0, void 0, function* () { let packet; try { var _DataTrackPacket$from = DataTrackPacket.fromBinary(bytes); var _DataTrackPacket$from2 = _slicedToArray(_DataTrackPacket$from, 1); packet = _DataTrackPacket$from2[0]; } catch (err) { log$1.error("Failed to deserialize packet: ".concat(err)); return; } const sid = this.subscriptionHandles.get(packet.header.trackHandle); if (!sid) { log$1.warn("Unknown subscriber handle ".concat(packet.header.trackHandle)); return; } const descriptor = this.descriptors.get(sid); if (!descriptor) { log$1.error("Missing descriptor for track ".concat(sid)); return; } if (descriptor.subscription.type !== 'active') { log$1.warn("Received packet for track ".concat(sid, " without active subscription")); return; } const internalFrame = yield descriptor.subscription.pipeline.processPacket(packet); if (!internalFrame) { // Not all packets have been received yet to form a complete frame return; } // Broadcast to all downstream subscribers for (const controller of descriptor.subscription.streamControllers.keys()) { if (controller.desiredSize !== null && controller.desiredSize <= 0) { log$1.warn("Cannot send frame to subscribers: readable stream is full (desiredSize is ".concat(controller.desiredSize, "). To increase this threshold, set a higher 'options.highWaterMark' when calling .subscribe().")); continue; } const frame = DataTrackFrameInternal.lossyIntoFrame(internalFrame); controller.enqueue(frame); } }); } /** Resend all subscription updates. * * This must be sent after a full reconnect to ensure the SFU knows which * tracks are subscribed to locally. */ resendSubscriptionUpdates() { for (const _ref11 of this.descriptors) { var _ref12 = _slicedToArray(_ref11, 2); const sid = _ref12[0]; const descriptor = _ref12[1]; if (descriptor.subscription.type === 'none') { continue; } this.emit('sfuUpdateSubscription', { sid, subscribe: true }); } } /** Called when a remote participant is disconnected so that any pending data tracks can be * cancelled. */ handleRemoteParticipantDisconnected(remoteParticipantIdentity) { var _a, _b; for (const descriptor of this.descriptors.values()) { if (descriptor.publisherIdentity !== remoteParticipantIdentity) { continue; } switch (descriptor.subscription.type) { case 'none': break; case 'pending': (_b = (_a = descriptor.subscription.completionFuture).reject) === null || _b === void 0 ? void 0 : _b.call(_a, DataTrackSubscribeError.disconnected()); break; case 'active': this.unSubscribeRequest(descriptor.info.sid); break; } } } /** Resets the manager, ending any subscriptions, and getting it ready for the next room * connection. */ reset() { var _a, _b; for (const descriptor of this.descriptors.values()) { this.emit('trackUnpublished', { sid: descriptor.info.sid, publisherIdentity: descriptor.publisherIdentity }); if (descriptor.subscription.type === 'pending') { (_b = (_a = descriptor.subscription.completionFuture).reject) === null || _b === void 0 ? void 0 : _b.call(_a, DataTrackSubscribeError.disconnected()); } if (descriptor.subscription.type === 'active') { this.closeStreamControllers(descriptor.subscription.streamControllers, descriptor.info.sid); } } this.descriptors.clear(); this.subscriptionHandles.clear(); } }class DataTrackPacketizerError extends LivekitReasonedError { constructor(message, reason, options) { super(19, message, options); this.name = 'DataTrackPacketizerError'; this.reason = reason; this.reasonName = DataTrackPacketizerReason[reason]; } static mtuTooShort() { return new DataTrackPacketizerError('MTU is too short to send frame', DataTrackPacketizerReason.MtuTooShort); } } var DataTrackPacketizerReason; (function (DataTrackPacketizerReason) { DataTrackPacketizerReason[DataTrackPacketizerReason["MtuTooShort"] = 0] = "MtuTooShort"; })(DataTrackPacketizerReason || (DataTrackPacketizerReason = {})); /** A packetizer takes a {@link DataTrackFrameInternal} as input and generates a series * of {@link DataTrackPacket}s for transmission to other clients over webrtc. */ class DataTrackPacketizer { constructor(trackHandle, mtuSizeBytes) { this.sequence = WrapAroundUnsignedInt.u16(0); this.frameNumber = WrapAroundUnsignedInt.u16(0); this.clock = DataTrackClock.rtpStartingNow(DataTrackTimestamp.rtpRandom()); this.handle = trackHandle; this.mtuSizeBytes = mtuSizeBytes; } /** @internal */ static computeFrameMarker(index, packetCount) { if (packetCount <= 1) { return FrameMarker.Single; } if (index === 0) { return FrameMarker.Start; } else if (index === packetCount - 1) { return FrameMarker.Final; } else { return FrameMarker.Inter; } } /** Generates a series of packets for the specified {@link DataTrackFrameInternal}. * * NOTE: The return value of this function is a generator, so it can be lazily ran if desired, * or converted to an array with {@link Array.from}. */ *packetize(frame, options) { var _a; const frameNumber = this.frameNumber.getThenIncrement(); const headerParams = { marker: FrameMarker.Inter, trackHandle: this.handle, sequence: WrapAroundUnsignedInt.u16(0), frameNumber, timestamp: (_a = options === null || options === void 0 ? void 0 : options.now) !== null && _a !== void 0 ? _a : this.clock.now(), extensions: frame.extensions }; const headerSerializedLengthBytes = new DataTrackPacketHeader(headerParams).toBinaryLengthBytes(); if (headerSerializedLengthBytes >= this.mtuSizeBytes) { throw DataTrackPacketizerError.mtuTooShort(); } const maxPayloadSizeBytes = this.mtuSizeBytes - headerSerializedLengthBytes; const packetCount = Math.ceil(frame.payload.byteLength / maxPayloadSizeBytes); for (let index = 0, indexBytes = 0; indexBytes < frame.payload.byteLength; _ref = [index + 1, indexBytes + maxPayloadSizeBytes], index = _ref[0], indexBytes = _ref[1], _ref) { var _ref; const sequence = this.sequence.getThenIncrement(); const packetHeader = new DataTrackPacketHeader(Object.assign(Object.assign({}, headerParams), { marker: DataTrackPacketizer.computeFrameMarker(index, packetCount), sequence })); const packetPayloadLengthBytes = Math.min( // All but the last packet will be max length ... maxPayloadSizeBytes, // ... and the last packet will be as long as it needs to be to finish out the buffer. frame.payload.byteLength - indexBytes); const packetPayload = new Uint8Array(frame.payload.buffer, frame.payload.byteOffset + indexBytes, packetPayloadLengthBytes); yield new DataTrackPacket(packetHeader, packetPayload); } } }var DataTrackPublishErrorReason; (function (DataTrackPublishErrorReason) { /** * Local participant does not have permission to publish data tracks. * * Ensure the participant's token contains the `canPublishData` grant. */ DataTrackPublishErrorReason[DataTrackPublishErrorReason["NotAllowed"] = 0] = "NotAllowed"; /** A track with the same name is already published by the local participant. */ DataTrackPublishErrorReason[DataTrackPublishErrorReason["DuplicateName"] = 1] = "DuplicateName"; /** Request to publish the track took long to complete. */ DataTrackPublishErrorReason[DataTrackPublishErrorReason["Timeout"] = 2] = "Timeout"; /** No additional data tracks can be published by the local participant. */ DataTrackPublishErrorReason[DataTrackPublishErrorReason["LimitReached"] = 3] = "LimitReached"; /** Cannot publish data track when the room is disconnected. */ DataTrackPublishErrorReason[DataTrackPublishErrorReason["Disconnected"] = 4] = "Disconnected"; // NOTE: this was introduced by web / there isn't a corresponding case in the rust version. DataTrackPublishErrorReason[DataTrackPublishErrorReason["Cancelled"] = 5] = "Cancelled"; /** The name requested is not able to be used when creating the data track. */ DataTrackPublishErrorReason[DataTrackPublishErrorReason["InvalidName"] = 6] = "InvalidName"; /** There was an error publishing, but it was not something that could be sorted into a known * category. */ DataTrackPublishErrorReason[DataTrackPublishErrorReason["Unknown"] = 7] = "Unknown"; })(DataTrackPublishErrorReason || (DataTrackPublishErrorReason = {})); class DataTrackPublishError extends LivekitReasonedError { constructor(message, reason, options) { super(21, message, options); this.name = 'DataTrackPublishError'; this.reason = reason; this.reasonName = DataTrackPublishErrorReason[reason]; this.rawMessage = options === null || options === void 0 ? void 0 : options.rawMessage; } static notAllowed(rawMessage) { return new DataTrackPublishError('Data track publishing unauthorized', DataTrackPublishErrorReason.NotAllowed, { rawMessage }); } static duplicateName(rawMessage) { return new DataTrackPublishError('Track name already taken', DataTrackPublishErrorReason.DuplicateName, { rawMessage }); } static invalidName(rawMessage) { return new DataTrackPublishError('Track name is invalid', DataTrackPublishErrorReason.InvalidName, { rawMessage }); } static timeout() { return new DataTrackPublishError('Publish data track timed-out. Does the LiveKit server support data tracks?', DataTrackPublishErrorReason.Timeout); } static limitReached(rawMessage) { return new DataTrackPublishError('Data track publication limit reached', DataTrackPublishErrorReason.LimitReached, { rawMessage }); } static unknown(reason, message) { return new DataTrackPublishError("Received RequestResponse for publishDataTrack, but reason was unrecognised (".concat(reason, ", ").concat(message, ")"), DataTrackPublishErrorReason.Unknown); } static disconnected() { return new DataTrackPublishError('Room disconnected', DataTrackPublishErrorReason.Disconnected); } // NOTE: this was introduced by web / there isn't a corresponding case in the rust version. static cancelled() { return new DataTrackPublishError('Publish data track cancelled by caller', DataTrackPublishErrorReason.Cancelled); } } var DataTrackPushFrameErrorReason; (function (DataTrackPushFrameErrorReason) { /** Track is no longer published. */ DataTrackPushFrameErrorReason[DataTrackPushFrameErrorReason["TrackUnpublished"] = 0] = "TrackUnpublished"; /** Frame was dropped. */ // NOTE: this should become a web specific error, the rust version of this "dropped" error means // something different and will be renamed to "QueueFull". DataTrackPushFrameErrorReason[DataTrackPushFrameErrorReason["Dropped"] = 1] = "Dropped"; })(DataTrackPushFrameErrorReason || (DataTrackPushFrameErrorReason = {})); class DataTrackPushFrameError extends LivekitReasonedError { constructor(message, reason, options) { super(22, message, options); this.name = 'DataTrackPushFrameError'; this.reason = reason; this.reasonName = DataTrackPushFrameErrorReason[reason]; } static trackUnpublished() { return new DataTrackPushFrameError('Track is no longer published', DataTrackPushFrameErrorReason.TrackUnpublished); } static dropped(cause) { return new DataTrackPushFrameError('Frame was dropped', DataTrackPushFrameErrorReason.Dropped, { cause }); } } var DataTrackOutgoingPipelineErrorReason; (function (DataTrackOutgoingPipelineErrorReason) { DataTrackOutgoingPipelineErrorReason[DataTrackOutgoingPipelineErrorReason["Packetizer"] = 0] = "Packetizer"; DataTrackOutgoingPipelineErrorReason[DataTrackOutgoingPipelineErrorReason["Encryption"] = 1] = "Encryption"; })(DataTrackOutgoingPipelineErrorReason || (DataTrackOutgoingPipelineErrorReason = {})); class DataTrackOutgoingPipelineError extends LivekitReasonedError { constructor(message, reason, options) { super(21, message, options); this.name = 'DataTrackOutgoingPipelineError'; this.reason = reason; this.reasonName = DataTrackOutgoingPipelineErrorReason[reason]; } static packetizer(cause) { return new DataTrackOutgoingPipelineError('Error packetizing frame', DataTrackOutgoingPipelineErrorReason.Packetizer, { cause }); } static encryption(cause) { return new DataTrackOutgoingPipelineError('Error encrypting frame', DataTrackOutgoingPipelineErrorReason.Encryption, { cause }); } }class LocalDataTrack { /** @internal */ constructor(options, manager) { this.trackSymbol = TrackSymbol; this.isLocal = true; this.typeSymbol = DataTrackSymbol; /** Represents the currently active {@link DataTrackHandle} for the publication. */ this.handle = null; this.log = livekitLogger; /** Resolves once the data track has sent all pending packets the rtc data channel buffer. */ this.flushedFuture = new Future(); this.isFlushed = true; this.handleManagerReset = () => { var _a, _b; // When the associated manager resets, mark any in flight flushes as complete // There's nothing actionable a user can do to get these to complete so no // error is being thrown. (_b = (_a = this.flushedFuture).resolve) === null || _b === void 0 ? void 0 : _b.call(_a); this.manager.off('packetsFlushedChange', this.handleManagerPacketsFlushedChange); this.manager.off('reset', this.handleManagerReset); }; this.handleManagerPacketsFlushedChange = event => { var _a, _b; this.isFlushed = event.isFlushed; if (event.isFlushed) { (_b = (_a = this.flushedFuture).resolve) === null || _b === void 0 ? void 0 : _b.call(_a); this.flushedFuture = new Future(); } }; this.options = options; this.manager = manager; this.log = getLogger(LoggerNames.DataTracks); this.manager.on('packetsFlushedChange', this.handleManagerPacketsFlushedChange); this.manager.on('reset', this.handleManagerReset); } /** @internal */ static withExplicitHandle(options, manager, handle) { const track = new LocalDataTrack(options, manager); track.handle = handle; return track; } /** Metrics about the data track publication. */ get info() { const descriptor = this.descriptor; if ((descriptor === null || descriptor === void 0 ? void 0 : descriptor.type) === 'active') { return descriptor.info; } else { return undefined; } } /** The raw descriptor from the manager containing the internal state for this local track. */ get descriptor() { return this.handle ? this.manager.getDescriptor(this.handle) : null; } /** * Publish the track to the SFU. This must be done before calling {@link tryPush} for the first time. * @internal * */ publish(signal) { return __awaiter(this, void 0, void 0, function* () { try { this.handle = yield this.manager.publishRequest(this.options, signal); } catch (err) { // NOTE: Rethrow errors to break Throws<...> type boundary throw err; } }); } isPublished() { var _a; // NOTE: a track which is internally in the "resubscribing" state is still considered // published from the public API perspective. return ((_a = this.descriptor) === null || _a === void 0 ? void 0 : _a.type) === 'active' && this.descriptor.publishState !== 'unpublished'; } /** Try pushing a frame to subscribers of the track. * * Pushing a frame can fail for several reasons: * * - The track has been unpublished by the local participant or SFU * - The room is no longer connected */ tryPush(frame) { if (!this.handle) { throw DataTrackPushFrameError.trackUnpublished(); } const internalFrame = DataTrackFrameInternal.from(frame); try { return this.manager.tryProcessAndSend(this.handle, internalFrame); } catch (err) { // NOTE: wrapping in the bare try/catch like this means that the Throws<...> type doesn't // propagate upwards into the public interface. throw err; } } /** * When called, waits for all in flight packets to be sent before resolving. * * Use this to: * * 1. Send frames exactly in order: * ```ts * await track.tryPush(/* ... *\/); * await track.flush(); * await track.tryPush(/* ... *\/); * await track.flush(); * // ... etc ... * ``` * * 2. Wait for frames to all be delivered before unpublishing a local data track: * * ```ts * await track.tryPush(/* ... *\/); * await track.tryPush(/* ... *\/); * await track.tryPush(/* ... *\/); * // ... etc ... * await track.flush(); * await track.unpublish(); * ``` **/ flush() { return __awaiter(this, void 0, void 0, function* () { if (this.isFlushed) { return; } return this.flushedFuture.promise; }); } /** * Unpublish the track from the SFU. Once this is called, any further calls to {@link tryPush} * will fail. * */ unpublish() { return __awaiter(this, void 0, void 0, function* () { if (!this.handle) { livekitLogger.warn("Data track \"".concat(this.options.name, "\" is not published, so unpublishing has no effect.")); return; } try { yield this.manager.unpublishRequest(this.handle); } catch (err) { // NOTE: Rethrow errors to break Throws<...> type boundary throw err; } }); } }/** Processes outgoing frames into final packets for distribution to the SFU. */ class DataTrackOutgoingPipeline { constructor(options) { this.e2eeManager = options.e2eeManager; this.packetizer = new DataTrackPacketizer(options.info.pubHandle, DataTrackOutgoingPipeline.TRANSPORT_MTU_BYTES); } updateE2eeManager(e2eeManager) { this.e2eeManager = e2eeManager; } processFrame(frame) { return __asyncGenerator(this, arguments, function* processFrame_1() { const encryptedFrame = yield __await(this.encryptIfNeeded(frame)); try { yield __await(yield* __asyncDelegator(__asyncValues(this.packetizer.packetize(encryptedFrame)))); } catch (error) { if (error instanceof DataTrackPacketizerError) { throw DataTrackOutgoingPipelineError.packetizer(error); } throw error; } }); } encryptIfNeeded(frame) { return __awaiter(this, void 0, void 0, function* () { if (!this.e2eeManager) { return frame; } let encryptedResult; try { encryptedResult = yield this.e2eeManager.encryptData(frame.payload); } catch (err) { throw DataTrackOutgoingPipelineError.encryption(err); } frame.payload = encryptedResult.payload; frame.extensions.e2ee = new DataTrackE2eeExtension(encryptedResult.keyIndex, encryptedResult.iv); return frame; }); } } /** Maximum transmission unit (MTU) of the transport. */ DataTrackOutgoingPipeline.TRANSPORT_MTU_BYTES = 16000;const log = getLogger(LoggerNames.DataTracks); const Descriptor = { pending() { return { type: 'pending', completionFuture: new Future() }; }, active(info, e2eeManager) { return { type: 'active', info, publishState: 'published', pipeline: new DataTrackOutgoingPipeline({ info, e2eeManager }), unpublishingFuture: new Future() }; } }; /** How long to wait when attempting to publish before timing out. */ const PUBLISH_TIMEOUT_MILLISECONDS = 10000; class OutgoingDataTrackManager extends eventsExports.EventEmitter { constructor(options) { var _a; super(); this.handleAllocator = new DataTrackHandleAllocator(); this.descriptors = new Map(); /** Number of packets for each data track which have been emitted via the `packetAvailable` event * and which have not yet been sent via the rtc data channel yet. Once this goes to 0, then * all in flight packets have been delivered, and the data tracks is "flushed". */ this.inFlightPacketCounter = new Map(); this.e2eeManager = (_a = options === null || options === void 0 ? void 0 : options.e2eeManager) !== null && _a !== void 0 ? _a : null; } static withDescriptors(descriptors) { const manager = new OutgoingDataTrackManager(); manager.descriptors = descriptors; return manager; } /** @internal */ updateE2eeManager(e2eeManager) { this.e2eeManager = e2eeManager; // Propegate downwards to all pre-existing pipelines for (const descriptor of this.descriptors.values()) { if (descriptor.type === 'active') { descriptor.pipeline.updateE2eeManager(e2eeManager); } } } /** * Used by attached {@link LocalDataTrack} instances to query their associated descriptor info. * @internal */ getDescriptor(handle) { var _a; return (_a = this.descriptors.get(handle)) !== null && _a !== void 0 ? _a : null; } /** Used by attached {@link LocalDataTrack} instances to broadcast data track packets to other * subscribers. * @internal */ tryProcessAndSend(handle, frame) { return __awaiter(this, void 0, void 0, function* () { var _a, e_1, _b, _c; var _d; const descriptor = this.getDescriptor(handle); if ((descriptor === null || descriptor === void 0 ? void 0 : descriptor.type) !== 'active') { throw DataTrackPushFrameError.trackUnpublished(); } if (descriptor.publishState === 'unpublished') { throw DataTrackPushFrameError.trackUnpublished(); } if (descriptor.publishState === 'republishing') { throw DataTrackPushFrameError.dropped('Data track republishing'); } try { try { for (var _e = true, _f = __asyncValues(descriptor.pipeline.processFrame(frame)), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) { _c = _g.value; _e = false; const packet = _c; const prev = (_d = this.inFlightPacketCounter.get(handle)) !== null && _d !== void 0 ? _d : 0; this.inFlightPacketCounter.set(handle, prev + 1); if (prev === 0) { // A new packet has been sent, so there are now packets in the rtc data channel buffer for // this data track frame this.emit('packetsFlushedChange', { handle, isFlushed: false }); } this.emit('packetAvailable', { handle, bytes: packet.toBinary() }); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (!_e && !_a && (_b = _f.return)) yield _b.call(_f); } finally { if (e_1) throw e_1.error; } } } catch (err) { // NOTE: In the rust implementation this "dropped" error means something different (not enough room // in the track mpsc channel) throw DataTrackPushFrameError.dropped(err); } }); } /** The client has sent a packet over the rtc data channel. This signal is used for determining * once all packets are sent and a data track has been "flushed". * * @internal */ handlePacketSendComplete(handle) { var _a; const prev = (_a = this.inFlightPacketCounter.get(handle)) !== null && _a !== void 0 ? _a : 0; let counter = prev - 1; if (counter < 0) { log.warn("OutgoingDataTrackManager.handlePacketSendComplete: inFlightPacketCounter was decremented below 0 (got ".concat(this.inFlightPacketCounter, " - resetting to 0. Were more packets send than were emitted?")); counter = 0; } this.inFlightPacketCounter.set(handle, counter); if (counter === 0) { this.emit('packetsFlushedChange', { handle, isFlushed: true }); } } /** * Client requested to publish a track. * * If the LiveKit server is too old and doesn't support data tracks, a * {@link DataTrackPublishError#timeout} will be thrown. * * @internal **/ publishRequest(options, signal) { return __awaiter(this, void 0, void 0, function* () { const handle = this.handleAllocator.get(); if (!handle) { throw DataTrackPublishError.limitReached(); } const timeoutSignal = abortSignalTimeout(PUBLISH_TIMEOUT_MILLISECONDS); const combinedSignal = signal ? abortSignalAny([signal, timeoutSignal]) : timeoutSignal; if (this.descriptors.has(handle)) { // @throws-transformer ignore - this should be treated as a "panic" and not be caught throw new Error('Descriptor for handle already exists'); } const descriptor = Descriptor.pending(); this.descriptors.set(handle, descriptor); const onAbort = () => { var _a, _b; const existingDescriptor = this.descriptors.get(handle); if (!existingDescriptor) { log.warn("No descriptor for ".concat(handle)); return; } this.descriptors.delete(handle); // Let the SFU know that the publish has been cancelled this.emit('sfuUnpublishRequest', { handle }); if (existingDescriptor.type === 'pending') { (_b = (_a = existingDescriptor.completionFuture).reject) === null || _b === void 0 ? void 0 : _b.call(_a, timeoutSignal.aborted ? DataTrackPublishError.timeout() : // NOTE: the below cancelled case was introduced by web / there isn't a corresponding case in the rust version. DataTrackPublishError.cancelled()); } }; if (combinedSignal.aborted) { onAbort(); // NOTE: this rejects `completionFuture`; the next line just returns the rejection return descriptor.completionFuture.promise.then(() => handle /* no-op, makes typescript happy */); } combinedSignal.addEventListener('abort', onAbort); this.emit('sfuPublishRequest', { handle, name: options.name, usesE2ee: this.e2eeManager !== null }); yield descriptor.completionFuture.promise; combinedSignal.removeEventListener('abort', onAbort); this.emit('trackPublished', { track: LocalDataTrack.withExplicitHandle(options, this, handle) }); return handle; }); } /** * Get information about all currently published tracks. * @internal **/ queryPublished() { const descriptorInfos = Array.from(this.descriptors.values()).filter(descriptor => descriptor.type === 'active').map(descriptor => descriptor.info); return descriptorInfos; } /** * Client request to unpublish a track. * @internal **/ unpublishRequest(handle) { return __awaiter(this, void 0, void 0, function* () { const descriptor = this.descriptors.get(handle); if (!descriptor) { log.warn("No descriptor for ".concat(handle)); return; } if (descriptor.type !== 'active') { log.warn("Track ".concat(handle, " not active")); return; } this.emit('sfuUnpublishRequest', { handle }); yield descriptor.unpublishingFuture.promise; this.inFlightPacketCounter.delete(handle); this.emit('trackUnpublished', { sid: descriptor.info.sid }); }); } /** * SFU responded to a request to publish a data track. * @internal **/ receivedSfuPublishResponse(handle, result) { var _a, _b, _c, _d; const descriptor = this.descriptors.get(handle); if (!descriptor) { log.warn("No descriptor for ".concat(handle)); return; } this.descriptors.delete(handle); switch (descriptor.type) { case 'pending': { if (result.type === 'ok') { const info = result.data; const e2eeManager = info.usesE2ee ? this.e2eeManager : null; this.descriptors.set(info.pubHandle, Descriptor.active(info, e2eeManager)); (_b = (_a = descriptor.completionFuture).resolve) === null || _b === void 0 ? void 0 : _b.call(_a); } else { (_d = (_c = descriptor.completionFuture).reject) === null || _d === void 0 ? void 0 : _d.call(_c, result.error); } return; } case 'active': { if (descriptor.publishState !== 'republishing') { log.warn("Track ".concat(handle, " already active")); return; } if (result.type === 'error') { log.warn("Republish failed for track ".concat(handle)); return; } log.debug("Track ".concat(handle, " republished")); descriptor.info.sid = result.data.sid; descriptor.publishState = 'published'; this.descriptors.set(descriptor.info.pubHandle, descriptor); } } } /** * SFU notification that a track has been unpublished. * @internal **/ receivedSfuUnpublishResponse(handle) { var _a, _b; const descriptor = this.descriptors.get(handle); if (!descriptor) { log.warn("No descriptor for ".concat(handle)); return; } this.descriptors.delete(handle); if (descriptor.type !== 'active') { log.warn("Track ".concat(handle, " not active")); return; } descriptor.publishState = 'unpublished'; (_b = (_a = descriptor.unpublishingFuture).resolve) === null || _b === void 0 ? void 0 : _b.call(_a); } /** Republish all tracks. * * This must be sent after a full reconnect in order for existing publications * to be recognized by the SFU. Each republished track will be assigned a new SID. * @internal */ sfuWillRepublishTracks() { var _a, _b; for (const _ref of this.descriptors.entries()) { var _ref2 = _slicedToArray(_ref, 2); const handle = _ref2[0]; const descriptor = _ref2[1]; switch (descriptor.type) { case 'pending': // TODO: support republish for pending publications this.descriptors.delete(handle); (_b = (_a = descriptor.completionFuture).reject) === null || _b === void 0 ? void 0 : _b.call(_a, DataTrackPublishError.disconnected()); break; case 'active': descriptor.publishState = 'republishing'; this.emit('sfuPublishRequest', { handle: descriptor.info.pubHandle, name: descriptor.info.name, usesE2ee: descriptor.info.usesE2ee }); } } } /** * Reset's the state of the manager and all associated tracks. Run on room disconnect to get * the manager ready for the next room connection. * @internal **/ reset() { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d; this.handleAllocator.reset(); for (const descriptor of this.descriptors.values()) { switch (descriptor.type) { case 'pending': (_b = (_a = descriptor.completionFuture).reject) === null || _b === void 0 ? void 0 : _b.call(_a, DataTrackPublishError.disconnected()); break; case 'active': // Abandon any unpublishing descriptors that were in flight and assume they will get // cleaned up automatically with the connection shutdown. (_d = (_c = descriptor.unpublishingFuture).resolve) === null || _d === void 0 ? void 0 : _d.call(_c); yield this.unpublishRequest(descriptor.info.pubHandle); break; } } this.descriptors.clear(); this.inFlightPacketCounter.clear(); this.emit('reset'); }); } }/** * Specialized error handling for RPC methods. * * Instances of this type, when thrown in a method handler, will have their `message` * serialized and sent across the wire. The sender will receive an equivalent error on the other side. * * Built-in types are included but developers may use any string, with a max length of 256 bytes. */ class RpcError extends Error { /** * Creates an error object with the given code and message, plus an optional data payload. * * If thrown in an RPC method handler, the error will be sent back to the caller. * * Error codes 1001-1999 are reserved for built-in errors (see RpcError.ErrorCode for their meanings). */ constructor(code, message, data, options) { super(message); this.code = code; this.message = truncateBytes(message, RpcError.MAX_MESSAGE_BYTES); this.data = data ? truncateBytes(data, RpcError.MAX_DATA_BYTES) : undefined; if (typeof (options === null || options === void 0 ? void 0 : options.cause) !== 'undefined') { this.cause = options === null || options === void 0 ? void 0 : options.cause; } } /** * @internal */ static fromProto(proto) { return new RpcError(proto.code, proto.message, proto.data); } /** * @internal */ toProto() { return new RpcError$1({ code: this.code, message: this.message, data: this.data }); } /** * Creates an error object from the code, with an auto-populated message. * * @internal */ static builtIn(key, data, options) { return new RpcError(RpcError.ErrorCode[key], RpcError.ErrorMessage[key], data, options); } } RpcError.MAX_MESSAGE_BYTES = 256; RpcError.MAX_DATA_BYTES = 15360; // 15 KB RpcError.ErrorCode = { APPLICATION_ERROR: 1500, CONNECTION_TIMEOUT: 1501, RESPONSE_TIMEOUT: 1502, RECIPIENT_DISCONNECTED: 1503, RESPONSE_PAYLOAD_TOO_LARGE: 1504, SEND_FAILED: 1505, UNSUPPORTED_METHOD: 1400, RECIPIENT_NOT_FOUND: 1401, REQUEST_PAYLOAD_TOO_LARGE: 1402, UNSUPPORTED_SERVER: 1403, UNSUPPORTED_VERSION: 1404 }; /** * @internal */ RpcError.ErrorMessage = { APPLICATION_ERROR: 'Application error in method handler', CONNECTION_TIMEOUT: 'Connection timeout', RESPONSE_TIMEOUT: 'Response timeout', RECIPIENT_DISCONNECTED: 'Recipient disconnected', RESPONSE_PAYLOAD_TOO_LARGE: 'Response payload too large', SEND_FAILED: 'Failed to send', UNSUPPORTED_METHOD: 'Method not supported at destination', RECIPIENT_NOT_FOUND: 'Recipient not found', REQUEST_PAYLOAD_TOO_LARGE: 'Request payload too large', UNSUPPORTED_SERVER: 'RPC not supported by server', UNSUPPORTED_VERSION: 'Unsupported RPC version' }; /* * Maximum payload size for RPC requests and responses for clients with a clientProtocol of less * than CLIENT_PROTOCOL_DATA_STREAM_RPC. * * If a payload exceeds this size and the remote client does not support compression, * the RPC call will fail with a REQUEST_PAYLOAD_TOO_LARGE(1402) or RESPONSE_PAYLOAD_TOO_LARGE(1504) error. */ const MAX_V1_PAYLOAD_BYTES = 15360; // 15 KB /** * Topic used for v2 RPC request data streams. * @internal */ const RPC_REQUEST_DATA_STREAM_TOPIC = 'lk.rpc_request'; /** * Topic used for v2 RPC response data streams. * @internal */ const RPC_RESPONSE_DATA_STREAM_TOPIC = 'lk.rpc_response'; /** @internal */ var RpcRequestAttrs; (function (RpcRequestAttrs) { RpcRequestAttrs["RPC_REQUEST_ID"] = "lk.rpc_request_id"; RpcRequestAttrs["RPC_REQUEST_METHOD"] = "lk.rpc_request_method"; RpcRequestAttrs["RPC_REQUEST_RESPONSE_TIMEOUT_MS"] = "lk.rpc_request_response_timeout_ms"; RpcRequestAttrs["RPC_REQUEST_VERSION"] = "lk.rpc_request_version"; })(RpcRequestAttrs || (RpcRequestAttrs = {})); /** Initial version of rpc which uses RpcRequest / RpcResponse messages. * @internal **/ const RPC_VERSION_V1 = 1; /** Rpc version backed by data streams instead of RpcRequest / RpcResponse. * @internal **/ const RPC_VERSION_V2 = 2; /** * @internal */ function byteLength(str) { const encoder = new TextEncoder(); return encoder.encode(str).length; } /** * @internal */ function truncateBytes(str, maxBytes) { if (byteLength(str) <= maxBytes) { return str; } let low = 0; let high = str.length; const encoder = new TextEncoder(); while (low < high) { const mid = Math.floor((low + high + 1) / 2); if (encoder.encode(str.slice(0, mid)).length <= maxBytes) { low = mid; } else { high = mid - 1; } } return str.slice(0, low); }/** * Manages the client (caller) side of RPC: sending requests, tracking pending * ack/response state, and handling incoming ack/response packets. * @internal */ class RpcClientManager extends eventsExports.EventEmitter { constructor(log, outgoingDataStreamManager, getRemoteParticipantClientProtocol, getServerVersion) { super(); this.pendingAcks = new Map(); this.pendingResponses = new Map(); this.log = log; this.outgoingDataStreamManager = outgoingDataStreamManager; this.getRemoteParticipantClientProtocol = getRemoteParticipantClientProtocol; this.getServerVersion = getServerVersion; } performRpc(_a) { return __awaiter(this, arguments, void 0, function (_ref) { var _this = this; let destinationIdentity = _ref.destinationIdentity, method = _ref.method, payload = _ref.payload, _ref$responseTimeout = _ref.responseTimeout, responseTimeoutMs = _ref$responseTimeout === void 0 ? 15000 : _ref$responseTimeout; return function* () { const maxRoundTripLatencyMs = 7000; const minEffectiveTimeoutMs = maxRoundTripLatencyMs + 1000; const remoteClientProtocol = _this.getRemoteParticipantClientProtocol(destinationIdentity); const payloadBytes = byteLength(payload); // Only enforce the legacy size limit when on rpc v1 if (payloadBytes > MAX_V1_PAYLOAD_BYTES && remoteClientProtocol < CLIENT_PROTOCOL_DATA_STREAM_RPC) { throw RpcError.builtIn('REQUEST_PAYLOAD_TOO_LARGE'); } const serverVersion = _this.getServerVersion(); if (serverVersion && compareVersions(serverVersion, '1.8.0') < 0) { throw RpcError.builtIn('UNSUPPORTED_SERVER'); } const effectiveTimeoutMs = Math.max(responseTimeoutMs, minEffectiveTimeoutMs); const id = crypto.randomUUID(); const completionFuture = new Future(); let responseTimeoutId = null; const ackTimeoutId = setTimeout(() => { var _a; _this.pendingAcks.delete(id); (_a = completionFuture.reject) === null || _a === void 0 ? void 0 : _a.call(completionFuture, RpcError.builtIn('CONNECTION_TIMEOUT')); _this.pendingResponses.delete(id); if (responseTimeoutId !== null) { clearTimeout(responseTimeoutId); } }, maxRoundTripLatencyMs); _this.pendingAcks.set(id, { resolve: () => { clearTimeout(ackTimeoutId); }, participantIdentity: destinationIdentity }); _this.pendingResponses.set(id, { completionFuture, participantIdentity: destinationIdentity }); yield _this.publishRpcRequest(destinationIdentity, id, method, payload, effectiveTimeoutMs, remoteClientProtocol); responseTimeoutId = setTimeout(() => { var _a; _this.pendingResponses.delete(id); (_a = completionFuture.reject) === null || _a === void 0 ? void 0 : _a.call(completionFuture, RpcError.builtIn('RESPONSE_TIMEOUT')); }, responseTimeoutMs); const completionPromise = completionFuture.promise.finally(() => { clearTimeout(responseTimeoutId); if (_this.pendingAcks.has(id)) { _this.log.warn('RPC response received before ack', id); _this.pendingAcks.delete(id); clearTimeout(ackTimeoutId); } }); return [id, completionPromise]; }(); }); } publishRpcRequest(destinationIdentity, requestId, method, payload, responseTimeout, remoteClientProtocol) { return __awaiter(this, void 0, void 0, function* () { if (remoteClientProtocol >= CLIENT_PROTOCOL_DATA_STREAM_RPC) { // Send payload as a data stream - a "version 2" rpc request. const writer = yield this.outgoingDataStreamManager.streamText({ topic: RPC_REQUEST_DATA_STREAM_TOPIC, destinationIdentities: [destinationIdentity], attributes: { [RpcRequestAttrs.RPC_REQUEST_ID]: requestId, [RpcRequestAttrs.RPC_REQUEST_METHOD]: method, [RpcRequestAttrs.RPC_REQUEST_RESPONSE_TIMEOUT_MS]: "".concat(responseTimeout), [RpcRequestAttrs.RPC_REQUEST_VERSION]: "".concat(RPC_VERSION_V2) } }); yield writer.write(payload); yield writer.close(); return; } // Fallback to sending a literal RpcRequest - a "version 1" rpc request. this.emit('sendDataPacket', { packet: new DataPacket({ destinationIdentities: [destinationIdentity], kind: DataPacket_Kind.RELIABLE, value: { case: 'rpcRequest', value: new RpcRequest({ id: requestId, method, payload, responseTimeoutMs: responseTimeout, version: RPC_VERSION_V1 }) } }) }); }); } /** * Handle an incoming data stream containing an RPC response payload. * @internal */ handleIncomingDataStream(reader, senderIdentity, attributes) { return __awaiter(this, void 0, void 0, function* () { const associatedRequestId = attributes[RpcRequestAttrs.RPC_REQUEST_ID]; if (!associatedRequestId) { this.log.warn("RPC data stream malformed: ".concat(RpcRequestAttrs.RPC_REQUEST_ID, " not set.")); // NOTE: no response can be sent here, because there's no request id so associate // so logging is the best we can do here. return; } const pending = this.pendingResponses.get(associatedRequestId); if (pending && pending.participantIdentity !== senderIdentity) { this.log.warn("RPC response stream for ".concat(associatedRequestId, " arrived from unexpected sender ").concat(senderIdentity, ", expected ").concat(pending.participantIdentity, ". Ignoring.")); return; } let payload; try { payload = yield reader.readAll(); } catch (e) { this.log.warn("Error reading RPC response payload: ".concat(e)); this.handleIncomingRpcResponseFailure(associatedRequestId, RpcError.builtIn('APPLICATION_ERROR', 'Error reading RPC response payload', { cause: e })); return; } this.handleIncomingRpcResponseSuccess(associatedRequestId, payload); }); } /** @internal */ handleIncomingRpcResponseSuccess(requestId, payload) { var _a, _b; const handler = this.pendingResponses.get(requestId); if (handler) { (_b = (_a = handler.completionFuture).resolve) === null || _b === void 0 ? void 0 : _b.call(_a, payload); this.pendingResponses.delete(requestId); } else { this.log.error('Response received for unexpected RPC request', requestId); } } /** @internal */ handleIncomingRpcResponseFailure(requestId, error) { var _a, _b; const handler = this.pendingResponses.get(requestId); if (handler) { (_b = (_a = handler.completionFuture).reject) === null || _b === void 0 ? void 0 : _b.call(_a, error); this.pendingResponses.delete(requestId); } else { this.log.error('Response received for unexpected RPC request', requestId); } } /** @internal */ handleIncomingRpcAck(requestId) { const handler = this.pendingAcks.get(requestId); if (handler) { handler.resolve(); this.pendingAcks.delete(requestId); } else { this.log.error("Ack received for unexpected RPC request: ".concat(requestId)); } } /** @internal */ handleParticipantDisconnected(participantIdentity) { var _a; for (const _ref2 of this.pendingAcks) { var _ref3 = _slicedToArray(_ref2, 2); const id = _ref3[0]; const pendingIdentity = _ref3[1].participantIdentity; if (pendingIdentity === participantIdentity) { this.pendingAcks.delete(id); } } for (const _ref4 of this.pendingResponses) { var _ref5 = _slicedToArray(_ref4, 2); const id = _ref5[0]; var _ref5$ = _ref5[1]; const pendingIdentity = _ref5$.participantIdentity; const completionFuture = _ref5$.completionFuture; if (pendingIdentity === participantIdentity) { (_a = completionFuture.reject) === null || _a === void 0 ? void 0 : _a.call(completionFuture, RpcError.builtIn('RECIPIENT_DISCONNECTED')); this.pendingResponses.delete(id); } } } }/** * Manages the server (handler) side of RPC: processing incoming requests, * managing registered method handlers, and sending responses. * @internal */ class RpcServerManager extends eventsExports.EventEmitter { constructor(log, outgoingDataStreamManager, getRemoteParticipantClientProtocol) { super(); this.rpcHandlers = new Map(); this.log = log; this.outgoingDataStreamManager = outgoingDataStreamManager; this.getRemoteParticipantClientProtocol = getRemoteParticipantClientProtocol; } registerRpcMethod(method, handler) { if (this.rpcHandlers.has(method)) { throw Error("RPC handler already registered for method ".concat(method, ", unregisterRpcMethod before trying to register again")); } this.rpcHandlers.set(method, handler); } unregisterRpcMethod(method) { this.rpcHandlers.delete(method); } /** * Handle an incoming RPCRequest message containing a payload. * This handles "version 1" of rpc requests. * @internal */ handleIncomingRpcRequest(callerIdentity, rpcRequest) { return __awaiter(this, void 0, void 0, function* () { var _a; this.publishRpcAck(callerIdentity, rpcRequest.id); if (rpcRequest.version !== 1) { this.publishRpcResponsePacket(callerIdentity, rpcRequest.id, null, RpcError.builtIn('UNSUPPORTED_VERSION')); return; } const handler = this.rpcHandlers.get(rpcRequest.method); if (!handler) { this.publishRpcResponsePacket(callerIdentity, rpcRequest.id, null, RpcError.builtIn('UNSUPPORTED_METHOD')); return; } let response; try { response = yield handler({ requestId: rpcRequest.id, callerIdentity, payload: rpcRequest.payload, responseTimeout: rpcRequest.responseTimeoutMs }); } catch (error) { let responseError; if (error instanceof RpcError) { responseError = error; } else { this.log.warn("Uncaught error returned by RPC handler for ".concat(rpcRequest.method, ". Returning APPLICATION_ERROR instead."), error); responseError = RpcError.builtIn('APPLICATION_ERROR', "Uncaught error: ".concat((_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : error), { cause: error }); } this.publishRpcResponsePacket(callerIdentity, rpcRequest.id, null, responseError); return; } yield this.publishRpcResponse(callerIdentity, rpcRequest.id, response !== null && response !== void 0 ? response : ''); }); } /** * Handle an incoming data stream containing a RPC request payload. * This handles "version 2" of rpc requests. * @internal */ handleIncomingDataStream(reader, callerIdentity, dataStreamAttrs) { return __awaiter(this, void 0, void 0, function* () { const requestId = dataStreamAttrs[RpcRequestAttrs.RPC_REQUEST_ID]; const method = dataStreamAttrs[RpcRequestAttrs.RPC_REQUEST_METHOD]; const responseTimeout = parseInt(dataStreamAttrs[RpcRequestAttrs.RPC_REQUEST_RESPONSE_TIMEOUT_MS], 10); const version = parseInt(dataStreamAttrs[RpcRequestAttrs.RPC_REQUEST_VERSION], 10); if (!requestId || !method || Number.isNaN(responseTimeout) || Number.isNaN(version)) { this.log.warn("RPC data stream malformed: ".concat(RpcRequestAttrs.RPC_REQUEST_ID, " / ").concat(RpcRequestAttrs.RPC_REQUEST_METHOD, " / ").concat(RpcRequestAttrs.RPC_REQUEST_RESPONSE_TIMEOUT_MS, " / ").concat(RpcRequestAttrs.RPC_REQUEST_VERSION, " not set.")); this.publishRpcResponsePacket(callerIdentity, requestId, null, RpcError.builtIn('APPLICATION_ERROR', 'RPC data stream malformed')); return; } this.publishRpcAck(callerIdentity, requestId); if (version !== RPC_VERSION_V2) { this.publishRpcResponsePacket(callerIdentity, requestId, null, RpcError.builtIn('UNSUPPORTED_VERSION')); return; } let payload; try { payload = yield reader.readAll(); } catch (e) { this.log.warn("Error reading RPC request payload: ".concat(e)); this.publishRpcResponsePacket(callerIdentity, requestId, null, RpcError.builtIn('APPLICATION_ERROR', 'Error reading RPC request payload', { cause: e })); return; } const handler = this.rpcHandlers.get(method); if (!handler) { this.publishRpcResponsePacket(callerIdentity, requestId, null, RpcError.builtIn('UNSUPPORTED_METHOD')); return; } let response; try { response = yield handler({ requestId, callerIdentity, payload, responseTimeout }); } catch (error) { let responseError; if (error instanceof RpcError) { responseError = error; } else { this.log.warn("Uncaught error returned by RPC handler for ".concat(method, ". Returning APPLICATION_ERROR instead."), error); responseError = RpcError.builtIn('APPLICATION_ERROR'); } this.publishRpcResponsePacket(callerIdentity, requestId, null, responseError); return; } yield this.publishRpcResponse(callerIdentity, requestId, response !== null && response !== void 0 ? response : ''); }); } publishRpcAck(destinationIdentity, requestId) { this.emit('sendDataPacket', { packet: new DataPacket({ destinationIdentities: [destinationIdentity], kind: DataPacket_Kind.RELIABLE, value: { case: 'rpcAck', value: new RpcAck({ requestId }) } }) }); } publishRpcResponsePacket(destinationIdentity, requestId, payload, error) { this.emit('sendDataPacket', { packet: new DataPacket({ destinationIdentities: [destinationIdentity], kind: DataPacket_Kind.RELIABLE, value: { case: 'rpcResponse', value: new RpcResponse({ requestId, value: error ? { case: 'error', value: error.toProto() } : { case: 'payload', value: payload !== null && payload !== void 0 ? payload : '' } }) } }) }); } /** * Send a successful RPC response payload, choosing the transport based on * the caller's client protocol version. */ publishRpcResponse(destinationIdentity, requestId, payload) { return __awaiter(this, void 0, void 0, function* () { const callerClientProtocol = this.getRemoteParticipantClientProtocol(destinationIdentity); if (callerClientProtocol >= CLIENT_PROTOCOL_DATA_STREAM_RPC) { // Send response as a data stream const writer = yield this.outgoingDataStreamManager.streamText({ topic: RPC_RESPONSE_DATA_STREAM_TOPIC, destinationIdentities: [destinationIdentity], attributes: { [RpcRequestAttrs.RPC_REQUEST_ID]: requestId } }); yield writer.write(payload); yield writer.close(); return; } // Legacy client: enforce size limit and send uncompressed payload inline const responseBytes = byteLength(payload); if (responseBytes > MAX_V1_PAYLOAD_BYTES) { this.log.warn("RPC Response payload too large for request ".concat(requestId, ". To send larger responses, consider updating the sending client.")); this.publishRpcResponsePacket(destinationIdentity, requestId, null, RpcError.builtIn('RESPONSE_PAYLOAD_TOO_LARGE')); return; } this.publishRpcResponsePacket(destinationIdentity, requestId, payload, null); }); } }class RemoteAudioTrack extends RemoteTrack { constructor(mediaTrack, sid, receiver, audioContext, audioOutput, loggerOptions) { super(mediaTrack, sid, Track.Kind.Audio, receiver, loggerOptions); this.monitorReceiver = () => __awaiter(this, void 0, void 0, function* () { if (!this.receiver) { this._currentBitrate = 0; return; } const stats = yield this.getReceiverStats(); if (stats && this.prevStats && this.receiver) { this._currentBitrate = computeBitrate(stats, this.prevStats); } this.prevStats = stats; }); this.audioContext = audioContext; this.webAudioPluginNodes = []; if (audioOutput) { this.sinkId = audioOutput.deviceId; } } /** * sets the volume for all attached audio elements */ setVolume(volume) { var _a; for (const el of this.attachedElements) { if (this.audioContext) { (_a = this.gainNode) === null || _a === void 0 ? void 0 : _a.gain.setTargetAtTime(volume, 0, 0.1); } else { el.volume = volume; } } if (isReactNative()) { // @ts-ignore this._mediaStreamTrack._setVolume(volume); } this.elementVolume = volume; } /** * gets the volume of attached audio elements (loudest) */ getVolume() { if (this.elementVolume) { return this.elementVolume; } if (isReactNative()) { // RN volume value defaults to 1.0 if hasn't been changed. return 1.0; } let highestVolume = 0; this.attachedElements.forEach(element => { if (element.volume > highestVolume) { highestVolume = element.volume; } }); return highestVolume; } /** * calls setSinkId on all attached elements, if supported * @param deviceId audio output device */ setSinkId(deviceId) { return __awaiter(this, void 0, void 0, function* () { this.sinkId = deviceId; yield Promise.all(this.attachedElements.map(elm => { if (!supportsSetSinkId(elm)) { return; } /* @ts-ignore */ return elm.setSinkId(deviceId); })); }); } attach(element) { const needsNewWebAudioConnection = this.attachedElements.length === 0; if (!element) { element = super.attach(); } else { super.attach(element); } if (this.sinkId && supportsSetSinkId(element)) { element.setSinkId(this.sinkId).catch(e => { this.log.error('Failed to set sink id on remote audio track', e, this.logContext); }); } if (this.audioContext && needsNewWebAudioConnection) { this.log.debug('using audio context mapping', this.logContext); this.connectWebAudio(this.audioContext, element); element.volume = 0; element.muted = true; } if (this.elementVolume) { // make sure volume setting is being applied to the newly attached element this.setVolume(this.elementVolume); } return element; } detach(element) { let detached; if (!element) { detached = super.detach(); this.disconnectWebAudio(); } else { detached = super.detach(element); // if there are still any attached elements after detaching, connect webaudio to the first element that's left // disconnect webaudio otherwise if (this.audioContext) { if (this.attachedElements.length > 0) { this.connectWebAudio(this.audioContext, this.attachedElements[0]); } else { this.disconnectWebAudio(); } } } return detached; } /** * @internal * @experimental */ setAudioContext(audioContext) { this.audioContext = audioContext; if (audioContext && this.attachedElements.length > 0) { this.connectWebAudio(audioContext, this.attachedElements[0]); } else if (!audioContext) { this.disconnectWebAudio(); } } /** * @internal * @experimental * @param {AudioNode[]} nodes - An array of WebAudio nodes. These nodes should not be connected to each other when passed, as the sdk will take care of connecting them in the order of the array. */ setWebAudioPlugins(nodes) { this.webAudioPluginNodes = nodes; if (this.attachedElements.length > 0 && this.audioContext) { this.connectWebAudio(this.audioContext, this.attachedElements[0]); } } connectWebAudio(context, element) { this.disconnectWebAudio(); // @ts-ignore attached elements always have a srcObject set this.sourceNode = context.createMediaStreamSource(element.srcObject); let lastNode = this.sourceNode; this.webAudioPluginNodes.forEach(node => { lastNode.connect(node); lastNode = node; }); this.gainNode = context.createGain(); lastNode.connect(this.gainNode); this.gainNode.connect(context.destination); if (this.elementVolume) { this.gainNode.gain.setTargetAtTime(this.elementVolume, 0, 0.1); } // try to resume the context if it isn't running already if (context.state !== 'running') { context.resume().then(() => { if (context.state !== 'running') { this.emit(TrackEvent.AudioPlaybackFailed, new Error("Audio Context couldn't be started automatically")); } }).catch(e => { this.emit(TrackEvent.AudioPlaybackFailed, e); }); } } disconnectWebAudio() { var _a, _b; (_a = this.gainNode) === null || _a === void 0 ? void 0 : _a.disconnect(); (_b = this.sourceNode) === null || _b === void 0 ? void 0 : _b.disconnect(); this.gainNode = undefined; this.sourceNode = undefined; } getReceiverStats() { return __awaiter(this, void 0, void 0, function* () { if (!this.receiver || !this.receiver.getStats) { return; } const stats = yield this.receiver.getStats(); let receiverStats; stats.forEach(v => { if (v.type === 'inbound-rtp') { receiverStats = { type: 'audio', streamId: v.id, timestamp: v.timestamp, jitter: v.jitter, bytesReceived: v.bytesReceived, concealedSamples: v.concealedSamples, concealmentEvents: v.concealmentEvents, silentConcealedSamples: v.silentConcealedSamples, silentConcealmentEvents: v.silentConcealmentEvents, totalAudioEnergy: v.totalAudioEnergy, totalSamplesDuration: v.totalSamplesDuration }; } }); return receiverStats; }); } }class TrackPublication extends eventsExports.EventEmitter { constructor(kind, id, name, loggerOptions) { var _a; super(); this.metadataMuted = false; this.encryption = Encryption_Type.NONE; this.log = livekitLogger; this.handleMuted = () => { this.emit(TrackEvent.Muted); }; this.handleUnmuted = () => { this.emit(TrackEvent.Unmuted); }; this.log = getLogger((_a = loggerOptions === null || loggerOptions === void 0 ? void 0 : loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.Publication); this.loggerContextCb = this.loggerContextCb; this.setMaxListeners(100); this.kind = kind; this.trackSid = id; this.trackName = name; this.source = Track.Source.Unknown; } /** @internal */ setTrack(track) { if (this.track) { this.track.off(TrackEvent.Muted, this.handleMuted); this.track.off(TrackEvent.Unmuted, this.handleUnmuted); } this.track = track; if (track) { // forward events track.on(TrackEvent.Muted, this.handleMuted); track.on(TrackEvent.Unmuted, this.handleUnmuted); } } get logContext() { var _a; return Object.assign(Object.assign({}, (_a = this.loggerContextCb) === null || _a === void 0 ? void 0 : _a.call(this)), getLogContextFromTrack(this)); } get isMuted() { return this.metadataMuted; } get isEnabled() { return true; } get isSubscribed() { return this.track !== undefined; } get isEncrypted() { return this.encryption !== Encryption_Type.NONE; } /** * an [AudioTrack] if this publication holds an audio track */ get audioTrack() { if (isAudioTrack(this.track)) { return this.track; } } /** * an [VideoTrack] if this publication holds a video track */ get videoTrack() { if (isVideoTrack(this.track)) { return this.track; } } /** @internal */ updateInfo(info) { this.trackSid = info.sid; this.trackName = info.name; this.source = Track.sourceFromProto(info.source); this.mimeType = info.mimeType; if (this.kind === Track.Kind.Video && info.width > 0) { this.dimensions = { width: info.width, height: info.height }; this.simulcasted = info.simulcast; } this.encryption = info.encryption; this.trackInfo = info; this.log.debug('update publication info', Object.assign(Object.assign({}, this.logContext), { info })); } } (function (TrackPublication) { (function (SubscriptionStatus) { SubscriptionStatus["Desired"] = "desired"; SubscriptionStatus["Subscribed"] = "subscribed"; SubscriptionStatus["Unsubscribed"] = "unsubscribed"; })(TrackPublication.SubscriptionStatus || (TrackPublication.SubscriptionStatus = {})); (function (PermissionStatus) { PermissionStatus["Allowed"] = "allowed"; PermissionStatus["NotAllowed"] = "not_allowed"; })(TrackPublication.PermissionStatus || (TrackPublication.PermissionStatus = {})); })(TrackPublication || (TrackPublication = {}));class LocalTrackPublication extends TrackPublication { get isUpstreamPaused() { var _a; return (_a = this.track) === null || _a === void 0 ? void 0 : _a.isUpstreamPaused; } constructor(kind, ti, track, loggerOptions) { super(kind, ti.sid, ti.name, loggerOptions); this.track = undefined; this.handleTrackEnded = () => { this.emit(TrackEvent.Ended); }; this.handleCpuConstrained = () => { if (this.track && isVideoTrack(this.track)) { this.emit(TrackEvent.CpuConstrained, this.track); } }; this.updateInfo(ti); this.setTrack(track); } setTrack(track) { if (this.track) { this.track.off(TrackEvent.Ended, this.handleTrackEnded); this.track.off(TrackEvent.CpuConstrained, this.handleCpuConstrained); } super.setTrack(track); if (track) { track.on(TrackEvent.Ended, this.handleTrackEnded); track.on(TrackEvent.CpuConstrained, this.handleCpuConstrained); } } get isMuted() { if (this.track) { return this.track.isMuted; } return super.isMuted; } get audioTrack() { return super.audioTrack; } get videoTrack() { return super.videoTrack; } get isLocal() { return true; } /** * Mute the track associated with this publication */ mute() { return __awaiter(this, void 0, void 0, function* () { var _a; return (_a = this.track) === null || _a === void 0 ? void 0 : _a.mute(); }); } /** * Unmute track associated with this publication */ unmute() { return __awaiter(this, void 0, void 0, function* () { var _a; return (_a = this.track) === null || _a === void 0 ? void 0 : _a.unmute(); }); } /** * Pauses the media stream track associated with this publication from being sent to the server * and signals "muted" event to other participants * Useful if you want to pause the stream without pausing the local media stream track */ pauseUpstream() { return __awaiter(this, void 0, void 0, function* () { var _a; yield (_a = this.track) === null || _a === void 0 ? void 0 : _a.pauseUpstream(); }); } /** * Resumes sending the media stream track associated with this publication to the server after a call to [[pauseUpstream()]] * and signals "unmuted" event to other participants (unless the track is explicitly muted) */ resumeUpstream() { return __awaiter(this, void 0, void 0, function* () { var _a; yield (_a = this.track) === null || _a === void 0 ? void 0 : _a.resumeUpstream(); }); } getTrackFeatures() { var _a; if (isAudioTrack(this.track)) { const settings = this.track.getSourceTrackSettings(); const features = new Set(); if (settings.autoGainControl) { features.add(AudioTrackFeature.TF_AUTO_GAIN_CONTROL); } if (settings.echoCancellation) { features.add(AudioTrackFeature.TF_ECHO_CANCELLATION); } if (settings.noiseSuppression) { features.add(AudioTrackFeature.TF_NOISE_SUPPRESSION); } if (settings.channelCount && settings.channelCount > 1) { features.add(AudioTrackFeature.TF_STEREO); } if (!((_a = this.options) === null || _a === void 0 ? void 0 : _a.dtx)) { features.add(AudioTrackFeature.TF_NO_DTX); } if (this.track.enhancedNoiseCancellation) { features.add(AudioTrackFeature.TF_ENHANCED_NOISE_CANCELLATION); } return Array.from(features.values()); } else return []; } }/** * Creates a local video and audio track at the same time. When acquiring both * audio and video tracks together, it'll display a single permission prompt to * the user instead of two separate ones. * @param options */ function createLocalTracks(options, loggerOptions) { return __awaiter(this, void 0, void 0, function* () { options !== null && options !== void 0 ? options : options = {}; let attemptExactMatch = false; const _extractProcessorsFro = extractProcessorsFromOptions(options), audioProcessor = _extractProcessorsFro.audioProcessor, videoProcessor = _extractProcessorsFro.videoProcessor, internalOptions = _extractProcessorsFro.optionsWithoutProcessor; let retryAudioOptions = internalOptions.audio; let retryVideoOptions = internalOptions.video; if (audioProcessor && typeof internalOptions.audio === 'object') { internalOptions.audio.processor = audioProcessor; } if (videoProcessor && typeof internalOptions.video === 'object') { internalOptions.video.processor = videoProcessor; } // if the user passes a device id as a string, we default to exact match if (options.audio && typeof internalOptions.audio === 'object' && typeof internalOptions.audio.deviceId === 'string') { const deviceId = internalOptions.audio.deviceId; internalOptions.audio.deviceId = { exact: deviceId }; attemptExactMatch = true; retryAudioOptions = Object.assign(Object.assign({}, internalOptions.audio), { deviceId: { ideal: deviceId } }); } if (internalOptions.video && typeof internalOptions.video === 'object' && typeof internalOptions.video.deviceId === 'string') { const deviceId = internalOptions.video.deviceId; internalOptions.video.deviceId = { exact: deviceId }; attemptExactMatch = true; retryVideoOptions = Object.assign(Object.assign({}, internalOptions.video), { deviceId: { ideal: deviceId } }); } if (internalOptions.audio === true) { internalOptions.audio = { deviceId: 'default' }; } else if (typeof internalOptions.audio === 'object' && internalOptions.audio !== null) { internalOptions.audio = Object.assign(Object.assign({}, internalOptions.audio), { deviceId: internalOptions.audio.deviceId || 'default' }); } if (internalOptions.video === true) { internalOptions.video = { deviceId: 'default' }; } else if (typeof internalOptions.video === 'object' && !internalOptions.video.deviceId) { internalOptions.video.deviceId = 'default'; } const opts = mergeDefaultOptions(internalOptions, audioDefaults, videoDefaults); const constraints = constraintsForOptions(opts); // Keep a reference to the promise on DeviceManager and await it in getLocalDevices() // works around iOS Safari Bug https://bugs.webkit.org/show_bug.cgi?id=179363 const mediaPromise = navigator.mediaDevices.getUserMedia(constraints); if (internalOptions.audio) { DeviceManager.userMediaPromiseMap.set('audioinput', mediaPromise); mediaPromise.catch(() => DeviceManager.userMediaPromiseMap.delete('audioinput')); } if (internalOptions.video) { DeviceManager.userMediaPromiseMap.set('videoinput', mediaPromise); mediaPromise.catch(() => DeviceManager.userMediaPromiseMap.delete('videoinput')); } try { const stream = yield mediaPromise; return yield Promise.all(stream.getTracks().map(mediaStreamTrack => __awaiter(this, void 0, void 0, function* () { const isAudio = mediaStreamTrack.kind === 'audio'; let trackConstraints; const conOrBool = isAudio ? constraints.audio : constraints.video; if (typeof conOrBool !== 'boolean') { trackConstraints = conOrBool; } // update the constraints with the device id the user gave permissions to in the permission prompt // otherwise each track restart (e.g. mute - unmute) will try to initialize the device again -> causing additional permission prompts const newDeviceId = mediaStreamTrack.getSettings().deviceId; if ((trackConstraints === null || trackConstraints === void 0 ? void 0 : trackConstraints.deviceId) && unwrapConstraint(trackConstraints.deviceId) !== newDeviceId) { trackConstraints.deviceId = newDeviceId; } else if (!trackConstraints) { trackConstraints = { deviceId: newDeviceId }; } const track = mediaTrackToLocalTrack(mediaStreamTrack, trackConstraints, loggerOptions); if (track.kind === Track.Kind.Video) { track.source = Track.Source.Camera; } else if (track.kind === Track.Kind.Audio) { track.source = Track.Source.Microphone; } track.mediaStream = stream; if (isAudioTrack(track) && audioProcessor) { yield track.setProcessor(audioProcessor); } else if (isVideoTrack(track) && videoProcessor) { yield track.setProcessor(videoProcessor); } return track; }))); } catch (e) { if (!attemptExactMatch) { throw e; } return createLocalTracks(Object.assign(Object.assign({}, options), { audio: retryAudioOptions, video: retryVideoOptions }), loggerOptions); } }); } /** * Creates a [[LocalVideoTrack]] with getUserMedia() * @param options */ function createLocalVideoTrack(options) { return __awaiter(this, void 0, void 0, function* () { const tracks = yield createLocalTracks({ audio: false, video: options !== null && options !== void 0 ? options : true }); return tracks[0]; }); } function createLocalAudioTrack(options) { return __awaiter(this, void 0, void 0, function* () { const tracks = yield createLocalTracks({ audio: options !== null && options !== void 0 ? options : true, video: false }); return tracks[0]; }); } /** * Creates a screen capture tracks with getDisplayMedia(). * A LocalVideoTrack is always created and returned. * If { audio: true }, and the browser supports audio capture, a LocalAudioTrack is also created. */ function createLocalScreenTracks(options) { return __awaiter(this, void 0, void 0, function* () { if (options === undefined) { options = {}; } if (options.resolution === undefined && !isSafari17Based()) { options.resolution = ScreenSharePresets.h1080fps30.resolution; } if (navigator.mediaDevices.getDisplayMedia === undefined) { throw new DeviceUnsupportedError('getDisplayMedia not supported'); } const constraints = screenCaptureToDisplayMediaStreamOptions(options); const stream = yield navigator.mediaDevices.getDisplayMedia(constraints); const tracks = stream.getVideoTracks(); if (tracks.length === 0) { throw new TrackInvalidError('no video track found'); } const screenVideo = new LocalVideoTrack(tracks[0], undefined, false); screenVideo.source = Track.Source.ScreenShare; const localTracks = [screenVideo]; if (stream.getAudioTracks().length > 0) { const screenAudio = new LocalAudioTrack(stream.getAudioTracks()[0], undefined, false); screenAudio.source = Track.Source.ScreenShareAudio; localTracks.push(screenAudio); } return localTracks; }); }var ConnectionQuality; (function (ConnectionQuality) { ConnectionQuality["Excellent"] = "excellent"; ConnectionQuality["Good"] = "good"; ConnectionQuality["Poor"] = "poor"; /** * Indicates that a participant has temporarily (or permanently) lost connection to LiveKit. * For permanent disconnection a `ParticipantDisconnected` event will be emitted after a timeout */ ConnectionQuality["Lost"] = "lost"; ConnectionQuality["Unknown"] = "unknown"; })(ConnectionQuality || (ConnectionQuality = {})); function qualityFromProto(q) { switch (q) { case ConnectionQuality$1.EXCELLENT: return ConnectionQuality.Excellent; case ConnectionQuality$1.GOOD: return ConnectionQuality.Good; case ConnectionQuality$1.POOR: return ConnectionQuality.Poor; case ConnectionQuality$1.LOST: return ConnectionQuality.Lost; default: return ConnectionQuality.Unknown; } } class Participant extends eventsExports.EventEmitter { get logContext() { var _a, _b; return Object.assign({}, (_b = (_a = this.loggerOptions) === null || _a === void 0 ? void 0 : _a.loggerContextCb) === null || _b === void 0 ? void 0 : _b.call(_a)); } get isEncrypted() { return this.trackPublications.size > 0 && Array.from(this.trackPublications.values()).every(tr => tr.isEncrypted); } get isAgent() { var _a; return ((_a = this.permissions) === null || _a === void 0 ? void 0 : _a.agent) || this.kind === ParticipantInfo_Kind.AGENT; } get isActive() { var _a; return ((_a = this.participantInfo) === null || _a === void 0 ? void 0 : _a.state) === ParticipantInfo_State.ACTIVE; } get kind() { return this._kind; } /** participant attributes, similar to metadata, but as a key/value map */ get attributes() { return Object.freeze(Object.assign({}, this._attributes)); } /** @internal */ constructor(sid, identity, name, metadata, attributes, loggerOptions) { let kind = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : ParticipantInfo_Kind.STANDARD; var _a; super(); /** audio level between 0-1.0, 1 being loudest, 0 being softest */ this.audioLevel = 0; /** if participant is currently speaking */ this.isSpeaking = false; this._connectionQuality = ConnectionQuality.Unknown; this.log = livekitLogger; this.loggerOptions = loggerOptions; this.log = getLogger((_a = loggerOptions === null || loggerOptions === void 0 ? void 0 : loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.Participant, () => this.logContext); this.setMaxListeners(100); this.sid = sid; this.identity = identity; this.name = name; this.metadata = metadata; this.audioTrackPublications = new Map(); this.videoTrackPublications = new Map(); this.trackPublications = new Map(); this._kind = kind; this._attributes = attributes !== null && attributes !== void 0 ? attributes : {}; } getTrackPublications() { return Array.from(this.trackPublications.values()); } /** * Finds the first track that matches the source filter, for example, getting * the user's camera track with getTrackBySource(Track.Source.Camera). */ getTrackPublication(source) { for (const _ref of this.trackPublications) { var _ref2 = _slicedToArray(_ref, 2); const pub = _ref2[1]; if (pub.source === source) { return pub; } } } /** * Finds the first track that matches the track's name. */ getTrackPublicationByName(name) { for (const _ref3 of this.trackPublications) { var _ref4 = _slicedToArray(_ref3, 2); const pub = _ref4[1]; if (pub.trackName === name) { return pub; } } } /** * Waits until the participant is active and ready to receive data messages * @returns a promise that resolves when the participant is active */ waitUntilActive() { if (this.isActive) { return Promise.resolve(); } if (this.activeFuture) { return this.activeFuture.promise; } this.activeFuture = new Future(); this.once(ParticipantEvent.Active, () => { var _a, _b; (_b = (_a = this.activeFuture) === null || _a === void 0 ? void 0 : _a.resolve) === null || _b === void 0 ? void 0 : _b.call(_a); this.activeFuture = undefined; }); return this.activeFuture.promise; } get connectionQuality() { return this._connectionQuality; } get isCameraEnabled() { var _a; const track = this.getTrackPublication(Track.Source.Camera); return !((_a = track === null || track === void 0 ? void 0 : track.isMuted) !== null && _a !== void 0 ? _a : true); } get isMicrophoneEnabled() { var _a; const track = this.getTrackPublication(Track.Source.Microphone); return !((_a = track === null || track === void 0 ? void 0 : track.isMuted) !== null && _a !== void 0 ? _a : true); } get isScreenShareEnabled() { const track = this.getTrackPublication(Track.Source.ScreenShare); return !!track; } get isLocal() { return false; } /** when participant joined the room */ get joinedAt() { if (this.participantInfo) { return new Date(Number.parseInt(this.participantInfo.joinedAt.toString()) * 1000); } return new Date(); } /** @internal */ updateInfo(info) { var _a; // it's possible the update could be applied out of order due to await // during reconnect sequences. when that happens, it's possible for server // to have sent more recent version of participant info while JS is waiting // to process the existing payload. // when the participant sid remains the same, and we already have a later version // of the payload, they can be safely skipped if (this.participantInfo && this.participantInfo.sid === info.sid && this.participantInfo.version > info.version) { return false; } this.identity = info.identity; this.sid = info.sid; this._setName(info.name); this._setMetadata(info.metadata); this._setAttributes(info.attributes); if (info.state === ParticipantInfo_State.ACTIVE && ((_a = this.participantInfo) === null || _a === void 0 ? void 0 : _a.state) !== ParticipantInfo_State.ACTIVE) { this.emit(ParticipantEvent.Active); } if (info.permission) { this.setPermissions(info.permission); } // set this last so setMetadata can detect changes this.participantInfo = info; return true; } /** * Updates metadata from server **/ _setMetadata(md) { const changed = this.metadata !== md; const prevMetadata = this.metadata; this.metadata = md; if (changed) { this.emit(ParticipantEvent.ParticipantMetadataChanged, prevMetadata); } } _setName(name) { const changed = this.name !== name; this.name = name; if (changed) { this.emit(ParticipantEvent.ParticipantNameChanged, name); } } /** * Updates metadata from server **/ _setAttributes(attributes) { const diff = diffAttributes(this.attributes, attributes); this._attributes = attributes; if (Object.keys(diff).length > 0) { this.emit(ParticipantEvent.AttributesChanged, diff); } } /** @internal */ setPermissions(permissions) { var _a, _b, _c, _d, _e, _f; const prevPermissions = this.permissions; const changed = permissions.canPublish !== ((_a = this.permissions) === null || _a === void 0 ? void 0 : _a.canPublish) || permissions.canSubscribe !== ((_b = this.permissions) === null || _b === void 0 ? void 0 : _b.canSubscribe) || permissions.canPublishData !== ((_c = this.permissions) === null || _c === void 0 ? void 0 : _c.canPublishData) || permissions.hidden !== ((_d = this.permissions) === null || _d === void 0 ? void 0 : _d.hidden) || permissions.recorder !== ((_e = this.permissions) === null || _e === void 0 ? void 0 : _e.recorder) || permissions.canPublishSources.length !== this.permissions.canPublishSources.length || permissions.canPublishSources.some((value, index) => { var _a; return value !== ((_a = this.permissions) === null || _a === void 0 ? void 0 : _a.canPublishSources[index]); }) || permissions.canSubscribeMetrics !== ((_f = this.permissions) === null || _f === void 0 ? void 0 : _f.canSubscribeMetrics); this.permissions = permissions; if (changed) { this.emit(ParticipantEvent.ParticipantPermissionsChanged, prevPermissions); } return changed; } /** @internal */ setIsSpeaking(speaking) { if (speaking === this.isSpeaking) { return; } this.isSpeaking = speaking; if (speaking) { this.lastSpokeAt = new Date(); } this.emit(ParticipantEvent.IsSpeakingChanged, speaking); } /** @internal */ setConnectionQuality(q) { const prevQuality = this._connectionQuality; this._connectionQuality = qualityFromProto(q); if (prevQuality !== this._connectionQuality) { this.emit(ParticipantEvent.ConnectionQualityChanged, this._connectionQuality); } } /** * @internal */ setDisconnected() { var _a, _b; if (this.activeFuture) { (_b = (_a = this.activeFuture).reject) === null || _b === void 0 ? void 0 : _b.call(_a, new Error('Participant disconnected')); this.activeFuture = undefined; } } /** * @internal */ setAudioContext(ctx) { this.audioContext = ctx; this.audioTrackPublications.forEach(track => isAudioTrack(track.track) && track.track.setAudioContext(ctx)); } addTrackPublication(publication) { // forward publication driven events publication.on(TrackEvent.Muted, () => { this.emit(ParticipantEvent.TrackMuted, publication); }); publication.on(TrackEvent.Unmuted, () => { this.emit(ParticipantEvent.TrackUnmuted, publication); }); const pub = publication; if (pub.track) { pub.track.sid = publication.trackSid; } this.trackPublications.set(publication.trackSid, publication); switch (publication.kind) { case Track.Kind.Audio: this.audioTrackPublications.set(publication.trackSid, publication); break; case Track.Kind.Video: this.videoTrackPublications.set(publication.trackSid, publication); break; } } }function trackPermissionToProto(perms) { var _a, _b, _c; if (!perms.participantSid && !perms.participantIdentity) { throw new Error('Invalid track permission, must provide at least one of participantIdentity and participantSid'); } return new TrackPermission({ participantIdentity: (_a = perms.participantIdentity) !== null && _a !== void 0 ? _a : '', participantSid: (_b = perms.participantSid) !== null && _b !== void 0 ? _b : '', allTracks: (_c = perms.allowAll) !== null && _c !== void 0 ? _c : false, trackSids: perms.allowedTrackSids || [] }); }class LocalParticipant extends Participant { /** @internal */ constructor(sid, identity, engine, options, roomOutgoingDataStreamManager, roomOutgoingDataTrackManager, rpcClientManager, rpcServerManager) { super(sid, identity, undefined, undefined, undefined, { loggerName: options.loggerName, loggerContextCb: () => this.engine.logContext }); this.pendingPublishing = new Set(); this.pendingPublishPromises = new Map(); this.participantTrackPermissions = []; this.allParticipantsAllowedToSubscribe = true; this.encryptionType = Encryption_Type.NONE; this.e2eeStateMutex = new _(); this.enabledPublishVideoCodecs = []; this.handleReconnecting = () => { if (!this.reconnectFuture) { this.reconnectFuture = new Future(); } }; this.handleReconnected = () => { var _a, _b; (_b = (_a = this.reconnectFuture) === null || _a === void 0 ? void 0 : _a.resolve) === null || _b === void 0 ? void 0 : _b.call(_a); this.reconnectFuture = undefined; this.updateTrackSubscriptionPermissions(); }; this.handleClosing = () => { var _a, _b, _c, _d, _e, _f; if (this.reconnectFuture) { // @throws-transformer ignore - introduced due to adding Throws into Future, investigate this // further this.reconnectFuture.promise.catch(e => this.log.warn(e.message)); (_b = (_a = this.reconnectFuture) === null || _a === void 0 ? void 0 : _a.reject) === null || _b === void 0 ? void 0 : _b.call(_a, new Error('Got disconnected during reconnection attempt')); this.reconnectFuture = undefined; } if (this.signalConnectedFuture) { (_d = (_c = this.signalConnectedFuture).reject) === null || _d === void 0 ? void 0 : _d.call(_c, new Error('Got disconnected without signal connected')); this.signalConnectedFuture = undefined; } (_f = (_e = this.activeAgentFuture) === null || _e === void 0 ? void 0 : _e.reject) === null || _f === void 0 ? void 0 : _f.call(_e, new Error('Got disconnected without active agent present')); this.activeAgentFuture = undefined; this.firstActiveAgent = undefined; }; this.handleSignalConnected = joinResponse => { var _a, _b; if (joinResponse.participant) { this.updateInfo(joinResponse.participant); } if (!this.signalConnectedFuture) { this.signalConnectedFuture = new Future(); } (_b = (_a = this.signalConnectedFuture).resolve) === null || _b === void 0 ? void 0 : _b.call(_a); }; this.handleSignalRequestResponse = response => { const requestId = response.requestId, reason = response.reason, message = response.message; const targetRequest = this.pendingSignalRequests.get(requestId); if (targetRequest) { if (reason !== RequestResponse_Reason.OK) { targetRequest.reject(new SignalRequestError(message, reason)); } this.pendingSignalRequests.delete(requestId); } switch (response.request.case) { case 'publishDataTrack': { let error; switch (response.reason) { case RequestResponse_Reason.NOT_ALLOWED: error = DataTrackPublishError.notAllowed(response.message); break; case RequestResponse_Reason.DUPLICATE_NAME: error = DataTrackPublishError.duplicateName(response.message); break; case RequestResponse_Reason.INVALID_NAME: error = DataTrackPublishError.invalidName(response.message); break; case RequestResponse_Reason.LIMIT_EXCEEDED: error = DataTrackPublishError.limitReached(response.message); break; default: error = DataTrackPublishError.unknown(response.reason, response.message); break; } this.roomOutgoingDataTrackManager.receivedSfuPublishResponse(response.request.value.pubHandle, { type: 'error', error }); break; } } }; this.updateTrackSubscriptionPermissions = () => { this.log.debug('updating track subscription permissions', { allParticipantsAllowed: this.allParticipantsAllowedToSubscribe, participantTrackPermissions: this.participantTrackPermissions }); this.engine.client.sendUpdateSubscriptionPermissions(this.allParticipantsAllowedToSubscribe, this.participantTrackPermissions.map(p => trackPermissionToProto(p))); }; /** @internal */ this.onTrackUnmuted = track => { this.onTrackMuted(track, track.isUpstreamPaused); }; // when the local track changes in mute status, we'll notify server as such /** @internal */ this.onTrackMuted = (track, muted) => { if (muted === undefined) { muted = true; } if (!track.sid) { this.log.error('could not update mute status for unpublished track', getLogContextFromTrack(track)); return; } this.engine.updateMuteStatus(track.sid, muted); }; this.onTrackUpstreamPaused = track => { this.log.debug('upstream paused', getLogContextFromTrack(track)); this.onTrackMuted(track, true); }; this.onTrackUpstreamResumed = track => { this.log.debug('upstream resumed', getLogContextFromTrack(track)); this.onTrackMuted(track, track.isMuted); }; this.onTrackFeatureUpdate = track => { const pub = this.audioTrackPublications.get(track.sid); if (!pub) { this.log.warn("Could not update local audio track settings, missing publication for track ".concat(track.sid)); return; } this.engine.client.sendUpdateLocalAudioTrack(pub.trackSid, pub.getTrackFeatures()); }; this.onTrackCpuConstrained = (track, publication) => { this.log.debug('track cpu constrained', getLogContextFromTrack(publication)); this.emit(ParticipantEvent.LocalTrackCpuConstrained, track, publication); }; this.handleSubscribedQualityUpdate = update => __awaiter(this, void 0, void 0, function* () { var _a, e_1, _b, _c; var _d; if (!((_d = this.roomOptions) === null || _d === void 0 ? void 0 : _d.dynacast)) { return; } const pub = this.videoTrackPublications.get(update.trackSid); if (!pub) { this.log.warn('received subscribed quality update for unknown track', { trackSid: update.trackSid }); return; } if (!pub.videoTrack) { return; } const newCodecs = yield pub.videoTrack.setPublishingCodecs(update.subscribedCodecs); try { for (var _e = true, newCodecs_1 = __asyncValues(newCodecs), newCodecs_1_1; newCodecs_1_1 = yield newCodecs_1.next(), _a = newCodecs_1_1.done, !_a; _e = true) { _c = newCodecs_1_1.value; _e = false; const codec = _c; if (isBackupCodec(codec)) { this.log.debug("publish ".concat(codec, " for ").concat(pub.videoTrack.sid), getLogContextFromTrack(pub)); yield this.publishAdditionalCodecForTrack(pub.videoTrack, codec, pub.options); } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (!_e && !_a && (_b = newCodecs_1.return)) yield _b.call(newCodecs_1); } finally { if (e_1) throw e_1.error; } } }); this.handleLocalTrackUnpublished = unpublished => { const track = this.trackPublications.get(unpublished.trackSid); if (!track) { this.log.warn('received unpublished event for unknown track', { trackSid: unpublished.trackSid }); return; } this.unpublishTrack(track.track); }; this.handleTrackEnded = track => __awaiter(this, void 0, void 0, function* () { if (track.source === Track.Source.ScreenShare || track.source === Track.Source.ScreenShareAudio) { this.log.debug('unpublishing local track due to TrackEnded', getLogContextFromTrack(track)); this.unpublishTrack(track); } else if (track.isUserProvided) { yield track.mute(); } else if (isLocalAudioTrack(track) || isLocalVideoTrack(track)) { try { if (isWeb()) { try { const currentPermissions = yield navigator === null || navigator === void 0 ? void 0 : navigator.permissions.query({ // the permission query for camera and microphone currently not supported in Safari and Firefox // @ts-ignore name: track.source === Track.Source.Camera ? 'camera' : 'microphone' }); if (currentPermissions && currentPermissions.state === 'denied') { this.log.warn("user has revoked access to ".concat(track.source), getLogContextFromTrack(track)); // detect granted change after permissions were denied to try and resume then currentPermissions.onchange = () => { if (currentPermissions.state !== 'denied') { if (!track.isMuted) { track.restartTrack(); } currentPermissions.onchange = null; } }; throw new Error('GetUserMedia Permission denied'); } } catch (e) { // permissions query fails for firefox, we continue and try to restart the track } } if (!track.isMuted) { this.log.debug('track ended, attempting to use a different device', getLogContextFromTrack(track)); if (isLocalAudioTrack(track)) { // fall back to default device if available yield track.restartTrack({ deviceId: 'default' }); } else { yield track.restartTrack(); } } } catch (e) { this.log.warn("could not restart track, muting instead", getLogContextFromTrack(track)); yield track.mute(); } } }); this.audioTrackPublications = new Map(); this.videoTrackPublications = new Map(); this.trackPublications = new Map(); this.engine = engine; this.roomOptions = options; this.setupEngine(engine); this.activeDeviceMap = new Map([['audioinput', 'default'], ['videoinput', 'default'], ['audiooutput', 'default']]); this.pendingSignalRequests = new Map(); this.roomOutgoingDataStreamManager = roomOutgoingDataStreamManager; this.roomOutgoingDataTrackManager = roomOutgoingDataTrackManager; this.rpcClientManager = rpcClientManager; this.rpcServerManager = rpcServerManager; } get lastCameraError() { return this.cameraError; } get lastMicrophoneError() { return this.microphoneError; } get isE2EEEnabled() { return this.encryptionType !== Encryption_Type.NONE; } getTrackPublication(source) { const track = super.getTrackPublication(source); if (track) { return track; } } getTrackPublicationByName(name) { const track = super.getTrackPublicationByName(name); if (track) { return track; } } /** * @internal */ setupEngine(engine) { var _a; this.engine = engine; this.engine.on(EngineEvent.RemoteMute, (trackSid, muted) => { const pub = this.trackPublications.get(trackSid); if (!pub || !pub.track) { return; } if (muted) { pub.mute(); } else { pub.unmute(); } }); if ((_a = this.signalConnectedFuture) === null || _a === void 0 ? void 0 : _a.isResolved) { this.signalConnectedFuture = undefined; } this.engine.on(EngineEvent.Connected, this.handleReconnected).on(EngineEvent.SignalConnected, this.handleSignalConnected).on(EngineEvent.SignalRestarted, this.handleReconnected).on(EngineEvent.SignalResumed, this.handleReconnected).on(EngineEvent.Restarting, this.handleReconnecting).on(EngineEvent.Resuming, this.handleReconnecting).on(EngineEvent.LocalTrackUnpublished, this.handleLocalTrackUnpublished).on(EngineEvent.SubscribedQualityUpdate, this.handleSubscribedQualityUpdate).on(EngineEvent.Closing, this.handleClosing).on(EngineEvent.SignalRequestResponse, this.handleSignalRequestResponse); } /** * Sets and updates the metadata of the local participant. * Note: this requires `canUpdateOwnMetadata` permission. * method will throw if the user doesn't have the required permissions * @param metadata */ setMetadata(metadata) { return __awaiter(this, void 0, void 0, function* () { yield this.requestMetadataUpdate({ metadata }); }); } /** * Sets and updates the name of the local participant. * Note: this requires `canUpdateOwnMetadata` permission. * method will throw if the user doesn't have the required permissions * @param metadata */ setName(name) { return __awaiter(this, void 0, void 0, function* () { yield this.requestMetadataUpdate({ name }); }); } /** * Set or update participant attributes. It will make updates only to keys that * are present in `attributes`, and will not override others. * Note: this requires `canUpdateOwnMetadata` permission. * @param attributes attributes to update */ setAttributes(attributes) { return __awaiter(this, void 0, void 0, function* () { yield this.requestMetadataUpdate({ attributes }); }); } requestMetadataUpdate(_a) { return __awaiter(this, arguments, void 0, function (_ref) { var _this = this; let metadata = _ref.metadata, name = _ref.name, attributes = _ref.attributes; return function* () { return new TypedPromise((resolve, reject) => __awaiter(_this, void 0, void 0, function* () { var _a, _b; try { let isRejected = false; const requestId = yield this.engine.client.sendUpdateLocalMetadata((_a = metadata !== null && metadata !== void 0 ? metadata : this.metadata) !== null && _a !== void 0 ? _a : '', (_b = name !== null && name !== void 0 ? name : this.name) !== null && _b !== void 0 ? _b : '', attributes); const startTime = performance.now(); this.pendingSignalRequests.set(requestId, { resolve, reject: error => { reject(error); isRejected = true; }, values: { name, metadata, attributes } }); while (performance.now() - startTime < 5000 && !isRejected) { if ((!name || this.name === name) && (!metadata || this.metadata === metadata) && (!attributes || Object.entries(attributes).every(_ref2 => { let _ref3 = _slicedToArray(_ref2, 2), key = _ref3[0], value = _ref3[1]; return this.attributes[key] === value || value === '' && !this.attributes[key]; }))) { this.pendingSignalRequests.delete(requestId); resolve(); return; } yield sleep(50); } reject(new SignalRequestError('Request to update local metadata timed out', 'TimeoutError')); } catch (e) { if (e instanceof Error) { reject(e); } else { reject(new Error(String(e))); } } })); }(); }); } /** * Enable or disable a participant's camera track. * * If a track has already published, it'll mute or unmute the track. * Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise */ setCameraEnabled(enabled, options, publishOptions) { return this.setTrackEnabled(Track.Source.Camera, enabled, options, publishOptions); } /** * Enable or disable a participant's microphone track. * * If a track has already published, it'll mute or unmute the track. * Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise */ setMicrophoneEnabled(enabled, options, publishOptions) { return this.setTrackEnabled(Track.Source.Microphone, enabled, options, publishOptions); } /** * Start or stop sharing a participant's screen * Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise */ setScreenShareEnabled(enabled, options, publishOptions) { return this.setTrackEnabled(Track.Source.ScreenShare, enabled, options, publishOptions); } /** @internal */ setE2EEEnabled(enabled) { return __awaiter(this, void 0, void 0, function* () { const unlock = yield this.e2eeStateMutex.lock(); try { this.encryptionType = enabled ? Encryption_Type.GCM : Encryption_Type.NONE; yield Promise.all(this.pendingPublishPromises.values()); if (this.trackPublications.size === 0 || Array.from(this.trackPublications.values()).every(pub => pub.isEncrypted === enabled)) { return; } yield this.republishAllTracks(undefined, false); } finally { unlock(); } }); } setTrackEnabled(source, enabled, options, publishOptions) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; this.log.debug('setTrackEnabled', { source, enabled }); if (this.republishPromise) { yield this.republishPromise; } let track = this.getTrackPublication(source); if (enabled) { if (track) { yield track.unmute(); } else { let localTracks; if (this.pendingPublishing.has(source)) { const pendingTrack = yield this.waitForPendingPublicationOfSource(source); if (!pendingTrack) { this.log.info('waiting for pending publication promise timed out', { source }); } yield pendingTrack === null || pendingTrack === void 0 ? void 0 : pendingTrack.unmute(); return pendingTrack; } this.pendingPublishing.add(source); try { switch (source) { case Track.Source.Camera: localTracks = yield this.createTracks({ video: (_a = options) !== null && _a !== void 0 ? _a : true }); break; case Track.Source.Microphone: localTracks = yield this.createTracks({ audio: (_b = options) !== null && _b !== void 0 ? _b : true }); break; case Track.Source.ScreenShare: localTracks = yield this.createScreenTracks(Object.assign({}, options)); break; default: throw new TrackInvalidError(source); } } catch (e) { localTracks === null || localTracks === void 0 ? void 0 : localTracks.forEach(tr => { tr.stop(); }); if (e instanceof Error) { this.emit(ParticipantEvent.MediaDevicesError, e, sourceToKind(source)); } this.pendingPublishing.delete(source); throw e; } for (const localTrack of localTracks) { const opts = Object.assign(Object.assign({}, this.roomOptions.publishDefaults), options); if (source === Track.Source.Microphone && isAudioTrack(localTrack) && opts.preConnectBuffer) { this.log.info('starting preconnect buffer for microphone'); localTrack.startPreConnectBuffer(); } } try { const publishPromises = []; for (const localTrack of localTracks) { this.log.info('publishing track', getLogContextFromTrack(localTrack)); publishPromises.push(this.publishTrack(localTrack, publishOptions)); } const publishedTracks = yield Promise.all(publishPromises); // for screen share publications including audio, this will only return the screen share publication, not the screen share audio one // revisit if we want to return an array of tracks instead for v2 var _publishedTracks = _slicedToArray(publishedTracks, 1); track = _publishedTracks[0]; } catch (e) { localTracks === null || localTracks === void 0 ? void 0 : localTracks.forEach(tr => { tr.stop(); }); throw e; } finally { this.pendingPublishing.delete(source); } } } else { if (!(track === null || track === void 0 ? void 0 : track.track) && this.pendingPublishing.has(source)) { // if there's no track available yet first wait for pending publishing promises of that source to see if it becomes available track = yield this.waitForPendingPublicationOfSource(source); if (!track) { this.log.info('waiting for pending publication promise timed out', { source }); } } if (track && track.track) { // screenshare cannot be muted, unpublish instead if (source === Track.Source.ScreenShare) { const unpublishPromises = [this.unpublishTrack(track.track)]; const screenAudioTrack = this.getTrackPublication(Track.Source.ScreenShareAudio); if (screenAudioTrack && screenAudioTrack.track) { unpublishPromises.push(this.unpublishTrack(screenAudioTrack.track)); } var _yield$Promise$all = yield Promise.all(unpublishPromises); var _yield$Promise$all2 = _slicedToArray(_yield$Promise$all, 1); track = _yield$Promise$all2[0]; } else { yield track.mute(); } } } return track; }); } /** * Publish both camera and microphone at the same time. This is useful for * displaying a single Permission Dialog box to the end user. */ enableCameraAndMicrophone() { return __awaiter(this, void 0, void 0, function* () { if (this.pendingPublishing.has(Track.Source.Camera) || this.pendingPublishing.has(Track.Source.Microphone)) { // no-op it's already been requested return; } this.pendingPublishing.add(Track.Source.Camera); this.pendingPublishing.add(Track.Source.Microphone); try { const tracks = yield this.createTracks({ audio: true, video: true }); yield Promise.all(tracks.map(track => this.publishTrack(track))); } finally { this.pendingPublishing.delete(Track.Source.Camera); this.pendingPublishing.delete(Track.Source.Microphone); } }); } /** * Create local camera and/or microphone tracks * @param options * @returns */ createTracks(options) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; options !== null && options !== void 0 ? options : options = {}; const mergedOptionsWithProcessors = mergeDefaultOptions(options, (_a = this.roomOptions) === null || _a === void 0 ? void 0 : _a.audioCaptureDefaults, (_b = this.roomOptions) === null || _b === void 0 ? void 0 : _b.videoCaptureDefaults); try { const tracks = yield createLocalTracks(mergedOptionsWithProcessors, { loggerName: this.roomOptions.loggerName, loggerContextCb: () => this.logContext }); const localTracks = tracks.map(track => { if (isAudioTrack(track)) { this.microphoneError = undefined; track.setAudioContext(this.audioContext); track.source = Track.Source.Microphone; this.emit(ParticipantEvent.AudioStreamAcquired); } if (isVideoTrack(track)) { this.cameraError = undefined; track.source = Track.Source.Camera; } return track; }); return localTracks; } catch (err) { if (err instanceof Error) { if (options.audio) { this.microphoneError = err; } if (options.video) { this.cameraError = err; } } throw err; } }); } /** * Creates a screen capture tracks with getDisplayMedia(). * A LocalVideoTrack is always created and returned. * If { audio: true }, and the browser supports audio capture, a LocalAudioTrack is also created. */ createScreenTracks(options) { return __awaiter(this, void 0, void 0, function* () { if (options === undefined) { options = {}; } if (navigator.mediaDevices.getDisplayMedia === undefined) { throw new DeviceUnsupportedError('getDisplayMedia not supported'); } if (options.resolution === undefined && !isSafari17Based()) { // we need to constrain the dimensions, otherwise it could lead to low bitrate // due to encoding a huge video. Encoding such large surfaces is really expensive // unfortunately Safari 17 has a but and cannot be constrained by default options.resolution = ScreenSharePresets.h1080fps30.resolution; } const constraints = screenCaptureToDisplayMediaStreamOptions(options); const stream = yield navigator.mediaDevices.getDisplayMedia(constraints); const tracks = stream.getVideoTracks(); if (tracks.length === 0) { throw new TrackInvalidError('no video track found'); } const screenVideo = new LocalVideoTrack(tracks[0], undefined, false, { loggerName: this.roomOptions.loggerName, loggerContextCb: () => this.logContext }); screenVideo.source = Track.Source.ScreenShare; if (options.contentHint) { screenVideo.mediaStreamTrack.contentHint = options.contentHint; } const localTracks = [screenVideo]; if (stream.getAudioTracks().length > 0) { this.emit(ParticipantEvent.AudioStreamAcquired); const screenAudio = new LocalAudioTrack(stream.getAudioTracks()[0], undefined, false, this.audioContext, { loggerName: this.roomOptions.loggerName, loggerContextCb: () => this.logContext }); screenAudio.source = Track.Source.ScreenShareAudio; localTracks.push(screenAudio); } return localTracks; }); } /** * Publish a new track to the room * @param track * @param options */ publishTrack(track, options) { return __awaiter(this, void 0, void 0, function* () { return this.publishOrRepublishTrack(track, options); }); } /** * Waits for the engine's next `Restarted` event. Unlike `engine.waitForRestarted`, this does * not short-circuit when `pcState === Connected` — at the point this is called (right after a * `NegotiationError`) the PC transport is still connected, but `fullReconnectOnNext` has been * set and `attemptReconnect` is queued via setTimeout. We need to wait for that restart to * actually complete (which clears `pendingTrackResolvers` via `cleanupClient`) before retrying. */ waitForNextEngineRestart() { let timeoutMs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 15000; return new Promise((resolve, reject) => { const cleanup = () => { clearTimeout(timeout); this.engine.off(EngineEvent.Restarted, onRestarted); this.engine.off(EngineEvent.Closing, onClosing); }; const onRestarted = () => { cleanup(); resolve(); }; const onClosing = () => { cleanup(); reject(new Error('engine closed before restart completed')); }; const timeout = setTimeout(() => { cleanup(); reject(new Error('timed out waiting for engine restart')); }, timeoutMs); this.engine.once(EngineEvent.Restarted, onRestarted); this.engine.once(EngineEvent.Closing, onClosing); }); } publishOrRepublishTrack(track_1, options_1) { return __awaiter(this, arguments, void 0, function (track, options) { var _this2 = this; let isRepublish = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; let hasRetriedAfterNegotiationError = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; return function* () { var _a, _b, _c, _d; if (isLocalAudioTrack(track)) { track.setAudioContext(_this2.audioContext); } yield (_a = _this2.reconnectFuture) === null || _a === void 0 ? void 0 : _a.promise; if (_this2.republishPromise && !isRepublish) { yield _this2.republishPromise; } if (isLocalTrack(track) && _this2.pendingPublishPromises.has(track)) { yield _this2.pendingPublishPromises.get(track); } let defaultConstraints; if (track instanceof MediaStreamTrack) { defaultConstraints = track.getConstraints(); } else { // we want to access constraints directly as `track.mediaStreamTrack` // might be pointing to a non-device track (e.g. processed track) already defaultConstraints = track.constraints; let deviceKind = undefined; switch (track.source) { case Track.Source.Microphone: deviceKind = 'audioinput'; break; case Track.Source.Camera: deviceKind = 'videoinput'; } if (deviceKind && _this2.activeDeviceMap.has(deviceKind)) { defaultConstraints = Object.assign(Object.assign({}, defaultConstraints), { deviceId: _this2.activeDeviceMap.get(deviceKind) }); } } // convert raw media track into audio or video track if (track instanceof MediaStreamTrack) { switch (track.kind) { case 'audio': track = new LocalAudioTrack(track, defaultConstraints, true, _this2.audioContext, { loggerName: _this2.roomOptions.loggerName, loggerContextCb: () => _this2.logContext }); break; case 'video': track = new LocalVideoTrack(track, defaultConstraints, true, { loggerName: _this2.roomOptions.loggerName, loggerContextCb: () => _this2.logContext }); break; default: throw new TrackInvalidError("unsupported MediaStreamTrack kind ".concat(track.kind)); } } else { track.updateLoggerOptions({ loggerName: _this2.roomOptions.loggerName, loggerContextCb: () => _this2.logContext }); } // is it already published? if so skip let existingPublication; _this2.trackPublications.forEach(publication => { if (!publication.track) { return; } if (publication.track === track) { existingPublication = publication; } }); if (existingPublication) { _this2.log.warn('track has already been published, skipping', getLogContextFromTrack(existingPublication)); return existingPublication; } const opts = Object.assign(Object.assign({}, _this2.roomOptions.publishDefaults), options); const isStereoInput = 'channelCount' in track.mediaStreamTrack.getSettings() && // @ts-ignore `channelCount` on getSettings() is currently only available for Safari, but is generally the best way to determine a stereo track https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings/channelCount track.mediaStreamTrack.getSettings().channelCount === 2 || track.mediaStreamTrack.getConstraints().channelCount === 2; const isStereo = (_b = opts.forceStereo) !== null && _b !== void 0 ? _b : isStereoInput; // disable dtx for stereo track if not enabled explicitly if (isStereo) { if (opts.dtx === undefined) { _this2.log.debug("Opus DTX will be disabled for stereo tracks by default. Enable them explicitly to make it work.", getLogContextFromTrack(track)); } if (opts.red === undefined) { _this2.log.debug("Opus RED will be disabled for stereo tracks by default. Enable them explicitly to make it work."); } (_c = opts.dtx) !== null && _c !== void 0 ? _c : opts.dtx = false; (_d = opts.red) !== null && _d !== void 0 ? _d : opts.red = false; } if (!isE2EESimulcastSupported() && _this2.roomOptions.e2ee) { _this2.log.info("End-to-end encryption is set up, simulcast publishing will be disabled on Safari versions and iOS browsers running iOS < v17.2"); opts.simulcast = false; } if (opts.source) { track.source = opts.source; } const publishPromise = new Promise((resolve, reject) => __awaiter(_this2, void 0, void 0, function* () { try { if (this.engine.client.currentState !== SignalConnectionState.CONNECTED) { this.log.debug('deferring track publication until signal is connected', { track: getLogContextFromTrack(track) }); let publicationTimedOut = false; const timeout = setTimeout(() => { publicationTimedOut = true; track.stop(); reject(new PublishTrackError('publishing rejected as engine not connected within timeout', 408)); }, 15000); yield this.waitUntilEngineConnected(); clearTimeout(timeout); if (publicationTimedOut) { return; } const publication = yield this.publish(track, opts, isStereo); resolve(publication); } else { try { const publication = yield this.publish(track, opts, isStereo); resolve(publication); } catch (e) { reject(e); } } } catch (e) { reject(e); } })); _this2.pendingPublishPromises.set(track, publishPromise); try { const publication = yield publishPromise; return publication; } catch (e) { if (!hasRetriedAfterNegotiationError && e instanceof NegotiationError) { _this2.log.warn('negotiation due to track publish failed, retrying after reconnect', { error: e }); _this2.pendingPublishPromises.delete(track); yield _this2.waitForNextEngineRestart(); return yield _this2.publishOrRepublishTrack(track, options, isRepublish, true); } throw e; } finally { _this2.pendingPublishPromises.delete(track); } }(); }); } waitUntilEngineConnected() { if (!this.signalConnectedFuture) { this.signalConnectedFuture = new Future(); } return this.signalConnectedFuture.promise; } hasPermissionsToPublish(track) { if (!this.permissions) { this.log.warn('no permissions present for publishing track', getLogContextFromTrack(track)); return false; } const _this$permissions = this.permissions, canPublish = _this$permissions.canPublish, canPublishSources = _this$permissions.canPublishSources; if (canPublish && (canPublishSources.length === 0 || canPublishSources.map(source => getTrackSourceFromProto(source)).includes(track.source))) { return true; } this.log.warn('insufficient permissions to publish', getLogContextFromTrack(track)); return false; } publish(track, opts, isStereo) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; if (!this.hasPermissionsToPublish(track)) { throw new PublishTrackError('failed to publish track, insufficient permissions', 403); } const existingTrackOfSource = Array.from(this.trackPublications.values()).find(publishedTrack => isLocalTrack(track) && publishedTrack.source === track.source); if (existingTrackOfSource && track.source !== Track.Source.Unknown) { this.log.info("publishing a second track with the same source: ".concat(track.source), getLogContextFromTrack(track)); } if (opts.stopMicTrackOnMute && isAudioTrack(track)) { track.stopOnMute = true; } if (track.source === Track.Source.ScreenShare && isFireFox()) { // Firefox does not work well with simulcasted screen share // we frequently get no data on layer 0 when enabled opts.simulcast = false; } // require full AV1/VP9 SVC support prior to using it if (opts.videoCodec === 'av1' && !supportsAV1()) { opts.videoCodec = undefined; } if (opts.videoCodec === 'vp9' && !supportsVP9()) { opts.videoCodec = undefined; } if (opts.videoCodec === undefined) { opts.videoCodec = defaultVideoCodec; } if (this.enabledPublishVideoCodecs.length > 0) { // fallback to a supported codec if it is not supported if (!this.enabledPublishVideoCodecs.some(c => opts.videoCodec === mimeTypeToVideoCodecString(c.mime))) { opts.videoCodec = mimeTypeToVideoCodecString(this.enabledPublishVideoCodecs[0].mime); } } const videoCodec = opts.videoCodec; // handle track actions track.on(TrackEvent.Muted, this.onTrackMuted); track.on(TrackEvent.Unmuted, this.onTrackUnmuted); track.on(TrackEvent.Ended, this.handleTrackEnded); track.on(TrackEvent.UpstreamPaused, this.onTrackUpstreamPaused); track.on(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed); track.on(TrackEvent.AudioTrackFeatureUpdate, this.onTrackFeatureUpdate); const audioFeatures = []; const disableDtx = !((_a = opts.dtx) !== null && _a !== void 0 ? _a : true); const settings = track.getSourceTrackSettings(); if (settings.autoGainControl) { audioFeatures.push(AudioTrackFeature.TF_AUTO_GAIN_CONTROL); } if (settings.echoCancellation) { audioFeatures.push(AudioTrackFeature.TF_ECHO_CANCELLATION); } if (settings.noiseSuppression) { audioFeatures.push(AudioTrackFeature.TF_NOISE_SUPPRESSION); } if (settings.channelCount && settings.channelCount > 1) { audioFeatures.push(AudioTrackFeature.TF_STEREO); } if (disableDtx) { audioFeatures.push(AudioTrackFeature.TF_NO_DTX); } if (isLocalAudioTrack(track) && track.hasPreConnectBuffer) { audioFeatures.push(AudioTrackFeature.TF_PRECONNECT_BUFFER); } const packetTrailerFeatures = this.normalizeRequestedFrameMetadataOptions(track, opts); // create track publication from track const req = new AddTrackRequest({ // get local track id for use during publishing cid: track.mediaStreamTrack.id, name: opts.name, type: Track.kindToProto(track.kind), muted: track.isMuted, source: Track.sourceToProto(track.source), disableDtx, encryption: this.encryptionType, stereo: isStereo, disableRed: this.isE2EEEnabled || !((_b = opts.red) !== null && _b !== void 0 ? _b : true), stream: opts === null || opts === void 0 ? void 0 : opts.stream, backupCodecPolicy: opts === null || opts === void 0 ? void 0 : opts.backupCodecPolicy, audioFeatures, packetTrailerFeatures }); // compute encodings and layers for video let encodings; if (track.kind === Track.Kind.Video) { let dims; try { dims = yield track.waitForDimensions(); } catch (e) { // use defaults, it's quite painful for congestion control without simulcast // so using default dims according to publish settings const defaultRes = (_d = (_c = this.roomOptions.videoCaptureDefaults) === null || _c === void 0 ? void 0 : _c.resolution) !== null && _d !== void 0 ? _d : VideoPresets.h720.resolution; dims = { width: defaultRes.width, height: defaultRes.height }; // log failure this.log.error('could not determine track dimensions, using defaults', Object.assign(Object.assign({}, getLogContextFromTrack(track)), { dims })); } // width and height should be defined for video req.width = dims.width; req.height = dims.height; // for svc codecs, disable simulcast and use vp8 for backup codec if (isLocalVideoTrack(track)) { if (isSVCCodec(videoCodec)) { if (track.source === Track.Source.ScreenShare) { // vp9 svc with screenshare cannot encode multiple spatial layers // doing so reduces publish resolution to minimal resolution opts.scalabilityMode = 'L1T3'; // Chrome does not allow more than 5 fps with L1T3, and it has encoding bugs with L3T3 // It has a different path for screenshare handling and it seems to be untested/buggy // As a workaround, we are setting contentHint to force it to go through the same // path as regular camera video. While this is not optimal, it delivers the performance // that we need if ('contentHint' in track.mediaStreamTrack) { track.mediaStreamTrack.contentHint = 'motion'; this.log.debug('forcing contentHint to motion for screenshare with SVC codecs', getLogContextFromTrack(track)); } } // set scalabilityMode to 'L3T3_KEY' by default opts.scalabilityMode = (_e = opts.scalabilityMode) !== null && _e !== void 0 ? _e : 'L3T3_KEY'; } req.simulcastCodecs = [new SimulcastCodec({ codec: videoCodec, cid: track.mediaStreamTrack.id })]; // set up backup if (opts.backupCodec === true) { opts.backupCodec = { codec: defaultVideoCodec }; } if (opts.backupCodec && videoCodec !== opts.backupCodec.codec && // TODO remove this once e2ee is supported for backup codecs req.encryption === Encryption_Type.NONE) { // multi-codec simulcast requires dynacast if (!this.roomOptions.dynacast) { this.roomOptions.dynacast = true; } req.simulcastCodecs.push(new SimulcastCodec({ codec: opts.backupCodec.codec, cid: '' })); } } encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, req.width, req.height, opts); req.layers = videoLayersFromEncodings(req.width, req.height, encodings, isSVCCodec(opts.videoCodec)); } else if (track.kind === Track.Kind.Audio) { encodings = [{ maxBitrate: (_f = opts.audioPreset) === null || _f === void 0 ? void 0 : _f.maxBitrate, priority: (_h = (_g = opts.audioPreset) === null || _g === void 0 ? void 0 : _g.priority) !== null && _h !== void 0 ? _h : 'high', networkPriority: (_k = (_j = opts.audioPreset) === null || _j === void 0 ? void 0 : _j.priority) !== null && _k !== void 0 ? _k : 'high' }]; } if (!this.engine || this.engine.isClosed) { throw new UnexpectedConnectionState('cannot publish track when not connected'); } const negotiate = () => __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; if (!this.engine.pcManager) { throw new UnexpectedConnectionState('pcManager is not ready'); } track.sender = yield this.engine.createSender(track, opts, encodings); if (isLocalVideoTrack(track)) { track.publishOptions = opts; } this.emit(ParticipantEvent.LocalSenderCreated, track.sender, track); if (isLocalVideoTrack(track)) { (_a = opts.degradationPreference) !== null && _a !== void 0 ? _a : opts.degradationPreference = getDefaultDegradationPreference(track); track.setDegradationPreference(opts.degradationPreference); } if (encodings) { if (isFireFox() && track.kind === Track.Kind.Audio) { /* Refer to RFC https://datatracker.ietf.org/doc/html/rfc7587#section-6.1, livekit-server uses maxaveragebitrate=510000 in the answer sdp to permit client to publish high quality audio track. But firefox always uses this value as the actual bitrates, causing the audio bitrates to rise to 510Kbps in any stereo case unexpectedly. So the client need to modify maxaverragebitrates in answer sdp to user provided value to fix the issue. */ let trackTransceiver = undefined; for (const transceiver of this.engine.pcManager.publisher.getTransceivers()) { if (transceiver.sender === track.sender) { trackTransceiver = transceiver; break; } } if (trackTransceiver) { this.engine.pcManager.publisher.setTrackCodecBitrate({ transceiver: trackTransceiver, codec: 'opus', maxbr: ((_b = encodings[0]) === null || _b === void 0 ? void 0 : _b.maxBitrate) ? encodings[0].maxBitrate / 1000 : 0 }); } } else if (track.codec && isSVCCodec(track.codec) && ((_c = encodings[0]) === null || _c === void 0 ? void 0 : _c.maxBitrate)) { this.engine.pcManager.publisher.setTrackCodecBitrate({ cid: req.cid, codec: track.codec, maxbr: encodings[0].maxBitrate / 1000 }); } } yield this.engine.negotiate(); }); let ti; const addTrackPromise = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { var _a; try { ti = yield this.engine.addTrack(req); resolve(ti); } catch (err) { if (track.sender && ((_a = this.engine.pcManager) === null || _a === void 0 ? void 0 : _a.publisher)) { try { this.engine.pcManager.publisher.removeTrack(track.sender); } catch (e) { this.log.error(e); } yield this.engine.negotiate().catch(negotiateErr => { this.log.error('failed to negotiate after removing track due to failed add track request', Object.assign(Object.assign({}, getLogContextFromTrack(track)), { error: negotiateErr })); }); } reject(err); } })); if (this.enabledPublishVideoCodecs.length > 0 && packetTrailerFeatures.length === 0) { const rets = yield Promise.all([addTrackPromise, negotiate()]); ti = rets[0]; } else { ti = yield addTrackPromise; // server might not support the codec the client has requested, in that case, fallback // to a supported codec let primaryCodecMime; ti.codecs.forEach(codec => { if (primaryCodecMime === undefined) { primaryCodecMime = codec.mimeType; } }); if (primaryCodecMime && track.kind === Track.Kind.Video) { const updatedCodec = mimeTypeToVideoCodecString(primaryCodecMime); if (updatedCodec !== videoCodec) { this.log.debug('falling back to server selected codec', Object.assign(Object.assign({}, getLogContextFromTrack(track)), { codec: updatedCodec })); opts.videoCodec = updatedCodec; // recompute encodings since bitrates/etc could have changed encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, req.width, req.height, opts); } } yield negotiate(); } const publication = new LocalTrackPublication(track.kind, ti, track, { loggerName: this.roomOptions.loggerName, loggerContextCb: () => this.logContext }); publication.on(TrackEvent.CpuConstrained, constrainedTrack => this.onTrackCpuConstrained(constrainedTrack, publication)); // save options for when it needs to be republished again publication.options = opts; track.sid = ti.sid; // keep publish options on the video track so that it can recompute encoding // parameters when the MediaStreamTrack is restarted (e.g. after switching cameras). // Seed the dimensions we encoded at publish time so the first no-op restart // (e.g. unmute with unchanged constraints) can skip the recompute. if (isLocalVideoTrack(track)) { track.publishOptions = opts; if (req.width && req.height) { track.lastEncodedDimensions = { width: req.width, height: req.height }; } } this.log.debug("publishing ".concat(track.kind, " with encodings"), { encodings, trackInfo: ti }); if (isLocalVideoTrack(track)) { track.startMonitor(this.engine.client); } else if (isLocalAudioTrack(track)) { track.startMonitor(); } this.addTrackPublication(publication); // send event for publication this.emit(ParticipantEvent.LocalTrackPublished, publication); if (isLocalAudioTrack(track) && ti.audioFeatures.includes(AudioTrackFeature.TF_PRECONNECT_BUFFER)) { const stream = track.getPreConnectBuffer(); const mimeType = track.getPreConnectBufferMimeType(); // TODO: we're registering the listener after negotiation, so there might be a race this.on(ParticipantEvent.LocalTrackSubscribed, pub => { if (pub.trackSid === ti.sid) { if (!track.hasPreConnectBuffer) { this.log.warn('subscribe event came to late, buffer already closed'); return; } this.log.debug('finished recording preconnect buffer', getLogContextFromTrack(track)); track.stopPreConnectBuffer(); } }); if (stream) { const bufferStreamPromise = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { var _a, e_2, _b, _c; var _d, _e; try { this.log.debug('waiting for agent', getLogContextFromTrack(track)); const agentActiveTimeout = setTimeout(() => { reject(new Error('agent not active within 10 seconds')); }, 10000); const agent = yield this.waitUntilActiveAgentPresent(); clearTimeout(agentActiveTimeout); this.log.debug('sending preconnect buffer', getLogContextFromTrack(track)); const writer = yield this.streamBytes({ name: 'preconnect-buffer', mimeType, topic: 'lk.agent.pre-connect-audio-buffer', destinationIdentities: [agent.identity], attributes: { trackId: publication.trackSid, sampleRate: String((_d = settings.sampleRate) !== null && _d !== void 0 ? _d : '48000'), channels: String((_e = settings.channelCount) !== null && _e !== void 0 ? _e : '1') } }); try { for (var _f = true, stream_1 = __asyncValues(stream), stream_1_1; stream_1_1 = yield stream_1.next(), _a = stream_1_1.done, !_a; _f = true) { _c = stream_1_1.value; _f = false; const chunk = _c; yield writer.write(chunk); } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (!_f && !_a && (_b = stream_1.return)) yield _b.call(stream_1); } finally { if (e_2) throw e_2.error; } } yield writer.close(); resolve(); } catch (e) { reject(e); } })); bufferStreamPromise.then(() => { this.log.debug('preconnect buffer sent successfully', getLogContextFromTrack(track)); }).catch(e => { this.log.error('error sending preconnect buffer', Object.assign(Object.assign({}, getLogContextFromTrack(track)), { error: e })); }); } } return publication; }); } canPublishFrameMetadata() { var _a; return !!(this.roomOptions.e2ee || this.roomOptions.encryption || isFrameMetadataSupported((_a = this.roomOptions.frameMetadata) !== null && _a !== void 0 ? _a : this.roomOptions.packetTrailer)); } normalizeRequestedFrameMetadataOptions(track, opts) { var _a; const fmOpts = (_a = opts.frameMetadata) !== null && _a !== void 0 ? _a : opts.packetTrailer; if (track.kind !== Track.Kind.Video || !hasFrameMetadataPublishOptions(fmOpts)) { opts.frameMetadata = undefined; opts.packetTrailer = undefined; return []; } if (!this.canPublishFrameMetadata()) { this.log.warn('frame metadata transform not supported; not advertising features', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track))); opts.frameMetadata = undefined; opts.packetTrailer = undefined; return []; } const features = getFrameMetadataFeatures(fmOpts); const normalized = getFrameMetadataPublishOptions(features); opts.frameMetadata = normalized; opts.packetTrailer = normalized; return features; } get isLocal() { return true; } /** @internal * publish additional codec to existing track */ publishAdditionalCodecForTrack(track, videoCodec, options) { return __awaiter(this, void 0, void 0, function* () { var _a; // TODO remove once e2ee is supported for backup tracks if (this.encryptionType !== Encryption_Type.NONE) { return; } // is it not published? if so skip let existingPublication; this.trackPublications.forEach(publication => { if (!publication.track) { return; } if (publication.track === track) { existingPublication = publication; } }); if (!existingPublication) { throw new TrackInvalidError('track is not published'); } if (!isLocalVideoTrack(track)) { throw new TrackInvalidError('track is not a video track'); } const opts = Object.assign(Object.assign({}, (_a = this.roomOptions) === null || _a === void 0 ? void 0 : _a.publishDefaults), options); const encodings = computeTrackBackupEncodings(track, videoCodec, opts); if (!encodings) { this.log.info("backup codec has been disabled, ignoring request to add additional codec for track", getLogContextFromTrack(track)); return; } const simulcastTrack = track.addSimulcastTrack(videoCodec, encodings); if (!simulcastTrack) { return; } const packetTrailerFeatures = this.normalizeRequestedFrameMetadataOptions(track, opts); const req = new AddTrackRequest({ cid: simulcastTrack.mediaStreamTrack.id, type: Track.kindToProto(track.kind), muted: track.isMuted, source: Track.sourceToProto(track.source), sid: track.sid, packetTrailerFeatures, simulcastCodecs: [{ codec: opts.videoCodec, cid: simulcastTrack.mediaStreamTrack.id }] }); req.layers = videoLayersFromEncodings(req.width, req.height, encodings); if (!this.engine || this.engine.isClosed) { throw new UnexpectedConnectionState('cannot publish track when not connected'); } const negotiate = () => __awaiter(this, void 0, void 0, function* () { yield this.engine.createSimulcastSender(track, simulcastTrack, opts, encodings); yield this.engine.negotiate(); }); const rets = yield Promise.all([this.engine.addTrack(req), negotiate()]); const ti = rets[0]; this.log.debug("published ".concat(videoCodec, " for track ").concat(track.sid), { encodings, trackInfo: ti }); }); } unpublishTrack(track, stopOnUnpublish) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; if (isLocalTrack(track)) { const publishPromise = this.pendingPublishPromises.get(track); if (publishPromise) { this.log.debug('awaiting publish promise before attempting to unpublish', getLogContextFromTrack(track)); yield publishPromise; } } // look through all published tracks to find the right ones const publication = this.getPublicationForTrack(track); const pubLogContext = publication ? getLogContextFromTrack(publication) : undefined; this.log.info('unpublishing track', pubLogContext); if (!publication || !publication.track) { this.log.warn('track was not unpublished because no publication was found', pubLogContext); return undefined; } track = publication.track; track.off(TrackEvent.Muted, this.onTrackMuted); track.off(TrackEvent.Unmuted, this.onTrackUnmuted); track.off(TrackEvent.Ended, this.handleTrackEnded); track.off(TrackEvent.UpstreamPaused, this.onTrackUpstreamPaused); track.off(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed); track.off(TrackEvent.AudioTrackFeatureUpdate, this.onTrackFeatureUpdate); if (stopOnUnpublish === undefined) { stopOnUnpublish = (_b = (_a = this.roomOptions) === null || _a === void 0 ? void 0 : _a.stopLocalTrackOnUnpublish) !== null && _b !== void 0 ? _b : true; } if (stopOnUnpublish) { track.stop(); } else { track.stopMonitor(); } let negotiationNeeded = false; const trackSender = track.sender; track.sender = undefined; if (this.engine.pcManager && this.engine.pcManager.currentState < PCTransportState.FAILED && trackSender) { try { for (const transceiver of this.engine.pcManager.publisher.getTransceivers()) { // if sender is not currently sending (after replaceTrack(null)) // removeTrack would have no effect. // to ensure we end up successfully removing the track, manually set // the transceiver to inactive if (transceiver.sender === trackSender) { transceiver.direction = 'inactive'; negotiationNeeded = true; } } try { negotiationNeeded = this.engine.removeTrack(trackSender); } catch (e) { this.log.warn(e); negotiationNeeded = true; } if (isLocalVideoTrack(track)) { for (const _ref4 of track.simulcastCodecs) { var _ref5 = _slicedToArray(_ref4, 2); const trackInfo = _ref5[1]; if (trackInfo.sender) { try { negotiationNeeded = this.engine.removeTrack(trackInfo.sender); } catch (e) { this.log.warn(e); negotiationNeeded = true; } trackInfo.sender = undefined; } } track.simulcastCodecs.clear(); } } catch (e) { this.log.warn('failed to unpublish track', Object.assign(Object.assign({}, pubLogContext), { error: e })); } } // remove from our maps this.trackPublications.delete(publication.trackSid); switch (publication.kind) { case Track.Kind.Audio: this.audioTrackPublications.delete(publication.trackSid); break; case Track.Kind.Video: this.videoTrackPublications.delete(publication.trackSid); break; } this.emit(ParticipantEvent.LocalTrackUnpublished, publication); publication.setTrack(undefined); if (negotiationNeeded) { yield this.engine.negotiate(); } return publication; }); } unpublishTracks(tracks) { return __awaiter(this, void 0, void 0, function* () { const results = yield Promise.all(tracks.map(track => this.unpublishTrack(track))); return results.filter(track => !!track); }); } republishAllTracks(options_1) { return __awaiter(this, arguments, void 0, function (options) { var _this3 = this; let restartTracks = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; return function* () { if (_this3.republishPromise) { yield _this3.republishPromise; } _this3.republishPromise = new TypedPromise((resolve, reject) => __awaiter(_this3, void 0, void 0, function* () { try { const localPubs = []; this.trackPublications.forEach(pub => { if (pub.track) { if (options) { pub.options = Object.assign(Object.assign({}, pub.options), options); } localPubs.push(pub); } }); yield Promise.all(localPubs.map(pub => __awaiter(this, void 0, void 0, function* () { const track = pub.track; yield this.unpublishTrack(track, false); if (restartTracks && !track.isMuted && track.source !== Track.Source.ScreenShare && track.source !== Track.Source.ScreenShareAudio && (isLocalAudioTrack(track) || isLocalVideoTrack(track)) && !track.isUserProvided) { // generally we need to restart the track before publishing, often a full reconnect // is necessary because computer had gone to sleep. this.log.debug('restarting existing track', { track: pub.trackSid }); yield track.restartTrack(); } yield this.publishOrRepublishTrack(track, pub.options, true); }))); resolve(); } catch (error) { if (error instanceof Error) { reject(error); } else { reject(new Error(String(error))); } } finally { this.republishPromise = undefined; } })); yield _this3.republishPromise; }(); }); } /** * Publish a new data payload to the room. Data will be forwarded to each * participant in the room if the destination field in publishOptions is empty * * @param data Uint8Array of the payload. To send string data, use TextEncoder.encode * @param options optionally specify a `reliable`, `topic` and `destination` */ publishData(data_1) { return __awaiter(this, arguments, void 0, function (data) { var _this4 = this; let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return function* () { const kind = options.reliable ? DataChannelKind.RELIABLE : DataChannelKind.LOSSY; const dataPacketKind = options.reliable ? DataPacket_Kind.RELIABLE : DataPacket_Kind.LOSSY; const destinationIdentities = options.destinationIdentities; const topic = options.topic; let userPacket = new UserPacket({ participantIdentity: _this4.identity, payload: data, destinationIdentities, topic }); const packet = new DataPacket({ kind: dataPacketKind, value: { case: 'user', value: userPacket } }); yield _this4.engine.sendDataPacket(packet, kind); }(); }); } /** * Publish SIP DTMF message to the room. * * @param code DTMF code * @param digit DTMF digit */ publishDtmf(code, digit) { return __awaiter(this, void 0, void 0, function* () { const packet = new DataPacket({ kind: DataPacket_Kind.RELIABLE, value: { case: 'sipDtmf', value: new SipDTMF({ code: code, digit: digit }) } }); yield this.engine.sendDataPacket(packet, DataChannelKind.RELIABLE); }); } /** @deprecated Consider migrating to {@link sendText} */ sendChatMessage(text, options) { return __awaiter(this, void 0, void 0, function* () { const msg = { id: crypto.randomUUID(), message: text, timestamp: Date.now(), attachedFiles: options === null || options === void 0 ? void 0 : options.attachments }; const packet = new DataPacket({ value: { case: 'chatMessage', value: new ChatMessage(Object.assign(Object.assign({}, msg), { timestamp: protoInt64.parse(msg.timestamp) })) } }); yield this.engine.sendDataPacket(packet, DataChannelKind.RELIABLE); this.emit(ParticipantEvent.ChatMessage, msg); return msg; }); } /** @deprecated Consider migrating to {@link sendText} */ editChatMessage(editText, originalMessage) { return __awaiter(this, void 0, void 0, function* () { const msg = Object.assign(Object.assign({}, originalMessage), { message: editText, editTimestamp: Date.now() }); const packet = new DataPacket({ value: { case: 'chatMessage', value: new ChatMessage(Object.assign(Object.assign({}, msg), { timestamp: protoInt64.parse(msg.timestamp), editTimestamp: protoInt64.parse(msg.editTimestamp) })) } }); yield this.engine.sendDataPacket(packet, DataChannelKind.RELIABLE); this.emit(ParticipantEvent.ChatMessage, msg); return msg; }); } /** * Sends the given string to participants in the room via the data channel. * For longer messages, consider using {@link streamText} instead. * * @param text The text payload * @param options.topic Topic identifier used to route the stream to appropriate handlers. */ sendText(text, options) { return __awaiter(this, void 0, void 0, function* () { return this.roomOutgoingDataStreamManager.sendText(text, options); }); } /** * Creates a new TextStreamWriter which can be used to stream text incrementally * to participants in the room via the data channel. * * @param options.topic Topic identifier used to route the stream to appropriate handlers. * * @internal * @experimental CAUTION, might get removed in a minor release */ streamText(options) { return __awaiter(this, void 0, void 0, function* () { return this.roomOutgoingDataStreamManager.streamText(options); }); } /** Send a File to all participants in the room via the data channel. * @param file The File object payload * @param options.topic Topic identifier used to route the stream to appropriate handlers. * @param options.onProgress A callback function used to monitor the upload progress percentage. */ sendFile(file, options) { return __awaiter(this, void 0, void 0, function* () { return this.roomOutgoingDataStreamManager.sendFile(file, options); }); } /** * Stream bytes incrementally to participants in the room via the data channel. * For sending files, consider using {@link sendFile} instead. * * @param options.topic Topic identifier used to route the stream to appropriate handlers. */ streamBytes(options) { return __awaiter(this, void 0, void 0, function* () { return this.roomOutgoingDataStreamManager.streamBytes(options); }); } /** * Initiate an RPC call to a remote participant * @param params - Parameters for initiating the RPC call, see {@link PerformRpcParams} * @returns A promise that resolves with the response payload or rejects with an error. * @throws Error on failure. Details in `message`. */ performRpc(params) { return this.rpcClientManager.performRpc(params).then(_ref6 => { let _ref7 = _slicedToArray(_ref6, 2); _ref7[0]; let completionPromise = _ref7[1]; return completionPromise; }); } /** * @deprecated use `room.registerRpcMethod` instead */ registerRpcMethod(method, handler) { this.rpcServerManager.registerRpcMethod(method, handler); } /** * @deprecated use `room.unregisterRpcMethod` instead */ unregisterRpcMethod(method) { this.rpcServerManager.unregisterRpcMethod(method); } /** * Control who can subscribe to LocalParticipant's published tracks. * * By default, all participants can subscribe. This allows fine-grained control over * who is able to subscribe at a participant and track level. * * Note: if access is given at a track-level (i.e. both [allParticipantsAllowed] and * [ParticipantTrackPermission.allTracksAllowed] are false), any newer published tracks * will not grant permissions to any participants and will require a subsequent * permissions update to allow subscription. * * @param allParticipantsAllowed Allows all participants to subscribe all tracks. * Takes precedence over [[participantTrackPermissions]] if set to true. * By default this is set to true. * @param participantTrackPermissions Full list of individual permissions per * participant/track. Any omitted participants will not receive any permissions. */ setTrackSubscriptionPermissions(allParticipantsAllowed) { let participantTrackPermissions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; this.participantTrackPermissions = participantTrackPermissions; this.allParticipantsAllowedToSubscribe = allParticipantsAllowed; if (!this.engine.client.isDisconnected) { this.updateTrackSubscriptionPermissions(); } } /** @internal */ setEnabledPublishCodecs(codecs) { this.enabledPublishVideoCodecs = codecs.filter(c => c.mime.split('/')[0].toLowerCase() === 'video'); } /** @internal */ updateInfo(info) { if (!super.updateInfo(info)) { return false; } // reconcile track mute status. // if server's track mute status doesn't match actual, we'll have to update // the server's copy info.tracks.forEach(ti => { var _a, _b; const pub = this.trackPublications.get(ti.sid); if (pub) { const mutedOnServer = pub.isMuted || ((_b = (_a = pub.track) === null || _a === void 0 ? void 0 : _a.isUpstreamPaused) !== null && _b !== void 0 ? _b : false); if (mutedOnServer !== ti.muted) { this.log.debug('updating server mute state after reconcile', Object.assign(Object.assign({}, getLogContextFromTrack(pub)), { mutedOnServer })); this.engine.client.sendMuteTrack(ti.sid, mutedOnServer); } } }); return true; } /** @internal */ setActiveAgent(agent) { var _a, _b, _c, _d; this.firstActiveAgent = agent; if (agent && !this.firstActiveAgent) { this.firstActiveAgent = agent; } if (agent) { (_b = (_a = this.activeAgentFuture) === null || _a === void 0 ? void 0 : _a.resolve) === null || _b === void 0 ? void 0 : _b.call(_a, agent); } else { (_d = (_c = this.activeAgentFuture) === null || _c === void 0 ? void 0 : _c.reject) === null || _d === void 0 ? void 0 : _d.call(_c, new Error('Agent disconnected')); } this.activeAgentFuture = undefined; } waitUntilActiveAgentPresent() { if (this.firstActiveAgent) { return Promise.resolve(this.firstActiveAgent); } if (!this.activeAgentFuture) { this.activeAgentFuture = new Future(); } return this.activeAgentFuture.promise; } getPublicationForTrack(track) { let publication; this.trackPublications.forEach(pub => { const localTrack = pub.track; if (!localTrack) { return; } // this looks overly complicated due to this object tree if (track instanceof MediaStreamTrack) { if (isLocalAudioTrack(localTrack) || isLocalVideoTrack(localTrack)) { if (localTrack.mediaStreamTrack === track) { publication = pub; } } } else if (track === localTrack) { publication = pub; } }); return publication; } waitForPendingPublicationOfSource(source) { return __awaiter(this, void 0, void 0, function* () { const waitForPendingTimeout = 10000; const startTime = Date.now(); while (Date.now() < startTime + waitForPendingTimeout) { const publishPromiseEntry = Array.from(this.pendingPublishPromises.entries()).find(_ref8 => { let _ref9 = _slicedToArray(_ref8, 1), pendingTrack = _ref9[0]; return pendingTrack.source === source; }); if (publishPromiseEntry) { return publishPromiseEntry[1]; } yield sleep(20); } }); } /** Publishes a data track. * * Returns the published data track if successful. Use {@link LocalDataTrack#tryPush} * to send data frames on the track. */ publishDataTrack(options) { return __awaiter(this, void 0, void 0, function* () { const track = new LocalDataTrack(options, this.roomOutgoingDataTrackManager); yield track.publish(); return track; }); } }/** An error which is thrown if a {@link DeferrableMap#getDeferred} call is aborted midway * through. */ class DeferrableMapAbortError extends DOMException { constructor(message, reason) { super(message, 'AbortError'); this.reason = reason; } } /** * A Map-like container keyed by unique strings that supports the ability to wait * for future keys to show up in the map. * * @example * // An already existing key: * const value = map.get("key"); * // Wait for a key which will be added soon: * const value = await map.getDeferred("key"); */ class DeferrableMap extends Map { constructor() { super(...arguments); this.pending = new Map(); } set(key, value) { var _a, _b; super.set(key, value); // Resolve any futures waiting on this key. const futures = (_a = this.pending) === null || _a === void 0 ? void 0 : _a.get(key); if (futures) { for (const future of futures) { if (!future.isResolved) { (_b = future.resolve) === null || _b === void 0 ? void 0 : _b.call(future, value); } } this.pending.delete(key); } return this; } get [Symbol.toStringTag]() { return 'DeferrableMap'; } getDeferred(key, signal) { return __awaiter(this, void 0, void 0, function* () { const existing = this.get(key); if (typeof existing !== 'undefined') { return existing; } // Bail out immediately if the signal is already aborted. if (signal === null || signal === void 0 ? void 0 : signal.aborted) { throw new DeferrableMapAbortError('The operation was aborted.', signal.reason); } const future = new Future(undefined, () => { // Clean up the pending list when the future settles. const futures = this.pending.get(key); if (!futures) { return; } const idx = futures.indexOf(future); if (idx !== -1) { futures.splice(idx, 1); } if (futures.length === 0) { this.pending.delete(key); } }); const existingFutures = this.pending.get(key); if (existingFutures) { existingFutures.push(future); } else { this.pending.set(key, [future]); } // If a signal was provided, listen for abort and reject the future. if (signal) { const onAbort = () => { var _a; if (!future.isResolved) { (_a = future.reject) === null || _a === void 0 ? void 0 : _a.call(future, new DeferrableMapAbortError('The operation was aborted.', signal.reason)); } }; signal.addEventListener('abort', onAbort, { once: true }); // Clean up the listener once the future settles (resolved or rejected). future.promise.finally(() => { signal.removeEventListener('abort', onAbort); }); } return future.promise; }); } }class RemoteTrackPublication extends TrackPublication { constructor(kind, ti, autoSubscribe, loggerOptions) { super(kind, ti.sid, ti.name, loggerOptions); this.track = undefined; /** @internal */ this.allowed = true; this.requestedDisabled = undefined; this.visible = true; this.handleEnded = track => { this.setTrack(undefined); this.emit(TrackEvent.Ended, track); }; this.handleVisibilityChange = visible => { this.log.debug("adaptivestream video visibility ".concat(this.trackSid, ", visible=").concat(visible), this.logContext); this.visible = visible; this.emitTrackUpdate(); }; this.handleVideoDimensionsChange = dimensions => { this.log.debug("adaptivestream video dimensions ".concat(dimensions.width, "x").concat(dimensions.height), this.logContext); this.videoDimensionsAdaptiveStream = dimensions; this.emitTrackUpdate(); }; this.subscribed = autoSubscribe; this.updateInfo(ti); } /** * Subscribe or unsubscribe to this remote track * @param subscribed true to subscribe to a track, false to unsubscribe */ setSubscribed(subscribed) { const prevStatus = this.subscriptionStatus; const prevPermission = this.permissionStatus; this.subscribed = subscribed; // reset allowed status when desired subscription state changes // server will notify client via signal message if it's not allowed if (subscribed) { this.allowed = true; } const sub = new UpdateSubscription({ trackSids: [this.trackSid], subscribe: this.subscribed, participantTracks: [new ParticipantTracks({ // sending an empty participant id since TrackPublication doesn't keep it // this is filled in by the participant that receives this message participantSid: '', trackSids: [this.trackSid] })] }); this.emit(TrackEvent.UpdateSubscription, sub); this.emitSubscriptionUpdateIfChanged(prevStatus); this.emitPermissionUpdateIfChanged(prevPermission); } get subscriptionStatus() { if (this.subscribed === false) { return TrackPublication.SubscriptionStatus.Unsubscribed; } if (!super.isSubscribed) { return TrackPublication.SubscriptionStatus.Desired; } return TrackPublication.SubscriptionStatus.Subscribed; } get permissionStatus() { return this.allowed ? TrackPublication.PermissionStatus.Allowed : TrackPublication.PermissionStatus.NotAllowed; } /** * Returns true if track is subscribed, and ready for playback */ get isSubscribed() { if (this.subscribed === false) { return false; } return super.isSubscribed; } // returns client's desire to subscribe to a track, also true if autoSubscribe is enabled get isDesired() { return this.subscribed !== false; } get isEnabled() { return this.requestedDisabled !== undefined ? !this.requestedDisabled : this.isAdaptiveStream ? this.visible : true; } get isLocal() { return false; } /** * disable server from sending down data for this track. this is useful when * the participant is off screen, you may disable streaming down their video * to reduce bandwidth requirements * @param enabled */ setEnabled(enabled) { if (!this.isManualOperationAllowed() || this.requestedDisabled === !enabled) { return; } this.requestedDisabled = !enabled; this.emitTrackUpdate(); } /** * for tracks that support simulcasting, adjust subscribed quality * * This indicates the highest quality the client can accept. if network * bandwidth does not allow, server will automatically reduce quality to * optimize for uninterrupted video */ setVideoQuality(quality) { if (!this.isManualOperationAllowed() || this.requestedMaxQuality === quality) { return; } this.requestedMaxQuality = quality; this.requestedVideoDimensions = undefined; this.emitTrackUpdate(); } /** * Explicitly set the video dimensions for this track. * * This will take precedence over adaptive stream dimensions. * * @param dimensions The video dimensions to set. */ setVideoDimensions(dimensions) { var _a, _b; if (!this.isManualOperationAllowed()) { return; } if (((_a = this.requestedVideoDimensions) === null || _a === void 0 ? void 0 : _a.width) === dimensions.width && ((_b = this.requestedVideoDimensions) === null || _b === void 0 ? void 0 : _b.height) === dimensions.height) { return; } if (isRemoteVideoTrack(this.track)) { this.requestedVideoDimensions = dimensions; } this.requestedMaxQuality = undefined; this.emitTrackUpdate(); } setVideoFPS(fps) { if (!this.isManualOperationAllowed()) { return; } if (!isRemoteVideoTrack(this.track)) { return; } if (this.fps === fps) { return; } this.fps = fps; this.emitTrackUpdate(); } get videoQuality() { var _a; return (_a = this.requestedMaxQuality) !== null && _a !== void 0 ? _a : VideoQuality.HIGH; } /** @internal */ setTrack(track) { const prevStatus = this.subscriptionStatus; const prevPermission = this.permissionStatus; const prevTrack = this.track; if (prevTrack === track) { return; } if (prevTrack) { // unregister listener prevTrack.off(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange); prevTrack.off(TrackEvent.VisibilityChanged, this.handleVisibilityChange); prevTrack.off(TrackEvent.Ended, this.handleEnded); prevTrack.detach(); prevTrack.stopMonitor(); this.emit(TrackEvent.Unsubscribed, prevTrack); } super.setTrack(track); if (track) { track.sid = this.trackSid; track.on(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange); track.on(TrackEvent.VisibilityChanged, this.handleVisibilityChange); track.on(TrackEvent.Ended, this.handleEnded); this.emit(TrackEvent.Subscribed, track); } this.emitPermissionUpdateIfChanged(prevPermission); this.emitSubscriptionUpdateIfChanged(prevStatus); } /** @internal */ setAllowed(allowed) { const prevStatus = this.subscriptionStatus; const prevPermission = this.permissionStatus; this.allowed = allowed; this.emitPermissionUpdateIfChanged(prevPermission); this.emitSubscriptionUpdateIfChanged(prevStatus); } /** @internal */ setSubscriptionError(error) { this.emit(TrackEvent.SubscriptionFailed, error); } /** @internal */ updateInfo(info) { super.updateInfo(info); const prevMetadataMuted = this.metadataMuted; this.metadataMuted = info.muted; if (this.track) { this.track.setMuted(info.muted); } else if (prevMetadataMuted !== info.muted) { this.emit(info.muted ? TrackEvent.Muted : TrackEvent.Unmuted); } } emitSubscriptionUpdateIfChanged(previousStatus) { const currentStatus = this.subscriptionStatus; if (previousStatus === currentStatus) { return; } this.emit(TrackEvent.SubscriptionStatusChanged, currentStatus, previousStatus); } emitPermissionUpdateIfChanged(previousPermissionStatus) { const currentPermissionStatus = this.permissionStatus; if (currentPermissionStatus !== previousPermissionStatus) { this.emit(TrackEvent.SubscriptionPermissionChanged, this.permissionStatus, previousPermissionStatus); } } isManualOperationAllowed() { if (!this.isDesired) { this.log.warn('cannot update track settings when not subscribed', this.logContext); return false; } return true; } get isAdaptiveStream() { return isRemoteVideoTrack(this.track) && this.track.isAdaptiveStream; } /* @internal */ emitTrackUpdate() { const settings = new UpdateTrackSettings({ trackSids: [this.trackSid], disabled: !this.isEnabled, fps: this.fps }); if (this.kind === Track.Kind.Video) { let minDimensions = this.requestedVideoDimensions; if (this.videoDimensionsAdaptiveStream !== undefined) { if (minDimensions) { // check whether the adaptive stream dimensions are smaller than the requested dimensions and use smaller one const smallerAdaptive = areDimensionsSmaller(this.videoDimensionsAdaptiveStream, minDimensions); if (smallerAdaptive) { this.log.debug('using adaptive stream dimensions instead of requested', Object.assign(Object.assign({}, this.logContext), this.videoDimensionsAdaptiveStream)); minDimensions = this.videoDimensionsAdaptiveStream; } } else if (this.requestedMaxQuality !== undefined && this.trackInfo) { // check whether adaptive stream dimensions are smaller than the max quality layer and use smaller one const maxQualityLayer = layerDimensionsFor(this.trackInfo, this.requestedMaxQuality); if (maxQualityLayer && areDimensionsSmaller(this.videoDimensionsAdaptiveStream, maxQualityLayer)) { this.log.debug('using adaptive stream dimensions instead of max quality layer', Object.assign(Object.assign({}, this.logContext), this.videoDimensionsAdaptiveStream)); minDimensions = this.videoDimensionsAdaptiveStream; } } else { this.log.debug('using adaptive stream dimensions', Object.assign(Object.assign({}, this.logContext), this.videoDimensionsAdaptiveStream)); minDimensions = this.videoDimensionsAdaptiveStream; } } if (minDimensions) { settings.width = Math.ceil(minDimensions.width); settings.height = Math.ceil(minDimensions.height); } else if (this.requestedMaxQuality !== undefined) { this.log.debug('using requested max quality', Object.assign(Object.assign({}, this.logContext), { quality: this.requestedMaxQuality })); settings.quality = this.requestedMaxQuality; } else { this.log.debug('using default quality', Object.assign(Object.assign({}, this.logContext), { quality: VideoQuality.HIGH })); // defaults to high quality settings.quality = VideoQuality.HIGH; } } this.emit(TrackEvent.UpdateSettings, settings); } }class RemoteParticipant extends Participant { /** @internal */ static fromParticipantInfo(signalClient, pi, loggerOptions, manager) { return new RemoteParticipant(signalClient, pi.sid, pi.identity, pi.name, pi.metadata, pi.attributes, loggerOptions, pi.kind, pi.dataTracks.map(dti => { const info = DataTrackInfo.from(dti); return new RemoteDataTrack(info, manager, { publisherIdentity: pi.identity }); }), pi.clientProtocol); } get logContext() { return Object.assign(Object.assign({}, super.logContext), { remoteParticipantID: this.sid, remoteParticipant: this.identity }); } /** @internal */ constructor(signalClient, sid, identity, name, metadata, attributes, loggerOptions) { let kind = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : ParticipantInfo_Kind.STANDARD; let remoteDataTracks = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : []; let clientProtocol = arguments.length > 9 && arguments[9] !== undefined ? arguments[9] : CLIENT_PROTOCOL_DEFAULT; super(sid, identity || '', name, metadata, attributes, loggerOptions, kind); this.signalClient = signalClient; this.trackPublications = new Map(); this.audioTrackPublications = new Map(); this.videoTrackPublications = new Map(); this.dataTracks = new DeferrableMap(remoteDataTracks.map(remoteDataTrack => { return [remoteDataTrack.info.name, remoteDataTrack]; })); this.volumeMap = new Map(); this.clientProtocol = clientProtocol; } addTrackPublication(publication) { super.addTrackPublication(publication); // register action events publication.on(TrackEvent.UpdateSettings, settings => { this.log.debug('send update settings', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication)), { settings })); this.signalClient.sendUpdateTrackSettings(settings); }); publication.on(TrackEvent.UpdateSubscription, sub => { sub.participantTracks.forEach(pt => { pt.participantSid = this.sid; }); this.signalClient.sendUpdateSubscription(sub); }); publication.on(TrackEvent.SubscriptionPermissionChanged, status => { this.emit(ParticipantEvent.TrackSubscriptionPermissionChanged, publication, status); }); publication.on(TrackEvent.SubscriptionStatusChanged, status => { this.emit(ParticipantEvent.TrackSubscriptionStatusChanged, publication, status); }); publication.on(TrackEvent.Subscribed, track => { this.emit(ParticipantEvent.TrackSubscribed, track, publication); }); publication.on(TrackEvent.Unsubscribed, previousTrack => { this.emit(ParticipantEvent.TrackUnsubscribed, previousTrack, publication); }); publication.on(TrackEvent.SubscriptionFailed, error => { this.emit(ParticipantEvent.TrackSubscriptionFailed, publication.trackSid, error); }); } getTrackPublication(source) { const track = super.getTrackPublication(source); if (track) { return track; } } getTrackPublicationByName(name) { const track = super.getTrackPublicationByName(name); if (track) { return track; } } /** * sets the volume on the participant's audio track * by default, this affects the microphone publication * a different source can be passed in as a second argument * if no track exists the volume will be applied when the microphone track is added */ setVolume(volume) { let source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Track.Source.Microphone; this.volumeMap.set(source, volume); const audioPublication = this.getTrackPublication(source); if (audioPublication && audioPublication.track) { audioPublication.track.setVolume(volume); } } /** * gets the volume on the participant's microphone track */ getVolume() { let source = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Track.Source.Microphone; const audioPublication = this.getTrackPublication(source); if (audioPublication && audioPublication.track) { return audioPublication.track.getVolume(); } return this.volumeMap.get(source); } /** @internal */ addSubscribedMediaTrack(mediaTrack, sid, mediaStream, receiver, adaptiveStreamSettings, triesLeft) { // find the track publication // it's possible for the media track to arrive before participant info let publication = this.getTrackPublicationBySid(sid); // it's also possible that the browser didn't honor our original track id // FireFox would use its own local uuid instead of server track id if (!publication) { if (!sid.startsWith('TR')) { // find the first track that matches type this.trackPublications.forEach(p => { if (!publication && mediaTrack.kind === p.kind.toString()) { publication = p; } }); } } // when we couldn't locate the track, it's possible that the metadata hasn't // yet arrived. Wait a bit longer for it to arrive, or fire an error if (!publication) { if (triesLeft === 0) { this.log.error('could not find published track', Object.assign(Object.assign({}, this.logContext), { trackSid: sid })); this.emit(ParticipantEvent.TrackSubscriptionFailed, sid); return; } if (triesLeft === undefined) triesLeft = 20; setTimeout(() => { this.addSubscribedMediaTrack(mediaTrack, sid, mediaStream, receiver, adaptiveStreamSettings, triesLeft - 1); }, 150); return; } if (mediaTrack.readyState === 'ended') { this.log.error('unable to subscribe because MediaStreamTrack is ended. Do not call MediaStreamTrack.stop()', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication))); this.emit(ParticipantEvent.TrackSubscriptionFailed, sid); return; } const isVideo = mediaTrack.kind === 'video'; let track; if (isVideo) { track = new RemoteVideoTrack(mediaTrack, sid, receiver, adaptiveStreamSettings); } else { track = new RemoteAudioTrack(mediaTrack, sid, receiver, this.audioContext, this.audioOutput); } // set track info track.source = publication.source; // keep publication's muted status track.isMuted = publication.isMuted; track.setMediaStream(mediaStream); track.start(); publication.setTrack(track); // set participant volumes on new audio tracks if (this.volumeMap.has(publication.source) && isRemoteTrack(track) && isAudioTrack(track)) { track.setVolume(this.volumeMap.get(publication.source)); } return publication; } /** @internal */ get hasMetadata() { return !!this.participantInfo; } /** * @internal */ getTrackPublicationBySid(sid) { return this.trackPublications.get(sid); } /** @internal */ updateInfo(info) { if (!super.updateInfo(info)) { return false; } // we are getting a list of all available tracks, reconcile in here // and send out events for changes // reconcile track publications, publish events only if metadata is already there // i.e. changes since the local participant has joined const validTracks = new Map(); const newTracks = new Map(); info.tracks.forEach(ti => { var _a, _b; let publication = this.getTrackPublicationBySid(ti.sid); if (!publication) { // new publication const kind = Track.kindFromProto(ti.type); if (!kind) { return; } publication = new RemoteTrackPublication(kind, ti, (_a = this.signalClient.connectOptions) === null || _a === void 0 ? void 0 : _a.autoSubscribe, { loggerContextCb: () => this.logContext, loggerName: (_b = this.loggerOptions) === null || _b === void 0 ? void 0 : _b.loggerName }); publication.updateInfo(ti); newTracks.set(ti.sid, publication); const existingTrackOfSource = Array.from(this.trackPublications.values()).find(publishedTrack => publishedTrack.source === (publication === null || publication === void 0 ? void 0 : publication.source)); if (existingTrackOfSource && publication.source !== Track.Source.Unknown) { this.log.debug("received a second track publication for ".concat(this.identity, " with the same source: ").concat(publication.source), Object.assign(Object.assign({}, this.logContext), { oldTrack: getLogContextFromTrack(existingTrackOfSource), newTrack: getLogContextFromTrack(publication) })); } this.addTrackPublication(publication); } else { publication.updateInfo(ti); } validTracks.set(ti.sid, publication); }); // detect removed tracks this.trackPublications.forEach(publication => { if (!validTracks.has(publication.trackSid)) { this.log.trace('detected removed track on remote participant, unpublishing', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication))); this.unpublishTrack(publication.trackSid, true); } }); // always emit events for new publications, Room will not forward them unless it's ready newTracks.forEach(publication => { this.emit(ParticipantEvent.TrackPublished, publication); }); return true; } /** @internal */ unpublishTrack(sid, sendUnpublish) { const publication = this.trackPublications.get(sid); if (!publication) { return; } // also send unsubscribe, if track is actively subscribed const track = publication.track; if (track) { track.stop(); publication.setTrack(undefined); } // remove track from maps only after unsubscribed has been fired this.trackPublications.delete(sid); // remove from the right type map switch (publication.kind) { case Track.Kind.Audio: this.audioTrackPublications.delete(sid); break; case Track.Kind.Video: this.videoTrackPublications.delete(sid); break; } if (sendUnpublish) { this.emit(ParticipantEvent.TrackUnpublished, publication); } } /** * @internal */ setAudioOutput(output) { return __awaiter(this, void 0, void 0, function* () { this.audioOutput = output; const promises = []; this.audioTrackPublications.forEach(pub => { var _a; if (isAudioTrack(pub.track) && isRemoteTrack(pub.track)) { promises.push(pub.track.setSinkId((_a = output.deviceId) !== null && _a !== void 0 ? _a : 'default')); } }); yield Promise.all(promises); }); } /** @internal */ addRemoteDataTrack(remoteDataTrack) { this.dataTracks.set(remoteDataTrack.info.name, remoteDataTrack); } /** @internal */ removeRemoteDataTrack(remoteDataTrackSid) { for (const _ref of this.dataTracks.entries()) { var _ref2 = _slicedToArray(_ref, 2); const name = _ref2[0]; const dataTrack = _ref2[1]; if (remoteDataTrackSid === dataTrack.info.sid) { this.dataTracks.delete(name); } } } /** @internal */ emit(event) { for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } this.log.trace('participant event', Object.assign(Object.assign({}, this.logContext), { event, args })); return super.emit(event, ...args); } }var ConnectionState; (function (ConnectionState) { ConnectionState["Disconnected"] = "disconnected"; ConnectionState["Connecting"] = "connecting"; ConnectionState["Connected"] = "connected"; ConnectionState["Reconnecting"] = "reconnecting"; ConnectionState["SignalReconnecting"] = "signalReconnecting"; })(ConnectionState || (ConnectionState = {})); const CONNECTION_RECONCILE_FREQUENCY_MS = 4 * 1000; /** * In LiveKit, a room is the logical grouping for a list of participants. * Participants in a room can publish tracks, and subscribe to others' tracks. * * a Room fires [[RoomEvent | RoomEvents]]. * * @noInheritDoc */ class Room extends eventsExports.EventEmitter { get hasE2EESetup() { return this.e2eeManager !== undefined; } /** * Creates a new Room, the primary construct for a LiveKit session. * @param options */ constructor(options) { var _this; var _a, _b, _c, _d, _e, _f; super(); _this = this; this.state = ConnectionState.Disconnected; /** * list of participants that are actively speaking. when this changes * a [[RoomEvent.ActiveSpeakersChanged]] event is fired */ this.activeSpeakers = []; /** reflects the sender encryption status of the local participant */ this.isE2EEEnabled = false; this.audioEnabled = true; this.e2eeStateMutex = new _(); this.isVideoPlaybackBlocked = false; this.log = livekitLogger; this.bufferedEvents = []; this.isResuming = false; this.connect = (url, token, opts) => __awaiter(this, void 0, void 0, function* () { var _a; if (!isBrowserSupported()) { if (isReactNative()) { throw Error("WebRTC isn't detected, have you called registerGlobals?"); } else { throw Error("LiveKit doesn't seem to be supported on this browser. Try to update your browser and make sure no browser extensions are disabling webRTC."); } } // In case a disconnect called happened right before the connect call, make sure the disconnect is completed first by awaiting its lock const unlockDisconnect = yield this.disconnectLock.lock(); if (this.state === ConnectionState.Connected) { // when the state is reconnecting or connected, this function returns immediately this.log.info("already connected to room ".concat(this.name)); unlockDisconnect(); return Promise.resolve(); } if (this.connectFuture) { unlockDisconnect(); return this.connectFuture.promise; } this.setAndEmitConnectionState(ConnectionState.Connecting); if (((_a = this.regionUrlProvider) === null || _a === void 0 ? void 0 : _a.getServerUrl().toString()) !== ensureTrailingSlash(url)) { this.regionUrl = undefined; this.regionUrlProvider = undefined; } if (isCloud(new URL(url))) { if (this.regionUrlProvider === undefined) { this.regionUrlProvider = new RegionUrlProvider(url, token); } else { this.regionUrlProvider.updateToken(token); } // trigger the first fetch without waiting for a response // if initial connection fails, this will speed up picking regional url // on subsequent runs this.regionUrlProvider.fetchRegionSettings().then(settings => { var _a; (_a = this.regionUrlProvider) === null || _a === void 0 ? void 0 : _a.setServerReportedRegions(settings); }).catch(e => { this.log.warn('could not fetch region settings', { error: e }); }); } const connectFn = (resolve, reject, regionUrl) => __awaiter(this, void 0, void 0, function* () { var _a, _b; if (this.abortController) { this.abortController.abort(); } // explicit creation as local var needed to satisfy TS compiler when passing it to `attemptConnection` further down const abortController = new AbortController(); this.abortController = abortController; // at this point the intention to connect has been signalled so we can allow cancelling of the connection via disconnect() again unlockDisconnect === null || unlockDisconnect === void 0 ? void 0 : unlockDisconnect(); try { yield BackOffStrategy.getInstance().getBackOffPromise(url); if (abortController.signal.aborted) { throw ConnectionError.cancelled('Connection attempt aborted'); } yield this.attemptConnection(regionUrl !== null && regionUrl !== void 0 ? regionUrl : url, token, opts, abortController); this.abortController = undefined; resolve(); } catch (error) { if (this.regionUrlProvider && error instanceof ConnectionError && error.reason !== ConnectionErrorReason.Cancelled && error.reason !== ConnectionErrorReason.NotAllowed) { let nextUrl = null; try { this.log.debug('Fetching next region'); nextUrl = yield this.regionUrlProvider.getNextBestRegionUrl((_a = this.abortController) === null || _a === void 0 ? void 0 : _a.signal); } catch (regionFetchError) { if (regionFetchError instanceof ConnectionError && (regionFetchError.status === 401 || regionFetchError.reason === ConnectionErrorReason.Cancelled)) { this.handleDisconnect(this.options.stopLocalTrackOnUnpublish); reject(regionFetchError); return; } } if ( // making sure we only register failed attempts on things we actually care about [ConnectionErrorReason.InternalError, ConnectionErrorReason.ServerUnreachable, ConnectionErrorReason.Timeout].includes(error.reason)) { this.log.debug('Adding failed connection attempt to back off'); BackOffStrategy.getInstance().addFailedConnectionAttempt(url); } if (nextUrl && !((_b = this.abortController) === null || _b === void 0 ? void 0 : _b.signal.aborted)) { this.log.info("Initial connection failed with ConnectionError: ".concat(error.message, ". Retrying with another region: ").concat(nextUrl)); this.recreateEngine(true); yield connectFn(resolve, reject, nextUrl); } else { this.handleDisconnect(this.options.stopLocalTrackOnUnpublish, getDisconnectReasonFromConnectionError(error)); reject(error); } } else { let disconnectReason = DisconnectReason.UNKNOWN_REASON; if (error instanceof ConnectionError) { disconnectReason = getDisconnectReasonFromConnectionError(error); } this.handleDisconnect(this.options.stopLocalTrackOnUnpublish, disconnectReason); reject(error); } } }); const regionUrl = this.regionUrl; this.regionUrl = undefined; this.connectFuture = new Future((resolve, reject) => { connectFn(resolve, reject, regionUrl); }, () => { this.clearConnectionFutures(); }); return this.connectFuture.promise; }); this.connectSignal = (url, token, engine, connectOptions, roomOptions, abortController) => __awaiter(this, void 0, void 0, function* () { var _a; const _yield$engine$join = yield engine.join(url, token, { autoSubscribe: connectOptions.autoSubscribe, adaptiveStream: typeof roomOptions.adaptiveStream === 'object' ? true : roomOptions.adaptiveStream, clientInfoCapabilities: isFrameMetadataSupported((_a = roomOptions.frameMetadata) !== null && _a !== void 0 ? _a : roomOptions.packetTrailer) || !!this.e2eeManager ? [ClientInfo_Capability.CAP_PACKET_TRAILER] : undefined, maxRetries: connectOptions.maxRetries, e2eeEnabled: !!this.e2eeManager, websocketTimeout: connectOptions.websocketTimeout }, abortController.signal, !roomOptions.singlePeerConnection), joinResponse = _yield$engine$join.joinResponse, serverInfo = _yield$engine$join.serverInfo; this.serverInfo = serverInfo; if (!serverInfo.version) { throw new UnsupportedServer('unknown server version'); } if (serverInfo.version === '0.15.1' && this.options.dynacast) { this.log.debug('disabling dynacast due to server version'); // dynacast has a bug in 0.15.1, so we cannot use it then roomOptions.dynacast = false; } return joinResponse; }); this.applyJoinResponse = joinResponse => { const pi = joinResponse.participant; this.localParticipant.sid = pi.sid; this.localParticipant.identity = pi.identity; this.localParticipant.setEnabledPublishCodecs(joinResponse.enabledPublishCodecs); if (this.e2eeManager) { try { this.e2eeManager.setSifTrailer(joinResponse.sifTrailer); } catch (e) { this.log.error(e instanceof Error ? e.message : 'Could not set SifTrailer', { error: e }); } } // populate remote participants, these should not trigger new events this.handleParticipantUpdates([pi, ...joinResponse.otherParticipants]); if (joinResponse.room) { this.handleRoomUpdate(joinResponse.room); } }; this.attemptConnection = (url, token, opts, abortController) => __awaiter(this, void 0, void 0, function* () { var _a, _b; if (this.state === ConnectionState.Reconnecting || this.isResuming || ((_a = this.engine) === null || _a === void 0 ? void 0 : _a.pendingReconnect)) { this.log.info('Reconnection attempt replaced by new connection attempt'); // make sure we close and recreate the existing engine in order to get rid of any potentially ongoing reconnection attempts this.recreateEngine(true); } else { // create engine if previously disconnected this.maybeCreateEngine(); } if ((_b = this.regionUrlProvider) === null || _b === void 0 ? void 0 : _b.isCloud()) { this.engine.setRegionStrategy(this.createRegionStrategy()); } this.acquireAudioContext(); this.connOptions = Object.assign(Object.assign({}, roomConnectOptionDefaults), opts); if (this.connOptions.rtcConfig) { this.engine.rtcConfig = this.connOptions.rtcConfig; } if (this.connOptions.peerConnectionTimeout) { this.engine.peerConnectionTimeout = this.connOptions.peerConnectionTimeout; } try { const joinResponse = yield this.connectSignal(url, token, this.engine, this.connOptions, this.options, abortController); this.applyJoinResponse(joinResponse); // forward metadata changed for the local participant this.setupLocalParticipantEvents(); this.emit(RoomEvent.SignalConnected); } catch (err) { yield this.engine.close(); this.recreateEngine(); const resultingError = abortController.signal.aborted ? ConnectionError.cancelled('Signal connection aborted') : ConnectionError.serverUnreachable('could not establish signal connection'); if (err instanceof Error) { resultingError.message = "".concat(resultingError.message, ": ").concat(err.message); } if (err instanceof ConnectionError) { resultingError.reason = err.reason; resultingError.status = err.status; } this.log.debug("error trying to establish signal connection", { error: err }); throw resultingError; } if (abortController.signal.aborted) { yield this.engine.close(); this.recreateEngine(); throw ConnectionError.cancelled("Connection attempt aborted"); } try { yield this.engine.waitForPCInitialConnection(this.connOptions.peerConnectionTimeout, abortController); } catch (e) { yield this.engine.close(); this.recreateEngine(); throw e; } // also hook unload event if (isWeb() && this.options.disconnectOnPageLeave) { // capturing both 'pagehide' and 'beforeunload' to capture broadest set of browser behaviors window.addEventListener('pagehide', this.onPageLeave); window.addEventListener('beforeunload', this.onPageLeave); } if (isWeb()) { window.addEventListener('freeze', this.onPageLeave); } this.setAndEmitConnectionState(ConnectionState.Connected); this.emit(RoomEvent.Connected); BackOffStrategy.getInstance().resetFailedConnectionAttempts(url); this.registerConnectionReconcile(); // Notify region provider about successful connection if (this.regionUrlProvider) { this.regionUrlProvider.notifyConnected(); } }); /** * disconnects the room, emits [[RoomEvent.Disconnected]] */ this.disconnect = function () { for (var _len = arguments.length, args_1 = new Array(_len), _key = 0; _key < _len; _key++) { args_1[_key] = arguments[_key]; } return __awaiter(_this, [...args_1], void 0, function () { var _this2 = this; let stopTracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; return function* () { var _a, _b, _c; const unlock = yield _this2.disconnectLock.lock(); try { if (_this2.state === ConnectionState.Disconnected) { _this2.log.debug('already disconnected'); return; } _this2.log.info('disconnect from room'); if (_this2.state === ConnectionState.Connecting || _this2.state === ConnectionState.Reconnecting || _this2.isResuming) { // try aborting pending connection attempt const msg = 'Abort connection attempt due to user initiated disconnect'; _this2.log.warn(msg); (_a = _this2.abortController) === null || _a === void 0 ? void 0 : _a.abort(msg); // in case the abort controller didn't manage to cancel the connection attempt, reject the connect promise explicitly (_c = (_b = _this2.connectFuture) === null || _b === void 0 ? void 0 : _b.reject) === null || _c === void 0 ? void 0 : _c.call(_b, ConnectionError.cancelled('Client initiated disconnect')); _this2.connectFuture = undefined; } // close engine (also closes client) if (_this2.engine) { // send leave if (!_this2.engine.client.isDisconnected) { yield _this2.engine.client.sendLeave(); } yield _this2.engine.close(); } _this2.handleDisconnect(stopTracks, DisconnectReason.CLIENT_INITIATED); /* @ts-ignore */ _this2.engine = undefined; } finally { unlock(); } }(); }); }; this.onPageLeave = () => __awaiter(this, void 0, void 0, function* () { this.log.info('Page leave detected, disconnecting'); yield this.disconnect(); }); /** * Browsers have different policies regarding audio playback. Most requiring * some form of user interaction (click/tap/etc). * In those cases, audio will be silent until a click/tap triggering one of the following * - `startAudio` * - `getUserMedia` */ this.startAudio = () => __awaiter(this, void 0, void 0, function* () { const elements = []; const browser = getBrowser(); if (browser && browser.os === 'iOS') { /** * iOS blocks audio element playback if * - user is not publishing audio themselves and * - no other audio source is playing * * as a workaround, we create an audio element with an empty track, so that * silent audio is always playing */ const audioId = 'livekit-dummy-audio-el'; let dummyAudioEl = document.getElementById(audioId); if (!dummyAudioEl) { dummyAudioEl = document.createElement('audio'); dummyAudioEl.id = audioId; dummyAudioEl.autoplay = true; dummyAudioEl.hidden = true; const track = getEmptyAudioStreamTrack(); track.enabled = true; const stream = new MediaStream([track]); dummyAudioEl.srcObject = stream; document.addEventListener('visibilitychange', () => { if (!dummyAudioEl) { return; } // set the srcObject to null on page hide in order to prevent lock screen controls to show up for it dummyAudioEl.srcObject = document.hidden ? null : stream; if (!document.hidden) { this.log.debug('page visible again, triggering startAudio to resume playback and update playback status'); this.startAudio(); } }); document.body.append(dummyAudioEl); this.once(RoomEvent.Disconnected, () => { dummyAudioEl === null || dummyAudioEl === void 0 ? void 0 : dummyAudioEl.remove(); dummyAudioEl = null; }); } elements.push(dummyAudioEl); } this.remoteParticipants.forEach(p => { p.audioTrackPublications.forEach(t => { if (t.track) { t.track.attachedElements.forEach(e => { elements.push(e); }); } }); }); try { yield Promise.all([this.acquireAudioContext(), ...elements.map(e => { e.muted = false; return e.play(); })]); this.handleAudioPlaybackStarted(); } catch (err) { this.handleAudioPlaybackFailed(err); throw err; } }); this.startVideo = () => __awaiter(this, void 0, void 0, function* () { const elements = []; for (const p of this.remoteParticipants.values()) { p.videoTrackPublications.forEach(tr => { var _a; (_a = tr.track) === null || _a === void 0 ? void 0 : _a.attachedElements.forEach(el => { if (!elements.includes(el)) { elements.push(el); } }); }); } yield Promise.all(elements.map(el => el.play())).then(() => { this.handleVideoPlaybackStarted(); }).catch(e => { if (e.name === 'NotAllowedError') { this.handleVideoPlaybackFailed(); } else { this.log.warn('Resuming video playback failed, make sure you call `startVideo` directly in a user gesture handler'); } }); }); this.handleRestarting = () => { this.clearConnectionReconcile(); // in case we went from resuming to full-reconnect, make sure to reflect it on the isResuming flag this.isResuming = false; // also unwind existing participants & existing subscriptions for (const p of this.remoteParticipants.values()) { this.handleParticipantDisconnected(p.identity, p); } if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) { this.emit(RoomEvent.Reconnecting); } }; this.handleRestarted = () => { this.outgoingDataTrackManager.sfuWillRepublishTracks(); this.incomingDataTrackManager.resendSubscriptionUpdates(); }; this.handleSignalRestarted = joinResponse => __awaiter(this, void 0, void 0, function* () { this.log.debug("signal reconnected to server, region ".concat(joinResponse.serverRegion), { region: joinResponse.serverRegion }); this.bufferedEvents = []; this.applyJoinResponse(joinResponse); try { // unpublish & republish tracks yield this.localParticipant.republishAllTracks(undefined, true); } catch (error) { this.log.error('error trying to re-publish tracks after reconnection', { error }); } try { yield this.engine.waitForRestarted(); this.log.debug("fully reconnected to server", { region: joinResponse.serverRegion }); } catch (_a) { // reconnection failed, handleDisconnect is being invoked already, just return here return; } this.setAndEmitConnectionState(ConnectionState.Connected); this.emit(RoomEvent.Reconnected); this.registerConnectionReconcile(); this.emitBufferedEvents(); }); this.handleParticipantUpdates = participantInfos => { var _a; // handle changes to participant state, and send events for (const info of participantInfos) { if (info.identity === this.localParticipant.identity) { this.localParticipant.updateInfo(info); continue; } // LiveKit server doesn't send identity info prior to version 1.5.2 in disconnect updates // so we try to map an empty identity to an already known sID manually if (info.identity === '') { info.identity = (_a = this.sidToIdentity.get(info.sid)) !== null && _a !== void 0 ? _a : ''; } let remoteParticipant = this.remoteParticipants.get(info.identity); // when it's disconnected, send updates if (info.state === ParticipantInfo_State.DISCONNECTED) { this.handleParticipantDisconnected(info.identity, remoteParticipant); } else { // create participant if doesn't exist this.getOrCreateParticipant(info.identity, info); } } // Ingest data track publication updates into data tracks infrastructure const mapped = new Map(participantInfos.filter(p => p.identity !== this.localParticipant.identity).map(info => { return [info.identity, info.dataTracks.map(dataTrack => DataTrackInfo.from(dataTrack))]; })); this.incomingDataTrackManager.receiveSfuPublicationUpdates(mapped); }; // updates are sent only when there's a change to speaker ordering this.handleActiveSpeakersUpdate = speakers => { const activeSpeakers = []; const seenSids = {}; speakers.forEach(speaker => { seenSids[speaker.sid] = true; if (speaker.sid === this.localParticipant.sid) { this.localParticipant.audioLevel = speaker.level; this.localParticipant.setIsSpeaking(true); activeSpeakers.push(this.localParticipant); } else { const p = this.getRemoteParticipantBySid(speaker.sid); if (p) { p.audioLevel = speaker.level; p.setIsSpeaking(true); activeSpeakers.push(p); } } }); if (!seenSids[this.localParticipant.sid]) { this.localParticipant.audioLevel = 0; this.localParticipant.setIsSpeaking(false); } this.remoteParticipants.forEach(p => { if (!seenSids[p.sid]) { p.audioLevel = 0; p.setIsSpeaking(false); } }); this.activeSpeakers = activeSpeakers; this.emitWhenConnected(RoomEvent.ActiveSpeakersChanged, activeSpeakers); }; // process list of changed speakers this.handleSpeakersChanged = speakerUpdates => { const lastSpeakers = new Map(); this.activeSpeakers.forEach(p => { const remoteParticipant = this.remoteParticipants.get(p.identity); if (remoteParticipant && remoteParticipant.sid !== p.sid) { return; } lastSpeakers.set(p.sid, p); }); speakerUpdates.forEach(speaker => { let p = this.getRemoteParticipantBySid(speaker.sid); if (speaker.sid === this.localParticipant.sid) { p = this.localParticipant; } if (!p) { return; } p.audioLevel = speaker.level; p.setIsSpeaking(speaker.active); if (speaker.active) { lastSpeakers.set(speaker.sid, p); } else { lastSpeakers.delete(speaker.sid); } }); const activeSpeakers = Array.from(lastSpeakers.values()); activeSpeakers.sort((a, b) => b.audioLevel - a.audioLevel); this.activeSpeakers = activeSpeakers; this.emitWhenConnected(RoomEvent.ActiveSpeakersChanged, activeSpeakers); }; this.handleStreamStateUpdate = streamStateUpdate => { streamStateUpdate.streamStates.forEach(streamState => { const participant = this.getRemoteParticipantBySid(streamState.participantSid); if (!participant) { return; } const pub = participant.getTrackPublicationBySid(streamState.trackSid); if (!pub || !pub.track) { return; } const newStreamState = Track.streamStateFromProto(streamState.state); pub.track.setStreamState(newStreamState); if (newStreamState !== pub.track.streamState) { participant.emit(ParticipantEvent.TrackStreamStateChanged, pub, pub.track.streamState); this.emitWhenConnected(RoomEvent.TrackStreamStateChanged, pub, pub.track.streamState, participant); } }); }; this.handleSubscriptionPermissionUpdate = update => { const participant = this.getRemoteParticipantBySid(update.participantSid); if (!participant) { return; } const pub = participant.getTrackPublicationBySid(update.trackSid); if (!pub) { return; } pub.setAllowed(update.allowed); }; this.handleSubscriptionError = update => { const participant = Array.from(this.remoteParticipants.values()).find(p => p.trackPublications.has(update.trackSid)); if (!participant) { return; } const pub = participant.getTrackPublicationBySid(update.trackSid); if (!pub) { return; } pub.setSubscriptionError(update.err); }; this.handleDataPacket = (packet, encryptionType) => { // find the participant const participant = this.remoteParticipants.get(packet.participantIdentity); if (packet.value.case === 'user') { this.handleUserPacket(participant, packet.value.value, packet.kind, encryptionType); } else if (packet.value.case === 'transcription') { this.handleTranscription(participant, packet.value.value); } else if (packet.value.case === 'sipDtmf') { this.handleSipDtmf(participant, packet.value.value); } else if (packet.value.case === 'chatMessage') { this.handleChatMessage(participant, packet.value.value); } else if (packet.value.case === 'metrics') { this.handleMetrics(packet.value.value, participant); } else if (packet.value.case === 'streamHeader' || packet.value.case === 'streamChunk' || packet.value.case === 'streamTrailer') { this.handleDataStream(packet, encryptionType); } else if (packet.value.case === 'rpcRequest') { const rpc = packet.value.value; this.rpcServerManager.handleIncomingRpcRequest(packet.participantIdentity, rpc); } else if (packet.value.case === 'rpcResponse') { const rpcResponse = packet.value.value; switch (rpcResponse.value.case) { case 'payload': this.rpcClientManager.handleIncomingRpcResponseSuccess(rpcResponse.requestId, rpcResponse.value.value); break; case 'error': this.rpcClientManager.handleIncomingRpcResponseFailure(rpcResponse.requestId, RpcError.fromProto(rpcResponse.value.value)); break; default: this.log.warn("Unknown rpcResponse.value.case: ".concat(rpcResponse.value.case), this.logContext); break; } } else if (packet.value.case === 'rpcAck') { this.rpcClientManager.handleIncomingRpcAck(packet.value.value.requestId); } }; this.handleUserPacket = (participant, userPacket, kind, encryptionType) => { this.emit(RoomEvent.DataReceived, userPacket.payload, participant, kind, userPacket.topic, encryptionType); // also emit on the participant participant === null || participant === void 0 ? void 0 : participant.emit(ParticipantEvent.DataReceived, userPacket.payload, kind, encryptionType); }; this.handleSipDtmf = (participant, dtmf) => { this.emit(RoomEvent.SipDTMFReceived, dtmf, participant); // also emit on the participant participant === null || participant === void 0 ? void 0 : participant.emit(ParticipantEvent.SipDTMFReceived, dtmf); }; this.handleTranscription = (_remoteParticipant, transcription) => { // find the participant const participant = transcription.transcribedParticipantIdentity === this.localParticipant.identity ? this.localParticipant : this.getParticipantByIdentity(transcription.transcribedParticipantIdentity); const publication = participant === null || participant === void 0 ? void 0 : participant.trackPublications.get(transcription.trackId); const segments = extractTranscriptionSegments(transcription, this.transcriptionReceivedTimes); publication === null || publication === void 0 ? void 0 : publication.emit(TrackEvent.TranscriptionReceived, segments); participant === null || participant === void 0 ? void 0 : participant.emit(ParticipantEvent.TranscriptionReceived, segments, publication); this.emit(RoomEvent.TranscriptionReceived, segments, participant, publication); }; this.handleChatMessage = (participant, chatMessage) => { const msg = extractChatMessage(chatMessage); this.emit(RoomEvent.ChatMessage, msg, participant); }; this.handleMetrics = (metrics, participant) => { this.emit(RoomEvent.MetricsReceived, metrics, participant); }; this.handleDataStream = (packet, encryptionType) => { this.incomingDataStreamManager.handleDataStreamPacket(packet, encryptionType); }; this.bufferedSegments = new Map(); this.handleAudioPlaybackStarted = () => { if (this.canPlaybackAudio) { return; } this.audioEnabled = true; this.emit(RoomEvent.AudioPlaybackStatusChanged, true); }; this.handleAudioPlaybackFailed = e => { this.log.warn('could not playback audio', { error: e }); if (!this.canPlaybackAudio) { return; } this.audioEnabled = false; this.emit(RoomEvent.AudioPlaybackStatusChanged, false); }; this.handleVideoPlaybackStarted = () => { if (this.isVideoPlaybackBlocked) { this.isVideoPlaybackBlocked = false; this.emit(RoomEvent.VideoPlaybackStatusChanged, true); } }; this.handleVideoPlaybackFailed = () => { if (!this.isVideoPlaybackBlocked) { this.isVideoPlaybackBlocked = true; this.emit(RoomEvent.VideoPlaybackStatusChanged, false); } }; this.handleDeviceChange = () => __awaiter(this, void 0, void 0, function* () { var _a; if (((_a = getBrowser()) === null || _a === void 0 ? void 0 : _a.os) !== 'iOS') { // default devices are non deterministic on iOS, so we don't attempt to select them here yield this.selectDefaultDevices(); } this.emit(RoomEvent.MediaDevicesChanged); }); this.handleRoomUpdate = room => { const oldRoom = this.roomInfo; this.roomInfo = room; if (oldRoom && oldRoom.metadata !== room.metadata) { this.emitWhenConnected(RoomEvent.RoomMetadataChanged, room.metadata); } if ((oldRoom === null || oldRoom === void 0 ? void 0 : oldRoom.activeRecording) !== room.activeRecording) { this.emitWhenConnected(RoomEvent.RecordingStatusChanged, room.activeRecording); } }; this.handleConnectionQualityUpdate = update => { update.updates.forEach(info => { if (info.participantSid === this.localParticipant.sid) { this.localParticipant.setConnectionQuality(info.quality); return; } const participant = this.getRemoteParticipantBySid(info.participantSid); if (participant) { participant.setConnectionQuality(info.quality); } }); }; this.getRemoteParticipantClientProtocol = identity => { var _a, _b; return (_b = (_a = this.remoteParticipants.get(identity)) === null || _a === void 0 ? void 0 : _a.clientProtocol) !== null && _b !== void 0 ? _b : CLIENT_PROTOCOL_DEFAULT; }; this.onLocalParticipantMetadataChanged = metadata => { this.emit(RoomEvent.ParticipantMetadataChanged, metadata, this.localParticipant); }; this.onLocalParticipantNameChanged = name => { this.emit(RoomEvent.ParticipantNameChanged, name, this.localParticipant); }; this.onLocalAttributesChanged = changedAttributes => { this.emit(RoomEvent.ParticipantAttributesChanged, changedAttributes, this.localParticipant); }; this.onLocalTrackMuted = pub => { this.emit(RoomEvent.TrackMuted, pub, this.localParticipant); }; this.onLocalTrackUnmuted = pub => { this.emit(RoomEvent.TrackUnmuted, pub, this.localParticipant); }; this.onTrackProcessorUpdate = processor => { var _a; (_a = processor === null || processor === void 0 ? void 0 : processor.onPublish) === null || _a === void 0 ? void 0 : _a.call(processor, this); }; this.onLocalTrackPublished = pub => __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e, _f; (_a = pub.track) === null || _a === void 0 ? void 0 : _a.on(TrackEvent.TrackProcessorUpdate, this.onTrackProcessorUpdate); (_b = pub.track) === null || _b === void 0 ? void 0 : _b.on(TrackEvent.Restarted, this.onLocalTrackRestarted); (_e = (_d = (_c = pub.track) === null || _c === void 0 ? void 0 : _c.getProcessor()) === null || _d === void 0 ? void 0 : _d.onPublish) === null || _e === void 0 ? void 0 : _e.call(_d, this); this.emit(RoomEvent.LocalTrackPublished, pub, this.localParticipant); if (isLocalAudioTrack(pub.track)) { const trackIsSilent = yield pub.track.checkForSilence(); if (trackIsSilent) { this.emit(RoomEvent.LocalAudioSilenceDetected, pub); } } const deviceId = yield (_f = pub.track) === null || _f === void 0 ? void 0 : _f.getDeviceId(false); const deviceKind = sourceToKind(pub.source); if (deviceKind && deviceId && deviceId !== this.localParticipant.activeDeviceMap.get(deviceKind)) { this.localParticipant.activeDeviceMap.set(deviceKind, deviceId); this.emit(RoomEvent.ActiveDeviceChanged, deviceKind, deviceId); } }); this.onLocalTrackUnpublished = pub => { var _a, _b; (_a = pub.track) === null || _a === void 0 ? void 0 : _a.off(TrackEvent.TrackProcessorUpdate, this.onTrackProcessorUpdate); (_b = pub.track) === null || _b === void 0 ? void 0 : _b.off(TrackEvent.Restarted, this.onLocalTrackRestarted); this.emit(RoomEvent.LocalTrackUnpublished, pub, this.localParticipant); }; this.onLocalTrackRestarted = track => __awaiter(this, void 0, void 0, function* () { const deviceId = yield track.getDeviceId(false); const deviceKind = sourceToKind(track.source); if (deviceKind && deviceId && deviceId !== this.localParticipant.activeDeviceMap.get(deviceKind)) { this.log.debug("local track restarted, setting ".concat(deviceKind, " ").concat(deviceId, " active")); this.localParticipant.activeDeviceMap.set(deviceKind, deviceId); this.emit(RoomEvent.ActiveDeviceChanged, deviceKind, deviceId); } }); this.onLocalConnectionQualityChanged = quality => { this.emit(RoomEvent.ConnectionQualityChanged, quality, this.localParticipant); }; this.onMediaDevicesError = (e, kind) => { this.emit(RoomEvent.MediaDevicesError, e, kind); }; this.onLocalParticipantPermissionsChanged = prevPermissions => { this.emit(RoomEvent.ParticipantPermissionsChanged, prevPermissions, this.localParticipant); }; this.onLocalChatMessageSent = msg => { this.emit(RoomEvent.ChatMessage, msg, this.localParticipant); }; this.setMaxListeners(100); this.remoteParticipants = new Map(); this.sidToIdentity = new Map(); this.options = Object.assign(Object.assign({}, roomOptionDefaults), options); this.log = getLogger((_a = this.options.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.Room, () => this.logContext); this.transcriptionReceivedTimes = new Map(); this.options.audioCaptureDefaults = Object.assign(Object.assign({}, audioDefaults), options === null || options === void 0 ? void 0 : options.audioCaptureDefaults); this.options.videoCaptureDefaults = Object.assign(Object.assign({}, videoDefaults), options === null || options === void 0 ? void 0 : options.videoCaptureDefaults); this.options.publishDefaults = Object.assign(Object.assign({}, publishDefaults), options === null || options === void 0 ? void 0 : options.publishDefaults); this.maybeCreateEngine(); this.incomingDataStreamManager = new IncomingDataStreamManager(); this.outgoingDataStreamManager = new OutgoingDataStreamManager(this.engine, this.log); this.incomingDataTrackManager = new IncomingDataTrackManager({ e2eeManager: this.e2eeManager }); this.incomingDataTrackManager.on('sfuUpdateSubscription', event => { this.engine.client.sendUpdateDataSubscription(event.sid, event.subscribe); }).on('trackPublished', event => { var _a; if (event.track.publisherIdentity === this.localParticipant.identity) { // Only advertize tracks from other participants return; } this.emit(RoomEvent.DataTrackPublished, event.track); (_a = this.remoteParticipants.get(event.track.publisherIdentity)) === null || _a === void 0 ? void 0 : _a.addRemoteDataTrack(event.track); }).on('trackUnpublished', event => { var _a; if (event.publisherIdentity === this.localParticipant.identity) { // Only advertize tracks from other participants return; } this.emit(RoomEvent.DataTrackUnpublished, event.sid); (_a = this.remoteParticipants.get(event.publisherIdentity)) === null || _a === void 0 ? void 0 : _a.removeRemoteDataTrack(event.sid); }); this.outgoingDataTrackManager = new OutgoingDataTrackManager({ e2eeManager: this.e2eeManager }); this.outgoingDataTrackManager.on('sfuPublishRequest', event => { this.engine.client.sendPublishDataTrackRequest(event.handle, event.name, event.usesE2ee); }).on('sfuUnpublishRequest', event => { this.engine.client.sendUnPublishDataTrackRequest(event.handle); }).on('trackPublished', event => { this.emit(RoomEvent.LocalDataTrackPublished, event.track); }).on('trackUnpublished', event => { this.emit(RoomEvent.LocalDataTrackUnpublished, event.sid); }).on('packetAvailable', _ref => { let handle = _ref.handle, bytes = _ref.bytes; this.engine.sendLossyBytes(bytes, DataChannelKind.DATA_TRACK_LOSSY, 'wait').finally(() => this.outgoingDataTrackManager.handlePacketSendComplete(handle)); }); this.registerRpcDataStreamHandler(); this.rpcClientManager = new RpcClientManager(this.log, this.outgoingDataStreamManager, this.getRemoteParticipantClientProtocol, () => { var _a, _b; return (_b = (_a = this.engine.latestJoinResponse) === null || _a === void 0 ? void 0 : _a.serverInfo) === null || _b === void 0 ? void 0 : _b.version; }); this.rpcClientManager.on('sendDataPacket', _ref2 => { let packet = _ref2.packet; var _a; (_a = this.engine) === null || _a === void 0 ? void 0 : _a.sendDataPacket(packet, DataChannelKind.RELIABLE); }); this.rpcServerManager = new RpcServerManager(this.log, this.outgoingDataStreamManager, this.getRemoteParticipantClientProtocol); this.rpcServerManager.on('sendDataPacket', _ref3 => { let packet = _ref3.packet; var _a; (_a = this.engine) === null || _a === void 0 ? void 0 : _a.sendDataPacket(packet, DataChannelKind.RELIABLE); }); this.disconnectLock = new _(); this.localParticipant = new LocalParticipant('', '', this.engine, this.options, this.outgoingDataStreamManager, this.outgoingDataTrackManager, this.rpcClientManager, this.rpcServerManager); this.setupFrameMetadata(); if (this.options.e2ee || this.options.encryption) { this.setupE2EE(); } this.engine.e2eeManager = this.e2eeManager; this.incomingDataTrackManager.updateE2eeManager((_b = this.e2eeManager) !== null && _b !== void 0 ? _b : null); this.outgoingDataTrackManager.updateE2eeManager((_c = this.e2eeManager) !== null && _c !== void 0 ? _c : null); if (this.options.videoCaptureDefaults.deviceId) { this.localParticipant.activeDeviceMap.set('videoinput', unwrapConstraint(this.options.videoCaptureDefaults.deviceId)); } if (this.options.audioCaptureDefaults.deviceId) { this.localParticipant.activeDeviceMap.set('audioinput', unwrapConstraint(this.options.audioCaptureDefaults.deviceId)); } if ((_d = this.options.audioOutput) === null || _d === void 0 ? void 0 : _d.deviceId) { this.switchActiveDevice('audiooutput', unwrapConstraint(this.options.audioOutput.deviceId)).catch(e => this.log.warn("Could not set audio output: ".concat(e.message))); } if (isWeb()) { const cleanupController = new AbortController(); let onDeviceChange; if (Room.cleanupRegistry) { // Wrap the listener in a WeakRef closure so navigator.mediaDevices does not // strongly retain the Room. When the user drops their Room ref, the // FinalizationRegistry callback aborts the controller and removes the listener. const roomRef = new WeakRef(this); onDeviceChange = () => { const self = roomRef.deref(); if (!self) { return; } self.handleDeviceChange(); }; Room.cleanupRegistry.register(this, () => { cleanupController.abort(); }); } else { // Legacy browsers without WeakRef/FinalizationRegistry: fall back to a // direct listener (matches pre-#1944 behavior). onDeviceChange = this.handleDeviceChange; } // in order to catch device changes prior to room connection we need to register the event in the constructor (_f = (_e = navigator.mediaDevices) === null || _e === void 0 ? void 0 : _e.addEventListener) === null || _f === void 0 ? void 0 : _f.call(_e, 'devicechange', onDeviceChange, { signal: cleanupController.signal }); } } registerTextStreamHandler(topic, callback) { return this.incomingDataStreamManager.registerTextStreamHandler(topic, callback); } unregisterTextStreamHandler(topic) { return this.incomingDataStreamManager.unregisterTextStreamHandler(topic); } registerByteStreamHandler(topic, callback) { return this.incomingDataStreamManager.registerByteStreamHandler(topic, callback); } unregisterByteStreamHandler(topic) { return this.incomingDataStreamManager.unregisterByteStreamHandler(topic); } /** * Establishes the participant as a receiver for calls of the specified RPC method. * * @param method - The name of the indicated RPC method * @param handler - Will be invoked when an RPC request for this method is received * @returns A promise that resolves when the method is successfully registered * @throws {Error} If a handler for this method is already registered (must call unregisterRpcMethod first) * * @example * ```typescript * room.localParticipant?.registerRpcMethod( * 'greet', * async (data: RpcInvocationData) => { * console.log(`Received greeting from ${data.callerIdentity}: ${data.payload}`); * return `Hello, ${data.callerIdentity}!`; * } * ); * ``` * * The handler should return a Promise that resolves to a string. * If unable to respond within `responseTimeout`, the request will result in an error on the caller's side. * * You may throw errors of type `RpcError` with a string `message` in the handler, * and they will be received on the caller's side with the message intact. * Other errors thrown in your handler will not be transmitted as-is, and will instead arrive to the caller as `1500` ("Application Error"). */ registerRpcMethod(method, handler) { this.rpcServerManager.registerRpcMethod(method, handler); } /** * Unregisters a previously registered RPC method. * * @param method - The name of the RPC method to unregister */ unregisterRpcMethod(method) { this.rpcServerManager.unregisterRpcMethod(method); } /** * @experimental */ setE2EEEnabled(enabled) { return __awaiter(this, void 0, void 0, function* () { const unlock = yield this.e2eeStateMutex.lock(); try { if (this.e2eeManager) { if (this.isE2EEEnabled !== enabled) { yield this.localParticipant.setE2EEEnabled(enabled); if (this.localParticipant.identity !== '') { this.e2eeManager.setParticipantCryptorEnabled(enabled, this.localParticipant.identity); } } } else { throw Error('e2ee not configured, please set e2ee settings within the room options'); } } finally { unlock(); } }); } setupE2EE() { // when encryption is enabled via `options.encryption`, we enable data channel encryption var _a, _b; const dcEncryptionEnabled = !!this.options.encryption; const e2eeOptions = this.options.encryption || this.options.e2ee; if (e2eeOptions) { if ('e2eeManager' in e2eeOptions) { this.e2eeManager = e2eeOptions.e2eeManager; this.e2eeManager.isDataChannelEncryptionEnabled = dcEncryptionEnabled; } else { this.e2eeManager = new E2EEManager(e2eeOptions, dcEncryptionEnabled); } this.e2eeManager.on(EncryptionEvent.ParticipantEncryptionStatusChanged, (enabled, participant) => { if (isLocalParticipant(participant)) { this.isE2EEEnabled = enabled; } this.emit(RoomEvent.ParticipantEncryptionStatusChanged, enabled, participant); }); this.e2eeManager.on(EncryptionEvent.EncryptionError, (error, participantIdentity) => { const participant = participantIdentity ? this.getParticipantByIdentity(participantIdentity) : undefined; this.emit(RoomEvent.EncryptionError, error, participant); }); (_a = this.e2eeManager) === null || _a === void 0 ? void 0 : _a.setup(this); (_b = this.e2eeManager) === null || _b === void 0 ? void 0 : _b.setupEngine(this.engine); } } setupFrameMetadata() { var _a; const opts = (_a = this.options.frameMetadata) !== null && _a !== void 0 ? _a : this.options.packetTrailer; this.frameMetadataManager = new FrameMetadataManager(opts); this.frameMetadataManager.setup(this); } get logContext() { var _a, _b, _c; return { room: this.name, roomID: (_a = this.roomInfo) === null || _a === void 0 ? void 0 : _a.sid, participant: (_b = this.localParticipant) === null || _b === void 0 ? void 0 : _b.identity, participantID: (_c = this.localParticipant) === null || _c === void 0 ? void 0 : _c.sid }; } /** * if the current room has a participant with `recorder: true` in its JWT grant **/ get isRecording() { var _a, _b; return (_b = (_a = this.roomInfo) === null || _a === void 0 ? void 0 : _a.activeRecording) !== null && _b !== void 0 ? _b : false; } /** * server assigned unique room id. * returns once a sid has been issued by the server. */ getSid() { if (this.state === ConnectionState.Disconnected) { return TypedPromise.resolve(''); } if (this.roomInfo && this.roomInfo.sid !== '') { return TypedPromise.resolve(this.roomInfo.sid); } return new TypedPromise((resolve, reject) => { const handleRoomUpdate = roomInfo => { if (roomInfo.sid !== '') { this.engine.off(EngineEvent.RoomUpdate, handleRoomUpdate); resolve(roomInfo.sid); } }; this.engine.on(EngineEvent.RoomUpdate, handleRoomUpdate); this.once(RoomEvent.Disconnected, () => { this.engine.off(EngineEvent.RoomUpdate, handleRoomUpdate); reject(new UnexpectedConnectionState('Room disconnected before room server id was available')); }); }); } /** user assigned name, derived from JWT token */ get name() { var _a, _b; return (_b = (_a = this.roomInfo) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : ''; } /** room metadata */ get metadata() { var _a; return (_a = this.roomInfo) === null || _a === void 0 ? void 0 : _a.metadata; } get numParticipants() { var _a, _b; return (_b = (_a = this.roomInfo) === null || _a === void 0 ? void 0 : _a.numParticipants) !== null && _b !== void 0 ? _b : 0; } get numPublishers() { var _a, _b; return (_b = (_a = this.roomInfo) === null || _a === void 0 ? void 0 : _a.numPublishers) !== null && _b !== void 0 ? _b : 0; } maybeCreateEngine() { if (this.engine && (this.engine.isNewlyCreated || !this.engine.isClosed)) { return; } this.engine = new RTCEngine(this.options); this.engine.e2eeManager = this.e2eeManager; this.engine.on(EngineEvent.ParticipantUpdate, this.handleParticipantUpdates).on(EngineEvent.RoomUpdate, this.handleRoomUpdate).on(EngineEvent.SpeakersChanged, this.handleSpeakersChanged).on(EngineEvent.StreamStateChanged, this.handleStreamStateUpdate).on(EngineEvent.ConnectionQualityUpdate, this.handleConnectionQualityUpdate).on(EngineEvent.SubscriptionError, this.handleSubscriptionError).on(EngineEvent.SubscriptionPermissionUpdate, this.handleSubscriptionPermissionUpdate).on(EngineEvent.MediaTrackAdded, (mediaTrack, stream, receiver) => { this.onTrackAdded(mediaTrack, stream, receiver); }).on(EngineEvent.Disconnected, reason => { this.handleDisconnect(this.options.stopLocalTrackOnUnpublish, reason); }).on(EngineEvent.ActiveSpeakersUpdate, this.handleActiveSpeakersUpdate).on(EngineEvent.DataPacketReceived, this.handleDataPacket).on(EngineEvent.Resuming, () => { this.clearConnectionReconcile(); this.isResuming = true; this.log.debug('Resuming signal connection'); if (this.setAndEmitConnectionState(ConnectionState.SignalReconnecting)) { this.emit(RoomEvent.SignalReconnecting); } }).on(EngineEvent.Resumed, () => { this.registerConnectionReconcile(); this.isResuming = false; this.log.debug('Resumed signal connection'); this.updateSubscriptions(); this.emitBufferedEvents(); if (this.setAndEmitConnectionState(ConnectionState.Connected)) { this.emit(RoomEvent.Reconnected); } }).on(EngineEvent.SignalResumed, () => { this.bufferedEvents = []; if (this.state === ConnectionState.Reconnecting || this.isResuming) { this.sendSyncState(); } }).on(EngineEvent.Restarting, this.handleRestarting).on(EngineEvent.Restarted, this.handleRestarted).on(EngineEvent.SignalRestarted, this.handleSignalRestarted).on(EngineEvent.Offline, () => { if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) { this.emit(RoomEvent.Reconnecting); } }).on(EngineEvent.DCBufferStatusChanged, (status, kind) => { this.emit(RoomEvent.DCBufferStatusChanged, status, kind); }).on(EngineEvent.LocalTrackSubscribed, subscribedSid => { this.handleLocalTrackSubscribed(subscribedSid); }).on(EngineEvent.RoomMoved, roomMoved => { this.log.debug('room moved', roomMoved); if (roomMoved.room) { this.handleRoomUpdate(roomMoved.room); } this.remoteParticipants.forEach((participant, identity) => { this.handleParticipantDisconnected(identity, participant); }); this.emit(RoomEvent.Moved, roomMoved.room.name); if (roomMoved.participant) { this.handleParticipantUpdates([roomMoved.participant, ...roomMoved.otherParticipants]); } else { this.handleParticipantUpdates(roomMoved.otherParticipants); } }).on(EngineEvent.PublishDataTrackResponse, event => { if (!event.info) { this.log.warn("received PublishDataTrackResponse, but event.info was ".concat(event.info, ", so skipping.")); return; } this.outgoingDataTrackManager.receivedSfuPublishResponse(event.info.pubHandle, { type: 'ok', data: { sid: event.info.sid, pubHandle: event.info.pubHandle, name: event.info.name, usesE2ee: event.info.encryption !== Encryption_Type.NONE } }); }).on(EngineEvent.UnPublishDataTrackResponse, event => { if (!event.info) { this.log.warn("received UnPublishDataTrackResponse, but event.info was ".concat(event.info, ", so skipping.")); return; } this.outgoingDataTrackManager.receivedSfuUnpublishResponse(event.info.pubHandle); }).on(EngineEvent.DataTrackSubscriberHandles, event => { const handleToSidMapping = new Map(Object.entries(event.subHandles).map(_ref4 => { let _ref5 = _slicedToArray(_ref4, 2), key = _ref5[0], value = _ref5[1]; return [parseInt(key, 10), value.trackSid]; })); this.incomingDataTrackManager.receivedSfuSubscriberHandles(handleToSidMapping); }).on(EngineEvent.DataTrackPacketReceived, packetBytes => { try { this.incomingDataTrackManager.packetReceived(packetBytes); } catch (err) { // NOTE: wrapping in the bare try/catch like this means that the Throws<...> type doesn't // propagate upwards into the public interface. throw err; } }).on(EngineEvent.Joined, joinResponse => { // Ingest data track publication updates into data tracks infrastructure const mapped = new Map(joinResponse.otherParticipants.map(participant => { return [participant.identity, participant.dataTracks.map(info => DataTrackInfo.from(info))]; })); this.incomingDataTrackManager.receiveSfuPublicationUpdates(mapped); }).on(EngineEvent.TokenRefreshed, token => { var _a; (_a = this.regionUrlProvider) === null || _a === void 0 ? void 0 : _a.updateToken(token); }).on(EngineEvent.ServerRegionsReported, regions => { var _a; (_a = this.regionUrlProvider) === null || _a === void 0 ? void 0 : _a.setServerReportedRegions({ regionSettings: regions, updatedAtInMs: Date.now(), maxAgeInMs: DEFAULT_MAX_AGE_MS }); }); if (this.localParticipant) { this.localParticipant.setupEngine(this.engine); } if (this.e2eeManager) { this.e2eeManager.setupEngine(this.engine); } if (this.outgoingDataStreamManager) { this.outgoingDataStreamManager.setupEngine(this.engine); } } createRegionStrategy() { return { getNextUrl: signal => __awaiter(this, void 0, void 0, function* () { return this.regionUrlProvider ? this.regionUrlProvider.getNextBestRegionUrl(signal) : null; }), resetAttempts: () => { var _a; return (_a = this.regionUrlProvider) === null || _a === void 0 ? void 0 : _a.resetAttempts(); } }; } /** * getLocalDevices abstracts navigator.mediaDevices.enumerateDevices. * In particular, it requests device permissions by default if needed * and makes sure the returned device does not consist of dummy devices * @param kind * @returns a list of available local devices */ static getLocalDevices(kind) { let requestPermissions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; return DeviceManager.getInstance().getDevices(kind, requestPermissions); } /** * prepareConnection should be called as soon as the page is loaded, in order * to speed up the connection attempt. This function will * - perform DNS resolution and pre-warm the DNS cache * - establish TLS connection and cache TLS keys * * With LiveKit Cloud, it will also determine the best edge data center for * the current client to connect to if a token is provided. */ prepareConnection(url, token) { return __awaiter(this, void 0, void 0, function* () { if (this.state !== ConnectionState.Disconnected) { return; } this.log.debug("prepareConnection to ".concat(url)); try { if (isCloud(new URL(url)) && token) { this.regionUrlProvider = new RegionUrlProvider(url, token); const regionUrl = yield this.regionUrlProvider.getNextBestRegionUrl(); // we will not replace the regionUrl if an attempt had already started // to avoid overriding regionUrl after a new connection attempt had started if (regionUrl && this.state === ConnectionState.Disconnected) { this.regionUrl = regionUrl; yield fetch(toHttpUrl(regionUrl), { method: 'HEAD' }); this.log.debug("prepared connection to ".concat(regionUrl)); } } else { yield fetch(toHttpUrl(url), { method: 'HEAD' }); } } catch (e) { this.log.warn('could not prepare connection', { error: e }); } }); } /** * retrieves a participant by identity * @param identity * @returns */ getParticipantByIdentity(identity) { if (this.localParticipant.identity === identity) { return this.localParticipant; } return this.remoteParticipants.get(identity); } clearConnectionFutures() { this.connectFuture = undefined; } /** * @internal for testing */ simulateScenario(scenario, arg) { return __awaiter(this, void 0, void 0, function* () { let postAction = () => __awaiter(this, void 0, void 0, function* () {}); let req; switch (scenario) { case 'signal-reconnect': // @ts-expect-error function is private yield this.engine.client.handleOnClose('simulate disconnect'); break; case 'fail-on-v1-path': this.engine.failNextV1Path(); break; case 'speaker': req = new SimulateScenario({ scenario: { case: 'speakerUpdate', value: 3 } }); break; case 'node-failure': req = new SimulateScenario({ scenario: { case: 'nodeFailure', value: true } }); break; case 'server-leave': req = new SimulateScenario({ scenario: { case: 'serverLeave', value: true } }); break; case 'migration': req = new SimulateScenario({ scenario: { case: 'migration', value: true } }); break; case 'resume-reconnect': this.engine.failNext(); // @ts-expect-error function is private yield this.engine.client.handleOnClose('simulate resume-disconnect'); break; case 'disconnect-signal-on-resume': postAction = () => __awaiter(this, void 0, void 0, function* () { // @ts-expect-error function is private yield this.engine.client.handleOnClose('simulate resume-disconnect'); }); req = new SimulateScenario({ scenario: { case: 'disconnectSignalOnResume', value: true } }); break; case 'disconnect-signal-on-resume-no-messages': postAction = () => __awaiter(this, void 0, void 0, function* () { // @ts-expect-error function is private yield this.engine.client.handleOnClose('simulate resume-disconnect'); }); req = new SimulateScenario({ scenario: { case: 'disconnectSignalOnResumeNoMessages', value: true } }); break; case 'full-reconnect': this.engine.fullReconnectOnNext = true; // @ts-expect-error function is private yield this.engine.client.handleOnClose('simulate full-reconnect'); break; case 'force-tcp': case 'force-tls': req = new SimulateScenario({ scenario: { case: 'switchCandidateProtocol', value: scenario === 'force-tls' ? 2 : 1 } }); postAction = () => __awaiter(this, void 0, void 0, function* () { const onLeave = this.engine.client.onLeave; if (onLeave) { onLeave(new LeaveRequest({ reason: DisconnectReason.CLIENT_INITIATED, action: LeaveRequest_Action.RECONNECT })); } }); break; case 'subscriber-bandwidth': if (arg === undefined || typeof arg !== 'number') { throw new Error('subscriber-bandwidth requires a number as argument'); } req = new SimulateScenario({ scenario: { case: 'subscriberBandwidth', value: numberToBigInt(arg) } }); break; case 'leave-full-reconnect': req = new SimulateScenario({ scenario: { case: 'leaveRequestFullReconnect', value: true } }); } if (req) { yield this.engine.client.sendSimulateScenario(req); yield postAction(); } }); } /** * Returns true if audio playback is enabled */ get canPlaybackAudio() { return this.audioEnabled; } /** * Returns true if video playback is enabled */ get canPlaybackVideo() { return !this.isVideoPlaybackBlocked; } getActiveDevice(kind) { return this.localParticipant.activeDeviceMap.get(kind); } /** * Switches all active devices used in this room to the given device. * * Note: setting AudioOutput is not supported on some browsers. See [setSinkId](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId#browser_compatibility) * * @param kind use `videoinput` for camera track, * `audioinput` for microphone track, * `audiooutput` to set speaker for all incoming audio tracks * @param deviceId */ switchActiveDevice(kind_1, deviceId_1) { return __awaiter(this, arguments, void 0, function (kind, deviceId) { var _this3 = this; let exact = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; return function* () { var _a, _b, _c, _d, _e, _f; var _g; let success = true; let shouldTriggerImmediateDeviceChange = false; const deviceConstraint = exact ? { exact: deviceId } : deviceId; if (kind === 'audioinput') { shouldTriggerImmediateDeviceChange = _this3.localParticipant.audioTrackPublications.size === 0; const prevDeviceId = (_a = _this3.getActiveDevice(kind)) !== null && _a !== void 0 ? _a : _this3.options.audioCaptureDefaults.deviceId; _this3.options.audioCaptureDefaults.deviceId = deviceConstraint; const tracks = Array.from(_this3.localParticipant.audioTrackPublications.values()).filter(track => track.source === Track.Source.Microphone); try { success = (yield Promise.all(tracks.map(t => { var _a; return (_a = t.audioTrack) === null || _a === void 0 ? void 0 : _a.setDeviceId(deviceConstraint); }))).every(val => val === true); } catch (e) { _this3.options.audioCaptureDefaults.deviceId = prevDeviceId; throw e; } const isMuted = tracks.some(t => { var _a, _b; return (_b = (_a = t.track) === null || _a === void 0 ? void 0 : _a.isMuted) !== null && _b !== void 0 ? _b : false; }); if (success && isMuted) shouldTriggerImmediateDeviceChange = true; } else if (kind === 'videoinput') { shouldTriggerImmediateDeviceChange = _this3.localParticipant.videoTrackPublications.size === 0; const prevDeviceId = (_b = _this3.getActiveDevice(kind)) !== null && _b !== void 0 ? _b : _this3.options.videoCaptureDefaults.deviceId; _this3.options.videoCaptureDefaults.deviceId = deviceConstraint; const tracks = Array.from(_this3.localParticipant.videoTrackPublications.values()).filter(track => track.source === Track.Source.Camera); try { success = (yield Promise.all(tracks.map(t => { var _a; return (_a = t.videoTrack) === null || _a === void 0 ? void 0 : _a.setDeviceId(deviceConstraint); }))).every(val => val === true); } catch (e) { _this3.options.videoCaptureDefaults.deviceId = prevDeviceId; throw e; } const isMuted = tracks.some(t => { var _a, _b; return (_b = (_a = t.track) === null || _a === void 0 ? void 0 : _a.isMuted) !== null && _b !== void 0 ? _b : false; }); if (success && isMuted) shouldTriggerImmediateDeviceChange = true; } else if (kind === 'audiooutput') { shouldTriggerImmediateDeviceChange = true; if (!supportsSetSinkId() && !_this3.options.webAudioMix || _this3.options.webAudioMix && _this3.audioContext && !('setSinkId' in _this3.audioContext)) { throw new Error('cannot switch audio output, the current browser does not support it'); } if (_this3.options.webAudioMix) { // setting `default` for web audio output doesn't work, so we need to normalize the id before deviceId = (_c = yield DeviceManager.getInstance().normalizeDeviceId('audiooutput', deviceId)) !== null && _c !== void 0 ? _c : ''; } (_d = (_g = _this3.options).audioOutput) !== null && _d !== void 0 ? _d : _g.audioOutput = {}; const prevDeviceId = (_e = _this3.getActiveDevice(kind)) !== null && _e !== void 0 ? _e : _this3.options.audioOutput.deviceId; _this3.options.audioOutput.deviceId = deviceId; try { if (_this3.options.webAudioMix) { // @ts-expect-error setSinkId is not yet in the typescript type of AudioContext (_f = _this3.audioContext) === null || _f === void 0 ? void 0 : _f.setSinkId(deviceId); } // also set audio output on all audio elements, even if webAudioMix is enabled in order to workaround echo cancellation not working on chrome with non-default output devices // see https://issues.chromium.org/issues/40252911#comment7 yield Promise.all(Array.from(_this3.remoteParticipants.values()).map(p => p.setAudioOutput({ deviceId }))); } catch (e) { _this3.options.audioOutput.deviceId = prevDeviceId; throw e; } } if (shouldTriggerImmediateDeviceChange) { _this3.localParticipant.activeDeviceMap.set(kind, deviceId); _this3.emit(RoomEvent.ActiveDeviceChanged, kind, deviceId); } return success; }(); }); } setupLocalParticipantEvents() { this.localParticipant.on(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged).on(ParticipantEvent.ParticipantNameChanged, this.onLocalParticipantNameChanged).on(ParticipantEvent.AttributesChanged, this.onLocalAttributesChanged).on(ParticipantEvent.TrackMuted, this.onLocalTrackMuted).on(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted).on(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished).on(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished).on(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged).on(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError).on(ParticipantEvent.AudioStreamAcquired, this.startAudio).on(ParticipantEvent.ChatMessage, this.onLocalChatMessageSent).on(ParticipantEvent.ParticipantPermissionsChanged, this.onLocalParticipantPermissionsChanged); } recreateEngine(sendLeave) { const oldEngine = this.engine; if (sendLeave && oldEngine && !oldEngine.client.isDisconnected) { oldEngine.client.sendLeave().finally(() => oldEngine.close()); } else { oldEngine === null || oldEngine === void 0 ? void 0 : oldEngine.close(); } /* @ts-ignore */ this.engine = undefined; this.isResuming = false; // clear out existing remote participants, since they may have attached // the old engine this.remoteParticipants.clear(); this.sidToIdentity.clear(); this.bufferedEvents = []; this.maybeCreateEngine(); } onTrackAdded(mediaTrack, stream, receiver) { // don't fire onSubscribed when connecting // WebRTC fires onTrack as soon as setRemoteDescription is called on the offer // at that time, ICE connectivity has not been established so the track is not // technically subscribed. // We'll defer these events until when the room is connected or eventually disconnected. if (this.state === ConnectionState.Connecting || this.state === ConnectionState.Reconnecting) { const reconnectedHandler = () => { this.log.debug('deferring on track for later', { mediaTrackId: mediaTrack.id, mediaStreamId: stream.id, tracksInStream: stream.getTracks().map(track => track.id) }); this.onTrackAdded(mediaTrack, stream, receiver); cleanup(); }; const cleanup = () => { this.off(RoomEvent.Reconnected, reconnectedHandler); this.off(RoomEvent.Connected, reconnectedHandler); this.off(RoomEvent.Disconnected, cleanup); }; this.once(RoomEvent.Reconnected, reconnectedHandler); this.once(RoomEvent.Connected, reconnectedHandler); this.once(RoomEvent.Disconnected, cleanup); return; } if (this.state === ConnectionState.Disconnected) { this.log.warn('skipping incoming track after Room disconnected'); return; } if (mediaTrack.readyState === 'ended') { this.log.debug('skipping incoming track as it already ended'); return; } const parts = unpackStreamId(stream.id); const participantSid = parts[0]; let streamId = parts[1]; let trackId = mediaTrack.id; // firefox will get streamId (pID|trackId) instead of (pID|streamId) as it doesn't support sync tracks by stream // and generates its own track id instead of infer from sdp track id. if (streamId && streamId.startsWith('TR')) trackId = streamId; if (participantSid === this.localParticipant.sid) { this.log.warn('tried to create RemoteParticipant for local participant'); return; } const participant = Array.from(this.remoteParticipants.values()).find(p => p.sid === participantSid); if (!participant) { // server could require extra media sections to accelerate subscription. if (participantSid.startsWith('PA')) { this.log.error("Tried to add a track for a participant, that's not present. Sid: ".concat(participantSid)); } return; } // in single peer connection case, the trackID is locally generated, // not the TR_ prefixed one generated by the server, // use `mid` to find the appropriate track. if (!trackId.startsWith('TR')) { const id = this.engine.getTrackIdForReceiver(receiver); if (!id) { this.log.error("Tried to add a track whose 'sid' could not be found for a participant, that's not present. Sid: ".concat(participantSid)); return; } trackId = id; } if (!trackId.startsWith('TR')) { this.log.warn("Tried to add a track whose 'sid' could not be determined for a participant, that's not present. Sid: ".concat(participantSid, ", streamId: ").concat(streamId, ", trackId: ").concat(trackId), { remoteParticipantID: participantSid, streamId, trackId }); } let adaptiveStreamSettings; if (this.options.adaptiveStream) { if (typeof this.options.adaptiveStream === 'object') { adaptiveStreamSettings = this.options.adaptiveStream; } else { adaptiveStreamSettings = {}; } } const publication = participant.addSubscribedMediaTrack(mediaTrack, trackId, stream, receiver, adaptiveStreamSettings); if ((publication === null || publication === void 0 ? void 0 : publication.isEncrypted) && !this.e2eeManager) { this.emit(RoomEvent.EncryptionError, new Error("Encrypted ".concat(publication.source, " track received from participant ").concat(participant.sid, ", but room does not have encryption enabled!"))); } } handleLocalTrackSubscribed(subscribedSid) { const findPublication = () => this.localParticipant.getTrackPublications().find(_ref6 => { let trackSid = _ref6.trackSid; return trackSid === subscribedSid; }); const trackPublication = findPublication(); if (trackPublication) { this.emitLocalTrackSubscribed(trackPublication); return; } // the track publication may not be registered yet if the server signals // the subscription before publishTrack has finished adding the publication. // defer with a timeout until LocalTrackPublished fires for the matching trackSid this.log.debug('deferring LocalTrackSubscribed, publication not yet available', { subscribedSid }); const TIMEOUT_MS = 10000; let timer; const onPublished = pub => { if (pub.trackSid === subscribedSid) { cleanup(); this.emitLocalTrackSubscribed(pub); } }; const cleanup = () => { clearTimeout(timer); this.localParticipant.off(ParticipantEvent.LocalTrackPublished, onPublished); this.off(RoomEvent.Disconnected, cleanup); }; this.localParticipant.on(ParticipantEvent.LocalTrackPublished, onPublished); this.once(RoomEvent.Disconnected, cleanup); timer = setTimeout(() => { cleanup(); // final attempt in case the publication was added without emitting the event const pub = findPublication(); if (pub) { this.emitLocalTrackSubscribed(pub); } else { this.log.warn('could not find local track publication for LocalTrackSubscribed event after timeout', { subscribedSid }); } }, TIMEOUT_MS); } emitLocalTrackSubscribed(trackPublication) { this.localParticipant.emit(ParticipantEvent.LocalTrackSubscribed, trackPublication); this.emitWhenConnected(RoomEvent.LocalTrackSubscribed, trackPublication, this.localParticipant); } handleDisconnect() { let shouldStopTracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; let reason = arguments.length > 1 ? arguments[1] : undefined; var _a, _b; this.clearConnectionReconcile(); this.isResuming = false; this.bufferedEvents = []; this.transcriptionReceivedTimes.clear(); this.incomingDataStreamManager.clearControllers(); this.incomingDataTrackManager.reset(); this.outgoingDataTrackManager.reset(); if (this.state === ConnectionState.Disconnected) { return; } this.regionUrl = undefined; // Notify region provider about disconnect to potentially stop auto-refetch if (this.regionUrlProvider) { this.regionUrlProvider.notifyDisconnected(); } try { this.remoteParticipants.forEach(p => { p.trackPublications.forEach(pub => { p.unpublishTrack(pub.trackSid); }); }); this.localParticipant.trackPublications.forEach(pub => { var _a, _b, _c; if (pub.track) { this.localParticipant.unpublishTrack(pub.track, shouldStopTracks); } if (shouldStopTracks) { (_a = pub.track) === null || _a === void 0 ? void 0 : _a.detach(); (_b = pub.track) === null || _b === void 0 ? void 0 : _b.stop(); } else { (_c = pub.track) === null || _c === void 0 ? void 0 : _c.stopMonitor(); } }); this.localParticipant.off(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged).off(ParticipantEvent.ParticipantNameChanged, this.onLocalParticipantNameChanged).off(ParticipantEvent.AttributesChanged, this.onLocalAttributesChanged).off(ParticipantEvent.TrackMuted, this.onLocalTrackMuted).off(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted).off(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished).off(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished).off(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged).off(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError).off(ParticipantEvent.AudioStreamAcquired, this.startAudio).off(ParticipantEvent.ChatMessage, this.onLocalChatMessageSent).off(ParticipantEvent.ParticipantPermissionsChanged, this.onLocalParticipantPermissionsChanged); this.localParticipant.trackPublications.clear(); this.localParticipant.videoTrackPublications.clear(); this.localParticipant.audioTrackPublications.clear(); this.remoteParticipants.clear(); this.sidToIdentity.clear(); this.activeSpeakers = []; if (this.audioContext && typeof this.options.webAudioMix === 'boolean') { this.audioContext.close(); this.audioContext = undefined; } if (isWeb()) { window.removeEventListener('beforeunload', this.onPageLeave); window.removeEventListener('pagehide', this.onPageLeave); window.removeEventListener('freeze', this.onPageLeave); (_b = (_a = navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.removeEventListener) === null || _b === void 0 ? void 0 : _b.call(_a, 'devicechange', this.handleDeviceChange); } } finally { this.setAndEmitConnectionState(ConnectionState.Disconnected); this.emit(RoomEvent.Disconnected, reason); } } handleParticipantDisconnected(identity, participant) { // remove and send event this.remoteParticipants.delete(identity); if (!participant) { return; } this.incomingDataStreamManager.validateParticipantHasNoActiveDataStreams(identity); this.incomingDataTrackManager.handleRemoteParticipantDisconnected(identity); participant.trackPublications.forEach(publication => { participant.unpublishTrack(publication.trackSid, true); }); this.emit(RoomEvent.ParticipantDisconnected, participant); participant.setDisconnected(); this.rpcClientManager.handleParticipantDisconnected(participant.identity); } /** * attempt to select the default devices if the previously selected devices are no longer available after a device change event */ selectDefaultDevices() { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; const previousDevices = DeviceManager.getInstance().previousDevices; // check for available devices, but don't request permissions in order to avoid prompts for kinds that haven't been used before const availableDevices = yield DeviceManager.getInstance().getDevices(undefined, false); const browser = getBrowser(); if ((browser === null || browser === void 0 ? void 0 : browser.name) === 'Chrome' && browser.os !== 'iOS') { for (let availableDevice of availableDevices) { const previousDevice = previousDevices.find(info => info.deviceId === availableDevice.deviceId); if (previousDevice && previousDevice.label !== '' && previousDevice.kind === availableDevice.kind && previousDevice.label !== availableDevice.label) { // label has changed on device the same deviceId, indicating that the default device has changed on the OS level if (this.getActiveDevice(availableDevice.kind) === 'default') { // emit an active device change event only if the selected output device is actually on `default` this.emit(RoomEvent.ActiveDeviceChanged, availableDevice.kind, availableDevice.deviceId); } } } } const kinds = ['audiooutput', 'audioinput', 'videoinput']; for (let kind of kinds) { const targetSource = kindToSource(kind); const targetPublication = this.localParticipant.getTrackPublication(targetSource); if (targetPublication && ((_a = targetPublication.track) === null || _a === void 0 ? void 0 : _a.isUserProvided)) { // if the track is user provided, we don't want to switch devices on behalf of the user continue; } const devicesOfKind = availableDevices.filter(d => d.kind === kind); const activeDevice = this.getActiveDevice(kind); if (activeDevice === ((_b = previousDevices.filter(info => info.kind === kind)[0]) === null || _b === void 0 ? void 0 : _b.deviceId)) { // in Safari the first device is always the default, so we assume a user on the default device would like to switch to the default once it changes // FF doesn't emit an event when the default device changes, so we perform the same best effort and switch to the new device once connected and if it's the first in the array if (devicesOfKind.length > 0 && ((_c = devicesOfKind[0]) === null || _c === void 0 ? void 0 : _c.deviceId) !== activeDevice) { yield this.switchActiveDevice(kind, devicesOfKind[0].deviceId); continue; } } if (kind === 'audioinput' && !isSafariBased() || kind === 'videoinput') { // airpods on Safari need special handling for audioinput as the track doesn't end as soon as you take them out continue; } // switch to first available device if previously active device is not available any more if (devicesOfKind.length > 0 && !devicesOfKind.find(deviceInfo => deviceInfo.deviceId === this.getActiveDevice(kind)) && ( // avoid switching audio output on safari without explicit user action as it leads to slowed down audio playback kind !== 'audiooutput' || !isSafariBased())) { yield this.switchActiveDevice(kind, devicesOfKind[0].deviceId); } } }); } acquireAudioContext() { return __awaiter(this, void 0, void 0, function* () { var _a, _b; if (typeof this.options.webAudioMix !== 'boolean' && this.options.webAudioMix.audioContext) { // override audio context with custom audio context if supplied by user this.audioContext = this.options.webAudioMix.audioContext; } else if (!this.audioContext || this.audioContext.state === 'closed') { // by using an AudioContext, it reduces lag on audio elements // https://stackoverflow.com/questions/9811429/html5-audio-tag-on-safari-has-a-delay/54119854#54119854 this.audioContext = (_a = getNewAudioContext()) !== null && _a !== void 0 ? _a : undefined; } if (this.options.webAudioMix) { this.remoteParticipants.forEach(participant => participant.setAudioContext(this.audioContext)); } this.localParticipant.setAudioContext(this.audioContext); if (this.audioContext && this.audioContext.state === 'suspended') { // for iOS a newly created AudioContext is always in `suspended` state. // we try our best to resume the context here, if that doesn't work, we just continue with regular processing try { yield Promise.race([this.audioContext.resume(), sleep(200)]); } catch (e) { this.log.warn('Could not resume audio context', { error: e }); } } const newContextIsRunning = ((_b = this.audioContext) === null || _b === void 0 ? void 0 : _b.state) === 'running'; if (newContextIsRunning !== this.canPlaybackAudio) { this.audioEnabled = newContextIsRunning; this.emit(RoomEvent.AudioPlaybackStatusChanged, newContextIsRunning); } }); } createParticipant(identity, info) { var _a; let participant; if (info) { participant = RemoteParticipant.fromParticipantInfo(this.engine.client, info, { loggerContextCb: () => this.logContext, loggerName: this.options.loggerName }, this.incomingDataTrackManager); } else { participant = new RemoteParticipant(this.engine.client, '', identity, undefined, undefined, undefined, { loggerContextCb: () => this.logContext, loggerName: this.options.loggerName }); } if (this.options.webAudioMix) { participant.setAudioContext(this.audioContext); } if ((_a = this.options.audioOutput) === null || _a === void 0 ? void 0 : _a.deviceId) { participant.setAudioOutput(this.options.audioOutput).catch(e => this.log.warn("Could not set audio output: ".concat(e.message))); } return participant; } getOrCreateParticipant(identity, info) { if (this.remoteParticipants.has(identity)) { const existingParticipant = this.remoteParticipants.get(identity); if (info) { const wasUpdated = existingParticipant.updateInfo(info); if (wasUpdated) { this.sidToIdentity.set(info.sid, info.identity); } } return existingParticipant; } const participant = this.createParticipant(identity, info); this.remoteParticipants.set(identity, participant); this.sidToIdentity.set(info.sid, info.identity); // if we have valid info and the participant wasn't in the map before, we can assume the participant is new // firing here to make sure that `ParticipantConnected` fires before the initial track events this.emitWhenConnected(RoomEvent.ParticipantConnected, participant); // also forward events // trackPublished is only fired for tracks added after both local participant // and remote participant joined the room participant.on(ParticipantEvent.TrackPublished, trackPublication => { this.emitWhenConnected(RoomEvent.TrackPublished, trackPublication, participant); }).on(ParticipantEvent.TrackSubscribed, (track, publication) => { // monitor playback status if (track.kind === Track.Kind.Audio) { track.on(TrackEvent.AudioPlaybackStarted, this.handleAudioPlaybackStarted); track.on(TrackEvent.AudioPlaybackFailed, this.handleAudioPlaybackFailed); } else if (track.kind === Track.Kind.Video) { track.on(TrackEvent.VideoPlaybackFailed, this.handleVideoPlaybackFailed); track.on(TrackEvent.VideoPlaybackStarted, this.handleVideoPlaybackStarted); } this.emitWhenConnected(RoomEvent.TrackSubscribed, track, publication, participant); }).on(ParticipantEvent.TrackUnpublished, publication => { this.emit(RoomEvent.TrackUnpublished, publication, participant); }).on(ParticipantEvent.TrackUnsubscribed, (track, publication) => { this.emit(RoomEvent.TrackUnsubscribed, track, publication, participant); }).on(ParticipantEvent.TrackMuted, pub => { this.emitWhenConnected(RoomEvent.TrackMuted, pub, participant); }).on(ParticipantEvent.TrackUnmuted, pub => { this.emitWhenConnected(RoomEvent.TrackUnmuted, pub, participant); }).on(ParticipantEvent.ParticipantMetadataChanged, metadata => { this.emitWhenConnected(RoomEvent.ParticipantMetadataChanged, metadata, participant); }).on(ParticipantEvent.ParticipantNameChanged, name => { this.emitWhenConnected(RoomEvent.ParticipantNameChanged, name, participant); }).on(ParticipantEvent.AttributesChanged, changedAttributes => { this.emitWhenConnected(RoomEvent.ParticipantAttributesChanged, changedAttributes, participant); }).on(ParticipantEvent.ConnectionQualityChanged, quality => { this.emitWhenConnected(RoomEvent.ConnectionQualityChanged, quality, participant); }).on(ParticipantEvent.ParticipantPermissionsChanged, prevPermissions => { this.emitWhenConnected(RoomEvent.ParticipantPermissionsChanged, prevPermissions, participant); }).on(ParticipantEvent.TrackSubscriptionStatusChanged, (pub, status) => { this.emitWhenConnected(RoomEvent.TrackSubscriptionStatusChanged, pub, status, participant); }).on(ParticipantEvent.TrackSubscriptionFailed, (trackSid, error) => { this.emit(RoomEvent.TrackSubscriptionFailed, trackSid, participant, error); }).on(ParticipantEvent.TrackSubscriptionPermissionChanged, (pub, status) => { this.emitWhenConnected(RoomEvent.TrackSubscriptionPermissionChanged, pub, status, participant); }).on(ParticipantEvent.Active, () => { this.emitWhenConnected(RoomEvent.ParticipantActive, participant); if (participant.kind === ParticipantInfo_Kind.AGENT) { this.localParticipant.setActiveAgent(participant); } }); // update info at the end after callbacks have been set up if (info) { participant.updateInfo(info); } return participant; } sendSyncState() { const remoteTracks = Array.from(this.remoteParticipants.values()).reduce((acc, participant) => { acc.push(...participant.getTrackPublications()); // FIXME would be nice to have this return RemoteTrackPublications directly instead of the type cast return acc; }, []); const localTracks = this.localParticipant.getTrackPublications(); // FIXME would be nice to have this return LocalTrackPublications directly instead of the type cast const localDataTrackInfos = this.outgoingDataTrackManager.queryPublished(); this.engine.sendSyncState(remoteTracks, localTracks, localDataTrackInfos); } /** * After resuming, we'll need to notify the server of the current * subscription settings. */ updateSubscriptions() { for (const p of this.remoteParticipants.values()) { for (const pub of p.videoTrackPublications.values()) { if (pub.isSubscribed && isRemotePub(pub)) { pub.emitTrackUpdate(); } } } } getRemoteParticipantBySid(sid) { const identity = this.sidToIdentity.get(sid); if (identity) { return this.remoteParticipants.get(identity); } } registerRpcDataStreamHandler() { this.incomingDataStreamManager.registerTextStreamHandler(RPC_REQUEST_DATA_STREAM_TOPIC, (reader_1, _a) => __awaiter(this, [reader_1, _a], void 0, function (reader, _ref7) { var _this4 = this; let identity = _ref7.identity; return function* () { var _b; const attributes = (_b = reader.info.attributes) !== null && _b !== void 0 ? _b : {}; yield _this4.rpcServerManager.handleIncomingDataStream(reader, identity, attributes); }(); })); this.incomingDataStreamManager.registerTextStreamHandler(RPC_RESPONSE_DATA_STREAM_TOPIC, (reader_1, _a) => __awaiter(this, [reader_1, _a], void 0, function (reader, _ref8) { var _this5 = this; let identity = _ref8.identity; return function* () { var _b; const attributes = (_b = reader.info.attributes) !== null && _b !== void 0 ? _b : {}; yield _this5.rpcClientManager.handleIncomingDataStream(reader, identity, attributes); }(); })); } registerConnectionReconcile() { this.clearConnectionReconcile(); let consecutiveFailures = 0; this.connectionReconcileInterval = CriticalTimers.setInterval(() => { if ( // ensure we didn't tear it down !this.engine || // engine detected close, but Room missed it this.engine.isClosed || // transports failed without notifying engine !this.engine.verifyTransport()) { consecutiveFailures++; this.log.warn('detected connection state mismatch', { numFailures: consecutiveFailures, engine: this.engine ? { closed: this.engine.isClosed, transportsConnectedOrConnecting: this.engine.verifyTransport() } : undefined }); if (consecutiveFailures >= 3) { this.recreateEngine(); this.handleDisconnect(this.options.stopLocalTrackOnUnpublish, DisconnectReason.STATE_MISMATCH); } } else { consecutiveFailures = 0; } }, CONNECTION_RECONCILE_FREQUENCY_MS); } clearConnectionReconcile() { if (this.connectionReconcileInterval) { CriticalTimers.clearInterval(this.connectionReconcileInterval); } } setAndEmitConnectionState(state) { if (state === this.state) { // unchanged return false; } this.log.info("connection state changed: ".concat(this.state, " -> ").concat(state)); this.state = state; this.incomingDataStreamManager.setConnected(state === ConnectionState.Connected); this.emit(RoomEvent.ConnectionStateChanged, this.state); return true; } emitBufferedEvents() { this.bufferedEvents.forEach(_ref9 => { let _ref0 = _slicedToArray(_ref9, 2), ev = _ref0[0], args = _ref0[1]; this.emit(ev, ...args); }); this.bufferedEvents = []; } emitWhenConnected(event) { for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { args[_key2 - 1] = arguments[_key2]; } if (this.state === ConnectionState.Reconnecting || this.isResuming || !this.engine || this.engine.pendingReconnect) { // in case the room is reconnecting, buffer the events by firing them later after emitting RoomEvent.Reconnected this.bufferedEvents.push([event, args]); } else if (this.state === ConnectionState.Connected) { return this.emit(event, ...args); } return false; } /** * Allows to populate a room with simulated participants. * No actual connection to a server will be established, all state is * @experimental */ simulateParticipants(options) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d; const publishOptions = Object.assign({ audio: true, video: true, useRealTracks: false }, options.publish); const participantOptions = Object.assign({ count: 9, audio: false, video: true, aspectRatios: [1.66, 1.7, 1.3] }, options.participants); this.handleDisconnect(); this.roomInfo = new Room$1({ sid: 'RM_SIMULATED', name: 'simulated-room', emptyTimeout: 0, maxParticipants: 0, creationTime: protoInt64.parse(new Date().getTime()), metadata: '', numParticipants: 1, numPublishers: 1, turnPassword: '', enabledCodecs: [], activeRecording: false }); this.localParticipant.updateInfo(new ParticipantInfo({ identity: 'simulated-local', name: 'local-name' })); this.setupLocalParticipantEvents(); this.emit(RoomEvent.SignalConnected); this.emit(RoomEvent.Connected); this.setAndEmitConnectionState(ConnectionState.Connected); if (publishOptions.video) { const camPub = new LocalTrackPublication(Track.Kind.Video, new TrackInfo({ source: TrackSource.CAMERA, sid: Math.floor(Math.random() * 10000).toString(), type: TrackType.AUDIO, name: 'video-dummy' }), new LocalVideoTrack(publishOptions.useRealTracks && ((_a = window.navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getUserMedia) ? (yield window.navigator.mediaDevices.getUserMedia({ video: true })).getVideoTracks()[0] : createDummyVideoStreamTrack(160 * ((_b = participantOptions.aspectRatios[0]) !== null && _b !== void 0 ? _b : 1), 160, true, true), undefined, false, { loggerName: this.options.loggerName, loggerContextCb: () => this.logContext }), { loggerName: this.options.loggerName, loggerContextCb: () => this.logContext }); // @ts-ignore this.localParticipant.addTrackPublication(camPub); this.localParticipant.emit(ParticipantEvent.LocalTrackPublished, camPub); } if (publishOptions.audio) { const audioPub = new LocalTrackPublication(Track.Kind.Audio, new TrackInfo({ source: TrackSource.MICROPHONE, sid: Math.floor(Math.random() * 10000).toString(), type: TrackType.AUDIO }), new LocalAudioTrack(publishOptions.useRealTracks && ((_c = navigator.mediaDevices) === null || _c === void 0 ? void 0 : _c.getUserMedia) ? (yield navigator.mediaDevices.getUserMedia({ audio: true })).getAudioTracks()[0] : getEmptyAudioStreamTrack(), undefined, false, this.audioContext, { loggerName: this.options.loggerName, loggerContextCb: () => this.logContext }), { loggerName: this.options.loggerName, loggerContextCb: () => this.logContext }); // @ts-ignore this.localParticipant.addTrackPublication(audioPub); this.localParticipant.emit(ParticipantEvent.LocalTrackPublished, audioPub); } for (let i = 0; i < participantOptions.count - 1; i += 1) { let info = new ParticipantInfo({ sid: Math.floor(Math.random() * 10000).toString(), identity: "simulated-".concat(i), state: ParticipantInfo_State.ACTIVE, tracks: [], joinedAt: protoInt64.parse(Date.now()) }); const p = this.getOrCreateParticipant(info.identity, info); if (participantOptions.video) { const dummyVideo = createDummyVideoStreamTrack(160 * ((_d = participantOptions.aspectRatios[i % participantOptions.aspectRatios.length]) !== null && _d !== void 0 ? _d : 1), 160, false, true); const videoTrack = new TrackInfo({ source: TrackSource.CAMERA, sid: Math.floor(Math.random() * 10000).toString(), type: TrackType.AUDIO }); p.addSubscribedMediaTrack(dummyVideo, videoTrack.sid, new MediaStream([dummyVideo]), new RTCRtpReceiver()); info.tracks = [...info.tracks, videoTrack]; } if (participantOptions.audio) { const dummyTrack = getEmptyAudioStreamTrack(); const audioTrack = new TrackInfo({ source: TrackSource.MICROPHONE, sid: Math.floor(Math.random() * 10000).toString(), type: TrackType.AUDIO }); p.addSubscribedMediaTrack(dummyTrack, audioTrack.sid, new MediaStream([dummyTrack]), new RTCRtpReceiver()); info.tracks = [...info.tracks, audioTrack]; } p.updateInfo(info); } }); } // /** @internal */ emit(event) { for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { args[_key3 - 1] = arguments[_key3]; } // active speaker updates are too spammy if (event !== RoomEvent.ActiveSpeakersChanged && event !== RoomEvent.TranscriptionReceived) { // only extract logContext from arguments in order to avoid logging the whole object tree const minimizedArgs = mapArgs(args).filter(arg => arg !== undefined); if (event === RoomEvent.TrackSubscribed || event === RoomEvent.TrackUnsubscribed) { this.log.trace("subscribe trace: ".concat(event), { event, args: minimizedArgs }); } this.log.debug("room event ".concat(event), { event, args: minimizedArgs }); } return super.emit(event, ...args); } } Room.cleanupRegistry = typeof FinalizationRegistry !== 'undefined' && typeof WeakRef !== 'undefined' && new FinalizationRegistry(cleanup => { cleanup(); }); function mapArgs(args) { return args.map(arg => { if (!arg) { return; } if (Array.isArray(arg)) { return mapArgs(arg); } if (typeof arg === 'object') { return 'logContext' in arg ? arg.logContext : undefined; } return arg; }); }// This file was generated from JSON Schema using quicktype, do not modify it directly. // The code generation lives at https://github.com/livekit/attribute-definitions // // To parse this data: // // import { Convert, AgentAttributes, TranscriptionAttributes } from "./file"; // // const agentAttributes = Convert.toAgentAttributes(json); // const transcriptionAttributes = Convert.toTranscriptionAttributes(json); // Converts JSON strings to/from your types class Convert { static toAgentAttributes(json) { return JSON.parse(json); } static agentAttributesToJson(value) { return JSON.stringify(value); } static toTranscriptionAttributes(json) { return JSON.parse(json); } static transcriptionAttributesToJson(value) { return JSON.stringify(value); } }var attributeTypings=/*#__PURE__*/Object.freeze({__proto__:null,Convert:Convert});var CheckStatus; (function (CheckStatus) { CheckStatus[CheckStatus["IDLE"] = 0] = "IDLE"; CheckStatus[CheckStatus["RUNNING"] = 1] = "RUNNING"; CheckStatus[CheckStatus["SKIPPED"] = 2] = "SKIPPED"; CheckStatus[CheckStatus["SUCCESS"] = 3] = "SUCCESS"; CheckStatus[CheckStatus["FAILED"] = 4] = "FAILED"; })(CheckStatus || (CheckStatus = {})); class Checker extends eventsExports.EventEmitter { constructor(url, token) { let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; super(); this.status = CheckStatus.IDLE; this.logs = []; this.options = {}; this.url = url; this.token = token; this.name = this.constructor.name; this.room = new Room(options.roomOptions); this.connectOptions = options.connectOptions; this.options = options; } run(onComplete) { return __awaiter(this, void 0, void 0, function* () { if (this.status !== CheckStatus.IDLE) { throw Error('check is running already'); } this.setStatus(CheckStatus.RUNNING); try { yield this.perform(); } catch (err) { if (err instanceof Error) { if (this.options.errorsAsWarnings) { this.appendWarning(err.message); } else { this.appendError(err.message); } } } yield this.disconnect(); // sleep for a bit to ensure disconnect yield new Promise(resolve => setTimeout(resolve, 500)); // @ts-ignore if (this.status !== CheckStatus.SKIPPED) { this.setStatus(this.isSuccess() ? CheckStatus.SUCCESS : CheckStatus.FAILED); } if (onComplete) { onComplete(); } return this.getInfo(); }); } isSuccess() { return !this.logs.some(l => l.level === 'error'); } connect(url) { return __awaiter(this, void 0, void 0, function* () { if (this.room.state === ConnectionState.Connected) { return this.room; } if (!url) { url = this.url; } yield this.room.connect(url, this.token, this.connectOptions); return this.room; }); } disconnect() { return __awaiter(this, void 0, void 0, function* () { if (this.room && this.room.state !== ConnectionState.Disconnected) { yield this.room.disconnect(); // wait for it to go through yield new Promise(resolve => setTimeout(resolve, 500)); } }); } skip() { this.setStatus(CheckStatus.SKIPPED); } switchProtocol(protocol) { return __awaiter(this, void 0, void 0, function* () { let hasReconnecting = false; let hasReconnected = false; this.room.on(RoomEvent.Reconnecting, () => { hasReconnecting = true; }); this.room.once(RoomEvent.Reconnected, () => { hasReconnected = true; }); this.room.simulateScenario("force-".concat(protocol)); yield new Promise(resolve => setTimeout(resolve, 1000)); if (!hasReconnecting) { // no need to wait for reconnection return; } // wait for 10 seconds for reconnection const timeout = Date.now() + 10000; while (Date.now() < timeout) { if (hasReconnected) { return; } yield sleep(100); } throw new Error("Could not reconnect using ".concat(protocol, " protocol after 10 seconds")); }); } appendMessage(message) { this.logs.push({ level: 'info', message }); this.emit('update', this.getInfo()); } appendWarning(message) { this.logs.push({ level: 'warning', message }); this.emit('update', this.getInfo()); } appendError(message) { this.logs.push({ level: 'error', message }); this.emit('update', this.getInfo()); } setStatus(status) { this.status = status; this.emit('update', this.getInfo()); } get engine() { var _a; return (_a = this.room) === null || _a === void 0 ? void 0 : _a.engine; } getInfo() { return { logs: this.logs, name: this.name, status: this.status, description: this.description }; } }/** * Checks for connections quality to closests Cloud regions and determining the best quality */ class CloudRegionCheck extends Checker { get description() { return 'Cloud regions'; } perform() { return __awaiter(this, void 0, void 0, function* () { const regionProvider = new RegionUrlProvider(this.url, this.token); if (!regionProvider.isCloud()) { this.skip(); return; } const regionStats = []; const seenUrls = new Set(); for (let i = 0; i < 3; i++) { const regionUrl = yield regionProvider.getNextBestRegionUrl(); if (!regionUrl) { break; } if (seenUrls.has(regionUrl)) { continue; } seenUrls.add(regionUrl); const stats = yield this.checkCloudRegion(regionUrl); this.appendMessage("".concat(stats.region, " RTT: ").concat(stats.rtt, "ms, duration: ").concat(stats.duration, "ms")); regionStats.push(stats); } regionStats.sort((a, b) => { return (a.duration - b.duration) * 0.5 + (a.rtt - b.rtt) * 0.5; }); const bestRegion = regionStats[0]; this.bestStats = bestRegion; this.appendMessage("best Cloud region: ".concat(bestRegion.region)); }); } getInfo() { const info = super.getInfo(); info.data = this.bestStats; return info; } checkCloudRegion(url) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; yield this.connect(url); if (this.options.protocol === 'tcp') { yield this.switchProtocol('tcp'); } const region = (_a = this.room.serverInfo) === null || _a === void 0 ? void 0 : _a.region; if (!region) { throw new Error('Region not found'); } const writer = yield this.room.localParticipant.streamText({ topic: 'test' }); const chunkSize = 1000; // each chunk is about 1000 bytes const totalSize = 1000000; // approximately 1MB of data const numChunks = totalSize / chunkSize; // will yield 1000 chunks const chunkData = 'A'.repeat(chunkSize); // create a string of 1000 'A' characters const startTime = Date.now(); for (let i = 0; i < numChunks; i++) { yield writer.write(chunkData); } yield writer.close(); const endTime = Date.now(); const stats = yield (_b = this.room.engine.pcManager) === null || _b === void 0 ? void 0 : _b.publisher.getStats(); const regionStats = { region: region, rtt: 10000, duration: endTime - startTime }; stats === null || stats === void 0 ? void 0 : stats.forEach(stat => { if (stat.type === 'candidate-pair' && stat.nominated) { regionStats.rtt = stat.currentRoundTripTime * 1000; } }); yield this.disconnect(); return regionStats; }); } }const TEST_DURATION = 10000; class ConnectionProtocolCheck extends Checker { get description() { return 'Connection via UDP vs TCP'; } perform() { return __awaiter(this, void 0, void 0, function* () { const udpStats = yield this.checkConnectionProtocol('udp'); const tcpStats = yield this.checkConnectionProtocol('tcp'); this.bestStats = udpStats; // udp should is the better protocol typically. however, we'd prefer TCP when either of these conditions are true: // 1. the bandwidth limitation is worse on UDP by 500ms // 2. the packet loss is higher on UDP by 1% if (udpStats.qualityLimitationDurations.bandwidth - tcpStats.qualityLimitationDurations.bandwidth > 0.5 || (udpStats.packetsLost - tcpStats.packetsLost) / udpStats.packetsSent > 0.01) { this.appendMessage('best connection quality via tcp'); this.bestStats = tcpStats; } else { this.appendMessage('best connection quality via udp'); } const stats = this.bestStats; this.appendMessage("upstream bitrate: ".concat((stats.bitrateTotal / stats.count / 1000 / 1000).toFixed(2), " mbps")); this.appendMessage("RTT: ".concat((stats.rttTotal / stats.count * 1000).toFixed(2), " ms")); this.appendMessage("jitter: ".concat((stats.jitterTotal / stats.count * 1000).toFixed(2), " ms")); if (stats.packetsLost > 0) { this.appendWarning("packets lost: ".concat((stats.packetsLost / stats.packetsSent * 100).toFixed(2), "%")); } if (stats.qualityLimitationDurations.bandwidth > 1) { this.appendWarning("bandwidth limited ".concat((stats.qualityLimitationDurations.bandwidth / (TEST_DURATION / 1000) * 100).toFixed(2), "%")); } if (stats.qualityLimitationDurations.cpu > 0) { this.appendWarning("cpu limited ".concat((stats.qualityLimitationDurations.cpu / (TEST_DURATION / 1000) * 100).toFixed(2), "%")); } }); } getInfo() { const info = super.getInfo(); info.data = this.bestStats; return info; } checkConnectionProtocol(protocol) { return __awaiter(this, void 0, void 0, function* () { yield this.connect(); if (protocol === 'tcp') { yield this.switchProtocol('tcp'); } else { yield this.switchProtocol('udp'); } // create a canvas with animated content const canvas = document.createElement('canvas'); canvas.width = 1280; canvas.height = 720; const ctx = canvas.getContext('2d'); if (!ctx) { throw new Error('Could not get canvas context'); } let hue = 0; const animate = () => { hue = (hue + 1) % 360; ctx.fillStyle = "hsl(".concat(hue, ", 100%, 50%)"); ctx.fillRect(0, 0, canvas.width, canvas.height); requestAnimationFrame(animate); }; animate(); // create video track from canvas const stream = canvas.captureStream(30); // 30fps const videoTrack = stream.getVideoTracks()[0]; // publish to room const pub = yield this.room.localParticipant.publishTrack(videoTrack, { simulcast: false, degradationPreference: 'maintain-resolution', videoEncoding: { maxBitrate: 2000000 } }); const track = pub.track; const protocolStats = { protocol, packetsLost: 0, packetsSent: 0, qualityLimitationDurations: {}, rttTotal: 0, jitterTotal: 0, bitrateTotal: 0, count: 0 }; // gather stats once a second const interval = setInterval(() => __awaiter(this, void 0, void 0, function* () { const stats = yield track.getRTCStatsReport(); stats === null || stats === void 0 ? void 0 : stats.forEach(stat => { if (stat.type === 'outbound-rtp') { protocolStats.packetsSent = stat.packetsSent; protocolStats.qualityLimitationDurations = stat.qualityLimitationDurations; protocolStats.bitrateTotal += stat.targetBitrate; protocolStats.count++; } else if (stat.type === 'remote-inbound-rtp') { protocolStats.packetsLost = stat.packetsLost; protocolStats.rttTotal += stat.roundTripTime; protocolStats.jitterTotal += stat.jitter; } }); }), 1000); // wait a bit to gather stats yield new Promise(resolve => setTimeout(resolve, TEST_DURATION)); clearInterval(interval); videoTrack.stop(); canvas.remove(); yield this.disconnect(); return protocolStats; }); } }class PublishAudioCheck extends Checker { get description() { return 'Can publish audio'; } perform() { return __awaiter(this, void 0, void 0, function* () { var _a; const room = yield this.connect(); const track = yield createLocalAudioTrack(); const trackIsSilent = yield detectSilence(track, 1000); if (trackIsSilent) { throw new Error('unable to detect audio from microphone'); } this.appendMessage('detected audio from microphone'); room.localParticipant.publishTrack(track); // wait for a few seconds to publish yield new Promise(resolve => setTimeout(resolve, 3000)); // verify RTC stats that it's publishing const stats = yield (_a = track.sender) === null || _a === void 0 ? void 0 : _a.getStats(); if (!stats) { throw new Error('Could not get RTCStats'); } let numPackets = 0; stats.forEach(stat => { if (stat.type === 'outbound-rtp' && (stat.kind === 'audio' || !stat.kind && stat.mediaType === 'audio')) { numPackets = stat.packetsSent; } }); if (numPackets === 0) { throw new Error('Could not determine packets are sent'); } this.appendMessage("published ".concat(numPackets, " audio packets")); }); } }class PublishVideoCheck extends Checker { get description() { return 'Can publish video'; } perform() { return __awaiter(this, void 0, void 0, function* () { var _a; const room = yield this.connect(); const track = yield createLocalVideoTrack(); // check if we have video from camera yield this.checkForVideo(track.mediaStreamTrack); room.localParticipant.publishTrack(track); // wait for a few seconds to publish yield new Promise(resolve => setTimeout(resolve, 5000)); // verify RTC stats that it's publishing const stats = yield (_a = track.sender) === null || _a === void 0 ? void 0 : _a.getStats(); if (!stats) { throw new Error('Could not get RTCStats'); } let numPackets = 0; stats.forEach(stat => { if (stat.type === 'outbound-rtp' && (stat.kind === 'video' || !stat.kind && stat.mediaType === 'video')) { numPackets += stat.packetsSent; } }); if (numPackets === 0) { throw new Error('Could not determine packets are sent'); } this.appendMessage("published ".concat(numPackets, " video packets")); }); } checkForVideo(track) { return __awaiter(this, void 0, void 0, function* () { const stream = new MediaStream(); stream.addTrack(track.clone()); // Create video element to check frames const video = document.createElement('video'); video.srcObject = stream; video.muted = true; video.autoplay = true; video.playsInline = true; // For iOS Safari video.setAttribute('playsinline', 'true'); document.body.appendChild(video); yield new Promise(resolve => { video.onplay = () => { setTimeout(() => { var _a, _b, _c, _d; const canvas = document.createElement('canvas'); const settings = track.getSettings(); const width = (_b = (_a = settings.width) !== null && _a !== void 0 ? _a : video.videoWidth) !== null && _b !== void 0 ? _b : 1280; const height = (_d = (_c = settings.height) !== null && _c !== void 0 ? _c : video.videoHeight) !== null && _d !== void 0 ? _d : 720; canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); // Draw video frame to canvas ctx.drawImage(video, 0, 0); // Get image data and check if all pixels are black const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; let isAllBlack = true; for (let i = 0; i < data.length; i += 4) { if (data[i] !== 0 || data[i + 1] !== 0 || data[i + 2] !== 0) { isAllBlack = false; break; } } if (isAllBlack) { this.appendError('camera appears to be producing only black frames'); } else { this.appendMessage('received video frames'); } resolve(); }, 1000); }; video.play(); }); stream.getTracks().forEach(t => t.stop()); video.remove(); }); } }class ReconnectCheck extends Checker { get description() { return 'Resuming connection after interruption'; } perform() { return __awaiter(this, void 0, void 0, function* () { var _a; const room = yield this.connect(); let reconnectingTriggered = false; let reconnected = false; let reconnectResolver; const reconnectTimeout = new Promise(resolve => { setTimeout(resolve, 5000); reconnectResolver = resolve; }); const handleReconnecting = () => { reconnectingTriggered = true; }; room.on(RoomEvent.SignalReconnecting, handleReconnecting).on(RoomEvent.Reconnecting, handleReconnecting).on(RoomEvent.Reconnected, () => { reconnected = true; reconnectResolver(true); }); (_a = room.engine.client.ws) === null || _a === void 0 ? void 0 : _a.close(); const onClose = room.engine.client.onClose; if (onClose) { onClose(''); } yield reconnectTimeout; if (!reconnectingTriggered) { throw new Error('Did not attempt to reconnect'); } else if (!reconnected || room.state !== ConnectionState.Connected) { this.appendWarning('reconnection is only possible in Redis-based configurations'); throw new Error('Not able to reconnect'); } }); } }class TURNCheck extends Checker { get description() { return 'Can connect via TURN'; } perform() { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; if (isCloud(new URL(this.url))) { this.appendMessage('Using region specific url'); this.url = (_a = yield new RegionUrlProvider(this.url, this.token).getNextBestRegionUrl()) !== null && _a !== void 0 ? _a : this.url; } const signalClient = new SignalClient(); const joinRes = yield signalClient.join(this.url, this.token, { autoSubscribe: true, maxRetries: 0, e2eeEnabled: false, websocketTimeout: 15000 }, undefined, true); let hasTLS = false; let hasTURN = false; let hasSTUN = false; for (let iceServer of joinRes.iceServers) { for (let url of iceServer.urls) { if (url.startsWith('turn:')) { hasTURN = true; hasSTUN = true; } else if (url.startsWith('turns:')) { hasTURN = true; hasSTUN = true; hasTLS = true; } if (url.startsWith('stun:')) { hasSTUN = true; } } } if (!hasSTUN) { this.appendWarning('No STUN servers configured on server side.'); } else if (hasTURN && !hasTLS) { this.appendWarning('TURN is configured server side, but TURN/TLS is unavailable.'); } yield signalClient.close(); if (((_c = (_b = this.connectOptions) === null || _b === void 0 ? void 0 : _b.rtcConfig) === null || _c === void 0 ? void 0 : _c.iceServers) || hasTURN) { yield this.room.connect(this.url, this.token, { rtcConfig: { iceTransportPolicy: 'relay' } }); } else { this.appendWarning('No TURN servers configured.'); this.skip(); yield new Promise(resolve => setTimeout(resolve, 0)); } }); } }class WebRTCCheck extends Checker { get description() { return 'Establishing WebRTC connection'; } perform() { return __awaiter(this, void 0, void 0, function* () { let hasTcp = false; let hasIpv4Udp = false; this.room.on(RoomEvent.SignalConnected, () => { var _a; const prevTrickle = this.room.engine.client.onTrickle; this.room.engine.client.onTrickle = (sd, target) => { if (sd.candidate) { const candidate = new RTCIceCandidate(sd); let str = "".concat(candidate.protocol, " ").concat(candidate.address, ":").concat(candidate.port, " ").concat(candidate.type); if (candidate.address) { if (isIPPrivate(candidate.address)) { str += ' (private)'; } else { if (candidate.protocol === 'tcp' && candidate.tcpType === 'passive') { hasTcp = true; str += ' (passive)'; } else if (candidate.protocol === 'udp') { hasIpv4Udp = true; } } } this.appendMessage(str); } if (prevTrickle) { prevTrickle(sd, target); } }; if ((_a = this.room.engine.pcManager) === null || _a === void 0 ? void 0 : _a.subscriber) { this.room.engine.pcManager.subscriber.onIceCandidateError = ev => { if (ev instanceof RTCPeerConnectionIceErrorEvent) { this.appendWarning("error with ICE candidate: ".concat(ev.errorCode, " ").concat(ev.errorText, " ").concat(ev.url)); } }; } }); try { yield this.connect(); livekitLogger.info('now the room is connected'); } catch (err) { this.appendWarning('ports need to be open on firewall in order to connect.'); throw err; } if (!hasTcp) { this.appendWarning('Server is not configured for ICE/TCP'); } if (!hasIpv4Udp) { this.appendWarning('No public IPv4 UDP candidates were found. Your server is likely not configured correctly'); } }); } } function isIPPrivate(address) { const parts = address.split('.'); if (parts.length === 4) { if (parts[0] === '10') { return true; } else if (parts[0] === '192' && parts[1] === '168') { return true; } else if (parts[0] === '172') { const second = parseInt(parts[1], 10); if (second >= 16 && second <= 31) { return true; } } } return false; }class WebSocketCheck extends Checker { get description() { return 'Connecting to signal connection via WebSocket'; } perform() { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; if (this.url.startsWith('ws:') || this.url.startsWith('http:')) { this.appendWarning('Server is insecure, clients may block connections to it'); } let signalClient = new SignalClient(); let joinRes; try { joinRes = yield signalClient.join(this.url, this.token, { autoSubscribe: true, maxRetries: 0, e2eeEnabled: false, websocketTimeout: 15000 }, undefined, true); } catch (e) { if (isCloud(new URL(this.url))) { this.appendMessage("Initial connection failed with error ".concat(e.message, ". Retrying with region fallback")); const regionProvider = new RegionUrlProvider(this.url, this.token); const regionUrl = yield regionProvider.getNextBestRegionUrl(); if (regionUrl) { joinRes = yield signalClient.join(regionUrl, this.token, { autoSubscribe: true, maxRetries: 0, e2eeEnabled: false, websocketTimeout: 15000 }, undefined, true); this.appendMessage("Fallback to region worked. To avoid initial connections failing, ensure you're calling room.prepareConnection() ahead of time"); } } } if (joinRes) { this.appendMessage("Connected to server, version ".concat(joinRes.serverVersion, ".")); if (((_a = joinRes.serverInfo) === null || _a === void 0 ? void 0 : _a.edition) === ServerInfo_Edition.Cloud && ((_b = joinRes.serverInfo) === null || _b === void 0 ? void 0 : _b.region)) { this.appendMessage("LiveKit Cloud: ".concat((_c = joinRes.serverInfo) === null || _c === void 0 ? void 0 : _c.region)); } } else { this.appendError("Websocket connection could not be established"); } yield signalClient.close(); }); } }class ConnectionCheck extends eventsExports.EventEmitter { constructor(url, token) { let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; super(); this.options = {}; this.checkResults = new Map(); this.url = url; this.token = token; this.options = options; } getNextCheckId() { const nextId = this.checkResults.size; this.checkResults.set(nextId, { logs: [], status: CheckStatus.IDLE, name: '', description: '' }); return nextId; } updateCheck(checkId, info) { this.checkResults.set(checkId, info); this.emit('checkUpdate', checkId, info); } isSuccess() { return Array.from(this.checkResults.values()).every(r => r.status !== CheckStatus.FAILED); } getResults() { return Array.from(this.checkResults.values()); } createAndRunCheck(check) { return __awaiter(this, void 0, void 0, function* () { const checkId = this.getNextCheckId(); const test = new check(this.url, this.token, this.options); const handleUpdate = info => { this.updateCheck(checkId, info); }; test.on('update', handleUpdate); const result = yield test.run(); test.off('update', handleUpdate); return result; }); } checkWebsocket() { return __awaiter(this, void 0, void 0, function* () { return this.createAndRunCheck(WebSocketCheck); }); } checkWebRTC() { return __awaiter(this, void 0, void 0, function* () { return this.createAndRunCheck(WebRTCCheck); }); } checkTURN() { return __awaiter(this, void 0, void 0, function* () { return this.createAndRunCheck(TURNCheck); }); } checkReconnect() { return __awaiter(this, void 0, void 0, function* () { return this.createAndRunCheck(ReconnectCheck); }); } checkPublishAudio() { return __awaiter(this, void 0, void 0, function* () { return this.createAndRunCheck(PublishAudioCheck); }); } checkPublishVideo() { return __awaiter(this, void 0, void 0, function* () { return this.createAndRunCheck(PublishVideoCheck); }); } checkConnectionProtocol() { return __awaiter(this, void 0, void 0, function* () { const info = yield this.createAndRunCheck(ConnectionProtocolCheck); if (info.data && 'protocol' in info.data) { const stats = info.data; this.options.protocol = stats.protocol; } return info; }); } checkCloudRegion() { return __awaiter(this, void 0, void 0, function* () { return this.createAndRunCheck(CloudRegionCheck); }); } }/** A Fixed TokenSource is a token source that takes no parameters and returns a completely * independently derived value on each fetch() call. * * The most common downstream implementer is {@link TokenSourceLiteral}. */ class TokenSourceFixed {} /** A Configurable TokenSource is a token source that takes a * {@link TokenSourceFetchOptions} object as input and returns a deterministic * {@link TokenSourceResponseObject} output based on the options specified. * * For example, if options.participantName is set, it should be expected that * all tokens that are generated will have participant name field set to the * provided value. * * A few common downstream implementers are {@link TokenSourceEndpoint} * and {@link TokenSourceCustom}. */ class TokenSourceConfigurable {}new TextEncoder(); const decoder = new TextDecoder();function decodeBase64(encoded) { if (Uint8Array.fromBase64) { return Uint8Array.fromBase64(encoded); } const binary = atob(encoded); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); } return bytes; }function decode(input) { if (Uint8Array.fromBase64) { return Uint8Array.fromBase64(typeof input === 'string' ? input : decoder.decode(input), { alphabet: 'base64url' }); } let encoded = input; if (encoded instanceof Uint8Array) { encoded = decoder.decode(encoded); } encoded = encoded.replace(/-/g, '+').replace(/_/g, '/'); try { return decodeBase64(encoded); } catch (_unused) { throw new TypeError('The input to be decoded is not correctly encoded.'); } }class JOSEError extends Error { constructor(message, options) { var _Error$captureStackTr; super(message, options); _defineProperty(this, "code", 'ERR_JOSE_GENERIC'); this.name = this.constructor.name; (_Error$captureStackTr = Error.captureStackTrace) === null || _Error$captureStackTr === void 0 || _Error$captureStackTr.call(Error, this, this.constructor); } } _defineProperty(JOSEError, "code", 'ERR_JOSE_GENERIC'); class JWTClaimValidationFailed extends JOSEError { constructor(message, payload) { let claim = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'unspecified'; let reason = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'unspecified'; super(message, { cause: { claim, reason, payload } }); _defineProperty(this, "code", 'ERR_JWT_CLAIM_VALIDATION_FAILED'); _defineProperty(this, "claim", void 0); _defineProperty(this, "reason", void 0); _defineProperty(this, "payload", void 0); this.claim = claim; this.reason = reason; this.payload = payload; } } _defineProperty(JWTClaimValidationFailed, "code", 'ERR_JWT_CLAIM_VALIDATION_FAILED'); class JWTExpired extends JOSEError { constructor(message, payload) { let claim = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'unspecified'; let reason = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'unspecified'; super(message, { cause: { claim, reason, payload } }); _defineProperty(this, "code", 'ERR_JWT_EXPIRED'); _defineProperty(this, "claim", void 0); _defineProperty(this, "reason", void 0); _defineProperty(this, "payload", void 0); this.claim = claim; this.reason = reason; this.payload = payload; } } _defineProperty(JWTExpired, "code", 'ERR_JWT_EXPIRED'); class JOSEAlgNotAllowed extends JOSEError { constructor() { super(...arguments); _defineProperty(this, "code", 'ERR_JOSE_ALG_NOT_ALLOWED'); } } _defineProperty(JOSEAlgNotAllowed, "code", 'ERR_JOSE_ALG_NOT_ALLOWED'); class JOSENotSupported extends JOSEError { constructor() { super(...arguments); _defineProperty(this, "code", 'ERR_JOSE_NOT_SUPPORTED'); } } _defineProperty(JOSENotSupported, "code", 'ERR_JOSE_NOT_SUPPORTED'); class JWEDecryptionFailed extends JOSEError { constructor() { let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'decryption operation failed'; let options = arguments.length > 1 ? arguments[1] : undefined; super(message, options); _defineProperty(this, "code", 'ERR_JWE_DECRYPTION_FAILED'); } } _defineProperty(JWEDecryptionFailed, "code", 'ERR_JWE_DECRYPTION_FAILED'); class JWEInvalid extends JOSEError { constructor() { super(...arguments); _defineProperty(this, "code", 'ERR_JWE_INVALID'); } } _defineProperty(JWEInvalid, "code", 'ERR_JWE_INVALID'); class JWSInvalid extends JOSEError { constructor() { super(...arguments); _defineProperty(this, "code", 'ERR_JWS_INVALID'); } } _defineProperty(JWSInvalid, "code", 'ERR_JWS_INVALID'); class JWTInvalid extends JOSEError { constructor() { super(...arguments); _defineProperty(this, "code", 'ERR_JWT_INVALID'); } } _defineProperty(JWTInvalid, "code", 'ERR_JWT_INVALID'); class JWKInvalid extends JOSEError { constructor() { super(...arguments); _defineProperty(this, "code", 'ERR_JWK_INVALID'); } } _defineProperty(JWKInvalid, "code", 'ERR_JWK_INVALID'); class JWKSInvalid extends JOSEError { constructor() { super(...arguments); _defineProperty(this, "code", 'ERR_JWKS_INVALID'); } } _defineProperty(JWKSInvalid, "code", 'ERR_JWKS_INVALID'); class JWKSNoMatchingKey extends JOSEError { constructor() { let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'no applicable key found in the JSON Web Key Set'; let options = arguments.length > 1 ? arguments[1] : undefined; super(message, options); _defineProperty(this, "code", 'ERR_JWKS_NO_MATCHING_KEY'); } } _defineProperty(JWKSNoMatchingKey, "code", 'ERR_JWKS_NO_MATCHING_KEY'); class JWKSMultipleMatchingKeys extends JOSEError { constructor() { let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'multiple matching keys found in the JSON Web Key Set'; let options = arguments.length > 1 ? arguments[1] : undefined; super(message, options); _defineProperty(this, Symbol.asyncIterator, void 0); _defineProperty(this, "code", 'ERR_JWKS_MULTIPLE_MATCHING_KEYS'); } } _defineProperty(JWKSMultipleMatchingKeys, "code", 'ERR_JWKS_MULTIPLE_MATCHING_KEYS'); class JWKSTimeout extends JOSEError { constructor() { let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'request timed out'; let options = arguments.length > 1 ? arguments[1] : undefined; super(message, options); _defineProperty(this, "code", 'ERR_JWKS_TIMEOUT'); } } _defineProperty(JWKSTimeout, "code", 'ERR_JWKS_TIMEOUT'); class JWSSignatureVerificationFailed extends JOSEError { constructor() { let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'signature verification failed'; let options = arguments.length > 1 ? arguments[1] : undefined; super(message, options); _defineProperty(this, "code", 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED'); } } _defineProperty(JWSSignatureVerificationFailed, "code", 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED');const isObjectLike = value => typeof value === 'object' && value !== null; function isObject(input) { if (!isObjectLike(input) || Object.prototype.toString.call(input) !== '[object Object]') { return false; } if (Object.getPrototypeOf(input) === null) { return true; } let proto = input; while (Object.getPrototypeOf(proto) !== null) { proto = Object.getPrototypeOf(proto); } return Object.getPrototypeOf(input) === proto; }function decodeJwt(jwt) { if (typeof jwt !== 'string') throw new JWTInvalid('JWTs must use Compact JWS serialization, JWT must be a string'); const _jwt$split = jwt.split('.'), payload = _jwt$split[1], length = _jwt$split.length; if (length === 5) throw new JWTInvalid('Only JWTs using Compact JWS serialization can be decoded'); if (length !== 3) throw new JWTInvalid('Invalid JWT'); if (!payload) throw new JWTInvalid('JWTs must contain a payload'); let decoded; try { decoded = decode(payload); } catch (_unused) { throw new JWTInvalid('Failed to base64url decode the payload'); } let result; try { result = JSON.parse(decoder.decode(decoded)); } catch (_unused2) { throw new JWTInvalid('Failed to parse the decoded payload as JSON'); } if (!isObject(result)) throw new JWTInvalid('Invalid JWT Claims Set'); return result; }const ONE_SECOND_IN_MILLISECONDS = 1000; const ONE_MINUTE_IN_MILLISECONDS = 60 * ONE_SECOND_IN_MILLISECONDS; function isResponseTokenValid(response) { const jwtPayload = decodeTokenPayload(response.participantToken); if (!(jwtPayload === null || jwtPayload === void 0 ? void 0 : jwtPayload.nbf) || !(jwtPayload === null || jwtPayload === void 0 ? void 0 : jwtPayload.exp)) { return true; } const now = new Date(); const nbfInMilliseconds = jwtPayload.nbf * ONE_SECOND_IN_MILLISECONDS; const nbfDate = new Date(nbfInMilliseconds); const expInMilliseconds = jwtPayload.exp * ONE_SECOND_IN_MILLISECONDS; const expDate = new Date(expInMilliseconds - ONE_MINUTE_IN_MILLISECONDS); return nbfDate <= now && expDate > now; } /** Given a LiveKit generated participant token, decodes and returns the associated {@link TokenPayload} data. */ function decodeTokenPayload(token) { const payload = decodeJwt(token); payload.roomConfig; const rest = __rest(payload, ["roomConfig"]); const mappedPayload = Object.assign(Object.assign({}, rest), { roomConfig: payload.roomConfig ? RoomConfiguration.fromJson(payload.roomConfig, { ignoreUnknownFields: true }) : undefined }); return mappedPayload; } /** Given two TokenSourceFetchOptions values, check to see if they are deep equal. */ function areTokenSourceFetchOptionsEqual(a, b) { const allKeysSet = new Set([...Object.keys(a), ...Object.keys(b)]); for (const key of allKeysSet) { switch (key) { case 'roomName': case 'participantName': case 'participantIdentity': case 'participantMetadata': case 'participantAttributes': case 'agentName': case 'agentMetadata': case 'deployment': if (a[key] !== b[key]) { return false; } break; default: // ref: https://stackoverflow.com/a/58009992 const exhaustiveCheckedKey = key; throw new Error("Options key ".concat(exhaustiveCheckedKey, " not being checked for equality!")); } } return true; }/** A TokenSourceCached is a TokenSource which caches the last {@link TokenSourceResponseObject} value and returns it * until a) it expires or b) the {@link TokenSourceFetchOptions} provided to .fetch(...) change. */ class TokenSourceCached extends TokenSourceConfigurable { constructor() { super(...arguments); this.cachedFetchOptions = null; this.cachedResponse = null; this.fetchMutex = new _(); } isSameAsCachedFetchOptions(options) { if (!this.cachedFetchOptions) { return false; } return areTokenSourceFetchOptionsEqual(options, this.cachedFetchOptions); } shouldReturnCachedValueFromFetch(fetchOptions) { if (!this.cachedResponse) { return false; } if (!isResponseTokenValid(this.cachedResponse)) { return false; } if (!this.isSameAsCachedFetchOptions(fetchOptions)) { return false; } return true; } getCachedResponseJwtPayload() { if (!this.cachedResponse) { return null; } return decodeTokenPayload(this.cachedResponse.participantToken); } fetch(options, force) { return __awaiter(this, void 0, void 0, function* () { const unlock = yield this.fetchMutex.lock(); try { if (force) { this.cachedResponse = null; } if (this.shouldReturnCachedValueFromFetch(options)) { return this.cachedResponse.toJson(); } this.cachedFetchOptions = options; const tokenResponse = yield this.update(options); this.cachedResponse = tokenResponse; return tokenResponse.toJson(); } finally { unlock(); } }); } } class TokenSourceLiteral extends TokenSourceFixed { constructor(literalOrFn) { super(); this.literalOrFn = literalOrFn; } fetch() { return __awaiter(this, void 0, void 0, function* () { if (typeof this.literalOrFn === 'function') { return this.literalOrFn(); } else { return this.literalOrFn; } }); } } class TokenSourceCustom extends TokenSourceCached { constructor(customFn) { super(); this.customFn = customFn; } update(options) { return __awaiter(this, void 0, void 0, function* () { const resultMaybePromise = this.customFn(options); let result; if (resultMaybePromise instanceof Promise) { result = yield resultMaybePromise; } else { result = resultMaybePromise; } return TokenSourceResponse.fromJson(result, { // NOTE: it could be possible that the response body could contain more fields than just // what's in TokenSourceResponse depending on the implementation ignoreUnknownFields: true }); }); } } class TokenSourceEndpoint extends TokenSourceCached { constructor(url) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; super(); this.url = url; this.endpointOptions = options; } createRequestFromOptions(options) { var _a, _b, _c, _d; const request = new TokenSourceRequest(); for (const key of Object.keys(options)) { switch (key) { case 'roomName': case 'participantName': case 'participantIdentity': case 'participantMetadata': request[key] = options[key]; break; case 'participantAttributes': request.participantAttributes = (_a = options.participantAttributes) !== null && _a !== void 0 ? _a : {}; break; case 'agentName': request.roomConfig = (_b = request.roomConfig) !== null && _b !== void 0 ? _b : new RoomConfiguration(); if (request.roomConfig.agents.length === 0) { request.roomConfig.agents.push(new RoomAgentDispatch()); } request.roomConfig.agents[0].agentName = options.agentName; break; case 'agentMetadata': request.roomConfig = (_c = request.roomConfig) !== null && _c !== void 0 ? _c : new RoomConfiguration(); if (request.roomConfig.agents.length === 0) { request.roomConfig.agents.push(new RoomAgentDispatch()); } request.roomConfig.agents[0].metadata = options.agentMetadata; break; case 'deployment': request.roomConfig = (_d = request.roomConfig) !== null && _d !== void 0 ? _d : new RoomConfiguration(); if (request.roomConfig.agents.length === 0) { request.roomConfig.agents.push(new RoomAgentDispatch()); } request.roomConfig.agents[0].deployment = options.deployment; break; default: // ref: https://stackoverflow.com/a/58009992 const exhaustiveCheckedKey = key; throw new Error("Options key ".concat(exhaustiveCheckedKey, " not being included in forming request!")); } } return request; } update(options) { return __awaiter(this, void 0, void 0, function* () { var _a; const request = this.createRequestFromOptions(options); const response = yield fetch(this.url, Object.assign(Object.assign({}, this.endpointOptions), { method: (_a = this.endpointOptions.method) !== null && _a !== void 0 ? _a : 'POST', headers: Object.assign({ 'Content-Type': 'application/json' }, this.endpointOptions.headers), body: request.toJsonString({ useProtoFieldName: true }) })); if (!response.ok) { throw new Error("Error generating token from endpoint ".concat(this.url, ": received ").concat(response.status, " / ").concat(yield response.text())); } const body = yield response.json(); return TokenSourceResponse.fromJson(body, { // NOTE: it could be possible that the response body could contain more fields than just // what's in TokenSourceResponse depending on the implementation (ie, SandboxTokenServer) ignoreUnknownFields: true }); }); } } class TokenSourceSandboxTokenServer extends TokenSourceEndpoint { constructor(sandboxId, options) { const _options$baseUrl = options.baseUrl, baseUrl = _options$baseUrl === void 0 ? 'https://cloud-api.livekit.io' : _options$baseUrl, rest = __rest(options, ["baseUrl"]); super("".concat(baseUrl, "/api/v2/sandbox/connection-details"), Object.assign(Object.assign({}, rest), { headers: { 'X-Sandbox-ID': sandboxId } })); } } const TokenSource = { /** TokenSource.literal contains a single, literal set of {@link TokenSourceResponseObject} * credentials, either provided directly or returned from a provided function. */ literal(literalOrFn) { return new TokenSourceLiteral(literalOrFn); }, /** * TokenSource.custom allows a user to define a manual function which generates new * {@link TokenSourceResponseObject} values on demand. * * Use this to get credentials from custom backends / etc. */ custom(customFn) { return new TokenSourceCustom(customFn); }, /** * TokenSource.endpoint creates a token source that fetches credentials from a given URL using * the standard endpoint format: * @see https://cloud.livekit.io/projects/p_/sandbox/templates/token-server */ endpoint(url) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return new TokenSourceEndpoint(url, options); }, /** * TokenSource.sandboxTokenServer queries a sandbox token server for credentials, * which supports quick prototyping / getting started types of use cases. * * This token provider is INSECURE and should NOT be used in production. * * For more info: * @see https://cloud.livekit.io/projects/p_/sandbox/templates/token-server */ sandboxTokenServer(sandboxId) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return new TokenSourceSandboxTokenServer(sandboxId, options); } };/** * Try to analyze the local track to determine the facing mode of a track. * * @remarks * There is no property supported by all browsers to detect whether a video track originated from a user- or environment-facing camera device. * For this reason, we use the `facingMode` property when available, but will fall back on a string-based analysis of the device label to determine the facing mode. * If both methods fail, the default facing mode will be used. * * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/facingMode | MDN docs on facingMode} * @experimental */ function facingModeFromLocalTrack(localTrack) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var _a; const track = isLocalTrack(localTrack) ? localTrack.mediaStreamTrack : localTrack; const trackSettings = track.getSettings(); let result = { facingMode: (_a = options.defaultFacingMode) !== null && _a !== void 0 ? _a : 'user', confidence: 'low' }; // 1. Try to get facingMode from track settings. if ('facingMode' in trackSettings) { const rawFacingMode = trackSettings.facingMode; livekitLogger.trace('rawFacingMode', { rawFacingMode }); if (rawFacingMode && typeof rawFacingMode === 'string' && isFacingModeValue(rawFacingMode)) { result = { facingMode: rawFacingMode, confidence: 'high' }; } } // 2. If we don't have a high confidence we try to get the facing mode from the device label. if (['low', 'medium'].includes(result.confidence)) { livekitLogger.trace("Try to get facing mode from device label: (".concat(track.label, ")")); const labelAnalysisResult = facingModeFromDeviceLabel(track.label); if (labelAnalysisResult !== undefined) { result = labelAnalysisResult; } } return result; } const knownDeviceLabels = new Map([['obs virtual camera', { facingMode: 'environment', confidence: 'medium' }]]); const knownDeviceLabelSections = new Map([['iphone', { facingMode: 'environment', confidence: 'medium' }], ['ipad', { facingMode: 'environment', confidence: 'medium' }]]); /** * Attempt to analyze the device label to determine the facing mode. * * @experimental */ function facingModeFromDeviceLabel(deviceLabel) { var _a; const label = deviceLabel.trim().toLowerCase(); // Empty string is a valid device label but we can't infer anything from it. if (label === '') { return undefined; } // Can we match against widely known device labels. if (knownDeviceLabels.has(label)) { return knownDeviceLabels.get(label); } // Can we match against sections of the device label. return (_a = Array.from(knownDeviceLabelSections.entries()).find(_ref => { let _ref2 = _slicedToArray(_ref, 1), section = _ref2[0]; return label.includes(section); })) === null || _a === void 0 ? void 0 : _a[1]; } function isFacingModeValue(item) { const allowedValues = ['user', 'environment', 'left', 'right']; return item === undefined || allowedValues.includes(item); }const SerializerSymbol = Symbol.for('lk.serializer'); function isSerializer(v) { return typeof v === 'object' && v !== null && 'symbol' in v && v.symbol === SerializerSymbol; } /** @internal */ function base(params) { return Object.assign(Object.assign({}, params), { symbol: SerializerSymbol }); } /** * JSON serializer — `JSON.parse` on the way in, `JSON.stringify` on the way out. * Defaults to `any` so individual handlers can annotate their own payload types. */ function json() { return base({ parse: rawString => JSON.parse(rawString), serialize: val => JSON.stringify(val) }); } /** Raw string serializer — passes payloads through as plain strings with no encoding. */ function raw() { return base({ parse: rawString => rawString, serialize: val => val }); } /** Custom serializer - allows custom defined parse and serialize functions */ function custom(params) { return base(params); } /** * Serializer helpers for message payload encoding. * * @example * ```ts * const a = serializers.raw(); // Serializer * const b = serializer.json<{ foo: string }, { bar: string }>(); // Serializer<{ foo: string }, { bar: string }> * ``` * * @beta */ const serializers = { json, raw, custom };export{AudioPresets,BackupCodecPolicy,BaseKeyProvider,CLIENT_PROTOCOL_DATA_STREAM_RPC,CLIENT_PROTOCOL_DEFAULT,CheckStatus,Checker,ConnectionCheck,ConnectionError,ConnectionErrorReason,ConnectionQuality,ConnectionState,CriticalTimers,CryptorError,CryptorErrorReason,CryptorEvent,DataPacket_Kind,DataStreamError,DataStreamErrorReason,DataTrackPacket,DefaultReconnectPolicy,DeviceUnsupportedError,DisconnectReason,EncryptionEvent,Encryption_Type,EngineEvent,ExternalE2EEKeyProvider,FrameMetadataManager,KeyHandlerEvent,KeyProviderEvent,LivekitError,LivekitReasonedError,LocalAudioTrack,LocalDataTrack,LocalParticipant,LocalTrack,LocalTrackPublication,LocalTrackRecorder,LocalVideoTrack,LogLevel,LoggerNames,MediaDeviceFailure,_ as Mutex,NegotiationError,PacketTrailerManager,Participant,ParticipantEvent,ParticipantInfo_Kind as ParticipantKind,PublishDataError,PublishTrackError,RemoteAudioTrack,RemoteDataTrack,RemoteParticipant,RemoteTrack,RemoteTrackPublication,RemoteVideoTrack,Room,RoomEvent,RpcError,ScreenSharePresets,SignalReconnectError,SignalRequestError,SimulatedError,SubscriptionError,TokenSource,TokenSourceConfigurable,TokenSourceFixed,Track,TrackEvent,TrackInvalidError,TrackPublication,TrackType,UnexpectedConnectionState,UnsupportedServer,VideoPreset,VideoPresets,VideoPresets43,VideoQuality,areTokenSourceFetchOptionsEqual,asEncryptablePacket,attachToElement,attributeTypings as attributes,audioCodecs,clientProtocol,compareVersions,createAudioAnalyser,createE2EEKey,createKeyMaterialFromBuffer,createKeyMaterialFromString,createLocalAudioTrack,createLocalScreenTracks,createLocalTracks,createLocalVideoTrack,decodeTokenPayload,deriveKeys,detachTrack,facingModeFromDeviceLabel,facingModeFromLocalTrack,getBrowser,getEmptyAudioStreamTrack,getEmptyVideoStreamTrack,getLogger,importKey,isAudioCodec,isAudioTrack,isBackupCodec,isBackupVideoCodec,isBrowserSupported,isE2EESupported,isInsertableStreamSupported,isLocalParticipant,isLocalTrack,isRemoteParticipant,isRemoteTrack,isScriptTransformSupported,isSerializer,isVideoCodec,isVideoFrame,isVideoTrack,needsRbspUnescaping,parseRbsp,protocolVersion,ratchet,serializers,setLogExtension,setLogLevel,supportsAV1,supportsAdaptiveStream,supportsAudioOutputSelection,supportsDynacast,supportsVP9,version,videoCodecs,writeRbsp};//# sourceMappingURL=livekit-client.esm.mjs.map