@spatialwalk/avatarkit-rtc 1.0.0-beta.7 → 1.0.0-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -571
- package/dist/assets/animation-worker-DOGeTjF0.js.map +1 -0
- package/dist/core/AvatarPlayer.d.ts +8 -3
- package/dist/core/AvatarPlayer.d.ts.map +1 -1
- package/dist/core/RTCProvider.d.ts +12 -5
- package/dist/core/RTCProvider.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index10.js +8 -2
- package/dist/index10.js.map +1 -1
- package/dist/index11.js.map +1 -1
- package/dist/index12.js +9 -5
- package/dist/index12.js.map +1 -1
- package/dist/index14.js.map +1 -1
- package/dist/index15.js +1 -1
- package/dist/index2.js +40 -17
- package/dist/index2.js.map +1 -1
- package/dist/index3.js +138 -39
- package/dist/index3.js.map +1 -1
- package/dist/index4.js +99 -68
- package/dist/index4.js.map +1 -1
- package/dist/index5.js +6 -2
- package/dist/index5.js.map +1 -1
- package/dist/index6.js +78 -18
- package/dist/index6.js.map +1 -1
- package/dist/index8.js +4 -1
- package/dist/index8.js.map +1 -1
- package/dist/index9.js +5 -1
- package/dist/index9.js.map +1 -1
- package/dist/providers/agora/AgoraProvider.d.ts.map +1 -1
- package/dist/providers/agora/types.d.ts.map +1 -1
- package/dist/providers/base/BaseProvider.d.ts +9 -13
- package/dist/providers/base/BaseProvider.d.ts.map +1 -1
- package/dist/providers/livekit/LiveKitProvider.d.ts +4 -2
- package/dist/providers/livekit/LiveKitProvider.d.ts.map +1 -1
- package/dist/providers/livekit/animation-worker.d.ts.map +1 -1
- package/dist/types/index.d.ts +21 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +14 -5
- package/dist/assets/animation-worker-CUXZycUw.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RTCConnectionConfig } from '../types';
|
|
1
|
+
import { RTCConnectionConfig, RTCPrepareConnectionConfig } from '../types';
|
|
2
2
|
/**
|
|
3
3
|
* RTC Provider interface.
|
|
4
4
|
*
|
|
@@ -17,12 +17,19 @@ import { RTCConnectionConfig } from '../types';
|
|
|
17
17
|
* }
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
|
+
export type RTCProviderEventHandler = (...args: unknown[]) => void;
|
|
20
21
|
export interface RTCProvider {
|
|
21
22
|
/**
|
|
22
23
|
* Provider name identifier (e.g., 'livekit', 'agora').
|
|
23
24
|
* Used for logging and debugging.
|
|
24
25
|
*/
|
|
25
26
|
readonly name: string;
|
|
27
|
+
/**
|
|
28
|
+
* Pre-warm a future connection if the provider supports it.
|
|
29
|
+
* This should not establish a full RTC session or change connection state,
|
|
30
|
+
* but may warm DNS/TLS caches or select an edge region.
|
|
31
|
+
*/
|
|
32
|
+
prepareConnection?(config: RTCPrepareConnectionConfig): Promise<void>;
|
|
26
33
|
/**
|
|
27
34
|
* Connect to RTC server.
|
|
28
35
|
* @param config - Connection configuration containing server URL and credentials
|
|
@@ -41,9 +48,9 @@ export interface RTCProvider {
|
|
|
41
48
|
getConnectionState(): string;
|
|
42
49
|
/**
|
|
43
50
|
* Publish local audio track (microphone) to the RTC server.
|
|
44
|
-
* @param track - MediaStreamTrack from getUserMedia
|
|
51
|
+
* @param track - Optional MediaStreamTrack from getUserMedia; when omitted the provider may create a microphone track
|
|
45
52
|
*/
|
|
46
|
-
publishAudioTrack(track
|
|
53
|
+
publishAudioTrack(track?: MediaStreamTrack): Promise<void>;
|
|
47
54
|
/**
|
|
48
55
|
* Unpublish audio track from the RTC server.
|
|
49
56
|
*/
|
|
@@ -53,13 +60,13 @@ export interface RTCProvider {
|
|
|
53
60
|
* @param event - Event name
|
|
54
61
|
* @param handler - Event handler function
|
|
55
62
|
*/
|
|
56
|
-
on(event: string, handler:
|
|
63
|
+
on(event: string, handler: RTCProviderEventHandler): void;
|
|
57
64
|
/**
|
|
58
65
|
* Remove event listener.
|
|
59
66
|
* @param event - Event name
|
|
60
67
|
* @param handler - Event handler function
|
|
61
68
|
*/
|
|
62
|
-
off(event: string, handler:
|
|
69
|
+
off(event: string, handler: RTCProviderEventHandler): void;
|
|
63
70
|
/**
|
|
64
71
|
* Get the native RTC client object.
|
|
65
72
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RTCProvider.d.ts","sourceRoot":"","sources":["../../src/core/RTCProvider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"RTCProvider.d.ts","sourceRoot":"","sources":["../../src/core/RTCProvider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,UAAU,CAAC;AAGhF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;AAEnE,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,iBAAiB,CAAC,CAAC,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtE;;;;OAIG;IACH,OAAO,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpD;;;OAGG;IACH,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5B;;;OAGG;IACH,kBAAkB,IAAI,MAAM,CAAC;IA4B7B;;;OAGG;IACH,iBAAiB,CAAC,KAAK,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3D;;OAEG;IACH,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAErC;;;;OAIG;IACH,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAAC;IAE1D;;;;OAIG;IACH,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAAC;IAE3D;;;;;;;OAOG;IACH,eAAe,IAAI,OAAO,CAAC;CAC5B"}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
export { AvatarPlayer } from './core';
|
|
10
10
|
export type { AvatarPlayerOptions } from './core';
|
|
11
11
|
export { LiveKitProvider, AgoraProvider } from './providers';
|
|
12
|
-
export type { AgoraProviderOptions, LiveKitRoom, AgoraClient } from './providers';
|
|
13
|
-
export { ConnectionState, isLiveKitConfig, isAgoraConfig } from './types';
|
|
14
|
-
export type { RTCConnectionConfig, LiveKitConnectionConfig, AgoraConnectionConfig, } from './types';
|
|
12
|
+
export type { AgoraProviderOptions, LiveKitRoom, AgoraClient, } from './providers';
|
|
13
|
+
export { ConnectionState, isLiveKitConfig, isLiveKitPrepareConfig, isAgoraConfig, } from './types';
|
|
14
|
+
export type { RTCConnectionConfig, RTCPrepareConnectionConfig, LiveKitConnectionConfig, LiveKitPrepareConnectionConfig, AgoraConnectionConfig, } from './types';
|
|
15
15
|
export type { LogLevel } from './utils';
|
|
16
16
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,YAAY,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAGlD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC7D,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,YAAY,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAGlD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC7D,YAAY,EACV,oBAAoB,EACpB,WAAW,EACX,WAAW,GACZ,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,YAAY,EACV,mBAAmB,EACnB,0BAA0B,EAC1B,uBAAuB,EACvB,8BAA8B,EAC9B,qBAAqB,GACtB,MAAM,SAAS,CAAC;AAGjB,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { AvatarPlayer } from "./index2.js";
|
|
2
2
|
import { LiveKitProvider } from "./index3.js";
|
|
3
3
|
import { AgoraProvider } from "./index4.js";
|
|
4
|
-
import { ConnectionState, isAgoraConfig, isLiveKitConfig } from "./index5.js";
|
|
4
|
+
import { ConnectionState, isAgoraConfig, isLiveKitConfig, isLiveKitPrepareConfig } from "./index5.js";
|
|
5
5
|
export {
|
|
6
6
|
AgoraProvider,
|
|
7
7
|
AvatarPlayer,
|
|
8
8
|
ConnectionState,
|
|
9
9
|
LiveKitProvider,
|
|
10
10
|
isAgoraConfig,
|
|
11
|
-
isLiveKitConfig
|
|
11
|
+
isLiveKitConfig,
|
|
12
|
+
isLiveKitPrepareConfig
|
|
12
13
|
};
|
|
13
14
|
//# sourceMappingURL=index.js.map
|
package/dist/index10.js
CHANGED
|
@@ -56,9 +56,15 @@ class VP8Extractor {
|
|
|
56
56
|
lastRenderedSeq: evt.lastRenderedSeq ?? -1
|
|
57
57
|
});
|
|
58
58
|
} else if (evt.type === "transition") {
|
|
59
|
-
this.callbacks.onTransition(
|
|
59
|
+
this.callbacks.onTransition(
|
|
60
|
+
evt.protobufData,
|
|
61
|
+
DEFAULT_TRANSITION_START_FRAMES
|
|
62
|
+
);
|
|
60
63
|
} else if (evt.type === "transitionEnd") {
|
|
61
|
-
this.callbacks.onTransitionEnd(
|
|
64
|
+
this.callbacks.onTransitionEnd(
|
|
65
|
+
evt.protobufData,
|
|
66
|
+
DEFAULT_TRANSITION_END_FRAMES
|
|
67
|
+
);
|
|
62
68
|
} else if (evt.type === "animation") {
|
|
63
69
|
if (!evt.isIdle) {
|
|
64
70
|
this.callbacks.onAnimationData(evt.protobufData, {
|
package/dist/index10.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index10.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
|
|
1
|
+
{"version":3,"file":"index10.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(\n evt.protobufData,\n DEFAULT_TRANSITION_START_FRAMES,\n );\n } else if (evt.type === 'transitionEnd') {\n this.callbacks.onTransitionEnd(\n evt.protobufData,\n DEFAULT_TRANSITION_END_FRAMES,\n );\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,QACb,IAAI;AAAA,QACJ;AAAA,MAAA;AAAA,IAEJ,WAAW,IAAI,SAAS,iBAAiB;AACvC,WAAK,UAAU;AAAA,QACb,IAAI;AAAA,QACJ;AAAA,MAAA;AAAA,IAEJ,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/index11.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index11.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
|
|
1
|
+
{"version":3,"file":"index11.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 =\n | 'scriptTransform'\n | 'encodedStreams'\n | 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;AAgBO,SAAS,6BAAsD;AACpE,MAAI,wBAAA,EAA2B,QAAO;AACtC,MAAI,uBAAA,EAA0B,QAAO;AACrC,SAAO;AACT;"}
|
package/dist/index12.js
CHANGED
|
@@ -246,9 +246,7 @@ class SEIExtractor {
|
|
|
246
246
|
if (this.debugLogging && (this.totalFrameCount <= 5 || this.totalFrameCount % 50 === 0)) {
|
|
247
247
|
logger.info("SEIExtractor", `Animation frame seq=${frameSeq}`);
|
|
248
248
|
}
|
|
249
|
-
protobufData = new Uint8Array(
|
|
250
|
-
payload.subarray(FRAME_SEQ_SIZE)
|
|
251
|
-
).buffer;
|
|
249
|
+
protobufData = new Uint8Array(payload.subarray(FRAME_SEQ_SIZE)).buffer;
|
|
252
250
|
}
|
|
253
251
|
if (isTransition) {
|
|
254
252
|
if (!this.isInStartTransition) {
|
|
@@ -257,7 +255,10 @@ class SEIExtractor {
|
|
|
257
255
|
if (this.debugLogging) {
|
|
258
256
|
logger.info("SEIExtractor", "Transition START (first packet)");
|
|
259
257
|
}
|
|
260
|
-
this.callbacks.onTransition(
|
|
258
|
+
this.callbacks.onTransition(
|
|
259
|
+
protobufData,
|
|
260
|
+
this.transitionStartFrameCount
|
|
261
|
+
);
|
|
261
262
|
}
|
|
262
263
|
return;
|
|
263
264
|
}
|
|
@@ -268,7 +269,10 @@ class SEIExtractor {
|
|
|
268
269
|
if (this.debugLogging) {
|
|
269
270
|
logger.info("SEIExtractor", "Transition END (first packet)");
|
|
270
271
|
}
|
|
271
|
-
this.callbacks.onTransitionEnd(
|
|
272
|
+
this.callbacks.onTransitionEnd(
|
|
273
|
+
protobufData,
|
|
274
|
+
this.transitionEndFrameCount
|
|
275
|
+
);
|
|
272
276
|
}
|
|
273
277
|
return;
|
|
274
278
|
}
|
package/dist/index12.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index12.js","sources":["../src/providers/agora/SEIExtractor.ts"],"sourcesContent":["/**\n * SEI Data Extractor for Agora.\n *\n * Extracts animation data from Agora's H.264 SEI events.\n *\n * SEI Packet Format:\n * ```\n * [1B flags][4B msgLen (LE)][msgLen bytes compressed data]\n * ```\n *\n * Packet Flags:\n * - 0x01: Idle (no payload)\n * - 0x02: Start (first frame of session)\n * - 0x04: End (last frame of session)\n * - 0x08: Gzipped (payload is zlib compressed)\n * - 0x10: Transition (transition from idle to animation)\n * - 0x20: TransitionEnd (transition from animation back to idle)\n *\n * Key differences from LiveKit VP8Extractor:\n * - No VP8 header parsing needed\n * - No ALR (Application-Level Redundancy) - Agora handles reliability\n * - Uses native SEI events instead of RTCRtpScriptTransform\n * - Requires EBSP to RBSP conversion (H.264 emulation prevention byte removal)\n *\n * @internal\n * @packageDocumentation\n */\n\nimport type { AnimationTrackCallbacks } from '../../core/types';\nimport type { StreamStats } from '../../types';\nimport { logger } from '../../utils';\n\n/**\n * SEI Packet Flags - must match backend animation_track.go\n * @internal\n */\nconst PacketFlags = {\n /** Idle packet - no payload */\n Idle: 0x01,\n /** First frame of animation session */\n Start: 0x02,\n /** Last frame of animation session */\n End: 0x04,\n /** Payload is zlib compressed */\n Gzipped: 0x08,\n /** Transition from idle to animation */\n Transition: 0x10,\n /** Transition from animation back to idle */\n TransitionEnd: 0x20,\n} as const;\n\n/** SEI header size: 1 byte flags + 4 bytes length */\nconst SEI_HEADER_SIZE = 5;\n\n/** Frame sequence prefix size (4 bytes) for normal animation frames */\nconst FRAME_SEQ_SIZE = 4;\n\n// Default transition frame counts (internal constants)\nconst DEFAULT_TRANSITION_START_FRAMES = 8;\nconst DEFAULT_TRANSITION_END_FRAMES = 12;\n\n/**\n * SEIExtractor - Extracts animation data from Agora SEI events.\n *\n * Handles SEI header parsing, EBSP conversion, decompression, and callback dispatch.\n *\n * @internal\n */\nexport class SEIExtractor {\n /** @internal */\n private callbacks: AnimationTrackCallbacks | null = null;\n\n // Session state tracking\n /** @internal */\n private lastWasIdle = true;\n\n // Transition state tracking (to handle consecutive packets)\n /** @internal */\n private isInStartTransition = false;\n /** @internal */\n private isInEndTransition = false;\n\n // Transition frame counts\n /** @internal */\n private transitionStartFrameCount: number;\n /** @internal */\n private transitionEndFrameCount: number;\n\n // Statistics tracking\n /** @internal */\n private totalFrameCount = 0;\n /** @internal */\n private intervalFrameCount = 0;\n /** @internal */\n private lastStatsTime = 0;\n /** @internal */\n private statsInterval: ReturnType<typeof setInterval> | null = null;\n\n // Debug logging\n /** @internal */\n private debugLogging = false;\n\n constructor() {\n this.transitionStartFrameCount = DEFAULT_TRANSITION_START_FRAMES;\n this.transitionEndFrameCount = DEFAULT_TRANSITION_END_FRAMES;\n }\n\n /**\n * Initialize the extractor with callbacks.\n * @param callbacks - Callbacks to receive extracted data\n * @internal\n */\n initialize(callbacks: AnimationTrackCallbacks): void {\n this.callbacks = callbacks;\n this.startStatsInterval();\n }\n\n /**\n * Enable or disable debug logging.\n * @param enabled - Whether to enable debug logging\n * @internal\n */\n setDebugLogging(enabled: boolean): void {\n this.debugLogging = enabled;\n }\n\n /**\n * Handle SEI data from Agora.\n * Wraps async processing to not block the event handler.\n * @param seiData - Raw SEI payload\n * @internal\n */\n handleSEIData(seiData: Uint8Array): void {\n this.processSEIData(seiData).catch((error) => {\n logger.error('SEIExtractor', 'Failed to process SEI data:', error);\n this.logDebugInfo(seiData);\n });\n }\n\n /**\n * Convert EBSP (Encapsulated Byte Sequence Payload) to RBSP (Raw Byte Sequence Payload).\n * This removes H.264 emulation prevention bytes (0x03) inserted after 00 00 sequences.\n *\n * H.264 spec: When encoding, 0x03 is inserted after 00 00 to prevent start code emulation:\n * - 00 00 00 → 00 00 03 00\n * - 00 00 01 → 00 00 03 01\n * - 00 00 02 → 00 00 03 02\n * - 00 00 03 → 00 00 03 03\n *\n * @param data - EBSP data\n * @returns RBSP data\n * @internal\n */\n private ebspToRbsp(data: Uint8Array): Uint8Array {\n if (data.length === 0) {\n return data;\n }\n\n const result: number[] = [];\n let zeroCount = 0;\n\n for (let i = 0; i < data.length; i++) {\n const byte = data[i];\n\n // Check for emulation prevention byte: 00 00 03 XX where XX <= 0x03\n if (zeroCount >= 2 && byte === 0x03) {\n if (i + 1 < data.length) {\n const nextByte = data[i + 1];\n if (nextByte <= 0x03) {\n // Skip the 0x03 emulation prevention byte\n zeroCount = 0;\n continue;\n }\n }\n }\n\n // Track consecutive zeros\n if (byte === 0x00) {\n zeroCount++;\n } else {\n zeroCount = 0;\n }\n\n result.push(byte);\n }\n\n return new Uint8Array(result);\n }\n\n /**\n * Unescape zero bytes that were escaped by the server.\n * Escape scheme: 0x00 0xFF -> 0x00, 0xFF 0xFF -> 0xFF\n * This reverses the escaping done to avoid H.264 emulation prevention issues.\n *\n * @param data - Escaped data\n * @returns Unescaped data\n * @internal\n */\n private unescapeZeroBytes(data: Uint8Array): Uint8Array {\n const result: number[] = [];\n let i = 0;\n while (i < data.length) {\n if (i + 1 < data.length && data[i] === 0x00 && data[i + 1] === 0xff) {\n // 00 FF -> 00\n result.push(0x00);\n i += 2;\n } else if (\n i + 1 < data.length &&\n data[i] === 0xff &&\n data[i + 1] === 0xff\n ) {\n // FF FF -> FF\n result.push(0xff);\n i += 2;\n } else {\n result.push(data[i]);\n i++;\n }\n }\n return new Uint8Array(result);\n }\n\n /**\n * Decompress zlib data using DecompressionStream API.\n * Uses 'deflate' format (not gzip) to avoid H.264 emulation prevention issues\n * with gzip's mtime field containing 00 00 00 00.\n *\n * @param data - Zlib compressed data\n * @returns Decompressed data\n * @internal\n */\n private async decompressZlib(data: Uint8Array): Promise<Uint8Array> {\n const ds = new DecompressionStream('deflate');\n const writer = ds.writable.getWriter();\n // Create a copy to ensure we have a plain ArrayBuffer\n const copy = new Uint8Array(data);\n writer.write(copy);\n writer.close();\n\n const reader = ds.readable.getReader();\n const chunks: Uint8Array[] = [];\n let totalLength = 0;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n totalLength += value.length;\n }\n\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n\n return result;\n }\n\n /**\n * Process SEI data asynchronously.\n * @param seiData - Raw SEI payload\n * @internal\n */\n private async processSEIData(seiData: Uint8Array): Promise<void> {\n if (!this.callbacks) {\n return;\n }\n\n this.totalFrameCount++;\n this.intervalFrameCount++;\n\n // Remove H.264 emulation prevention bytes using EBSP to RBSP conversion\n const cleanedData = this.ebspToRbsp(seiData);\n\n // Parse SEI header: [1B flags][4B msgLen (LE)][payload]\n if (cleanedData.length < SEI_HEADER_SIZE) {\n logger.warn('SEIExtractor', `SEI data too short: ${cleanedData.length}`);\n return;\n }\n\n const flags = cleanedData[0];\n const msgLen = new DataView(\n cleanedData.buffer,\n cleanedData.byteOffset + 1,\n 4\n ).getUint32(0, true);\n\n // Debug logging for first few packets\n if (\n this.debugLogging &&\n (this.totalFrameCount <= 5 || this.totalFrameCount % 50 === 0)\n ) {\n logger.info(\n 'SEIExtractor',\n `SEI packet #${this.totalFrameCount}: flags=0x${flags.toString(16)}, msgLen=${msgLen}, cleanedLen=${cleanedData.length}`\n );\n }\n\n // Check for idle packet (no payload)\n const isIdle = (flags & PacketFlags.Idle) !== 0;\n if (isIdle || msgLen === 0) {\n if (!this.lastWasIdle) {\n this.lastWasIdle = true;\n // Reset transition states when idle\n this.isInStartTransition = false;\n this.isInEndTransition = false;\n this.callbacks.onIdleStart();\n }\n return;\n }\n\n // Extract payload - use all remaining bytes after header instead of msgLen\n // This handles cases where EP removal changes the data length\n const rawPayload = cleanedData.slice(SEI_HEADER_SIZE);\n\n // Unescape zero bytes that were escaped by the server\n const compressedPayload = this.unescapeZeroBytes(rawPayload);\n\n // Decompress if compressed using zlib\n let payload: Uint8Array;\n if ((flags & PacketFlags.Gzipped) !== 0) {\n payload = await this.decompressZlib(compressedPayload);\n } else {\n payload = compressedPayload;\n }\n\n // Check packet type flags\n const isTransition = (flags & PacketFlags.Transition) !== 0;\n const isTransitionEnd = (flags & PacketFlags.TransitionEnd) !== 0;\n const isStart = (flags & PacketFlags.Start) !== 0;\n const isEnd = (flags & PacketFlags.End) !== 0;\n\n // Extract protobuf data\n // Transition frames don't have frame sequence prefix\n let protobufData: ArrayBuffer;\n let frameSeq: number | undefined;\n\n if (isTransition || isTransitionEnd) {\n // Create a copy to ensure we have a plain ArrayBuffer\n protobufData = new Uint8Array(payload).buffer;\n } else {\n // Normal animation frame: [4B frameSeq][protobuf]\n if (payload.length < FRAME_SEQ_SIZE) {\n logger.warn('SEIExtractor', 'Payload too short for frame sequence');\n return;\n }\n frameSeq = new DataView(payload.buffer, payload.byteOffset, 4).getUint32(\n 0,\n true\n );\n\n if (\n this.debugLogging &&\n (this.totalFrameCount <= 5 || this.totalFrameCount % 50 === 0)\n ) {\n logger.info('SEIExtractor', `Animation frame seq=${frameSeq}`);\n }\n\n // Create a copy of the protobuf data portion\n protobufData = new Uint8Array(\n payload.subarray(FRAME_SEQ_SIZE)\n ).buffer;\n }\n\n // Handle transition packets\n // Only trigger transition on first packet of consecutive transition packets\n if (isTransition) {\n if (!this.isInStartTransition) {\n this.isInStartTransition = true;\n this.isInEndTransition = false;\n if (this.debugLogging) {\n logger.info('SEIExtractor', 'Transition START (first packet)');\n }\n this.callbacks.onTransition(protobufData, this.transitionStartFrameCount);\n }\n return;\n }\n\n if (isTransitionEnd) {\n if (!this.isInEndTransition) {\n this.isInEndTransition = true;\n this.isInStartTransition = false;\n if (this.debugLogging) {\n logger.info('SEIExtractor', 'Transition END (first packet)');\n }\n this.callbacks.onTransitionEnd(protobufData, this.transitionEndFrameCount);\n }\n return;\n }\n\n // Normal animation frame - clear transition states\n this.isInStartTransition = false;\n this.isInEndTransition = false;\n\n // Normal animation frame\n const isFirstFrame = this.lastWasIdle || isStart;\n this.lastWasIdle = false;\n\n // Session start callback\n if (isFirstFrame) {\n if (this.debugLogging) {\n logger.info('SEIExtractor', 'Session start');\n }\n this.callbacks.onSessionStart?.();\n }\n\n // Call animation data callback\n this.callbacks.onAnimationData(protobufData, {\n frameSeq,\n isStart: isFirstFrame,\n isEnd,\n isIdle: false,\n isRecovered: false, // Agora handles reliability internally\n });\n\n // Session end callback\n if (isEnd) {\n if (this.debugLogging) {\n logger.info('SEIExtractor', 'Session end');\n }\n this.callbacks.onSessionEnd?.();\n }\n }\n\n /**\n * Log debug information for failed SEI processing.\n * @internal\n */\n private logDebugInfo(seiData: Uint8Array): void {\n const totalLen = seiData.length;\n logger.error('SEIExtractor', `Total SEI length: ${totalLen}`);\n if (seiData.length >= 20) {\n const hexBytes = Array.from(seiData.slice(0, 20))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join(' ');\n logger.error('SEIExtractor', 'First 20 bytes of SEI:', hexBytes);\n }\n if (seiData.length >= SEI_HEADER_SIZE) {\n const flags = seiData[0];\n const msgLen = new DataView(\n seiData.buffer,\n seiData.byteOffset + 1,\n 4\n ).getUint32(0, true);\n logger.error(\n 'SEIExtractor',\n `SEI header: flags=0x${flags.toString(16)}, msgLen=${msgLen}`\n );\n }\n }\n\n /**\n * Start the statistics reporting interval.\n * @internal\n */\n private startStatsInterval(): void {\n this.lastStatsTime = Date.now();\n this.statsInterval = setInterval(() => {\n const now = Date.now();\n const elapsedSeconds = (now - this.lastStatsTime) / 1000;\n\n if (elapsedSeconds > 0 && this.callbacks) {\n const fps = this.intervalFrameCount / elapsedSeconds;\n\n const stats: StreamStats = {\n framesPerSec: Math.round(fps * 10) / 10,\n totalFrames: this.totalFrameCount,\n framesSent: this.totalFrameCount,\n framesLost: 0,\n framesRecovered: 0,\n framesDropped: 0,\n framesOutOfOrder: 0,\n framesDuplicate: 0,\n lastRenderedSeq: -1,\n };\n\n this.callbacks.onStreamStats(stats);\n }\n\n this.intervalFrameCount = 0;\n this.lastStatsTime = now;\n }, 1000);\n }\n\n /**\n * Stop the statistics reporting interval.\n * @internal\n */\n private stopStatsInterval(): void {\n if (this.statsInterval) {\n clearInterval(this.statsInterval);\n this.statsInterval = null;\n }\n }\n\n /**\n * Reset session state.\n * Call this when reconnecting or starting a new session.\n * @internal\n */\n reset(): void {\n this.lastWasIdle = true;\n this.isInStartTransition = false;\n this.isInEndTransition = false;\n this.totalFrameCount = 0;\n this.intervalFrameCount = 0;\n this.lastStatsTime = Date.now();\n }\n\n /**\n * Dispose the extractor and release resources.\n * @internal\n */\n dispose(): void {\n this.stopStatsInterval();\n this.callbacks = null;\n this.lastWasIdle = true;\n this.isInStartTransition = false;\n this.isInEndTransition = false;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index12.js","sources":["../src/providers/agora/SEIExtractor.ts"],"sourcesContent":["/**\n * SEI Data Extractor for Agora.\n *\n * Extracts animation data from Agora's H.264 SEI events.\n *\n * SEI Packet Format:\n * ```\n * [1B flags][4B msgLen (LE)][msgLen bytes compressed data]\n * ```\n *\n * Packet Flags:\n * - 0x01: Idle (no payload)\n * - 0x02: Start (first frame of session)\n * - 0x04: End (last frame of session)\n * - 0x08: Gzipped (payload is zlib compressed)\n * - 0x10: Transition (transition from idle to animation)\n * - 0x20: TransitionEnd (transition from animation back to idle)\n *\n * Key differences from LiveKit VP8Extractor:\n * - No VP8 header parsing needed\n * - No ALR (Application-Level Redundancy) - Agora handles reliability\n * - Uses native SEI events instead of RTCRtpScriptTransform\n * - Requires EBSP to RBSP conversion (H.264 emulation prevention byte removal)\n *\n * @internal\n * @packageDocumentation\n */\n\nimport type { AnimationTrackCallbacks } from '../../core/types';\nimport type { StreamStats } from '../../types';\nimport { logger } from '../../utils';\n\n/**\n * SEI Packet Flags - must match backend animation_track.go\n * @internal\n */\nconst PacketFlags = {\n /** Idle packet - no payload */\n Idle: 0x01,\n /** First frame of animation session */\n Start: 0x02,\n /** Last frame of animation session */\n End: 0x04,\n /** Payload is zlib compressed */\n Gzipped: 0x08,\n /** Transition from idle to animation */\n Transition: 0x10,\n /** Transition from animation back to idle */\n TransitionEnd: 0x20,\n} as const;\n\n/** SEI header size: 1 byte flags + 4 bytes length */\nconst SEI_HEADER_SIZE = 5;\n\n/** Frame sequence prefix size (4 bytes) for normal animation frames */\nconst FRAME_SEQ_SIZE = 4;\n\n// Default transition frame counts (internal constants)\nconst DEFAULT_TRANSITION_START_FRAMES = 8;\nconst DEFAULT_TRANSITION_END_FRAMES = 12;\n\n/**\n * SEIExtractor - Extracts animation data from Agora SEI events.\n *\n * Handles SEI header parsing, EBSP conversion, decompression, and callback dispatch.\n *\n * @internal\n */\nexport class SEIExtractor {\n /** @internal */\n private callbacks: AnimationTrackCallbacks | null = null;\n\n // Session state tracking\n /** @internal */\n private lastWasIdle = true;\n\n // Transition state tracking (to handle consecutive packets)\n /** @internal */\n private isInStartTransition = false;\n /** @internal */\n private isInEndTransition = false;\n\n // Transition frame counts\n /** @internal */\n private transitionStartFrameCount: number;\n /** @internal */\n private transitionEndFrameCount: number;\n\n // Statistics tracking\n /** @internal */\n private totalFrameCount = 0;\n /** @internal */\n private intervalFrameCount = 0;\n /** @internal */\n private lastStatsTime = 0;\n /** @internal */\n private statsInterval: ReturnType<typeof setInterval> | null = null;\n\n // Debug logging\n /** @internal */\n private debugLogging = false;\n\n constructor() {\n this.transitionStartFrameCount = DEFAULT_TRANSITION_START_FRAMES;\n this.transitionEndFrameCount = DEFAULT_TRANSITION_END_FRAMES;\n }\n\n /**\n * Initialize the extractor with callbacks.\n * @param callbacks - Callbacks to receive extracted data\n * @internal\n */\n initialize(callbacks: AnimationTrackCallbacks): void {\n this.callbacks = callbacks;\n this.startStatsInterval();\n }\n\n /**\n * Enable or disable debug logging.\n * @param enabled - Whether to enable debug logging\n * @internal\n */\n setDebugLogging(enabled: boolean): void {\n this.debugLogging = enabled;\n }\n\n /**\n * Handle SEI data from Agora.\n * Wraps async processing to not block the event handler.\n * @param seiData - Raw SEI payload\n * @internal\n */\n handleSEIData(seiData: Uint8Array): void {\n this.processSEIData(seiData).catch((error) => {\n logger.error('SEIExtractor', 'Failed to process SEI data:', error);\n this.logDebugInfo(seiData);\n });\n }\n\n /**\n * Convert EBSP (Encapsulated Byte Sequence Payload) to RBSP (Raw Byte Sequence Payload).\n * This removes H.264 emulation prevention bytes (0x03) inserted after 00 00 sequences.\n *\n * H.264 spec: When encoding, 0x03 is inserted after 00 00 to prevent start code emulation:\n * - 00 00 00 → 00 00 03 00\n * - 00 00 01 → 00 00 03 01\n * - 00 00 02 → 00 00 03 02\n * - 00 00 03 → 00 00 03 03\n *\n * @param data - EBSP data\n * @returns RBSP data\n * @internal\n */\n private ebspToRbsp(data: Uint8Array): Uint8Array {\n if (data.length === 0) {\n return data;\n }\n\n const result: number[] = [];\n let zeroCount = 0;\n\n for (let i = 0; i < data.length; i++) {\n const byte = data[i];\n\n // Check for emulation prevention byte: 00 00 03 XX where XX <= 0x03\n if (zeroCount >= 2 && byte === 0x03) {\n if (i + 1 < data.length) {\n const nextByte = data[i + 1];\n if (nextByte <= 0x03) {\n // Skip the 0x03 emulation prevention byte\n zeroCount = 0;\n continue;\n }\n }\n }\n\n // Track consecutive zeros\n if (byte === 0x00) {\n zeroCount++;\n } else {\n zeroCount = 0;\n }\n\n result.push(byte);\n }\n\n return new Uint8Array(result);\n }\n\n /**\n * Unescape zero bytes that were escaped by the server.\n * Escape scheme: 0x00 0xFF -> 0x00, 0xFF 0xFF -> 0xFF\n * This reverses the escaping done to avoid H.264 emulation prevention issues.\n *\n * @param data - Escaped data\n * @returns Unescaped data\n * @internal\n */\n private unescapeZeroBytes(data: Uint8Array): Uint8Array {\n const result: number[] = [];\n let i = 0;\n while (i < data.length) {\n if (i + 1 < data.length && data[i] === 0x00 && data[i + 1] === 0xff) {\n // 00 FF -> 00\n result.push(0x00);\n i += 2;\n } else if (\n i + 1 < data.length &&\n data[i] === 0xff &&\n data[i + 1] === 0xff\n ) {\n // FF FF -> FF\n result.push(0xff);\n i += 2;\n } else {\n result.push(data[i]);\n i++;\n }\n }\n return new Uint8Array(result);\n }\n\n /**\n * Decompress zlib data using DecompressionStream API.\n * Uses 'deflate' format (not gzip) to avoid H.264 emulation prevention issues\n * with gzip's mtime field containing 00 00 00 00.\n *\n * @param data - Zlib compressed data\n * @returns Decompressed data\n * @internal\n */\n private async decompressZlib(data: Uint8Array): Promise<Uint8Array> {\n const ds = new DecompressionStream('deflate');\n const writer = ds.writable.getWriter();\n // Create a copy to ensure we have a plain ArrayBuffer\n const copy = new Uint8Array(data);\n writer.write(copy);\n writer.close();\n\n const reader = ds.readable.getReader();\n const chunks: Uint8Array[] = [];\n let totalLength = 0;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n totalLength += value.length;\n }\n\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n\n return result;\n }\n\n /**\n * Process SEI data asynchronously.\n * @param seiData - Raw SEI payload\n * @internal\n */\n private async processSEIData(seiData: Uint8Array): Promise<void> {\n if (!this.callbacks) {\n return;\n }\n\n this.totalFrameCount++;\n this.intervalFrameCount++;\n\n // Remove H.264 emulation prevention bytes using EBSP to RBSP conversion\n const cleanedData = this.ebspToRbsp(seiData);\n\n // Parse SEI header: [1B flags][4B msgLen (LE)][payload]\n if (cleanedData.length < SEI_HEADER_SIZE) {\n logger.warn('SEIExtractor', `SEI data too short: ${cleanedData.length}`);\n return;\n }\n\n const flags = cleanedData[0];\n const msgLen = new DataView(\n cleanedData.buffer,\n cleanedData.byteOffset + 1,\n 4,\n ).getUint32(0, true);\n\n // Debug logging for first few packets\n if (\n this.debugLogging &&\n (this.totalFrameCount <= 5 || this.totalFrameCount % 50 === 0)\n ) {\n logger.info(\n 'SEIExtractor',\n `SEI packet #${this.totalFrameCount}: flags=0x${flags.toString(16)}, msgLen=${msgLen}, cleanedLen=${cleanedData.length}`,\n );\n }\n\n // Check for idle packet (no payload)\n const isIdle = (flags & PacketFlags.Idle) !== 0;\n if (isIdle || msgLen === 0) {\n if (!this.lastWasIdle) {\n this.lastWasIdle = true;\n // Reset transition states when idle\n this.isInStartTransition = false;\n this.isInEndTransition = false;\n this.callbacks.onIdleStart();\n }\n return;\n }\n\n // Extract payload - use all remaining bytes after header instead of msgLen\n // This handles cases where EP removal changes the data length\n const rawPayload = cleanedData.slice(SEI_HEADER_SIZE);\n\n // Unescape zero bytes that were escaped by the server\n const compressedPayload = this.unescapeZeroBytes(rawPayload);\n\n // Decompress if compressed using zlib\n let payload: Uint8Array;\n if ((flags & PacketFlags.Gzipped) !== 0) {\n payload = await this.decompressZlib(compressedPayload);\n } else {\n payload = compressedPayload;\n }\n\n // Check packet type flags\n const isTransition = (flags & PacketFlags.Transition) !== 0;\n const isTransitionEnd = (flags & PacketFlags.TransitionEnd) !== 0;\n const isStart = (flags & PacketFlags.Start) !== 0;\n const isEnd = (flags & PacketFlags.End) !== 0;\n\n // Extract protobuf data\n // Transition frames don't have frame sequence prefix\n let protobufData: ArrayBuffer;\n let frameSeq: number | undefined;\n\n if (isTransition || isTransitionEnd) {\n // Create a copy to ensure we have a plain ArrayBuffer\n protobufData = new Uint8Array(payload).buffer;\n } else {\n // Normal animation frame: [4B frameSeq][protobuf]\n if (payload.length < FRAME_SEQ_SIZE) {\n logger.warn('SEIExtractor', 'Payload too short for frame sequence');\n return;\n }\n frameSeq = new DataView(payload.buffer, payload.byteOffset, 4).getUint32(\n 0,\n true,\n );\n\n if (\n this.debugLogging &&\n (this.totalFrameCount <= 5 || this.totalFrameCount % 50 === 0)\n ) {\n logger.info('SEIExtractor', `Animation frame seq=${frameSeq}`);\n }\n\n // Create a copy of the protobuf data portion\n protobufData = new Uint8Array(payload.subarray(FRAME_SEQ_SIZE)).buffer;\n }\n\n // Handle transition packets\n // Only trigger transition on first packet of consecutive transition packets\n if (isTransition) {\n if (!this.isInStartTransition) {\n this.isInStartTransition = true;\n this.isInEndTransition = false;\n if (this.debugLogging) {\n logger.info('SEIExtractor', 'Transition START (first packet)');\n }\n this.callbacks.onTransition(\n protobufData,\n this.transitionStartFrameCount,\n );\n }\n return;\n }\n\n if (isTransitionEnd) {\n if (!this.isInEndTransition) {\n this.isInEndTransition = true;\n this.isInStartTransition = false;\n if (this.debugLogging) {\n logger.info('SEIExtractor', 'Transition END (first packet)');\n }\n this.callbacks.onTransitionEnd(\n protobufData,\n this.transitionEndFrameCount,\n );\n }\n return;\n }\n\n // Normal animation frame - clear transition states\n this.isInStartTransition = false;\n this.isInEndTransition = false;\n\n // Normal animation frame\n const isFirstFrame = this.lastWasIdle || isStart;\n this.lastWasIdle = false;\n\n // Session start callback\n if (isFirstFrame) {\n if (this.debugLogging) {\n logger.info('SEIExtractor', 'Session start');\n }\n this.callbacks.onSessionStart?.();\n }\n\n // Call animation data callback\n this.callbacks.onAnimationData(protobufData, {\n frameSeq,\n isStart: isFirstFrame,\n isEnd,\n isIdle: false,\n isRecovered: false, // Agora handles reliability internally\n });\n\n // Session end callback\n if (isEnd) {\n if (this.debugLogging) {\n logger.info('SEIExtractor', 'Session end');\n }\n this.callbacks.onSessionEnd?.();\n }\n }\n\n /**\n * Log debug information for failed SEI processing.\n * @internal\n */\n private logDebugInfo(seiData: Uint8Array): void {\n const totalLen = seiData.length;\n logger.error('SEIExtractor', `Total SEI length: ${totalLen}`);\n if (seiData.length >= 20) {\n const hexBytes = Array.from(seiData.slice(0, 20))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join(' ');\n logger.error('SEIExtractor', 'First 20 bytes of SEI:', hexBytes);\n }\n if (seiData.length >= SEI_HEADER_SIZE) {\n const flags = seiData[0];\n const msgLen = new DataView(\n seiData.buffer,\n seiData.byteOffset + 1,\n 4,\n ).getUint32(0, true);\n logger.error(\n 'SEIExtractor',\n `SEI header: flags=0x${flags.toString(16)}, msgLen=${msgLen}`,\n );\n }\n }\n\n /**\n * Start the statistics reporting interval.\n * @internal\n */\n private startStatsInterval(): void {\n this.lastStatsTime = Date.now();\n this.statsInterval = setInterval(() => {\n const now = Date.now();\n const elapsedSeconds = (now - this.lastStatsTime) / 1000;\n\n if (elapsedSeconds > 0 && this.callbacks) {\n const fps = this.intervalFrameCount / elapsedSeconds;\n\n const stats: StreamStats = {\n framesPerSec: Math.round(fps * 10) / 10,\n totalFrames: this.totalFrameCount,\n framesSent: this.totalFrameCount,\n framesLost: 0,\n framesRecovered: 0,\n framesDropped: 0,\n framesOutOfOrder: 0,\n framesDuplicate: 0,\n lastRenderedSeq: -1,\n };\n\n this.callbacks.onStreamStats(stats);\n }\n\n this.intervalFrameCount = 0;\n this.lastStatsTime = now;\n }, 1000);\n }\n\n /**\n * Stop the statistics reporting interval.\n * @internal\n */\n private stopStatsInterval(): void {\n if (this.statsInterval) {\n clearInterval(this.statsInterval);\n this.statsInterval = null;\n }\n }\n\n /**\n * Reset session state.\n * Call this when reconnecting or starting a new session.\n * @internal\n */\n reset(): void {\n this.lastWasIdle = true;\n this.isInStartTransition = false;\n this.isInEndTransition = false;\n this.totalFrameCount = 0;\n this.intervalFrameCount = 0;\n this.lastStatsTime = Date.now();\n }\n\n /**\n * Dispose the extractor and release resources.\n * @internal\n */\n dispose(): void {\n this.stopStatsInterval();\n this.callbacks = null;\n this.lastWasIdle = true;\n this.isInStartTransition = false;\n this.isInEndTransition = false;\n }\n}\n"],"names":[],"mappings":";;;;AAoCA,MAAM,cAAc;AAAA;AAAA,EAElB,MAAM;AAAA;AAAA,EAEN,OAAO;AAAA;AAAA,EAEP,KAAK;AAAA;AAAA,EAEL,SAAS;AAAA;AAAA,EAET,YAAY;AAAA;AAAA,EAEZ,eAAe;AACjB;AAGA,MAAM,kBAAkB;AAGxB,MAAM,iBAAiB;AAGvB,MAAM,kCAAkC;AACxC,MAAM,gCAAgC;AAS/B,MAAM,aAAa;AAAA,EAkCxB,cAAc;AAhCN;AAAA,qCAA4C;AAI5C;AAAA;AAAA,uCAAc;AAId;AAAA;AAAA,+CAAsB;AAEtB;AAAA,6CAAoB;AAIpB;AAAA;AAAA;AAEA;AAAA;AAIA;AAAA;AAAA,2CAAkB;AAElB;AAAA,8CAAqB;AAErB;AAAA,yCAAgB;AAEhB;AAAA,yCAAuD;AAIvD;AAAA;AAAA,wCAAe;AAGrB,SAAK,4BAA4B;AACjC,SAAK,0BAA0B;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,WAAW,WAA0C;AACnD,SAAK,YAAY;AACjB,SAAK,mBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAgB,SAAwB;AACtC,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,cAAc,SAA2B;AACvC,SAAK,eAAe,OAAO,EAAE,MAAM,CAAC,UAAU;AAC5C,aAAO,MAAM,gBAAgB,+BAA+B,KAAK;AACjE,WAAK,aAAa,OAAO;AAAA,IAC3B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBQ,WAAW,MAA8B;AAC/C,QAAI,KAAK,WAAW,GAAG;AACrB,aAAO;AAAA,IACT;AAEA,UAAM,SAAmB,CAAA;AACzB,QAAI,YAAY;AAEhB,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,YAAM,OAAO,KAAK,CAAC;AAGnB,UAAI,aAAa,KAAK,SAAS,GAAM;AACnC,YAAI,IAAI,IAAI,KAAK,QAAQ;AACvB,gBAAM,WAAW,KAAK,IAAI,CAAC;AAC3B,cAAI,YAAY,GAAM;AAEpB,wBAAY;AACZ;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAGA,UAAI,SAAS,GAAM;AACjB;AAAA,MACF,OAAO;AACL,oBAAY;AAAA,MACd;AAEA,aAAO,KAAK,IAAI;AAAA,IAClB;AAEA,WAAO,IAAI,WAAW,MAAM;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,kBAAkB,MAA8B;AACtD,UAAM,SAAmB,CAAA;AACzB,QAAI,IAAI;AACR,WAAO,IAAI,KAAK,QAAQ;AACtB,UAAI,IAAI,IAAI,KAAK,UAAU,KAAK,CAAC,MAAM,KAAQ,KAAK,IAAI,CAAC,MAAM,KAAM;AAEnE,eAAO,KAAK,CAAI;AAChB,aAAK;AAAA,MACP,WACE,IAAI,IAAI,KAAK,UACb,KAAK,CAAC,MAAM,OACZ,KAAK,IAAI,CAAC,MAAM,KAChB;AAEA,eAAO,KAAK,GAAI;AAChB,aAAK;AAAA,MACP,OAAO;AACL,eAAO,KAAK,KAAK,CAAC,CAAC;AACnB;AAAA,MACF;AAAA,IACF;AACA,WAAO,IAAI,WAAW,MAAM;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,eAAe,MAAuC;AAClE,UAAM,KAAK,IAAI,oBAAoB,SAAS;AAC5C,UAAM,SAAS,GAAG,SAAS,UAAA;AAE3B,UAAM,OAAO,IAAI,WAAW,IAAI;AAChC,WAAO,MAAM,IAAI;AACjB,WAAO,MAAA;AAEP,UAAM,SAAS,GAAG,SAAS,UAAA;AAC3B,UAAM,SAAuB,CAAA;AAC7B,QAAI,cAAc;AAElB,WAAO,MAAM;AACX,YAAM,EAAE,MAAM,MAAA,IAAU,MAAM,OAAO,KAAA;AACrC,UAAI,KAAM;AACV,aAAO,KAAK,KAAK;AACjB,qBAAe,MAAM;AAAA,IACvB;AAEA,UAAM,SAAS,IAAI,WAAW,WAAW;AACzC,QAAI,SAAS;AACb,eAAW,SAAS,QAAQ;AAC1B,aAAO,IAAI,OAAO,MAAM;AACxB,gBAAU,MAAM;AAAA,IAClB;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,eAAe,SAAoC;;AAC/D,QAAI,CAAC,KAAK,WAAW;AACnB;AAAA,IACF;AAEA,SAAK;AACL,SAAK;AAGL,UAAM,cAAc,KAAK,WAAW,OAAO;AAG3C,QAAI,YAAY,SAAS,iBAAiB;AACxC,aAAO,KAAK,gBAAgB,uBAAuB,YAAY,MAAM,EAAE;AACvE;AAAA,IACF;AAEA,UAAM,QAAQ,YAAY,CAAC;AAC3B,UAAM,SAAS,IAAI;AAAA,MACjB,YAAY;AAAA,MACZ,YAAY,aAAa;AAAA,MACzB;AAAA,IAAA,EACA,UAAU,GAAG,IAAI;AAGnB,QACE,KAAK,iBACJ,KAAK,mBAAmB,KAAK,KAAK,kBAAkB,OAAO,IAC5D;AACA,aAAO;AAAA,QACL;AAAA,QACA,eAAe,KAAK,eAAe,aAAa,MAAM,SAAS,EAAE,CAAC,YAAY,MAAM,gBAAgB,YAAY,MAAM;AAAA,MAAA;AAAA,IAE1H;AAGA,UAAM,UAAU,QAAQ,YAAY,UAAU;AAC9C,QAAI,UAAU,WAAW,GAAG;AAC1B,UAAI,CAAC,KAAK,aAAa;AACrB,aAAK,cAAc;AAEnB,aAAK,sBAAsB;AAC3B,aAAK,oBAAoB;AACzB,aAAK,UAAU,YAAA;AAAA,MACjB;AACA;AAAA,IACF;AAIA,UAAM,aAAa,YAAY,MAAM,eAAe;AAGpD,UAAM,oBAAoB,KAAK,kBAAkB,UAAU;AAG3D,QAAI;AACJ,SAAK,QAAQ,YAAY,aAAa,GAAG;AACvC,gBAAU,MAAM,KAAK,eAAe,iBAAiB;AAAA,IACvD,OAAO;AACL,gBAAU;AAAA,IACZ;AAGA,UAAM,gBAAgB,QAAQ,YAAY,gBAAgB;AAC1D,UAAM,mBAAmB,QAAQ,YAAY,mBAAmB;AAChE,UAAM,WAAW,QAAQ,YAAY,WAAW;AAChD,UAAM,SAAS,QAAQ,YAAY,SAAS;AAI5C,QAAI;AACJ,QAAI;AAEJ,QAAI,gBAAgB,iBAAiB;AAEnC,qBAAe,IAAI,WAAW,OAAO,EAAE;AAAA,IACzC,OAAO;AAEL,UAAI,QAAQ,SAAS,gBAAgB;AACnC,eAAO,KAAK,gBAAgB,sCAAsC;AAClE;AAAA,MACF;AACA,iBAAW,IAAI,SAAS,QAAQ,QAAQ,QAAQ,YAAY,CAAC,EAAE;AAAA,QAC7D;AAAA,QACA;AAAA,MAAA;AAGF,UACE,KAAK,iBACJ,KAAK,mBAAmB,KAAK,KAAK,kBAAkB,OAAO,IAC5D;AACA,eAAO,KAAK,gBAAgB,uBAAuB,QAAQ,EAAE;AAAA,MAC/D;AAGA,qBAAe,IAAI,WAAW,QAAQ,SAAS,cAAc,CAAC,EAAE;AAAA,IAClE;AAIA,QAAI,cAAc;AAChB,UAAI,CAAC,KAAK,qBAAqB;AAC7B,aAAK,sBAAsB;AAC3B,aAAK,oBAAoB;AACzB,YAAI,KAAK,cAAc;AACrB,iBAAO,KAAK,gBAAgB,iCAAiC;AAAA,QAC/D;AACA,aAAK,UAAU;AAAA,UACb;AAAA,UACA,KAAK;AAAA,QAAA;AAAA,MAET;AACA;AAAA,IACF;AAEA,QAAI,iBAAiB;AACnB,UAAI,CAAC,KAAK,mBAAmB;AAC3B,aAAK,oBAAoB;AACzB,aAAK,sBAAsB;AAC3B,YAAI,KAAK,cAAc;AACrB,iBAAO,KAAK,gBAAgB,+BAA+B;AAAA,QAC7D;AACA,aAAK,UAAU;AAAA,UACb;AAAA,UACA,KAAK;AAAA,QAAA;AAAA,MAET;AACA;AAAA,IACF;AAGA,SAAK,sBAAsB;AAC3B,SAAK,oBAAoB;AAGzB,UAAM,eAAe,KAAK,eAAe;AACzC,SAAK,cAAc;AAGnB,QAAI,cAAc;AAChB,UAAI,KAAK,cAAc;AACrB,eAAO,KAAK,gBAAgB,eAAe;AAAA,MAC7C;AACA,uBAAK,WAAU,mBAAf;AAAA,IACF;AAGA,SAAK,UAAU,gBAAgB,cAAc;AAAA,MAC3C;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA,QAAQ;AAAA,MACR,aAAa;AAAA;AAAA,IAAA,CACd;AAGD,QAAI,OAAO;AACT,UAAI,KAAK,cAAc;AACrB,eAAO,KAAK,gBAAgB,aAAa;AAAA,MAC3C;AACA,uBAAK,WAAU,iBAAf;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,aAAa,SAA2B;AAC9C,UAAM,WAAW,QAAQ;AACzB,WAAO,MAAM,gBAAgB,qBAAqB,QAAQ,EAAE;AAC5D,QAAI,QAAQ,UAAU,IAAI;AACxB,YAAM,WAAW,MAAM,KAAK,QAAQ,MAAM,GAAG,EAAE,CAAC,EAC7C,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAC1C,KAAK,GAAG;AACX,aAAO,MAAM,gBAAgB,0BAA0B,QAAQ;AAAA,IACjE;AACA,QAAI,QAAQ,UAAU,iBAAiB;AACrC,YAAM,QAAQ,QAAQ,CAAC;AACvB,YAAM,SAAS,IAAI;AAAA,QACjB,QAAQ;AAAA,QACR,QAAQ,aAAa;AAAA,QACrB;AAAA,MAAA,EACA,UAAU,GAAG,IAAI;AACnB,aAAO;AAAA,QACL;AAAA,QACA,uBAAuB,MAAM,SAAS,EAAE,CAAC,YAAY,MAAM;AAAA,MAAA;AAAA,IAE/D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,qBAA2B;AACjC,SAAK,gBAAgB,KAAK,IAAA;AAC1B,SAAK,gBAAgB,YAAY,MAAM;AACrC,YAAM,MAAM,KAAK,IAAA;AACjB,YAAM,kBAAkB,MAAM,KAAK,iBAAiB;AAEpD,UAAI,iBAAiB,KAAK,KAAK,WAAW;AACxC,cAAM,MAAM,KAAK,qBAAqB;AAEtC,cAAM,QAAqB;AAAA,UACzB,cAAc,KAAK,MAAM,MAAM,EAAE,IAAI;AAAA,UACrC,aAAa,KAAK;AAAA,UAClB,YAAY,KAAK;AAAA,UACjB,YAAY;AAAA,UACZ,iBAAiB;AAAA,UACjB,eAAe;AAAA,UACf,kBAAkB;AAAA,UAClB,iBAAiB;AAAA,UACjB,iBAAiB;AAAA,QAAA;AAGnB,aAAK,UAAU,cAAc,KAAK;AAAA,MACpC;AAEA,WAAK,qBAAqB;AAC1B,WAAK,gBAAgB;AAAA,IACvB,GAAG,GAAI;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,oBAA0B;AAChC,QAAI,KAAK,eAAe;AACtB,oBAAc,KAAK,aAAa;AAChC,WAAK,gBAAgB;AAAA,IACvB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAc;AACZ,SAAK,cAAc;AACnB,SAAK,sBAAsB;AAC3B,SAAK,oBAAoB;AACzB,SAAK,kBAAkB;AACvB,SAAK,qBAAqB;AAC1B,SAAK,gBAAgB,KAAK,IAAA;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAgB;AACd,SAAK,kBAAA;AACL,SAAK,YAAY;AACjB,SAAK,cAAc;AACnB,SAAK,sBAAsB;AAC3B,SAAK,oBAAoB;AAAA,EAC3B;AACF;"}
|
package/dist/index14.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index14.js","sources":["../src/providers/livekit/animation-transform.ts"],"sourcesContent":["/**\n * Animation Transform for Video Track.\n *\n * Uses RTCRtpScriptTransform to extract animation data from the video track.\n * The video track carries animation data in binary format (not actual video).\n *\n * @internal\n * @packageDocumentation\n */\n\nimport type { AnimationTransformEvent } from './types';\nimport { logger } from '../../utils';\n\nexport { METADATA_FIXED_HEADER_SIZE, PACKET_FLAGS } from './types';\n\n/**\n * Check if browser supports RTCRtpScriptTransform.\n * @returns true if RTCRtpScriptTransform is available\n * @internal\n */\nexport function supportsScriptTransform(): boolean {\n return typeof RTCRtpScriptTransform !== 'undefined';\n}\n\n/**\n * Create RTCRtpScriptTransform for animation receiver (video track).\n * @param workerOrUrl - Either a Worker instance or a URL string to the worker script\n * @param onEvent - Optional callback for transform events\n * @returns RTCRtpScriptTransform instance\n * @internal\n */\nexport function createAnimationReceiverTransform(\n workerOrUrl: Worker | string | URL,\n onEvent?: (evt: AnimationTransformEvent) => void
|
|
1
|
+
{"version":3,"file":"index14.js","sources":["../src/providers/livekit/animation-transform.ts"],"sourcesContent":["/**\n * Animation Transform for Video Track.\n *\n * Uses RTCRtpScriptTransform to extract animation data from the video track.\n * The video track carries animation data in binary format (not actual video).\n *\n * @internal\n * @packageDocumentation\n */\n\nimport type { AnimationTransformEvent } from './types';\nimport { logger } from '../../utils';\n\nexport { METADATA_FIXED_HEADER_SIZE, PACKET_FLAGS } from './types';\n\n/**\n * Check if browser supports RTCRtpScriptTransform.\n * @returns true if RTCRtpScriptTransform is available\n * @internal\n */\nexport function supportsScriptTransform(): boolean {\n return typeof RTCRtpScriptTransform !== 'undefined';\n}\n\n/**\n * Create RTCRtpScriptTransform for animation receiver (video track).\n * @param workerOrUrl - Either a Worker instance or a URL string to the worker script\n * @param onEvent - Optional callback for transform events\n * @returns RTCRtpScriptTransform instance\n * @internal\n */\nexport function createAnimationReceiverTransform(\n workerOrUrl: Worker | string | URL,\n onEvent?: (evt: AnimationTransformEvent) => void,\n): RTCRtpScriptTransform {\n // Create worker if URL is provided, otherwise use the provided Worker\n const worker =\n workerOrUrl instanceof Worker\n ? workerOrUrl\n : new Worker(workerOrUrl, { type: 'module', name: 'animation-receiver' });\n\n worker.onmessage = (event) => {\n const { type } = event.data;\n\n if (type === 'ready') {\n onEvent?.({ type: 'ready', operation: 'receiver' });\n } else if (type === 'metadata') {\n onEvent?.(event.data);\n } else if (type === 'animation') {\n onEvent?.({\n type: 'animation',\n protobufData: event.data.protobufData,\n flags: event.data.flags,\n isIdle: event.data.isIdle,\n isStart: event.data.isStart,\n isEnd: event.data.isEnd,\n frameSeq: event.data.frameSeq,\n isRecovered: event.data.isRecovered,\n });\n } else if (type === 'transition') {\n onEvent?.({\n type: 'transition',\n protobufData: event.data.protobufData,\n flags: event.data.flags,\n });\n } else if (type === 'transitionEnd') {\n onEvent?.({\n type: 'transitionEnd',\n protobufData: event.data.protobufData,\n flags: event.data.flags,\n });\n } else if (type === 'idleStart') {\n onEvent?.({ type: 'idleStart' });\n } else if (type === 'error') {\n logger.error('AnimationTransform', 'Error:', event.data.error);\n onEvent?.(event.data);\n }\n };\n\n worker.onerror = (event) => {\n logger.error('AnimationTransform', 'Worker error:', event.message);\n };\n\n return new RTCRtpScriptTransform(worker, { operation: 'receiver' });\n}\n"],"names":[],"mappings":";AA+BO,SAAS,iCACd,aACA,SACuB;AAEvB,QAAM,SACJ,uBAAuB,SACnB,cACA,IAAI,OAAO,aAAa,EAAE,MAAM,UAAU,MAAM,qBAAA,CAAsB;AAE5E,SAAO,YAAY,CAAC,UAAU;AAC5B,UAAM,EAAE,SAAS,MAAM;AAEvB,QAAI,SAAS,SAAS;AACpB,yCAAU,EAAE,MAAM,SAAS,WAAW;IACxC,WAAW,SAAS,YAAY;AAC9B,yCAAU,MAAM;AAAA,IAClB,WAAW,SAAS,aAAa;AAC/B,yCAAU;AAAA,QACR,MAAM;AAAA,QACN,cAAc,MAAM,KAAK;AAAA,QACzB,OAAO,MAAM,KAAK;AAAA,QAClB,QAAQ,MAAM,KAAK;AAAA,QACnB,SAAS,MAAM,KAAK;AAAA,QACpB,OAAO,MAAM,KAAK;AAAA,QAClB,UAAU,MAAM,KAAK;AAAA,QACrB,aAAa,MAAM,KAAK;AAAA,MAAA;AAAA,IAE5B,WAAW,SAAS,cAAc;AAChC,yCAAU;AAAA,QACR,MAAM;AAAA,QACN,cAAc,MAAM,KAAK;AAAA,QACzB,OAAO,MAAM,KAAK;AAAA,MAAA;AAAA,IAEtB,WAAW,SAAS,iBAAiB;AACnC,yCAAU;AAAA,QACR,MAAM;AAAA,QACN,cAAc,MAAM,KAAK;AAAA,QACzB,OAAO,MAAM,KAAK;AAAA,MAAA;AAAA,IAEtB,WAAW,SAAS,aAAa;AAC/B,yCAAU,EAAE,MAAM;IACpB,WAAW,SAAS,SAAS;AAC3B,aAAO,MAAM,sBAAsB,UAAU,MAAM,KAAK,KAAK;AAC7D,yCAAU,MAAM;AAAA,IAClB;AAAA,EACF;AAEA,SAAO,UAAU,CAAC,UAAU;AAC1B,WAAO,MAAM,sBAAsB,iBAAiB,MAAM,OAAO;AAAA,EACnE;AAEA,SAAO,IAAI,sBAAsB,QAAQ,EAAE,WAAW,YAAY;AACpE;"}
|
package/dist/index15.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const jsContent = 'const VP8_FRAME_HEADER_SIZE = 10;\nconst METADATA_FIXED_HEADER_SIZE = 5;\nconst PACKET_FLAG_IDLE = 1;\nconst PACKET_FLAG_START = 2;\nconst PACKET_FLAG_END = 4;\nconst PACKET_FLAG_GZIPPED = 8;\nconst PACKET_FLAG_TRANSITION = 16;\nconst PACKET_FLAG_TRANSITION_END = 32;\nconst PACKET_FLAG_REDUNDANT = 64;\nlet receiverMetaCount = 0;\nlet lastLogTime = 0;\nlet totalFrames = 0;\nlet framesWithMeta = 0;\nlet wasIdle = false;\nlet lastReceivedTimestamp = null;\nlet framesRecovered = 0;\nlet framesLost = 0;\nconst EXPECTED_TIMESTAMP_INCREMENT = 3600;\nlet lastRenderedSeq = -1;\nlet framesOutOfOrder = 0;\nlet framesDuplicate = 0;\nlet framesDropped = 0;\nlet framesSent = 0;\nfunction parseMetadataHeader(data) {\n if (data.byteLength < METADATA_FIXED_HEADER_SIZE) return null;\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n const flags = data[0];\n const msgLen = view.getUint32(1, true);\n const headerSize = METADATA_FIXED_HEADER_SIZE + msgLen;\n if (data.byteLength < headerSize) return null;\n const compressedData = data.slice(METADATA_FIXED_HEADER_SIZE, METADATA_FIXED_HEADER_SIZE + msgLen);\n const hasRedundant = (flags & PACKET_FLAG_REDUNDANT) !== 0;\n return {\n meta: {\n flags,\n protobufLength: msgLen,\n protobufData: compressedData,\n // This is still compressed at this point\n hasRedundant,\n redundantLength: 0,\n // Will be determined after decompression\n redundantData: null\n // Will be extracted after decompression\n },\n headerSize\n };\n}\nfunction isIdlePacket(flags) {\n return (flags & PACKET_FLAG_IDLE) !== 0;\n}\nfunction isStartPacket(flags) {\n return (flags & PACKET_FLAG_START) !== 0;\n}\nfunction isEndPacket(flags) {\n return (flags & PACKET_FLAG_END) !== 0;\n}\nfunction isGzipped(flags) {\n return (flags & PACKET_FLAG_GZIPPED) !== 0;\n}\nfunction isTransitionPacket(flags) {\n return (flags & PACKET_FLAG_TRANSITION) !== 0;\n}\nfunction isTransitionEndPacket(flags) {\n return (flags & PACKET_FLAG_TRANSITION_END) !== 0;\n}\nasync function decompressGzip(data) {\n const ds = new DecompressionStream("gzip");\n const writer = ds.writable.getWriter();\n const copy = new Uint8Array(data);\n writer.write(copy);\n writer.close();\n const reader = ds.readable.getReader();\n const chunks = [];\n let totalLength = 0;\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n totalLength += value.length;\n }\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n return result;\n}\nlet totalCompressedBytes = 0;\nlet totalUncompressedBytes = 0;\nfunction parseALRPayload(decompressed, hasRedundant) {\n if (decompressed.byteLength < 4) return null;\n const view = new DataView(decompressed.buffer, decompressed.byteOffset, decompressed.byteLength);\n let offset = 0;\n const frameSeq = view.getUint32(offset, true);\n offset += 4;\n if (!hasRedundant) {\n const currentData2 = decompressed.slice(offset);\n return { frameSeq, currentData: currentData2, prev1Data: null, prev2Data: null };\n }\n if (decompressed.byteLength < offset + 4) return null;\n const currentLen = view.getUint32(offset, true);\n offset += 4;\n if (decompressed.byteLength < offset + currentLen) return null;\n const currentData = decompressed.slice(offset, offset + currentLen);\n offset += currentLen;\n let prev1Data = null;\n if (decompressed.byteLength >= offset + 4) {\n const prev1Len = view.getUint32(offset, true);\n offset += 4;\n if (prev1Len > 0 && decompressed.byteLength >= offset + prev1Len) {\n prev1Data = decompressed.slice(offset, offset + prev1Len);\n offset += prev1Len;\n }\n }\n let prev2Data = null;\n if (decompressed.byteLength >= offset + 4) {\n const prev2Len = view.getUint32(offset, true);\n offset += 4;\n if (prev2Len > 0 && decompressed.byteLength >= offset + prev2Len) {\n prev2Data = decompressed.slice(offset, offset + prev2Len);\n }\n }\n return { frameSeq, currentData, prev1Data, prev2Data };\n}\nfunction sendAnimationToMainThread(protobufData, flags, frameSeq, isRecovered = false) {\n const isIdle = isIdlePacket(flags);\n const isStart = isStartPacket(flags);\n const isEnd = isEndPacket(flags);\n if (frameSeq <= lastRenderedSeq && lastRenderedSeq !== -1 && !isStart) {\n if (frameSeq === lastRenderedSeq) {\n framesDuplicate++;\n } else {\n framesOutOfOrder++;\n }\n return false;\n }\n if (lastRenderedSeq !== -1 && frameSeq > lastRenderedSeq + 1 && !isStart) {\n const gap = frameSeq - lastRenderedSeq - 1;\n framesDropped += gap;\n }\n framesSent++;\n lastRenderedSeq = frameSeq;\n const protobufBuffer = new ArrayBuffer(protobufData.byteLength);\n new Uint8Array(protobufBuffer).set(protobufData);\n self.postMessage({\n type: "animation",\n flags,\n isIdle,\n isStart,\n isEnd,\n isRecovered,\n frameSeq,\n protobufData: protobufBuffer\n }, { transfer: [protobufBuffer] });\n return true;\n}\nfunction receiverTransform(frame, _controller) {\n totalFrames++;\n const data = new Uint8Array(frame.data);\n const currentTimestamp = frame.timestamp;\n if (data.length <= VP8_FRAME_HEADER_SIZE) {\n return;\n }\n const animData = data.subarray(VP8_FRAME_HEADER_SIZE);\n const parsed = parseMetadataHeader(animData);\n if (parsed) {\n const { meta } = parsed;\n framesWithMeta++;\n receiverMetaCount++;\n const isIdle = isIdlePacket(meta.flags);\n const isStart = isStartPacket(meta.flags);\n const isEnd = isEndPacket(meta.flags);\n if (lastReceivedTimestamp !== null && !isIdle && !isStart) {\n const timestampDelta = currentTimestamp - lastReceivedTimestamp;\n if (timestampDelta > EXPECTED_TIMESTAMP_INCREMENT * 1.5) {\n const missedFrames = Math.round(timestampDelta / EXPECTED_TIMESTAMP_INCREMENT) - 1;\n framesLost += missedFrames;\n if (meta.hasRedundant && isGzipped(meta.flags)) {\n totalCompressedBytes += meta.protobufData.byteLength;\n decompressGzip(meta.protobufData).then((decompressed) => {\n totalUncompressedBytes += decompressed.byteLength;\n const parsed2 = parseALRPayload(decompressed, true);\n if (parsed2) {\n const currentSeq = parsed2.frameSeq;\n const framesToRecover = [];\n if (missedFrames >= 2 && parsed2.prev2Data) {\n framesToRecover.push({ data: parsed2.prev2Data, seq: currentSeq - 2 });\n }\n if (missedFrames >= 1 && parsed2.prev1Data) {\n framesToRecover.push({ data: parsed2.prev1Data, seq: currentSeq - 1 });\n }\n const recovered = framesToRecover.length;\n if (recovered > 0) {\n framesRecovered += recovered;\n for (const frame2 of framesToRecover) {\n sendAnimationToMainThread(frame2.data, meta.flags & ~PACKET_FLAG_REDUNDANT, frame2.seq, true);\n }\n }\n sendAnimationToMainThread(parsed2.currentData, meta.flags & ~PACKET_FLAG_REDUNDANT, currentSeq, false);\n }\n }).catch((err) => {\n console.error(`[Animation Worker] ALR decompression error:`, err);\n });\n lastReceivedTimestamp = currentTimestamp;\n return;\n }\n }\n }\n if (!isIdle) {\n lastReceivedTimestamp = currentTimestamp;\n }\n if (isStart) {\n lastReceivedTimestamp = currentTimestamp;\n framesRecovered = 0;\n framesLost = 0;\n lastRenderedSeq = -1;\n framesOutOfOrder = 0;\n framesDuplicate = 0;\n framesDropped = 0;\n framesSent = 0;\n }\n const isTransition = isTransitionPacket(meta.flags);\n if (isIdle) {\n if (!wasIdle) {\n self.postMessage({ type: "idleStart" });\n wasIdle = true;\n }\n } else if (isTransition && meta.protobufLength > 0) {\n wasIdle = false;\n const gzipped = isGzipped(meta.flags);\n if (gzipped) {\n totalCompressedBytes += meta.protobufData.byteLength;\n decompressGzip(meta.protobufData).then((decompressed) => {\n totalUncompressedBytes += decompressed.byteLength;\n const protobufBuffer = new ArrayBuffer(decompressed.byteLength);\n new Uint8Array(protobufBuffer).set(decompressed);\n self.postMessage({\n type: "transition",\n flags: meta.flags,\n protobufData: protobufBuffer\n }, { transfer: [protobufBuffer] });\n }).catch((err) => {\n console.error(`[Animation Worker] Gzip decompress error (transition):`, err);\n });\n } else {\n const protobufBuffer = new ArrayBuffer(meta.protobufData.byteLength);\n new Uint8Array(protobufBuffer).set(meta.protobufData);\n self.postMessage({\n type: "transition",\n flags: meta.flags,\n protobufData: protobufBuffer\n }, { transfer: [protobufBuffer] });\n }\n } else if (isTransitionEndPacket(meta.flags) && meta.protobufLength > 0) {\n const gzipped = isGzipped(meta.flags);\n if (gzipped) {\n totalCompressedBytes += meta.protobufData.byteLength;\n decompressGzip(meta.protobufData).then((decompressed) => {\n totalUncompressedBytes += decompressed.byteLength;\n const protobufBuffer = new ArrayBuffer(decompressed.byteLength);\n new Uint8Array(protobufBuffer).set(decompressed);\n self.postMessage({\n type: "transitionEnd",\n flags: meta.flags,\n protobufData: protobufBuffer\n }, { transfer: [protobufBuffer] });\n }).catch((err) => {\n console.error(`[Animation Worker] Gzip decompress error (transitionEnd):`, err);\n });\n } else {\n const protobufBuffer = new ArrayBuffer(meta.protobufData.byteLength);\n new Uint8Array(protobufBuffer).set(meta.protobufData);\n self.postMessage({\n type: "transitionEnd",\n flags: meta.flags,\n protobufData: protobufBuffer\n }, { transfer: [protobufBuffer] });\n }\n } else if (meta.protobufLength > 0) {\n if (wasIdle) {\n wasIdle = false;\n }\n const gzipped = isGzipped(meta.flags);\n if (gzipped) {\n totalCompressedBytes += meta.protobufData.byteLength;\n decompressGzip(meta.protobufData).then((decompressed) => {\n totalUncompressedBytes += decompressed.byteLength;\n const parsed2 = parseALRPayload(decompressed, meta.hasRedundant);\n if (parsed2) {\n sendAnimationToMainThread(parsed2.currentData, meta.flags & ~PACKET_FLAG_REDUNDANT, parsed2.frameSeq, false);\n }\n }).catch(() => {\n });\n } else {\n const protobufBuffer = new ArrayBuffer(meta.protobufData.byteLength);\n new Uint8Array(protobufBuffer).set(meta.protobufData);\n self.postMessage({\n type: "animation",\n flags: meta.flags,\n isIdle: false,\n isStart,\n isEnd,\n frameSeq: -1,\n // Unknown sequence\n protobufData: protobufBuffer\n }, { transfer: [protobufBuffer] });\n }\n }\n const now = performance.now();\n if (now - lastLogTime > 1e3) {\n lastLogTime = now;\n self.postMessage({\n type: "metadata",\n protobufLength: meta.protobufLength,\n framesPerSec: receiverMetaCount,\n totalFrames,\n framesWithMeta,\n framesLost,\n framesRecovered,\n framesOutOfOrder,\n framesDuplicate,\n framesDropped,\n framesSent,\n lastRenderedSeq\n });\n receiverMetaCount = 0;\n }\n }\n}\nself.onrtctransform = (event) => {\n const transformer = event.transformer;\n const options = transformer.options;\n try {\n if (options.operation === "receiver") {\n receiverMetaCount = 0;\n lastLogTime = 0;\n totalFrames = 0;\n framesWithMeta = 0;\n wasIdle = false;\n lastReceivedTimestamp = null;\n framesRecovered = 0;\n framesLost = 0;\n lastRenderedSeq = -1;\n framesOutOfOrder = 0;\n framesDuplicate = 0;\n framesDropped = 0;\n framesSent = 0;\n transformer.readable.pipeThrough(new TransformStream({ transform: receiverTransform })).pipeTo(transformer.writable).catch((err) => {\n console.error("[Animation Worker] Pipeline error:", err);\n self.postMessage({ type: "error", error: `Animation receiver pipe error: ${err}` });\n });\n self.postMessage({ type: "ready", operation: "receiver" });\n }\n } catch (err) {\n console.error("[Animation Worker] Setup error:", err);\n self.postMessage({ type: "error", error: `Animation transform setup error: ${err}` });\n }\n};\nself.onmessage = (event) => {\n const { type } = event.data;\n if (type === "init") {\n self.postMessage({ type: "initialized" });\n }\n};\n//# sourceMappingURL=animation-worker-CUXZycUw.js.map\n';
|
|
1
|
+
const jsContent = 'const VP8_FRAME_HEADER_SIZE = 10;\nconst METADATA_FIXED_HEADER_SIZE = 5;\nconst PACKET_FLAG_IDLE = 1;\nconst PACKET_FLAG_START = 2;\nconst PACKET_FLAG_END = 4;\nconst PACKET_FLAG_GZIPPED = 8;\nconst PACKET_FLAG_TRANSITION = 16;\nconst PACKET_FLAG_TRANSITION_END = 32;\nconst PACKET_FLAG_REDUNDANT = 64;\nlet receiverMetaCount = 0;\nlet lastLogTime = 0;\nlet totalFrames = 0;\nlet framesWithMeta = 0;\nlet wasIdle = false;\nlet isInStartTransition = false;\nlet isInEndTransition = false;\nlet lastReceivedTimestamp = null;\nlet framesRecovered = 0;\nlet framesLost = 0;\nconst EXPECTED_TIMESTAMP_INCREMENT = 3600;\nlet lastRenderedSeq = -1;\nlet framesOutOfOrder = 0;\nlet framesDuplicate = 0;\nlet framesDropped = 0;\nlet framesSent = 0;\nfunction parseMetadataHeader(data) {\n if (data.byteLength < METADATA_FIXED_HEADER_SIZE) return null;\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n const flags = data[0];\n const msgLen = view.getUint32(1, true);\n const headerSize = METADATA_FIXED_HEADER_SIZE + msgLen;\n if (data.byteLength < headerSize) return null;\n const compressedData = data.slice(\n METADATA_FIXED_HEADER_SIZE,\n METADATA_FIXED_HEADER_SIZE + msgLen\n );\n const hasRedundant = (flags & PACKET_FLAG_REDUNDANT) !== 0;\n return {\n meta: {\n flags,\n protobufLength: msgLen,\n protobufData: compressedData,\n // This is still compressed at this point\n hasRedundant,\n redundantLength: 0,\n // Will be determined after decompression\n redundantData: null\n // Will be extracted after decompression\n },\n headerSize\n };\n}\nfunction isIdlePacket(flags) {\n return (flags & PACKET_FLAG_IDLE) !== 0;\n}\nfunction isStartPacket(flags) {\n return (flags & PACKET_FLAG_START) !== 0;\n}\nfunction isEndPacket(flags) {\n return (flags & PACKET_FLAG_END) !== 0;\n}\nfunction isGzipped(flags) {\n return (flags & PACKET_FLAG_GZIPPED) !== 0;\n}\nfunction isTransitionPacket(flags) {\n return (flags & PACKET_FLAG_TRANSITION) !== 0;\n}\nfunction isTransitionEndPacket(flags) {\n return (flags & PACKET_FLAG_TRANSITION_END) !== 0;\n}\nasync function decompressGzip(data) {\n const ds = new DecompressionStream("gzip");\n const writer = ds.writable.getWriter();\n const copy = new Uint8Array(data);\n writer.write(copy);\n writer.close();\n const reader = ds.readable.getReader();\n const chunks = [];\n let totalLength = 0;\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n totalLength += value.length;\n }\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n return result;\n}\nlet totalCompressedBytes = 0;\nlet totalUncompressedBytes = 0;\nfunction parseALRPayload(decompressed, hasRedundant) {\n if (decompressed.byteLength < 4) return null;\n const view = new DataView(\n decompressed.buffer,\n decompressed.byteOffset,\n decompressed.byteLength\n );\n let offset = 0;\n const frameSeq = view.getUint32(offset, true);\n offset += 4;\n if (!hasRedundant) {\n const currentData2 = decompressed.slice(offset);\n return { frameSeq, currentData: currentData2, prev1Data: null, prev2Data: null };\n }\n if (decompressed.byteLength < offset + 4) return null;\n const currentLen = view.getUint32(offset, true);\n offset += 4;\n if (decompressed.byteLength < offset + currentLen) return null;\n const currentData = decompressed.slice(offset, offset + currentLen);\n offset += currentLen;\n let prev1Data = null;\n if (decompressed.byteLength >= offset + 4) {\n const prev1Len = view.getUint32(offset, true);\n offset += 4;\n if (prev1Len > 0 && decompressed.byteLength >= offset + prev1Len) {\n prev1Data = decompressed.slice(offset, offset + prev1Len);\n offset += prev1Len;\n }\n }\n let prev2Data = null;\n if (decompressed.byteLength >= offset + 4) {\n const prev2Len = view.getUint32(offset, true);\n offset += 4;\n if (prev2Len > 0 && decompressed.byteLength >= offset + prev2Len) {\n prev2Data = decompressed.slice(offset, offset + prev2Len);\n }\n }\n return { frameSeq, currentData, prev1Data, prev2Data };\n}\nfunction sendAnimationToMainThread(protobufData, flags, frameSeq, isRecovered = false) {\n const isIdle = isIdlePacket(flags);\n const isStart = isStartPacket(flags);\n const isEnd = isEndPacket(flags);\n if (frameSeq <= lastRenderedSeq && lastRenderedSeq !== -1 && !isStart) {\n if (frameSeq === lastRenderedSeq) {\n framesDuplicate++;\n } else {\n framesOutOfOrder++;\n }\n return false;\n }\n if (lastRenderedSeq !== -1 && frameSeq > lastRenderedSeq + 1 && !isStart) {\n const gap = frameSeq - lastRenderedSeq - 1;\n framesDropped += gap;\n }\n framesSent++;\n lastRenderedSeq = frameSeq;\n const protobufBuffer = new ArrayBuffer(protobufData.byteLength);\n new Uint8Array(protobufBuffer).set(protobufData);\n self.postMessage(\n {\n type: "animation",\n flags,\n isIdle,\n isStart,\n isEnd,\n isRecovered,\n frameSeq,\n protobufData: protobufBuffer\n },\n { transfer: [protobufBuffer] }\n );\n return true;\n}\nfunction receiverTransform(frame, _controller) {\n totalFrames++;\n const data = new Uint8Array(frame.data);\n const currentTimestamp = frame.timestamp;\n if (data.length <= VP8_FRAME_HEADER_SIZE) {\n return;\n }\n const animData = data.subarray(VP8_FRAME_HEADER_SIZE);\n const parsed = parseMetadataHeader(animData);\n if (parsed) {\n const { meta } = parsed;\n framesWithMeta++;\n receiverMetaCount++;\n const isIdle = isIdlePacket(meta.flags);\n const isStart = isStartPacket(meta.flags);\n const isEnd = isEndPacket(meta.flags);\n if (lastReceivedTimestamp !== null && !isIdle && !isStart) {\n const timestampDelta = currentTimestamp - lastReceivedTimestamp;\n if (timestampDelta > EXPECTED_TIMESTAMP_INCREMENT * 1.5) {\n const missedFrames = Math.round(timestampDelta / EXPECTED_TIMESTAMP_INCREMENT) - 1;\n framesLost += missedFrames;\n if (meta.hasRedundant && isGzipped(meta.flags)) {\n totalCompressedBytes += meta.protobufData.byteLength;\n decompressGzip(meta.protobufData).then((decompressed) => {\n totalUncompressedBytes += decompressed.byteLength;\n const parsed2 = parseALRPayload(decompressed, true);\n if (parsed2) {\n const currentSeq = parsed2.frameSeq;\n const framesToRecover = [];\n if (missedFrames >= 2 && parsed2.prev2Data) {\n framesToRecover.push({\n data: parsed2.prev2Data,\n seq: currentSeq - 2\n });\n }\n if (missedFrames >= 1 && parsed2.prev1Data) {\n framesToRecover.push({\n data: parsed2.prev1Data,\n seq: currentSeq - 1\n });\n }\n const recovered = framesToRecover.length;\n if (recovered > 0) {\n framesRecovered += recovered;\n for (const frame2 of framesToRecover) {\n sendAnimationToMainThread(\n frame2.data,\n meta.flags & ~PACKET_FLAG_REDUNDANT,\n frame2.seq,\n true\n );\n }\n }\n sendAnimationToMainThread(\n parsed2.currentData,\n meta.flags & ~PACKET_FLAG_REDUNDANT,\n currentSeq,\n false\n );\n }\n }).catch((err) => {\n console.error(`[Animation Worker] ALR decompression error:`, err);\n });\n lastReceivedTimestamp = currentTimestamp;\n return;\n }\n }\n }\n if (!isIdle) {\n lastReceivedTimestamp = currentTimestamp;\n }\n if (isStart) {\n lastReceivedTimestamp = currentTimestamp;\n framesRecovered = 0;\n framesLost = 0;\n lastRenderedSeq = -1;\n framesOutOfOrder = 0;\n framesDuplicate = 0;\n framesDropped = 0;\n framesSent = 0;\n }\n const isTransition = isTransitionPacket(meta.flags);\n const isTransitionEnd = isTransitionEndPacket(meta.flags);\n if (isIdle) {\n if (!wasIdle) {\n self.postMessage({ type: "idleStart" });\n wasIdle = true;\n }\n isInStartTransition = false;\n isInEndTransition = false;\n } else if (isTransition && meta.protobufLength > 0) {\n wasIdle = false;\n if (!isInStartTransition) {\n isInStartTransition = true;\n isInEndTransition = false;\n const gzipped = isGzipped(meta.flags);\n if (gzipped) {\n totalCompressedBytes += meta.protobufData.byteLength;\n decompressGzip(meta.protobufData).then((decompressed) => {\n totalUncompressedBytes += decompressed.byteLength;\n const protobufBuffer = new ArrayBuffer(decompressed.byteLength);\n new Uint8Array(protobufBuffer).set(decompressed);\n self.postMessage(\n {\n type: "transition",\n flags: meta.flags,\n protobufData: protobufBuffer\n },\n { transfer: [protobufBuffer] }\n );\n }).catch((err) => {\n console.error(\n `[Animation Worker] Gzip decompress error (transition):`,\n err\n );\n });\n } else {\n const protobufBuffer = new ArrayBuffer(meta.protobufData.byteLength);\n new Uint8Array(protobufBuffer).set(meta.protobufData);\n self.postMessage(\n {\n type: "transition",\n flags: meta.flags,\n protobufData: protobufBuffer\n },\n { transfer: [protobufBuffer] }\n );\n }\n }\n } else if (isTransitionEnd && meta.protobufLength > 0) {\n if (!isInEndTransition) {\n isInEndTransition = true;\n isInStartTransition = false;\n const gzipped = isGzipped(meta.flags);\n if (gzipped) {\n totalCompressedBytes += meta.protobufData.byteLength;\n decompressGzip(meta.protobufData).then((decompressed) => {\n totalUncompressedBytes += decompressed.byteLength;\n const protobufBuffer = new ArrayBuffer(decompressed.byteLength);\n new Uint8Array(protobufBuffer).set(decompressed);\n self.postMessage(\n {\n type: "transitionEnd",\n flags: meta.flags,\n protobufData: protobufBuffer\n },\n { transfer: [protobufBuffer] }\n );\n }).catch((err) => {\n console.error(\n `[Animation Worker] Gzip decompress error (transitionEnd):`,\n err\n );\n });\n } else {\n const protobufBuffer = new ArrayBuffer(meta.protobufData.byteLength);\n new Uint8Array(protobufBuffer).set(meta.protobufData);\n self.postMessage(\n {\n type: "transitionEnd",\n flags: meta.flags,\n protobufData: protobufBuffer\n },\n { transfer: [protobufBuffer] }\n );\n }\n }\n } else if (meta.protobufLength > 0) {\n if (wasIdle) {\n wasIdle = false;\n }\n isInStartTransition = false;\n isInEndTransition = false;\n const gzipped = isGzipped(meta.flags);\n if (gzipped) {\n totalCompressedBytes += meta.protobufData.byteLength;\n decompressGzip(meta.protobufData).then((decompressed) => {\n totalUncompressedBytes += decompressed.byteLength;\n const parsed2 = parseALRPayload(decompressed, meta.hasRedundant);\n if (parsed2) {\n sendAnimationToMainThread(\n parsed2.currentData,\n meta.flags & ~PACKET_FLAG_REDUNDANT,\n parsed2.frameSeq,\n false\n );\n }\n }).catch(() => {\n });\n } else {\n const protobufBuffer = new ArrayBuffer(meta.protobufData.byteLength);\n new Uint8Array(protobufBuffer).set(meta.protobufData);\n self.postMessage(\n {\n type: "animation",\n flags: meta.flags,\n isIdle: false,\n isStart,\n isEnd,\n frameSeq: -1,\n // Unknown sequence\n protobufData: protobufBuffer\n },\n { transfer: [protobufBuffer] }\n );\n }\n }\n const now = performance.now();\n if (now - lastLogTime > 1e3) {\n lastLogTime = now;\n self.postMessage({\n type: "metadata",\n protobufLength: meta.protobufLength,\n framesPerSec: receiverMetaCount,\n totalFrames,\n framesWithMeta,\n framesLost,\n framesRecovered,\n totalCompressedBytes,\n totalUncompressedBytes,\n framesOutOfOrder,\n framesDuplicate,\n framesDropped,\n framesSent,\n lastRenderedSeq\n });\n receiverMetaCount = 0;\n }\n }\n}\nself.onrtctransform = (event) => {\n const transformer = event.transformer;\n const options = transformer.options;\n try {\n if (options.operation === "receiver") {\n receiverMetaCount = 0;\n lastLogTime = 0;\n totalFrames = 0;\n framesWithMeta = 0;\n wasIdle = false;\n isInStartTransition = false;\n isInEndTransition = false;\n lastReceivedTimestamp = null;\n framesRecovered = 0;\n framesLost = 0;\n lastRenderedSeq = -1;\n framesOutOfOrder = 0;\n framesDuplicate = 0;\n framesDropped = 0;\n framesSent = 0;\n transformer.readable.pipeThrough(new TransformStream({ transform: receiverTransform })).pipeTo(transformer.writable).catch((err) => {\n console.error("[Animation Worker] Pipeline error:", err);\n self.postMessage({\n type: "error",\n error: `Animation receiver pipe error: ${err}`\n });\n });\n self.postMessage({ type: "ready", operation: "receiver" });\n }\n } catch (err) {\n console.error("[Animation Worker] Setup error:", err);\n self.postMessage({\n type: "error",\n error: `Animation transform setup error: ${err}`\n });\n }\n};\nself.onmessage = (event) => {\n const { type } = event.data;\n if (type === "init") {\n self.postMessage({ type: "initialized" });\n }\n};\n//# sourceMappingURL=animation-worker-DOGeTjF0.js.map\n';
|
|
2
2
|
const blob = typeof self !== "undefined" && self.Blob && new Blob(["URL.revokeObjectURL(import.meta.url);", jsContent], { type: "text/javascript;charset=utf-8" });
|
|
3
3
|
function WorkerWrapper(options) {
|
|
4
4
|
let objURL;
|
package/dist/index2.js
CHANGED
|
@@ -70,6 +70,14 @@ class AvatarPlayer {
|
|
|
70
70
|
await this.provider.connect(config);
|
|
71
71
|
this._isConnected = true;
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Pre-warm a future RTC connection when the provider supports it.
|
|
75
|
+
* This is best-effort and does not change the connected state.
|
|
76
|
+
*/
|
|
77
|
+
async prepareConnection(config) {
|
|
78
|
+
var _a, _b;
|
|
79
|
+
await ((_b = (_a = this.provider).prepareConnection) == null ? void 0 : _b.call(_a, config));
|
|
80
|
+
}
|
|
73
81
|
/**
|
|
74
82
|
* Disconnect from RTC server.
|
|
75
83
|
* Stops all tracks and cleans up resources.
|
|
@@ -89,22 +97,22 @@ class AvatarPlayer {
|
|
|
89
97
|
}
|
|
90
98
|
/**
|
|
91
99
|
* Publish an audio track to the RTC server.
|
|
92
|
-
*
|
|
100
|
+
*
|
|
93
101
|
* The audio source is controlled externally - you can pass any audio track:
|
|
94
102
|
* - Microphone: `getUserMedia({ audio: true })`
|
|
95
103
|
* - Browser audio: `audioElement.captureStream()`
|
|
96
104
|
* - Web Audio API: `audioContext.createMediaStreamDestination().stream`
|
|
97
105
|
* - Screen share audio: `getDisplayMedia({ audio: true })`
|
|
98
|
-
*
|
|
106
|
+
*
|
|
99
107
|
* @param track - MediaStreamTrack to publish (must be an audio track)
|
|
100
108
|
* @throws Error if not connected or track is invalid
|
|
101
|
-
*
|
|
109
|
+
*
|
|
102
110
|
* @example
|
|
103
111
|
* ```typescript
|
|
104
112
|
* // Microphone
|
|
105
113
|
* const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
106
114
|
* await player.publishAudio(stream.getAudioTracks()[0]);
|
|
107
|
-
*
|
|
115
|
+
*
|
|
108
116
|
* // Browser audio element
|
|
109
117
|
* const audioEl = document.querySelector('audio');
|
|
110
118
|
* const stream = audioEl.captureStream();
|
|
@@ -137,12 +145,12 @@ class AvatarPlayer {
|
|
|
137
145
|
}
|
|
138
146
|
/**
|
|
139
147
|
* Start publishing microphone audio.
|
|
140
|
-
*
|
|
148
|
+
*
|
|
141
149
|
* This requests microphone permission and publishes it to the RTC session.
|
|
142
150
|
* For custom audio sources (e.g., audio files), use `publishAudio(track)` instead.
|
|
143
|
-
*
|
|
151
|
+
*
|
|
144
152
|
* @throws Error if not connected, permission denied, or no microphone found
|
|
145
|
-
*
|
|
153
|
+
*
|
|
146
154
|
* @example
|
|
147
155
|
* ```typescript
|
|
148
156
|
* await player.startPublishing();
|
|
@@ -157,13 +165,15 @@ class AvatarPlayer {
|
|
|
157
165
|
if (this.microphoneStream) {
|
|
158
166
|
return;
|
|
159
167
|
}
|
|
160
|
-
this.microphoneStream = await navigator.mediaDevices.getUserMedia({
|
|
168
|
+
this.microphoneStream = await navigator.mediaDevices.getUserMedia({
|
|
169
|
+
audio: true
|
|
170
|
+
});
|
|
161
171
|
const track = this.microphoneStream.getAudioTracks()[0];
|
|
162
172
|
await this.publishAudio(track);
|
|
163
173
|
}
|
|
164
174
|
/**
|
|
165
175
|
* Stop publishing microphone audio and release the microphone.
|
|
166
|
-
*
|
|
176
|
+
*
|
|
167
177
|
* This stops the microphone track and releases the device.
|
|
168
178
|
* If you used `publishAudio()` with a custom track, use `unpublishAudio()` instead.
|
|
169
179
|
*/
|
|
@@ -184,19 +194,19 @@ class AvatarPlayer {
|
|
|
184
194
|
}
|
|
185
195
|
/**
|
|
186
196
|
* Get the native RTC client object.
|
|
187
|
-
*
|
|
197
|
+
*
|
|
188
198
|
* Returns the underlying RTC client for advanced use cases:
|
|
189
199
|
* - LiveKit: Returns the Room instance
|
|
190
200
|
* - Agora: Returns the IAgoraRTCClient instance
|
|
191
|
-
*
|
|
201
|
+
*
|
|
192
202
|
* @returns The native RTC client object, or null if not connected
|
|
193
|
-
*
|
|
203
|
+
*
|
|
194
204
|
* @example
|
|
195
205
|
* ```typescript
|
|
196
206
|
* // Access LiveKit Room
|
|
197
207
|
* const room = player.getNativeClient() as Room;
|
|
198
208
|
* console.log('Participants:', room?.remoteParticipants.size);
|
|
199
|
-
*
|
|
209
|
+
*
|
|
200
210
|
* // Access Agora Client
|
|
201
211
|
* const client = player.getNativeClient() as IAgoraRTCClient;
|
|
202
212
|
* console.log('State:', client?.connectionState);
|
|
@@ -269,7 +279,9 @@ class AvatarPlayer {
|
|
|
269
279
|
return;
|
|
270
280
|
}
|
|
271
281
|
if (!this.lastConnectionConfig) {
|
|
272
|
-
throw new Error(
|
|
282
|
+
throw new Error(
|
|
283
|
+
"Cannot reconnect: no previous connection. Call connect() first."
|
|
284
|
+
);
|
|
273
285
|
}
|
|
274
286
|
this.isReconnecting = true;
|
|
275
287
|
logger.info("AvatarPlayer", "Attempting reconnection...");
|
|
@@ -346,10 +358,16 @@ class AvatarPlayer {
|
|
|
346
358
|
);
|
|
347
359
|
},
|
|
348
360
|
onTransition: (protobufData, transitionFrameCount) => {
|
|
349
|
-
this.animationHandler.handleTransitionData(
|
|
361
|
+
this.animationHandler.handleTransitionData(
|
|
362
|
+
protobufData,
|
|
363
|
+
transitionFrameCount
|
|
364
|
+
);
|
|
350
365
|
},
|
|
351
366
|
onTransitionEnd: (protobufData, transitionFrameCount) => {
|
|
352
|
-
this.animationHandler.handleTransitionToIdle(
|
|
367
|
+
this.animationHandler.handleTransitionToIdle(
|
|
368
|
+
protobufData,
|
|
369
|
+
transitionFrameCount
|
|
370
|
+
);
|
|
353
371
|
},
|
|
354
372
|
onSessionStart: () => {
|
|
355
373
|
},
|
|
@@ -357,7 +375,12 @@ class AvatarPlayer {
|
|
|
357
375
|
},
|
|
358
376
|
onIdleStart: () => {
|
|
359
377
|
if (this.animationHandler.isInTransition()) {
|
|
360
|
-
|
|
378
|
+
this.hasActiveAnimationSession = false;
|
|
379
|
+
this.isTrackingPrimed = false;
|
|
380
|
+
logger.info(
|
|
381
|
+
"AvatarPlayer",
|
|
382
|
+
"Deferring idleStart while transition is active"
|
|
383
|
+
);
|
|
361
384
|
return;
|
|
362
385
|
}
|
|
363
386
|
this.hasActiveAnimationSession = false;
|