@spatialwalk/avatarkit-rtc 1.0.0-beta.5 → 1.0.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index12.js","sources":["../src/providers/livekit/utils.ts"],"sourcesContent":["/**\n * Insertable Streams utilities.\n *\n * Note: Audio tracks no longer embed metadata - animation data is now sent via a separate video track.\n * These utilities are kept for potential future use and backwards compatibility.\n *\n * @internal\n * @packageDocumentation\n */\n\n/**\n * Check if browser supports RTCRtpScriptTransform (the modern Insertable Streams API).\n * @returns true if RTCRtpScriptTransform is available\n * @internal\n */\nexport function supportsScriptTransform(): boolean {\n return typeof RTCRtpScriptTransform !== 'undefined';\n}\n\n/**\n * Check if browser supports createEncodedStreams (older Insertable Streams API).\n * @returns true if createEncodedStreams is available\n * @internal\n */\nexport function supportsEncodedStreams(): boolean {\n // This is deprecated but still works in some browsers\n // @ts-expect-error - experimental API\n return typeof RTCRtpSender?.prototype?.createEncodedStreams === 'function';\n}\n\n/**\n * Insertable Streams method type.\n * @internal\n */\nexport type InsertableStreamsMethod = 'scriptTransform' | 'encodedStreams' | null;\n\n/**\n * Get supported Insertable Streams method.\n * @returns The supported method or null if none available\n * @internal\n */\nexport function getInsertableStreamsMethod(): InsertableStreamsMethod {\n if (supportsScriptTransform()) return 'scriptTransform';\n if (supportsEncodedStreams()) return 'encodedStreams';\n return null;\n}\n"],"names":[],"mappings":"AAeO,SAAS,0BAAmC;AACjD,SAAO,OAAO,0BAA0B;AAC1C;AAOO,SAAS,yBAAkC;AAT3C;AAYL,SAAO,SAAO,kDAAc,cAAd,mBAAyB,0BAAyB;AAClE;AAaO,SAAS,6BAAsD;AACpE,MAAI,wBAAA,EAA2B,QAAO;AACtC,MAAI,uBAAA,EAA0B,QAAO;AACrC,SAAO;AACT;"}
1
+ {"version":3,"file":"index12.js","sources":["../src/providers/livekit/VP8Extractor.ts"],"sourcesContent":["/**\n * VP8 Data Extractor for LiveKit.\n *\n * Extracts animation data from VP8 video tracks using RTCRtpScriptTransform.\n *\n * @internal\n * @packageDocumentation\n */\n\nimport type { AnimationTrackCallbacks } from '../../core/types';\nimport type { AnimationTransformEvent } from './types';\nimport { createAnimationReceiverTransform } from './animation-transform';\nimport { logger } from '../../utils';\n\n// Import worker with inline - this bundles the worker code as a blob URL\nimport AnimationWorker from './animation-worker.ts?worker&inline';\n\n// Default transition frame counts (used when protocol doesn't specify)\nconst DEFAULT_TRANSITION_START_FRAMES = 8;\nconst DEFAULT_TRANSITION_END_FRAMES = 12;\n\n/**\n * VP8Extractor - Extracts animation data from VP8 video tracks.\n *\n * Uses RTCRtpScriptTransform and a Web Worker to parse VP8 frames\n * and extract embedded animation data.\n *\n * @internal\n */\nexport class VP8Extractor {\n /** @internal */\n private callbacks: AnimationTrackCallbacks | null = null;\n /** @internal */\n private receiver: RTCRtpReceiver | null = null;\n /** @internal */\n private transform: RTCRtpScriptTransform | null = null;\n\n /**\n * Initialize the extractor with a receiver and callbacks.\n * @param receiver - RTCRtpReceiver to extract data from\n * @param callbacks - Callbacks to receive extracted data\n * @internal\n */\n async initialize(\n receiver: RTCRtpReceiver,\n callbacks: AnimationTrackCallbacks\n ): Promise<void> {\n if (this.receiver || this.transform) {\n throw new Error('VP8Extractor already initialized');\n }\n\n this.receiver = receiver;\n this.callbacks = callbacks;\n\n // Check if transform is already set\n if (receiver.transform) {\n return;\n }\n\n // Create worker and transform\n const worker = new AnimationWorker();\n const onEvent = (evt: AnimationTransformEvent) => {\n this.handleTransformEvent(evt);\n };\n\n this.transform = createAnimationReceiverTransform(worker, onEvent);\n receiver.transform = this.transform;\n }\n\n /**\n * Handle events from the animation transform.\n * @internal\n */\n private handleTransformEvent(evt: AnimationTransformEvent): void {\n if (!this.callbacks) return;\n\n if (evt.type === 'metadata') {\n this.callbacks.onStreamStats({\n framesPerSec: evt.framesPerSec,\n totalFrames: evt.totalFrames ?? 0,\n framesSent: evt.framesSent ?? 0,\n framesLost: evt.framesLost ?? 0,\n framesRecovered: evt.framesRecovered ?? 0,\n framesDropped: evt.framesDropped ?? 0,\n framesOutOfOrder: evt.framesOutOfOrder ?? 0,\n framesDuplicate: evt.framesDuplicate ?? 0,\n lastRenderedSeq: evt.lastRenderedSeq ?? -1,\n });\n } else if (evt.type === 'transition') {\n this.callbacks.onTransition(evt.protobufData, DEFAULT_TRANSITION_START_FRAMES);\n } else if (evt.type === 'transitionEnd') {\n this.callbacks.onTransitionEnd(evt.protobufData, DEFAULT_TRANSITION_END_FRAMES);\n } else if (evt.type === 'animation') {\n if (!evt.isIdle) {\n this.callbacks.onAnimationData(evt.protobufData, {\n frameSeq: evt.frameSeq,\n isStart: evt.isStart,\n isEnd: evt.isEnd,\n isIdle: evt.isIdle,\n isRecovered: evt.isRecovered,\n });\n }\n } else if (evt.type === 'idleStart') {\n this.callbacks.onIdleStart();\n } else if (evt.type === 'error') {\n logger.error('VP8Extractor', 'Error:', evt.error);\n }\n }\n\n /**\n * Check if this extractor is connected to the given receiver.\n * @param receiver - RTCRtpReceiver to check\n * @returns true if connected to this receiver\n * @internal\n */\n isConnectedTo(receiver: RTCRtpReceiver): boolean {\n return this.receiver === receiver;\n }\n\n /**\n * Get the receiver this extractor is connected to.\n * @returns The connected RTCRtpReceiver or null\n * @internal\n */\n getReceiver(): RTCRtpReceiver | null {\n return this.receiver;\n }\n\n /**\n * Dispose the extractor and release resources.\n * @internal\n */\n dispose(): void {\n this.receiver = null;\n this.transform = null;\n this.callbacks = null;\n }\n}\n"],"names":["AnimationWorker"],"mappings":";;;;;;AAkBA,MAAM,kCAAkC;AACxC,MAAM,gCAAgC;AAU/B,MAAM,aAAa;AAAA,EAAnB;AAEG;AAAA,qCAA4C;AAE5C;AAAA,oCAAkC;AAElC;AAAA,qCAA0C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQlD,MAAM,WACJ,UACA,WACe;AACf,QAAI,KAAK,YAAY,KAAK,WAAW;AACnC,YAAM,IAAI,MAAM,kCAAkC;AAAA,IACpD;AAEA,SAAK,WAAW;AAChB,SAAK,YAAY;AAGjB,QAAI,SAAS,WAAW;AACtB;AAAA,IACF;AAGA,UAAM,SAAS,IAAIA,cAAA;AACnB,UAAM,UAAU,CAAC,QAAiC;AAChD,WAAK,qBAAqB,GAAG;AAAA,IAC/B;AAEA,SAAK,YAAY,iCAAiC,QAAQ,OAAO;AACjE,aAAS,YAAY,KAAK;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,qBAAqB,KAAoC;AAC/D,QAAI,CAAC,KAAK,UAAW;AAErB,QAAI,IAAI,SAAS,YAAY;AAC3B,WAAK,UAAU,cAAc;AAAA,QAC3B,cAAc,IAAI;AAAA,QAClB,aAAa,IAAI,eAAe;AAAA,QAChC,YAAY,IAAI,cAAc;AAAA,QAC9B,YAAY,IAAI,cAAc;AAAA,QAC9B,iBAAiB,IAAI,mBAAmB;AAAA,QACxC,eAAe,IAAI,iBAAiB;AAAA,QACpC,kBAAkB,IAAI,oBAAoB;AAAA,QAC1C,iBAAiB,IAAI,mBAAmB;AAAA,QACxC,iBAAiB,IAAI,mBAAmB;AAAA,MAAA,CACzC;AAAA,IACH,WAAW,IAAI,SAAS,cAAc;AACpC,WAAK,UAAU,aAAa,IAAI,cAAc,+BAA+B;AAAA,IAC/E,WAAW,IAAI,SAAS,iBAAiB;AACvC,WAAK,UAAU,gBAAgB,IAAI,cAAc,6BAA6B;AAAA,IAChF,WAAW,IAAI,SAAS,aAAa;AACnC,UAAI,CAAC,IAAI,QAAQ;AACf,aAAK,UAAU,gBAAgB,IAAI,cAAc;AAAA,UAC/C,UAAU,IAAI;AAAA,UACd,SAAS,IAAI;AAAA,UACb,OAAO,IAAI;AAAA,UACX,QAAQ,IAAI;AAAA,UACZ,aAAa,IAAI;AAAA,QAAA,CAClB;AAAA,MACH;AAAA,IACF,WAAW,IAAI,SAAS,aAAa;AACnC,WAAK,UAAU,YAAA;AAAA,IACjB,WAAW,IAAI,SAAS,SAAS;AAC/B,aAAO,MAAM,gBAAgB,UAAU,IAAI,KAAK;AAAA,IAClD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,cAAc,UAAmC;AAC/C,WAAO,KAAK,aAAa;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAqC;AACnC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAgB;AACd,SAAK,WAAW;AAChB,SAAK,YAAY;AACjB,SAAK,YAAY;AAAA,EACnB;AACF;"}
package/dist/index13.js CHANGED
@@ -1,178 +1,18 @@
1
- import { varint64read, varint32read } from "./index16.js";
2
- import { protoInt64 } from "./index17.js";
3
- import { getTextEncoding } from "./index18.js";
4
- var WireType;
5
- (function(WireType2) {
6
- WireType2[WireType2["Varint"] = 0] = "Varint";
7
- WireType2[WireType2["Bit64"] = 1] = "Bit64";
8
- WireType2[WireType2["LengthDelimited"] = 2] = "LengthDelimited";
9
- WireType2[WireType2["StartGroup"] = 3] = "StartGroup";
10
- WireType2[WireType2["EndGroup"] = 4] = "EndGroup";
11
- WireType2[WireType2["Bit32"] = 5] = "Bit32";
12
- })(WireType || (WireType = {}));
13
- class BinaryReader {
14
- constructor(buf, decodeUtf8 = getTextEncoding().decodeUtf8) {
15
- this.decodeUtf8 = decodeUtf8;
16
- this.varint64 = varint64read;
17
- this.uint32 = varint32read;
18
- this.buf = buf;
19
- this.len = buf.length;
20
- this.pos = 0;
21
- this.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
22
- }
23
- /**
24
- * Reads a tag - field number and wire type.
25
- */
26
- tag() {
27
- let tag = this.uint32(), fieldNo = tag >>> 3, wireType = tag & 7;
28
- if (fieldNo <= 0 || wireType < 0 || wireType > 5)
29
- throw new Error("illegal tag: field no " + fieldNo + " wire type " + wireType);
30
- return [fieldNo, wireType];
31
- }
32
- /**
33
- * Skip one element and return the skipped data.
34
- *
35
- * When skipping StartGroup, provide the tags field number to check for
36
- * matching field number in the EndGroup tag.
37
- */
38
- skip(wireType, fieldNo) {
39
- let start = this.pos;
40
- switch (wireType) {
41
- case WireType.Varint:
42
- while (this.buf[this.pos++] & 128) {
43
- }
44
- break;
45
- // @ts-ignore TS7029: Fallthrough case in switch -- ignore instead of expect-error for compiler settings without noFallthroughCasesInSwitch: true
46
- case WireType.Bit64:
47
- this.pos += 4;
48
- case WireType.Bit32:
49
- this.pos += 4;
50
- break;
51
- case WireType.LengthDelimited:
52
- let len = this.uint32();
53
- this.pos += len;
54
- break;
55
- case WireType.StartGroup:
56
- for (; ; ) {
57
- const [fn, wt] = this.tag();
58
- if (wt === WireType.EndGroup) {
59
- if (fieldNo !== void 0 && fn !== fieldNo) {
60
- throw new Error("invalid end group tag");
61
- }
62
- break;
63
- }
64
- this.skip(wt, fn);
65
- }
66
- break;
67
- default:
68
- throw new Error("cant skip wire type " + wireType);
69
- }
70
- this.assertBounds();
71
- return this.buf.subarray(start, this.pos);
72
- }
73
- /**
74
- * Throws error if position in byte array is out of range.
75
- */
76
- assertBounds() {
77
- if (this.pos > this.len)
78
- throw new RangeError("premature EOF");
79
- }
80
- /**
81
- * Read a `int32` field, a signed 32 bit varint.
82
- */
83
- int32() {
84
- return this.uint32() | 0;
85
- }
86
- /**
87
- * Read a `sint32` field, a signed, zigzag-encoded 32-bit varint.
88
- */
89
- sint32() {
90
- let zze = this.uint32();
91
- return zze >>> 1 ^ -(zze & 1);
92
- }
93
- /**
94
- * Read a `int64` field, a signed 64-bit varint.
95
- */
96
- int64() {
97
- return protoInt64.dec(...this.varint64());
98
- }
99
- /**
100
- * Read a `uint64` field, an unsigned 64-bit varint.
101
- */
102
- uint64() {
103
- return protoInt64.uDec(...this.varint64());
104
- }
105
- /**
106
- * Read a `sint64` field, a signed, zig-zag-encoded 64-bit varint.
107
- */
108
- sint64() {
109
- let [lo, hi] = this.varint64();
110
- let s = -(lo & 1);
111
- lo = (lo >>> 1 | (hi & 1) << 31) ^ s;
112
- hi = hi >>> 1 ^ s;
113
- return protoInt64.dec(lo, hi);
114
- }
115
- /**
116
- * Read a `bool` field, a variant.
117
- */
118
- bool() {
119
- let [lo, hi] = this.varint64();
120
- return lo !== 0 || hi !== 0;
121
- }
122
- /**
123
- * Read a `fixed32` field, an unsigned, fixed-length 32-bit integer.
124
- */
125
- fixed32() {
126
- return this.view.getUint32((this.pos += 4) - 4, true);
127
- }
128
- /**
129
- * Read a `sfixed32` field, a signed, fixed-length 32-bit integer.
130
- */
131
- sfixed32() {
132
- return this.view.getInt32((this.pos += 4) - 4, true);
133
- }
134
- /**
135
- * Read a `fixed64` field, an unsigned, fixed-length 64 bit integer.
136
- */
137
- fixed64() {
138
- return protoInt64.uDec(this.sfixed32(), this.sfixed32());
139
- }
140
- /**
141
- * Read a `fixed64` field, a signed, fixed-length 64-bit integer.
142
- */
143
- sfixed64() {
144
- return protoInt64.dec(this.sfixed32(), this.sfixed32());
145
- }
146
- /**
147
- * Read a `float` field, 32-bit floating point number.
148
- */
149
- float() {
150
- return this.view.getFloat32((this.pos += 4) - 4, true);
151
- }
152
- /**
153
- * Read a `double` field, a 64-bit floating point number.
154
- */
155
- double() {
156
- return this.view.getFloat64((this.pos += 8) - 8, true);
157
- }
158
- /**
159
- * Read a `bytes` field, length-delimited arbitrary data.
160
- */
161
- bytes() {
162
- let len = this.uint32(), start = this.pos;
163
- this.pos += len;
164
- this.assertBounds();
165
- return this.buf.subarray(start, start + len);
166
- }
167
- /**
168
- * Read a `string` field, length-delimited data converted to UTF-8 text.
169
- */
170
- string() {
171
- return this.decodeUtf8(this.bytes());
172
- }
1
+ function supportsScriptTransform() {
2
+ return typeof RTCRtpScriptTransform !== "undefined";
3
+ }
4
+ function supportsEncodedStreams() {
5
+ var _a;
6
+ return typeof ((_a = RTCRtpSender == null ? void 0 : RTCRtpSender.prototype) == null ? void 0 : _a.createEncodedStreams) === "function";
7
+ }
8
+ function getInsertableStreamsMethod() {
9
+ if (supportsScriptTransform()) return "scriptTransform";
10
+ if (supportsEncodedStreams()) return "encodedStreams";
11
+ return null;
173
12
  }
174
13
  export {
175
- BinaryReader,
176
- WireType
14
+ getInsertableStreamsMethod,
15
+ supportsEncodedStreams,
16
+ supportsScriptTransform
177
17
  };
178
18
  //# sourceMappingURL=index13.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index13.js","sources":["../node_modules/.pnpm/@bufbuild+protobuf@2.11.0/node_modules/@bufbuild/protobuf/dist/esm/wire/binary-encoding.js"],"sourcesContent":["// Copyright 2021-2026 Buf Technologies, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\nimport { varint32read, varint32write, varint64read, varint64write, } from \"./varint.js\";\nimport { protoInt64 } from \"../proto-int64.js\";\nimport { getTextEncoding } from \"./text-encoding.js\";\n/**\n * Protobuf binary format wire types.\n *\n * A wire type provides just enough information to find the length of the\n * following value.\n *\n * See https://developers.google.com/protocol-buffers/docs/encoding#structure\n */\nexport var WireType;\n(function (WireType) {\n /**\n * Used for int32, int64, uint32, uint64, sint32, sint64, bool, enum\n */\n WireType[WireType[\"Varint\"] = 0] = \"Varint\";\n /**\n * Used for fixed64, sfixed64, double.\n * Always 8 bytes with little-endian byte order.\n */\n WireType[WireType[\"Bit64\"] = 1] = \"Bit64\";\n /**\n * Used for string, bytes, embedded messages, packed repeated fields\n *\n * Only repeated numeric types (types which use the varint, 32-bit,\n * or 64-bit wire types) can be packed. In proto3, such fields are\n * packed by default.\n */\n WireType[WireType[\"LengthDelimited\"] = 2] = \"LengthDelimited\";\n /**\n * Start of a tag-delimited aggregate, such as a proto2 group, or a message\n * in editions with message_encoding = DELIMITED.\n */\n WireType[WireType[\"StartGroup\"] = 3] = \"StartGroup\";\n /**\n * End of a tag-delimited aggregate.\n */\n WireType[WireType[\"EndGroup\"] = 4] = \"EndGroup\";\n /**\n * Used for fixed32, sfixed32, float.\n * Always 4 bytes with little-endian byte order.\n */\n WireType[WireType[\"Bit32\"] = 5] = \"Bit32\";\n})(WireType || (WireType = {}));\n/**\n * Maximum value for a 32-bit floating point value (Protobuf FLOAT).\n */\nexport const FLOAT32_MAX = 3.4028234663852886e38;\n/**\n * Minimum value for a 32-bit floating point value (Protobuf FLOAT).\n */\nexport const FLOAT32_MIN = -3.4028234663852886e38;\n/**\n * Maximum value for an unsigned 32-bit integer (Protobuf UINT32, FIXED32).\n */\nexport const UINT32_MAX = 0xffffffff;\n/**\n * Maximum value for a signed 32-bit integer (Protobuf INT32, SFIXED32, SINT32).\n */\nexport const INT32_MAX = 0x7fffffff;\n/**\n * Minimum value for a signed 32-bit integer (Protobuf INT32, SFIXED32, SINT32).\n */\nexport const INT32_MIN = -0x80000000;\nexport class BinaryWriter {\n constructor(encodeUtf8 = getTextEncoding().encodeUtf8) {\n this.encodeUtf8 = encodeUtf8;\n /**\n * Previous fork states.\n */\n this.stack = [];\n this.chunks = [];\n this.buf = [];\n }\n /**\n * Return all bytes written and reset this writer.\n */\n finish() {\n if (this.buf.length) {\n this.chunks.push(new Uint8Array(this.buf)); // flush the buffer\n this.buf = [];\n }\n let len = 0;\n for (let i = 0; i < this.chunks.length; i++)\n len += this.chunks[i].length;\n let bytes = new Uint8Array(len);\n let offset = 0;\n for (let i = 0; i < this.chunks.length; i++) {\n bytes.set(this.chunks[i], offset);\n offset += this.chunks[i].length;\n }\n this.chunks = [];\n return bytes;\n }\n /**\n * Start a new fork for length-delimited data like a message\n * or a packed repeated field.\n *\n * Must be joined later with `join()`.\n */\n fork() {\n this.stack.push({ chunks: this.chunks, buf: this.buf });\n this.chunks = [];\n this.buf = [];\n return this;\n }\n /**\n * Join the last fork. Write its length and bytes, then\n * return to the previous state.\n */\n join() {\n // get chunk of fork\n let chunk = this.finish();\n // restore previous state\n let prev = this.stack.pop();\n if (!prev)\n throw new Error(\"invalid state, fork stack empty\");\n this.chunks = prev.chunks;\n this.buf = prev.buf;\n // write length of chunk as varint\n this.uint32(chunk.byteLength);\n return this.raw(chunk);\n }\n /**\n * Writes a tag (field number and wire type).\n *\n * Equivalent to `uint32( (fieldNo << 3 | type) >>> 0 )`.\n *\n * Generated code should compute the tag ahead of time and call `uint32()`.\n */\n tag(fieldNo, type) {\n return this.uint32(((fieldNo << 3) | type) >>> 0);\n }\n /**\n * Write a chunk of raw bytes.\n */\n raw(chunk) {\n if (this.buf.length) {\n this.chunks.push(new Uint8Array(this.buf));\n this.buf = [];\n }\n this.chunks.push(chunk);\n return this;\n }\n /**\n * Write a `uint32` value, an unsigned 32 bit varint.\n */\n uint32(value) {\n assertUInt32(value);\n // write value as varint 32, inlined for speed\n while (value > 0x7f) {\n this.buf.push((value & 0x7f) | 0x80);\n value = value >>> 7;\n }\n this.buf.push(value);\n return this;\n }\n /**\n * Write a `int32` value, a signed 32 bit varint.\n */\n int32(value) {\n assertInt32(value);\n varint32write(value, this.buf);\n return this;\n }\n /**\n * Write a `bool` value, a variant.\n */\n bool(value) {\n this.buf.push(value ? 1 : 0);\n return this;\n }\n /**\n * Write a `bytes` value, length-delimited arbitrary data.\n */\n bytes(value) {\n this.uint32(value.byteLength); // write length of chunk as varint\n return this.raw(value);\n }\n /**\n * Write a `string` value, length-delimited data converted to UTF-8 text.\n */\n string(value) {\n let chunk = this.encodeUtf8(value);\n this.uint32(chunk.byteLength); // write length of chunk as varint\n return this.raw(chunk);\n }\n /**\n * Write a `float` value, 32-bit floating point number.\n */\n float(value) {\n assertFloat32(value);\n let chunk = new Uint8Array(4);\n new DataView(chunk.buffer).setFloat32(0, value, true);\n return this.raw(chunk);\n }\n /**\n * Write a `double` value, a 64-bit floating point number.\n */\n double(value) {\n let chunk = new Uint8Array(8);\n new DataView(chunk.buffer).setFloat64(0, value, true);\n return this.raw(chunk);\n }\n /**\n * Write a `fixed32` value, an unsigned, fixed-length 32-bit integer.\n */\n fixed32(value) {\n assertUInt32(value);\n let chunk = new Uint8Array(4);\n new DataView(chunk.buffer).setUint32(0, value, true);\n return this.raw(chunk);\n }\n /**\n * Write a `sfixed32` value, a signed, fixed-length 32-bit integer.\n */\n sfixed32(value) {\n assertInt32(value);\n let chunk = new Uint8Array(4);\n new DataView(chunk.buffer).setInt32(0, value, true);\n return this.raw(chunk);\n }\n /**\n * Write a `sint32` value, a signed, zigzag-encoded 32-bit varint.\n */\n sint32(value) {\n assertInt32(value);\n // zigzag encode\n value = ((value << 1) ^ (value >> 31)) >>> 0;\n varint32write(value, this.buf);\n return this;\n }\n /**\n * Write a `fixed64` value, a signed, fixed-length 64-bit integer.\n */\n sfixed64(value) {\n let chunk = new Uint8Array(8), view = new DataView(chunk.buffer), tc = protoInt64.enc(value);\n view.setInt32(0, tc.lo, true);\n view.setInt32(4, tc.hi, true);\n return this.raw(chunk);\n }\n /**\n * Write a `fixed64` value, an unsigned, fixed-length 64 bit integer.\n */\n fixed64(value) {\n let chunk = new Uint8Array(8), view = new DataView(chunk.buffer), tc = protoInt64.uEnc(value);\n view.setInt32(0, tc.lo, true);\n view.setInt32(4, tc.hi, true);\n return this.raw(chunk);\n }\n /**\n * Write a `int64` value, a signed 64-bit varint.\n */\n int64(value) {\n let tc = protoInt64.enc(value);\n varint64write(tc.lo, tc.hi, this.buf);\n return this;\n }\n /**\n * Write a `sint64` value, a signed, zig-zag-encoded 64-bit varint.\n */\n sint64(value) {\n const tc = protoInt64.enc(value), \n // zigzag encode\n sign = tc.hi >> 31, lo = (tc.lo << 1) ^ sign, hi = ((tc.hi << 1) | (tc.lo >>> 31)) ^ sign;\n varint64write(lo, hi, this.buf);\n return this;\n }\n /**\n * Write a `uint64` value, an unsigned 64-bit varint.\n */\n uint64(value) {\n const tc = protoInt64.uEnc(value);\n varint64write(tc.lo, tc.hi, this.buf);\n return this;\n }\n}\nexport class BinaryReader {\n constructor(buf, decodeUtf8 = getTextEncoding().decodeUtf8) {\n this.decodeUtf8 = decodeUtf8;\n this.varint64 = varint64read; // dirty cast for `this`\n /**\n * Read a `uint32` field, an unsigned 32 bit varint.\n */\n this.uint32 = varint32read;\n this.buf = buf;\n this.len = buf.length;\n this.pos = 0;\n this.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);\n }\n /**\n * Reads a tag - field number and wire type.\n */\n tag() {\n let tag = this.uint32(), fieldNo = tag >>> 3, wireType = tag & 7;\n if (fieldNo <= 0 || wireType < 0 || wireType > 5)\n throw new Error(\"illegal tag: field no \" + fieldNo + \" wire type \" + wireType);\n return [fieldNo, wireType];\n }\n /**\n * Skip one element and return the skipped data.\n *\n * When skipping StartGroup, provide the tags field number to check for\n * matching field number in the EndGroup tag.\n */\n skip(wireType, fieldNo) {\n let start = this.pos;\n switch (wireType) {\n case WireType.Varint:\n while (this.buf[this.pos++] & 0x80) {\n // ignore\n }\n break;\n // @ts-ignore TS7029: Fallthrough case in switch -- ignore instead of expect-error for compiler settings without noFallthroughCasesInSwitch: true\n case WireType.Bit64:\n this.pos += 4;\n case WireType.Bit32:\n this.pos += 4;\n break;\n case WireType.LengthDelimited:\n let len = this.uint32();\n this.pos += len;\n break;\n case WireType.StartGroup:\n for (;;) {\n const [fn, wt] = this.tag();\n if (wt === WireType.EndGroup) {\n if (fieldNo !== undefined && fn !== fieldNo) {\n throw new Error(\"invalid end group tag\");\n }\n break;\n }\n this.skip(wt, fn);\n }\n break;\n default:\n throw new Error(\"cant skip wire type \" + wireType);\n }\n this.assertBounds();\n return this.buf.subarray(start, this.pos);\n }\n /**\n * Throws error if position in byte array is out of range.\n */\n assertBounds() {\n if (this.pos > this.len)\n throw new RangeError(\"premature EOF\");\n }\n /**\n * Read a `int32` field, a signed 32 bit varint.\n */\n int32() {\n return this.uint32() | 0;\n }\n /**\n * Read a `sint32` field, a signed, zigzag-encoded 32-bit varint.\n */\n sint32() {\n let zze = this.uint32();\n // decode zigzag\n return (zze >>> 1) ^ -(zze & 1);\n }\n /**\n * Read a `int64` field, a signed 64-bit varint.\n */\n int64() {\n return protoInt64.dec(...this.varint64());\n }\n /**\n * Read a `uint64` field, an unsigned 64-bit varint.\n */\n uint64() {\n return protoInt64.uDec(...this.varint64());\n }\n /**\n * Read a `sint64` field, a signed, zig-zag-encoded 64-bit varint.\n */\n sint64() {\n let [lo, hi] = this.varint64();\n // decode zig zag\n let s = -(lo & 1);\n lo = ((lo >>> 1) | ((hi & 1) << 31)) ^ s;\n hi = (hi >>> 1) ^ s;\n return protoInt64.dec(lo, hi);\n }\n /**\n * Read a `bool` field, a variant.\n */\n bool() {\n let [lo, hi] = this.varint64();\n return lo !== 0 || hi !== 0;\n }\n /**\n * Read a `fixed32` field, an unsigned, fixed-length 32-bit integer.\n */\n fixed32() {\n // biome-ignore lint/suspicious/noAssignInExpressions: no\n return this.view.getUint32((this.pos += 4) - 4, true);\n }\n /**\n * Read a `sfixed32` field, a signed, fixed-length 32-bit integer.\n */\n sfixed32() {\n // biome-ignore lint/suspicious/noAssignInExpressions: no\n return this.view.getInt32((this.pos += 4) - 4, true);\n }\n /**\n * Read a `fixed64` field, an unsigned, fixed-length 64 bit integer.\n */\n fixed64() {\n return protoInt64.uDec(this.sfixed32(), this.sfixed32());\n }\n /**\n * Read a `fixed64` field, a signed, fixed-length 64-bit integer.\n */\n sfixed64() {\n return protoInt64.dec(this.sfixed32(), this.sfixed32());\n }\n /**\n * Read a `float` field, 32-bit floating point number.\n */\n float() {\n // biome-ignore lint/suspicious/noAssignInExpressions: no\n return this.view.getFloat32((this.pos += 4) - 4, true);\n }\n /**\n * Read a `double` field, a 64-bit floating point number.\n */\n double() {\n // biome-ignore lint/suspicious/noAssignInExpressions: no\n return this.view.getFloat64((this.pos += 8) - 8, true);\n }\n /**\n * Read a `bytes` field, length-delimited arbitrary data.\n */\n bytes() {\n let len = this.uint32(), start = this.pos;\n this.pos += len;\n this.assertBounds();\n return this.buf.subarray(start, start + len);\n }\n /**\n * Read a `string` field, length-delimited data converted to UTF-8 text.\n */\n string() {\n return this.decodeUtf8(this.bytes());\n }\n}\n/**\n * Assert a valid signed protobuf 32-bit integer as a number or string.\n */\nfunction assertInt32(arg) {\n if (typeof arg == \"string\") {\n arg = Number(arg);\n }\n else if (typeof arg != \"number\") {\n throw new Error(\"invalid int32: \" + typeof arg);\n }\n if (!Number.isInteger(arg) ||\n arg > INT32_MAX ||\n arg < INT32_MIN)\n throw new Error(\"invalid int32: \" + arg);\n}\n/**\n * Assert a valid unsigned protobuf 32-bit integer as a number or string.\n */\nfunction assertUInt32(arg) {\n if (typeof arg == \"string\") {\n arg = Number(arg);\n }\n else if (typeof arg != \"number\") {\n throw new Error(\"invalid uint32: \" + typeof arg);\n }\n if (!Number.isInteger(arg) ||\n arg > UINT32_MAX ||\n arg < 0)\n throw new Error(\"invalid uint32: \" + arg);\n}\n/**\n * Assert a valid protobuf float value as a number or string.\n */\nfunction assertFloat32(arg) {\n if (typeof arg == \"string\") {\n const o = arg;\n arg = Number(arg);\n if (Number.isNaN(arg) && o !== \"NaN\") {\n throw new Error(\"invalid float32: \" + o);\n }\n }\n else if (typeof arg != \"number\") {\n throw new Error(\"invalid float32: \" + typeof arg);\n }\n if (Number.isFinite(arg) &&\n (arg > FLOAT32_MAX || arg < FLOAT32_MIN))\n throw new Error(\"invalid float32: \" + arg);\n}\n"],"names":["WireType"],"mappings":"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","x_google_ignoreList":[0]}
1
+ {"version":3,"file":"index13.js","sources":["../src/providers/livekit/utils.ts"],"sourcesContent":["/**\n * Insertable Streams utilities.\n *\n * Note: Audio tracks no longer embed metadata - animation data is now sent via a separate video track.\n * These utilities are kept for potential future use and backwards compatibility.\n *\n * @internal\n * @packageDocumentation\n */\n\n/**\n * Check if browser supports RTCRtpScriptTransform (the modern Insertable Streams API).\n * @returns true if RTCRtpScriptTransform is available\n * @internal\n */\nexport function supportsScriptTransform(): boolean {\n return typeof RTCRtpScriptTransform !== 'undefined';\n}\n\n/**\n * Check if browser supports createEncodedStreams (older Insertable Streams API).\n * @returns true if createEncodedStreams is available\n * @internal\n */\nexport function supportsEncodedStreams(): boolean {\n // This is deprecated but still works in some browsers\n // @ts-expect-error - experimental API\n return typeof RTCRtpSender?.prototype?.createEncodedStreams === 'function';\n}\n\n/**\n * Insertable Streams method type.\n * @internal\n */\nexport type InsertableStreamsMethod = 'scriptTransform' | 'encodedStreams' | null;\n\n/**\n * Get supported Insertable Streams method.\n * @returns The supported method or null if none available\n * @internal\n */\nexport function getInsertableStreamsMethod(): InsertableStreamsMethod {\n if (supportsScriptTransform()) return 'scriptTransform';\n if (supportsEncodedStreams()) return 'encodedStreams';\n return null;\n}\n"],"names":[],"mappings":"AAeO,SAAS,0BAAmC;AACjD,SAAO,OAAO,0BAA0B;AAC1C;AAOO,SAAS,yBAAkC;AAT3C;AAYL,SAAO,SAAO,kDAAc,cAAd,mBAAyB,0BAAyB;AAClE;AAaO,SAAS,6BAAsD;AACpE,MAAI,wBAAA,EAA2B,QAAO;AACtC,MAAI,uBAAA,EAA0B,QAAO;AACrC,SAAO;AACT;"}
package/dist/index2.js CHANGED
@@ -317,7 +317,6 @@ class AvatarPlayer {
317
317
  onAnimationData: (protobufData, metadata) => {
318
318
  if (metadata.isStart) {
319
319
  this.animationHandler.resetTracking();
320
- this.provider.playRemoteAudio();
321
320
  }
322
321
  this.animationHandler.handleAnimationData(
323
322
  protobufData,
@@ -326,7 +325,6 @@ class AvatarPlayer {
326
325
  );
327
326
  },
328
327
  onTransition: (protobufData, transitionFrameCount) => {
329
- this.provider.pauseRemoteAudio();
330
328
  this.animationHandler.handleTransitionData(protobufData, transitionFrameCount);
331
329
  },
332
330
  onTransitionEnd: (protobufData, transitionFrameCount) => {
@@ -1 +1 @@
1
- {"version":3,"file":"index2.js","sources":["../src/core/AvatarPlayer.ts"],"sourcesContent":["/**\n * AvatarPlayer - Unified entry point for RTC avatar streaming.\n *\n * This class provides a unified API that shields applications from\n * differences between different RTC providers (LiveKit, Agora, etc.)\n * and integrates seamlessly with @spatialwalk/avatarkit for rendering.\n *\n * @packageDocumentation\n */\n\nimport type { RTCProvider } from './RTCProvider';\nimport type { RTCConnectionConfig } from '../types';\nimport { AnimationHandler } from './AnimationHandler';\nimport type { AnimationTrackCallbacks, RTCProviderEvents } from './types';\nimport { configureLogger, logger, type LogLevel } from '../utils';\n\n// Import AvatarView type from avatarkit\nimport type { AvatarView, KeyframeData } from '@spatialwalk/avatarkit';\n\n/**\n * Options for configuring AvatarPlayer.\n */\nexport interface AvatarPlayerOptions {\n /**\n * Log level for SDK output.\n * - 'info': Show all logs (debug mode)\n * - 'warning': Show warnings and errors (default)\n * - 'error': Show only errors\n * - 'none': Disable all logs\n */\n logLevel?: LogLevel;\n\n /**\n * Enable jitter buffer for smoother animation playback.\n * When enabled, frames are buffered and rendered in sequence order at a steady 25fps,\n * absorbing network jitter and out-of-order delivery.\n * Default: true\n */\n enableJitterBuffer?: boolean;\n\n /**\n * Maximum delay (in ms) a frame can sit in the jitter buffer before being rendered.\n * Only used when enableJitterBuffer is true.\n * Default: 80 (2 frames at 25fps)\n */\n maxBufferDelayMs?: number;\n}\n\n/**\n * Unified Avatar Player.\n *\n * Main entry point for applications to interact with RTC avatar streaming.\n * Handles connection management, audio publishing, animation rendering,\n * and coordinates with avatarkit for display.\n *\n * @example\n * ```typescript\n * import { AvatarPlayer, LiveKitProvider } from '@spatialwalk/avatarkit-rtc';\n * import { AvatarView, Avatar } from '@spatialwalk/avatarkit';\n *\n * // Create avatar and view (from avatarkit)\n * const avatar = await Avatar.create(characterId);\n * const avatarView = new AvatarView(avatar, container);\n *\n * // Create player\n * const provider = new LiveKitProvider();\n * const player = new AvatarPlayer(provider, avatarView);\n *\n * // Connect\n * await player.connect({ url: 'wss://...', token: '...' });\n *\n * // Option 1: Use microphone (most common)\n * await player.startPublishing();\n * await player.stopPublishing();\n *\n * // Option 2: Use custom audio source\n * const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n * await player.publishAudio(stream.getAudioTracks()[0]);\n * await player.unpublishAudio();\n * stream.getTracks().forEach(t => t.stop());\n *\n * // Disconnect\n * await player.disconnect();\n * ```\n */\nexport class AvatarPlayer {\n /** @internal */\n private provider: RTCProvider;\n\n /** @internal */\n private avatarView: AvatarView;\n\n /** @internal */\n private animationHandler: AnimationHandler;\n\n /** @internal */\n private _isConnected = false;\n\n /** @internal */\n private localAudioTrack: MediaStreamTrack | null = null;\n\n /** @internal */\n private microphoneStream: MediaStream | null = null;\n\n /** @internal */\n private eventHandlers: Map<string, Set<Function>> = new Map();\n\n /** @internal */\n private lastConnectionConfig: RTCConnectionConfig | null = null;\n\n /** @internal */\n private isReconnecting = false;\n\n /**\n * Create a new AvatarPlayer instance.\n * @param provider - RTC provider implementation (e.g., LiveKitProvider)\n * @param avatarView - AvatarView instance from @spatialwalk/avatarkit\n * @param options - Optional configuration for transitions\n */\n constructor(\n provider: RTCProvider,\n avatarView: AvatarView,\n options?: AvatarPlayerOptions\n ) {\n this.provider = provider;\n this.avatarView = avatarView;\n\n // Configure logging if specified\n if (options?.logLevel !== undefined) {\n configureLogger({ level: options.logLevel });\n }\n\n // Create internal renderer adapter\n const renderer = this.createRendererAdapter();\n\n this.animationHandler = new AnimationHandler(renderer, {\n enableJitterBuffer: options?.enableJitterBuffer,\n maxBufferDelayMs: options?.maxBufferDelayMs,\n onStreamStalled: () => {\n // AnimationHandler already switches to idle state\n // Just notify external listeners, let them decide what to do (e.g., call reconnect())\n this.emit('stalled');\n },\n });\n\n // Setup provider event handlers\n this.setupProviderEvents();\n }\n\n /**\n * Check if currently connected to RTC server.\n */\n get isConnected(): boolean {\n return this._isConnected;\n }\n\n /**\n * Connect to RTC server.\n * @param config - Connection configuration (URL, token, room name, etc.)\n * @throws Error if already connected or connection fails\n */\n async connect(config: RTCConnectionConfig): Promise<void> {\n if (this._isConnected) {\n throw new Error('Already connected. Please disconnect first.');\n }\n\n // Save config for potential reconnection\n this.lastConnectionConfig = config;\n\n // Setup animation callbacks before connecting\n await this.setupAnimationCallbacks();\n await this.provider.connect(config);\n this._isConnected = true;\n }\n\n /**\n * Disconnect from RTC server.\n * Stops all tracks and cleans up resources.\n */\n async disconnect(): Promise<void> {\n if (!this._isConnected) {\n return;\n }\n\n // Stop microphone if active\n if (this.microphoneStream) {\n await this.stopPublishing();\n } else if (this.localAudioTrack) {\n // Stop custom audio track\n await this.unpublishAudio();\n }\n\n await this.provider.disconnect();\n this.animationHandler.dispose();\n this._isConnected = false;\n }\n\n /**\n * Publish an audio track to the RTC server.\n * \n * The audio source is controlled externally - you can pass any audio track:\n * - Microphone: `getUserMedia({ audio: true })`\n * - Browser audio: `audioElement.captureStream()`\n * - Web Audio API: `audioContext.createMediaStreamDestination().stream`\n * - Screen share audio: `getDisplayMedia({ audio: true })`\n * \n * @param track - MediaStreamTrack to publish (must be an audio track)\n * @throws Error if not connected or track is invalid\n * \n * @example\n * ```typescript\n * // Microphone\n * const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n * await player.publishAudio(stream.getAudioTracks()[0]);\n * \n * // Browser audio element\n * const audioEl = document.querySelector('audio');\n * const stream = audioEl.captureStream();\n * await player.publishAudio(stream.getAudioTracks()[0]);\n * ```\n */\n async publishAudio(track: MediaStreamTrack): Promise<void> {\n if (!this._isConnected) {\n throw new Error('Not connected. Please call connect() first.');\n }\n\n if (track.kind !== 'audio') {\n throw new Error('Invalid track: expected audio track');\n }\n\n // Unpublish existing track if any\n if (this.localAudioTrack) {\n await this.unpublishAudio();\n }\n\n this.localAudioTrack = track;\n await this.provider.publishAudioTrack(track);\n }\n\n /**\n * Stop publishing audio.\n * Note: This does NOT stop the track - the caller is responsible for managing the track lifecycle.\n */\n async unpublishAudio(): Promise<void> {\n if (!this.localAudioTrack) {\n return;\n }\n\n await this.provider.unpublishAudioTrack();\n this.localAudioTrack = null;\n }\n\n /**\n * Start publishing microphone audio.\n * \n * This requests microphone permission and publishes it to the RTC session.\n * For custom audio sources (e.g., audio files), use `publishAudio(track)` instead.\n * \n * @throws Error if not connected, permission denied, or no microphone found\n * \n * @example\n * ```typescript\n * await player.startPublishing();\n * // ... later\n * await player.stopPublishing();\n * ```\n */\n async startPublishing(): Promise<void> {\n if (!this._isConnected) {\n throw new Error('Not connected. Please call connect() first.');\n }\n\n if (this.microphoneStream) {\n return; // Already using microphone\n }\n\n // Request microphone permission\n this.microphoneStream = await navigator.mediaDevices.getUserMedia({ audio: true });\n const track = this.microphoneStream.getAudioTracks()[0];\n \n await this.publishAudio(track);\n }\n\n /**\n * Stop publishing microphone audio and release the microphone.\n * \n * This stops the microphone track and releases the device.\n * If you used `publishAudio()` with a custom track, use `unpublishAudio()` instead.\n */\n async stopPublishing(): Promise<void> {\n if (!this.microphoneStream) {\n return;\n }\n\n await this.unpublishAudio();\n\n // Stop and release microphone\n this.microphoneStream.getTracks().forEach((track) => track.stop());\n this.microphoneStream = null;\n }\n\n /**\n * Get current connection state.\n * @returns Connection state string (e.g., 'connected', 'disconnected')\n */\n getConnectionState(): string {\n return this.provider.getConnectionState();\n }\n\n /**\n * Get the native RTC client object.\n * \n * Returns the underlying RTC client for advanced use cases:\n * - LiveKit: Returns the Room instance\n * - Agora: Returns the IAgoraRTCClient instance\n * \n * @returns The native RTC client object, or null if not connected\n * \n * @example\n * ```typescript\n * // Access LiveKit Room\n * const room = player.getNativeClient() as Room;\n * console.log('Participants:', room?.remoteParticipants.size);\n * \n * // Access Agora Client\n * const client = player.getNativeClient() as IAgoraRTCClient;\n * console.log('State:', client?.connectionState);\n * ```\n */\n getNativeClient(): unknown {\n return this.provider.getNativeClient();\n }\n\n /**\n * Add event listener.\n * @param event - Event name ('connected', 'disconnected', 'error', 'stalled', etc.)\n * @param handler - Event handler function\n */\n on(event: string, handler: Function): void {\n if (event === 'stalled') {\n // Handle stalled event locally\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler);\n } else {\n // Forward other events to provider\n this.provider.on(event as any, handler as any);\n }\n }\n\n /**\n * Remove event listener.\n * @param event - Event name\n * @param handler - Event handler function (must be same reference as added)\n */\n off(event: string, handler: Function): void {\n if (event === 'stalled') {\n this.eventHandlers.get(event)?.delete(handler);\n } else {\n this.provider.off(event as any, handler as any);\n }\n }\n\n /**\n * Emit an event to local listeners.\n * @internal\n */\n private emit(event: string, ...args: unknown[]): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(...args);\n } catch (e) {\n logger.error('AvatarPlayer', `Error in ${event} handler:`, e);\n }\n });\n }\n }\n\n /**\n * Reconnect to RTC server using the last connection configuration.\n * Useful for recovering from connection issues or stream stalls.\n *\n * @example\n * ```typescript\n * player.on('stalled', async () => {\n * console.log('Stream stalled, attempting reconnection...');\n * await player.reconnect();\n * });\n * ```\n *\n * @throws Error if no previous connection config exists or reconnection fails\n */\n async reconnect(): Promise<void> {\n if (this.isReconnecting) {\n logger.warn('AvatarPlayer', 'Already attempting reconnection, skipping');\n return;\n }\n\n if (!this.lastConnectionConfig) {\n throw new Error('Cannot reconnect: no previous connection. Call connect() first.');\n }\n\n this.isReconnecting = true;\n logger.info('AvatarPlayer', 'Attempting reconnection...');\n\n try {\n // Disconnect first if connected\n if (this._isConnected) {\n await this.disconnect();\n }\n\n // Wait a short moment before reconnecting\n await new Promise((resolve) => setTimeout(resolve, 500));\n\n // Reconnect\n await this.connect(this.lastConnectionConfig);\n logger.info('AvatarPlayer', 'Reconnection successful');\n } catch (error) {\n logger.error('AvatarPlayer', 'Reconnection failed:', error);\n throw error;\n } finally {\n this.isReconnecting = false;\n }\n }\n\n /**\n * Create internal renderer adapter that bridges AnimationHandler to AvatarView.\n * @internal\n */\n private createRendererAdapter() {\n const avatarView = this.avatarView;\n\n return {\n renderFrame(flame: KeyframeData | undefined, startIdle?: boolean): void {\n if (startIdle) {\n // Enable idle rendering mode\n avatarView.renderFlame(undefined as unknown as KeyframeData, true);\n } else if (flame) {\n // Render the animation frame\n avatarView.renderFlame(flame);\n }\n },\n\n async generateTransitionFromIdle(\n targetFrame: KeyframeData,\n frameCount: number\n ): Promise<KeyframeData[]> {\n // Use avatarkit's generateTransition with from=undefined (current idle frame)\n return avatarView.generateTransition({\n to: targetFrame,\n frameCount,\n useLinear: true, // Linear interpolation for idle->speaking\n });\n },\n\n isReady(): boolean {\n return true; // AvatarView is always ready once created\n },\n };\n }\n\n /**\n * Setup animation track callbacks.\n * @internal\n */\n private async setupAnimationCallbacks(): Promise<void> {\n const callbacks: AnimationTrackCallbacks = {\n onAnimationData: (protobufData, metadata) => {\n // Reset tracking on session start\n if (metadata.isStart) {\n this.animationHandler.resetTracking();\n // Start audio playback on session start (fallback if transition didn't trigger it)\n this.provider.playRemoteAudio();\n }\n\n this.animationHandler.handleAnimationData(\n protobufData,\n metadata.frameSeq,\n metadata.isRecovered\n );\n },\n onTransition: (protobufData, transitionFrameCount) => {\n this.provider.pauseRemoteAudio();\n this.animationHandler.handleTransitionData(protobufData, transitionFrameCount);\n },\n onTransitionEnd: (protobufData, transitionFrameCount) => {\n this.animationHandler.handleTransitionToIdle(protobufData, transitionFrameCount);\n },\n onSessionStart: () => {\n // Session started - can be used for UI feedback\n },\n onSessionEnd: () => {\n // Session ended - can be used for UI feedback\n },\n onIdleStart: () => {\n this.animationHandler.startIdle();\n },\n onStreamStats: (stats) => {\n // Log packet loss/recovery stats\n if (stats.framesLost > 0 || stats.framesRecovered > 0 || stats.framesDropped > 0) {\n logger.warn(\n 'AvatarPlayer',\n `Stream stats: lost=${stats.framesLost}, recovered=${stats.framesRecovered}, dropped=${stats.framesDropped}, fps=${stats.framesPerSec}`\n );\n }\n },\n };\n\n await this.provider.subscribeAnimationTrack(callbacks);\n }\n\n /**\n * Setup provider event handlers.\n * @internal\n */\n private setupProviderEvents(): void {\n this.provider.on('connected', () => {\n this._isConnected = true;\n });\n\n this.provider.on('disconnected', () => {\n this._isConnected = false;\n // Return to idle animation when disconnected\n this.animationHandler.startIdle();\n });\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index2.js","sources":["../src/core/AvatarPlayer.ts"],"sourcesContent":["/**\n * AvatarPlayer - Unified entry point for RTC avatar streaming.\n *\n * This class provides a unified API that shields applications from\n * differences between different RTC providers (LiveKit, Agora, etc.)\n * and integrates seamlessly with @spatialwalk/avatarkit for rendering.\n *\n * @packageDocumentation\n */\n\nimport type { RTCProvider } from './RTCProvider';\nimport type { RTCConnectionConfig } from '../types';\nimport { AnimationHandler } from './AnimationHandler';\nimport type { AnimationTrackCallbacks, RTCProviderEvents } from './types';\nimport { configureLogger, logger, type LogLevel } from '../utils';\n\n// Import AvatarView type from avatarkit\nimport type { AvatarView, KeyframeData } from '@spatialwalk/avatarkit';\n\n/**\n * Options for configuring AvatarPlayer.\n */\nexport interface AvatarPlayerOptions {\n /**\n * Log level for SDK output.\n * - 'info': Show all logs (debug mode)\n * - 'warning': Show warnings and errors (default)\n * - 'error': Show only errors\n * - 'none': Disable all logs\n */\n logLevel?: LogLevel;\n\n /**\n * Enable jitter buffer for smoother animation playback.\n * When enabled, frames are buffered and rendered in sequence order at a steady 25fps,\n * absorbing network jitter and out-of-order delivery.\n * Default: true\n */\n enableJitterBuffer?: boolean;\n\n /**\n * Maximum delay (in ms) a frame can sit in the jitter buffer before being rendered.\n * Only used when enableJitterBuffer is true.\n * Default: 80 (2 frames at 25fps)\n */\n maxBufferDelayMs?: number;\n}\n\n/**\n * Unified Avatar Player.\n *\n * Main entry point for applications to interact with RTC avatar streaming.\n * Handles connection management, audio publishing, animation rendering,\n * and coordinates with avatarkit for display.\n *\n * @example\n * ```typescript\n * import { AvatarPlayer, LiveKitProvider } from '@spatialwalk/avatarkit-rtc';\n * import { AvatarView, Avatar } from '@spatialwalk/avatarkit';\n *\n * // Create avatar and view (from avatarkit)\n * const avatar = await Avatar.create(characterId);\n * const avatarView = new AvatarView(avatar, container);\n *\n * // Create player\n * const provider = new LiveKitProvider();\n * const player = new AvatarPlayer(provider, avatarView);\n *\n * // Connect\n * await player.connect({ url: 'wss://...', token: '...' });\n *\n * // Option 1: Use microphone (most common)\n * await player.startPublishing();\n * await player.stopPublishing();\n *\n * // Option 2: Use custom audio source\n * const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n * await player.publishAudio(stream.getAudioTracks()[0]);\n * await player.unpublishAudio();\n * stream.getTracks().forEach(t => t.stop());\n *\n * // Disconnect\n * await player.disconnect();\n * ```\n */\nexport class AvatarPlayer {\n /** @internal */\n private provider: RTCProvider;\n\n /** @internal */\n private avatarView: AvatarView;\n\n /** @internal */\n private animationHandler: AnimationHandler;\n\n /** @internal */\n private _isConnected = false;\n\n /** @internal */\n private localAudioTrack: MediaStreamTrack | null = null;\n\n /** @internal */\n private microphoneStream: MediaStream | null = null;\n\n /** @internal */\n private eventHandlers: Map<string, Set<Function>> = new Map();\n\n /** @internal */\n private lastConnectionConfig: RTCConnectionConfig | null = null;\n\n /** @internal */\n private isReconnecting = false;\n\n /**\n * Create a new AvatarPlayer instance.\n * @param provider - RTC provider implementation (e.g., LiveKitProvider)\n * @param avatarView - AvatarView instance from @spatialwalk/avatarkit\n * @param options - Optional configuration for transitions\n */\n constructor(\n provider: RTCProvider,\n avatarView: AvatarView,\n options?: AvatarPlayerOptions\n ) {\n this.provider = provider;\n this.avatarView = avatarView;\n\n // Configure logging if specified\n if (options?.logLevel !== undefined) {\n configureLogger({ level: options.logLevel });\n }\n\n // Create internal renderer adapter\n const renderer = this.createRendererAdapter();\n\n this.animationHandler = new AnimationHandler(renderer, {\n enableJitterBuffer: options?.enableJitterBuffer,\n maxBufferDelayMs: options?.maxBufferDelayMs,\n onStreamStalled: () => {\n // AnimationHandler already switches to idle state\n // Just notify external listeners, let them decide what to do (e.g., call reconnect())\n this.emit('stalled');\n },\n });\n\n // Setup provider event handlers\n this.setupProviderEvents();\n }\n\n /**\n * Check if currently connected to RTC server.\n */\n get isConnected(): boolean {\n return this._isConnected;\n }\n\n /**\n * Connect to RTC server.\n * @param config - Connection configuration (URL, token, room name, etc.)\n * @throws Error if already connected or connection fails\n */\n async connect(config: RTCConnectionConfig): Promise<void> {\n if (this._isConnected) {\n throw new Error('Already connected. Please disconnect first.');\n }\n\n // Save config for potential reconnection\n this.lastConnectionConfig = config;\n\n // Setup animation callbacks before connecting\n await this.setupAnimationCallbacks();\n await this.provider.connect(config);\n this._isConnected = true;\n }\n\n /**\n * Disconnect from RTC server.\n * Stops all tracks and cleans up resources.\n */\n async disconnect(): Promise<void> {\n if (!this._isConnected) {\n return;\n }\n\n // Stop microphone if active\n if (this.microphoneStream) {\n await this.stopPublishing();\n } else if (this.localAudioTrack) {\n // Stop custom audio track\n await this.unpublishAudio();\n }\n\n await this.provider.disconnect();\n this.animationHandler.dispose();\n this._isConnected = false;\n }\n\n /**\n * Publish an audio track to the RTC server.\n * \n * The audio source is controlled externally - you can pass any audio track:\n * - Microphone: `getUserMedia({ audio: true })`\n * - Browser audio: `audioElement.captureStream()`\n * - Web Audio API: `audioContext.createMediaStreamDestination().stream`\n * - Screen share audio: `getDisplayMedia({ audio: true })`\n * \n * @param track - MediaStreamTrack to publish (must be an audio track)\n * @throws Error if not connected or track is invalid\n * \n * @example\n * ```typescript\n * // Microphone\n * const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n * await player.publishAudio(stream.getAudioTracks()[0]);\n * \n * // Browser audio element\n * const audioEl = document.querySelector('audio');\n * const stream = audioEl.captureStream();\n * await player.publishAudio(stream.getAudioTracks()[0]);\n * ```\n */\n async publishAudio(track: MediaStreamTrack): Promise<void> {\n if (!this._isConnected) {\n throw new Error('Not connected. Please call connect() first.');\n }\n\n if (track.kind !== 'audio') {\n throw new Error('Invalid track: expected audio track');\n }\n\n // Unpublish existing track if any\n if (this.localAudioTrack) {\n await this.unpublishAudio();\n }\n\n this.localAudioTrack = track;\n await this.provider.publishAudioTrack(track);\n }\n\n /**\n * Stop publishing audio.\n * Note: This does NOT stop the track - the caller is responsible for managing the track lifecycle.\n */\n async unpublishAudio(): Promise<void> {\n if (!this.localAudioTrack) {\n return;\n }\n\n await this.provider.unpublishAudioTrack();\n this.localAudioTrack = null;\n }\n\n /**\n * Start publishing microphone audio.\n * \n * This requests microphone permission and publishes it to the RTC session.\n * For custom audio sources (e.g., audio files), use `publishAudio(track)` instead.\n * \n * @throws Error if not connected, permission denied, or no microphone found\n * \n * @example\n * ```typescript\n * await player.startPublishing();\n * // ... later\n * await player.stopPublishing();\n * ```\n */\n async startPublishing(): Promise<void> {\n if (!this._isConnected) {\n throw new Error('Not connected. Please call connect() first.');\n }\n\n if (this.microphoneStream) {\n return; // Already using microphone\n }\n\n // Request microphone permission\n this.microphoneStream = await navigator.mediaDevices.getUserMedia({ audio: true });\n const track = this.microphoneStream.getAudioTracks()[0];\n \n await this.publishAudio(track);\n }\n\n /**\n * Stop publishing microphone audio and release the microphone.\n * \n * This stops the microphone track and releases the device.\n * If you used `publishAudio()` with a custom track, use `unpublishAudio()` instead.\n */\n async stopPublishing(): Promise<void> {\n if (!this.microphoneStream) {\n return;\n }\n\n await this.unpublishAudio();\n\n // Stop and release microphone\n this.microphoneStream.getTracks().forEach((track) => track.stop());\n this.microphoneStream = null;\n }\n\n /**\n * Get current connection state.\n * @returns Connection state string (e.g., 'connected', 'disconnected')\n */\n getConnectionState(): string {\n return this.provider.getConnectionState();\n }\n\n /**\n * Get the native RTC client object.\n * \n * Returns the underlying RTC client for advanced use cases:\n * - LiveKit: Returns the Room instance\n * - Agora: Returns the IAgoraRTCClient instance\n * \n * @returns The native RTC client object, or null if not connected\n * \n * @example\n * ```typescript\n * // Access LiveKit Room\n * const room = player.getNativeClient() as Room;\n * console.log('Participants:', room?.remoteParticipants.size);\n * \n * // Access Agora Client\n * const client = player.getNativeClient() as IAgoraRTCClient;\n * console.log('State:', client?.connectionState);\n * ```\n */\n getNativeClient(): unknown {\n return this.provider.getNativeClient();\n }\n\n /**\n * Add event listener.\n * @param event - Event name ('connected', 'disconnected', 'error', 'stalled', etc.)\n * @param handler - Event handler function\n */\n on(event: string, handler: Function): void {\n if (event === 'stalled') {\n // Handle stalled event locally\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler);\n } else {\n // Forward other events to provider\n this.provider.on(event as any, handler as any);\n }\n }\n\n /**\n * Remove event listener.\n * @param event - Event name\n * @param handler - Event handler function (must be same reference as added)\n */\n off(event: string, handler: Function): void {\n if (event === 'stalled') {\n this.eventHandlers.get(event)?.delete(handler);\n } else {\n this.provider.off(event as any, handler as any);\n }\n }\n\n /**\n * Emit an event to local listeners.\n * @internal\n */\n private emit(event: string, ...args: unknown[]): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(...args);\n } catch (e) {\n logger.error('AvatarPlayer', `Error in ${event} handler:`, e);\n }\n });\n }\n }\n\n /**\n * Reconnect to RTC server using the last connection configuration.\n * Useful for recovering from connection issues or stream stalls.\n *\n * @example\n * ```typescript\n * player.on('stalled', async () => {\n * console.log('Stream stalled, attempting reconnection...');\n * await player.reconnect();\n * });\n * ```\n *\n * @throws Error if no previous connection config exists or reconnection fails\n */\n async reconnect(): Promise<void> {\n if (this.isReconnecting) {\n logger.warn('AvatarPlayer', 'Already attempting reconnection, skipping');\n return;\n }\n\n if (!this.lastConnectionConfig) {\n throw new Error('Cannot reconnect: no previous connection. Call connect() first.');\n }\n\n this.isReconnecting = true;\n logger.info('AvatarPlayer', 'Attempting reconnection...');\n\n try {\n // Disconnect first if connected\n if (this._isConnected) {\n await this.disconnect();\n }\n\n // Wait a short moment before reconnecting\n await new Promise((resolve) => setTimeout(resolve, 500));\n\n // Reconnect\n await this.connect(this.lastConnectionConfig);\n logger.info('AvatarPlayer', 'Reconnection successful');\n } catch (error) {\n logger.error('AvatarPlayer', 'Reconnection failed:', error);\n throw error;\n } finally {\n this.isReconnecting = false;\n }\n }\n\n /**\n * Create internal renderer adapter that bridges AnimationHandler to AvatarView.\n * @internal\n */\n private createRendererAdapter() {\n const avatarView = this.avatarView;\n\n return {\n renderFrame(flame: KeyframeData | undefined, startIdle?: boolean): void {\n if (startIdle) {\n // Enable idle rendering mode\n avatarView.renderFlame(undefined as unknown as KeyframeData, true);\n } else if (flame) {\n // Render the animation frame\n avatarView.renderFlame(flame);\n }\n },\n\n async generateTransitionFromIdle(\n targetFrame: KeyframeData,\n frameCount: number\n ): Promise<KeyframeData[]> {\n // Use avatarkit's generateTransition with from=undefined (current idle frame)\n return avatarView.generateTransition({\n to: targetFrame,\n frameCount,\n useLinear: true, // Linear interpolation for idle->speaking\n });\n },\n\n isReady(): boolean {\n return true; // AvatarView is always ready once created\n },\n };\n }\n\n /**\n * Setup animation track callbacks.\n * @internal\n */\n private async setupAnimationCallbacks(): Promise<void> {\n const callbacks: AnimationTrackCallbacks = {\n onAnimationData: (protobufData, metadata) => {\n // Reset tracking on session start\n if (metadata.isStart) {\n this.animationHandler.resetTracking();\n }\n\n this.animationHandler.handleAnimationData(\n protobufData,\n metadata.frameSeq,\n metadata.isRecovered\n );\n },\n onTransition: (protobufData, transitionFrameCount) => {\n this.animationHandler.handleTransitionData(protobufData, transitionFrameCount);\n },\n onTransitionEnd: (protobufData, transitionFrameCount) => {\n this.animationHandler.handleTransitionToIdle(protobufData, transitionFrameCount);\n },\n onSessionStart: () => {\n // Session started - can be used for UI feedback\n },\n onSessionEnd: () => {\n // Session ended - can be used for UI feedback\n },\n onIdleStart: () => {\n this.animationHandler.startIdle();\n },\n onStreamStats: (stats) => {\n // Log packet loss/recovery stats\n if (stats.framesLost > 0 || stats.framesRecovered > 0 || stats.framesDropped > 0) {\n logger.warn(\n 'AvatarPlayer',\n `Stream stats: lost=${stats.framesLost}, recovered=${stats.framesRecovered}, dropped=${stats.framesDropped}, fps=${stats.framesPerSec}`\n );\n }\n },\n };\n\n await this.provider.subscribeAnimationTrack(callbacks);\n }\n\n /**\n * Setup provider event handlers.\n * @internal\n */\n private setupProviderEvents(): void {\n this.provider.on('connected', () => {\n this._isConnected = true;\n });\n\n this.provider.on('disconnected', () => {\n this._isConnected = false;\n // Return to idle animation when disconnected\n this.animationHandler.startIdle();\n });\n }\n}\n"],"names":[],"mappings":"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"}
package/dist/index3.js CHANGED
@@ -1,10 +1,10 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import { BaseProvider } from "./index9.js";
4
+ import { BaseProvider } from "./index10.js";
5
5
  import { isLiveKitConfig } from "./index5.js";
6
- import { VP8Extractor } from "./index11.js";
7
- import { getInsertableStreamsMethod } from "./index12.js";
6
+ import { VP8Extractor } from "./index12.js";
7
+ import { getInsertableStreamsMethod } from "./index13.js";
8
8
  import { logger } from "./index7.js";
9
9
  const globalLiveKitProviderRegistry = /* @__PURE__ */ new Set();
10
10
  let rtcPeerConnectionPatched = false;
@@ -85,6 +85,18 @@ class LiveKitProvider extends BaseProvider {
85
85
  __publicField(this, "audioCallbacks", null);
86
86
  /** @internal */
87
87
  __publicField(this, "audioElements", /* @__PURE__ */ new Map());
88
+ /** @internal */
89
+ __publicField(this, "awaitingAudioUnlock", false);
90
+ /** @internal */
91
+ __publicField(this, "audioUnlockEventNames", [
92
+ "pointerdown",
93
+ "keydown",
94
+ "touchstart"
95
+ ]);
96
+ /** @internal */
97
+ __publicField(this, "handleAudioUnlockGesture", () => {
98
+ void this.ensureAudioPlaybackUnlocked("user-gesture");
99
+ });
88
100
  // Microphone publishing
89
101
  /** @internal */
90
102
  __publicField(this, "localAudioTrack", null);
@@ -269,6 +281,104 @@ class LiveKitProvider extends BaseProvider {
269
281
  this.vp8Extractors.delete(receiver);
270
282
  }
271
283
  }
284
+ /**
285
+ * Add one-time user gesture listeners to recover from autoplay blocking.
286
+ * @internal
287
+ */
288
+ addAudioUnlockListeners() {
289
+ if (typeof window === "undefined" || this.awaitingAudioUnlock) {
290
+ return;
291
+ }
292
+ this.awaitingAudioUnlock = true;
293
+ for (const eventName of this.audioUnlockEventNames) {
294
+ window.addEventListener(eventName, this.handleAudioUnlockGesture, {
295
+ capture: true,
296
+ passive: true
297
+ });
298
+ }
299
+ logger.warn("LiveKit", "Audio playback blocked by autoplay policy, waiting for user interaction");
300
+ }
301
+ /**
302
+ * Remove user gesture listeners after playback is unlocked.
303
+ * @internal
304
+ */
305
+ removeAudioUnlockListeners() {
306
+ if (typeof window === "undefined" || !this.awaitingAudioUnlock) {
307
+ return;
308
+ }
309
+ this.awaitingAudioUnlock = false;
310
+ for (const eventName of this.audioUnlockEventNames) {
311
+ window.removeEventListener(eventName, this.handleAudioUnlockGesture, true);
312
+ }
313
+ }
314
+ /**
315
+ * Detect browser autoplay blocking errors from audio play/start calls.
316
+ * @internal
317
+ */
318
+ isAutoplayBlockedError(error) {
319
+ if (typeof DOMException !== "undefined" && error instanceof DOMException) {
320
+ return error.name === "NotAllowedError";
321
+ }
322
+ if (error instanceof Error) {
323
+ return error.name === "NotAllowedError" || /didn't interact|user didn't interact|NotAllowedError/i.test(error.message);
324
+ }
325
+ return false;
326
+ }
327
+ /**
328
+ * Try to resume all attached remote audio elements.
329
+ * @returns true if at least one element is still blocked by autoplay policy
330
+ * @internal
331
+ */
332
+ async resumeAttachedAudioElements(reason) {
333
+ let hasAutoplayBlockedElement = false;
334
+ for (const [participantIdentity, audioElement] of this.audioElements) {
335
+ if (!audioElement.paused) {
336
+ continue;
337
+ }
338
+ try {
339
+ await audioElement.play();
340
+ } catch (error) {
341
+ if (this.isAutoplayBlockedError(error)) {
342
+ hasAutoplayBlockedElement = true;
343
+ }
344
+ logger.warn(
345
+ "LiveKit",
346
+ `Failed to resume remote audio element (${reason}) for ${participantIdentity}:`,
347
+ error
348
+ );
349
+ }
350
+ }
351
+ return hasAutoplayBlockedElement;
352
+ }
353
+ /**
354
+ * Ensure remote audio playback is unlocked and resumed.
355
+ * @internal
356
+ */
357
+ async ensureAudioPlaybackUnlocked(reason) {
358
+ if (!this.room) {
359
+ return;
360
+ }
361
+ const roomWithAudioControl = this.room;
362
+ if (!roomWithAudioControl.canPlaybackAudio && typeof roomWithAudioControl.startAudio === "function") {
363
+ try {
364
+ await roomWithAudioControl.startAudio();
365
+ logger.info("LiveKit", `Audio context resumed (${reason})`);
366
+ } catch (error) {
367
+ if (this.isAutoplayBlockedError(error)) {
368
+ this.addAudioUnlockListeners();
369
+ } else {
370
+ logger.warn("LiveKit", `Failed to call room.startAudio() (${reason}):`, error);
371
+ }
372
+ return;
373
+ }
374
+ }
375
+ const hasAutoplayBlockedElement = await this.resumeAttachedAudioElements(reason);
376
+ if (roomWithAudioControl.canPlaybackAudio && !hasAutoplayBlockedElement) {
377
+ this.removeAudioUnlockListeners();
378
+ } else {
379
+ this.addAudioUnlockListeners();
380
+ }
381
+ }
272
382
  async connect(config) {
273
383
  var _a;
274
384
  logger.info("LiveKit", "connect() called with config:", {
@@ -283,6 +393,7 @@ class LiveKitProvider extends BaseProvider {
283
393
  }
284
394
  const livekitConfig = config;
285
395
  logger.info("LiveKit", "Config validated, connecting to:", livekitConfig.url);
396
+ this.removeAudioUnlockListeners();
286
397
  const livekit = await this.loadSDK();
287
398
  logger.info("LiveKit", "SDK loaded successfully");
288
399
  const { Room } = livekit;
@@ -306,7 +417,6 @@ class LiveKitProvider extends BaseProvider {
306
417
  });
307
418
  });
308
419
  this.setConnectionState(this.mapLiveKitConnectionState(livekit, this.room.state));
309
- this.emit("connected");
310
420
  } catch (error) {
311
421
  logger.error("LiveKit", "Connection failed:", error);
312
422
  this.setConnectionState("failed");
@@ -325,6 +435,10 @@ class LiveKitProvider extends BaseProvider {
325
435
  room.on(RoomEvent.Connected, () => {
326
436
  this.setConnectionState("connected");
327
437
  this.emit("connected");
438
+ const roomWithAudioControl = room;
439
+ if (!roomWithAudioControl.canPlaybackAudio) {
440
+ this.addAudioUnlockListeners();
441
+ }
328
442
  });
329
443
  room.on(RoomEvent.ParticipantConnected, (participant) => {
330
444
  logger.info("LiveKit", `Participant connected: ${participant.identity}`);
@@ -344,6 +458,14 @@ class LiveKitProvider extends BaseProvider {
344
458
  this.setConnectionState("disconnected");
345
459
  this.emit("disconnected");
346
460
  });
461
+ room.on(RoomEvent.AudioPlaybackStatusChanged, (canPlaybackAudio) => {
462
+ logger.info("LiveKit", `Audio playback status changed: ${canPlaybackAudio ? "enabled" : "blocked"}`);
463
+ if (canPlaybackAudio) {
464
+ void this.ensureAudioPlaybackUnlocked("audio-playback-status-changed");
465
+ } else {
466
+ this.addAudioUnlockListeners();
467
+ }
468
+ });
347
469
  this.room.on(
348
470
  RoomEvent.TrackSubscribed,
349
471
  (track, publication, participant) => {
@@ -362,11 +484,16 @@ class LiveKitProvider extends BaseProvider {
362
484
  audioElement.autoplay = true;
363
485
  audioElement.controls = false;
364
486
  audioElement.style.display = "none";
487
+ const previousAudioElement = this.audioElements.get(participant.identity);
488
+ if (previousAudioElement && previousAudioElement !== audioElement) {
489
+ previousAudioElement.remove();
490
+ }
365
491
  if (typeof document !== "undefined") {
366
492
  document.body.appendChild(audioElement);
367
493
  logger.info("LiveKit", "Audio element appended to body");
368
494
  }
369
495
  this.audioElements.set(participant.identity, audioElement);
496
+ void this.ensureAudioPlaybackUnlocked("audio-track-subscribed");
370
497
  if (this.audioCallbacks) {
371
498
  (_b = (_a = this.audioCallbacks).onAudioReceived) == null ? void 0 : _b.call(_a, participant);
372
499
  }
@@ -477,7 +604,6 @@ class LiveKitProvider extends BaseProvider {
477
604
  async unsubscribeAudioTrack() {
478
605
  this.audioCallbacks = null;
479
606
  this.audioElements.forEach((el) => {
480
- el.pause();
481
607
  el.remove();
482
608
  });
483
609
  this.audioElements.clear();
@@ -524,27 +650,6 @@ class LiveKitProvider extends BaseProvider {
524
650
  this.localAudioTrack = null;
525
651
  this.isMicrophoneEnabled = false;
526
652
  }
527
- /**
528
- * Play remote audio (resume playback)
529
- */
530
- playRemoteAudio() {
531
- this.audioElements.forEach((audioElement) => {
532
- if (audioElement.paused) {
533
- audioElement.play().catch(() => {
534
- });
535
- }
536
- });
537
- }
538
- /**
539
- * Pause remote audio
540
- */
541
- pauseRemoteAudio() {
542
- this.audioElements.forEach((audioElement) => {
543
- if (!audioElement.paused) {
544
- audioElement.pause();
545
- }
546
- });
547
- }
548
653
  /**
549
654
  * Get the native LiveKit Room instance.
550
655
  *
@@ -570,8 +675,8 @@ class LiveKitProvider extends BaseProvider {
570
675
  * @internal
571
676
  */
572
677
  cleanup() {
678
+ this.removeAudioUnlockListeners();
573
679
  this.audioElements.forEach((el) => {
574
- el.pause();
575
680
  el.remove();
576
681
  });
577
682
  this.audioElements.clear();