@spatialwalk/avatarkit-rtc 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +417 -0
- package/dist/assets/animation-worker-CUXZycUw.js.map +1 -0
- package/dist/core/AnimationHandler.d.ts +17 -0
- package/dist/core/AnimationHandler.d.ts.map +1 -0
- package/dist/core/AvatarPlayer.d.ts +119 -0
- package/dist/core/AvatarPlayer.d.ts.map +1 -0
- package/dist/core/RTCProvider.d.ts +84 -0
- package/dist/core/RTCProvider.d.ts.map +1 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/types.d.ts +7 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/index10.js +67 -0
- package/dist/index10.js.map +1 -0
- package/dist/index11.js +390 -0
- package/dist/index11.js.map +1 -0
- package/dist/index12.js +108 -0
- package/dist/index12.js.map +1 -0
- package/dist/index13.js +18 -0
- package/dist/index13.js.map +1 -0
- package/dist/index14.js +48 -0
- package/dist/index14.js.map +1 -0
- package/dist/index15.js +29 -0
- package/dist/index15.js.map +1 -0
- package/dist/index16.js +144 -0
- package/dist/index16.js.map +1 -0
- package/dist/index17.js +106 -0
- package/dist/index17.js.map +1 -0
- package/dist/index18.js +28 -0
- package/dist/index18.js.map +1 -0
- package/dist/index2.js +220 -0
- package/dist/index2.js.map +1 -0
- package/dist/index3.js +586 -0
- package/dist/index3.js.map +1 -0
- package/dist/index4.js +410 -0
- package/dist/index4.js.map +1 -0
- package/dist/index5.js +20 -0
- package/dist/index5.js.map +1 -0
- package/dist/index6.js +282 -0
- package/dist/index6.js.map +1 -0
- package/dist/index7.js +53 -0
- package/dist/index7.js.map +1 -0
- package/dist/index8.js +189 -0
- package/dist/index8.js.map +1 -0
- package/dist/index9.js +178 -0
- package/dist/index9.js.map +1 -0
- package/dist/proto/animation.d.ts +12 -0
- package/dist/proto/animation.d.ts.map +1 -0
- package/dist/providers/agora/AgoraProvider.d.ts +71 -0
- package/dist/providers/agora/AgoraProvider.d.ts.map +1 -0
- package/dist/providers/agora/SEIExtractor.d.ts +29 -0
- package/dist/providers/agora/SEIExtractor.d.ts.map +1 -0
- package/dist/providers/agora/index.d.ts +11 -0
- package/dist/providers/agora/index.d.ts.map +1 -0
- package/dist/providers/agora/types.d.ts +14 -0
- package/dist/providers/agora/types.d.ts.map +1 -0
- package/dist/providers/base/BaseProvider.d.ts +11 -0
- package/dist/providers/base/BaseProvider.d.ts.map +1 -0
- package/dist/providers/index.d.ts +10 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/livekit/LiveKitProvider.d.ts +64 -0
- package/dist/providers/livekit/LiveKitProvider.d.ts.map +1 -0
- package/dist/providers/livekit/VP8Extractor.d.ts +10 -0
- package/dist/providers/livekit/VP8Extractor.d.ts.map +1 -0
- package/dist/providers/livekit/animation-transform.d.ts +11 -0
- package/dist/providers/livekit/animation-transform.d.ts.map +1 -0
- package/dist/providers/livekit/animation-worker.d.ts +14 -0
- package/dist/providers/livekit/animation-worker.d.ts.map +1 -0
- package/dist/providers/livekit/index.d.ts +11 -0
- package/dist/providers/livekit/index.d.ts.map +1 -0
- package/dist/providers/livekit/types.d.ts +11 -0
- package/dist/providers/livekit/types.d.ts.map +1 -0
- package/dist/providers/livekit/utils.d.ts +11 -0
- package/dist/providers/livekit/utils.d.ts.map +1 -0
- package/dist/types/index.d.ts +77 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/logger.d.ts +13 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { RTCConnectionConfig } from '../types';
|
|
2
|
+
import { RTCProviderEvents } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* RTC Provider interface.
|
|
5
|
+
*
|
|
6
|
+
* This interface defines the contract that all RTC providers
|
|
7
|
+
* (LiveKit, Agora, etc.) must implement. Applications typically
|
|
8
|
+
* don't interact with this interface directly - use AvatarPlayer instead.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* // For most use cases, use AvatarPlayer:
|
|
13
|
+
* const player = new AvatarPlayer(new LiveKitProvider(), renderer);
|
|
14
|
+
*
|
|
15
|
+
* // Only implement RTCProvider for custom providers:
|
|
16
|
+
* class MyCustomProvider implements RTCProvider {
|
|
17
|
+
* // ...implementation
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export interface RTCProvider {
|
|
22
|
+
/**
|
|
23
|
+
* Provider name identifier (e.g., 'livekit', 'agora').
|
|
24
|
+
* Used for logging and debugging.
|
|
25
|
+
*/
|
|
26
|
+
readonly name: string;
|
|
27
|
+
/**
|
|
28
|
+
* Connect to RTC server.
|
|
29
|
+
* @param config - Connection configuration containing server URL and credentials
|
|
30
|
+
* @throws Error if connection fails
|
|
31
|
+
*/
|
|
32
|
+
connect(config: RTCConnectionConfig): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Disconnect from RTC server.
|
|
35
|
+
* Cleans up all resources and stops all tracks.
|
|
36
|
+
*/
|
|
37
|
+
disconnect(): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Get current connection state.
|
|
40
|
+
* @returns Current connection state string
|
|
41
|
+
*/
|
|
42
|
+
getConnectionState(): string;
|
|
43
|
+
/**
|
|
44
|
+
* Publish local audio track (microphone) to the RTC server.
|
|
45
|
+
* @param track - MediaStreamTrack from getUserMedia
|
|
46
|
+
*/
|
|
47
|
+
publishAudioTrack(track: MediaStreamTrack): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Unpublish audio track from the RTC server.
|
|
50
|
+
*/
|
|
51
|
+
unpublishAudioTrack(): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Play remote audio (resume playback).
|
|
54
|
+
* Used to sync audio with animation transitions.
|
|
55
|
+
*/
|
|
56
|
+
playRemoteAudio(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Pause remote audio.
|
|
59
|
+
* Used to sync audio with animation transitions.
|
|
60
|
+
*/
|
|
61
|
+
pauseRemoteAudio(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Add event listener.
|
|
64
|
+
* @param event - Event name
|
|
65
|
+
* @param handler - Event handler function
|
|
66
|
+
*/
|
|
67
|
+
on<K extends keyof RTCProviderEvents>(event: K, handler: RTCProviderEvents[K]): void;
|
|
68
|
+
/**
|
|
69
|
+
* Remove event listener.
|
|
70
|
+
* @param event - Event name
|
|
71
|
+
* @param handler - Event handler function
|
|
72
|
+
*/
|
|
73
|
+
off<K extends keyof RTCProviderEvents>(event: K, handler: RTCProviderEvents[K]): void;
|
|
74
|
+
/**
|
|
75
|
+
* Get the native RTC client object.
|
|
76
|
+
*
|
|
77
|
+
* For LiveKit: returns the Room instance
|
|
78
|
+
* For Agora: returns the IAgoraRTCClient instance
|
|
79
|
+
*
|
|
80
|
+
* @returns The native RTC client object, or null if not connected
|
|
81
|
+
*/
|
|
82
|
+
getNativeClient(): unknown;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=RTCProvider.d.ts.map
|
|
@@ -0,0 +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;AACpD,OAAO,KAAK,EAGV,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;;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,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1D;;OAEG;IACH,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAErC;;;OAGG;IACH,eAAe,IAAI,IAAI,CAAC;IAExB;;;OAGG;IACH,gBAAgB,IAAI,IAAI,CAAC;IAEzB;;;;OAIG;IACH,EAAE,CAAC,CAAC,SAAS,MAAM,iBAAiB,EAClC,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAC5B,IAAI,CAAC;IAER;;;;OAIG;IACH,GAAG,CAAC,CAAC,SAAS,MAAM,iBAAiB,EACnC,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAC5B,IAAI,CAAC;IAER;;;;;;;OAOG;IACH,eAAe,IAAI,OAAO,CAAC;CAC5B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @spatialwalk/avatarkit-rtc
|
|
3
|
+
*
|
|
4
|
+
* Unified RTC adapter for avatarkit - supports LiveKit, Agora and other RTC providers.
|
|
5
|
+
* This SDK integrates with @spatialwalk/avatarkit for seamless avatar rendering.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
export { AvatarPlayer } from './core';
|
|
10
|
+
export type { AvatarPlayerOptions } from './core';
|
|
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';
|
|
15
|
+
export type { LogLevel } from './utils';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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,EAAE,oBAAoB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAGlF,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC1E,YAAY,EACV,mBAAmB,EACnB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,SAAS,CAAC;AAGjB,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AvatarPlayer } from "./index2.js";
|
|
2
|
+
import { LiveKitProvider } from "./index3.js";
|
|
3
|
+
import { AgoraProvider } from "./index4.js";
|
|
4
|
+
import { ConnectionState, isAgoraConfig, isLiveKitConfig } from "./index5.js";
|
|
5
|
+
export {
|
|
6
|
+
AgoraProvider,
|
|
7
|
+
AvatarPlayer,
|
|
8
|
+
ConnectionState,
|
|
9
|
+
LiveKitProvider,
|
|
10
|
+
isAgoraConfig,
|
|
11
|
+
isLiveKitConfig
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
|
package/dist/index10.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
import { logger } from "./index7.js";
|
|
5
|
+
class BaseProvider {
|
|
6
|
+
constructor() {
|
|
7
|
+
/** @internal */
|
|
8
|
+
__publicField(this, "connectionState", "disconnected");
|
|
9
|
+
/** @internal */
|
|
10
|
+
__publicField(this, "eventHandlers", /* @__PURE__ */ new Map());
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Add event listener.
|
|
14
|
+
* @param event - Event name
|
|
15
|
+
* @param handler - Event handler
|
|
16
|
+
*/
|
|
17
|
+
on(event, handler) {
|
|
18
|
+
if (!this.eventHandlers.has(event)) {
|
|
19
|
+
this.eventHandlers.set(event, /* @__PURE__ */ new Set());
|
|
20
|
+
}
|
|
21
|
+
this.eventHandlers.get(event).add(handler);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Remove event listener.
|
|
25
|
+
* @param event - Event name
|
|
26
|
+
* @param handler - Event handler
|
|
27
|
+
*/
|
|
28
|
+
off(event, handler) {
|
|
29
|
+
const handlers = this.eventHandlers.get(event);
|
|
30
|
+
if (handlers) {
|
|
31
|
+
handlers.delete(handler);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Emit event to all listeners.
|
|
36
|
+
* @param event - Event name
|
|
37
|
+
* @param args - Event arguments
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
emit(event, ...args) {
|
|
41
|
+
const handlers = this.eventHandlers.get(event);
|
|
42
|
+
if (handlers) {
|
|
43
|
+
handlers.forEach((handler) => {
|
|
44
|
+
try {
|
|
45
|
+
handler(...args);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
logger.error(this.name, `Error in event handler for ${event}:`, error);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Update connection state and emit event.
|
|
54
|
+
* @param state - New connection state
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
setConnectionState(state) {
|
|
58
|
+
if (this.connectionState !== state) {
|
|
59
|
+
this.connectionState = state;
|
|
60
|
+
this.emit("connection-state-changed", state);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
BaseProvider
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=index10.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index10.js","sources":["../src/providers/base/BaseProvider.ts"],"sourcesContent":["/**\n * Base Provider - Common implementation for RTC providers.\n *\n * This class provides common functionality that can be shared\n * across different provider implementations.\n *\n * @internal\n * @packageDocumentation\n */\n\nimport type { RTCProvider } from '../../core/RTCProvider';\nimport type { RTCConnectionConfig } from '../../types';\nimport type { RTCProviderEvents, AnimationTrackCallbacks, AudioTrackCallbacks } from '../../core/types';\nimport { logger } from '../../utils';\n\n/**\n * Base class for RTC providers.\n * Provides common event handling and state management.\n *\n * @internal\n */\nexport abstract class BaseProvider implements RTCProvider {\n /** Provider name identifier */\n abstract readonly name: string;\n\n /** @internal */\n protected connectionState: string = 'disconnected';\n /** @internal */\n protected eventHandlers: Map<string, Set<Function>> = new Map();\n\n /**\n * Connect to RTC server.\n * @param config - Connection configuration\n */\n abstract connect(config: RTCConnectionConfig): Promise<void>;\n\n /**\n * Disconnect from RTC server.\n */\n abstract disconnect(): Promise<void>;\n\n /**\n * Get current connection state.\n */\n abstract getConnectionState(): string;\n\n /**\n * Subscribe to animation track.\n * @param callbacks - Animation track callbacks\n * @internal\n */\n abstract subscribeAnimationTrack(callbacks: AnimationTrackCallbacks): Promise<void>;\n\n /**\n * Unsubscribe from animation track.\n * @internal\n */\n abstract unsubscribeAnimationTrack(): Promise<void>;\n\n /**\n * Subscribe to audio track.\n * @param callbacks - Audio track callbacks\n * @internal\n */\n abstract subscribeAudioTrack(callbacks: AudioTrackCallbacks): Promise<void>;\n\n /**\n * Unsubscribe from audio track.\n * @internal\n */\n abstract unsubscribeAudioTrack(): Promise<void>;\n\n /**\n * Publish local audio track.\n * @param track - MediaStreamTrack to publish\n */\n abstract publishAudioTrack(track: MediaStreamTrack): Promise<void>;\n\n /**\n * Unpublish audio track.\n */\n abstract unpublishAudioTrack(): Promise<void>;\n\n /**\n * Play remote audio (resume playback).\n */\n abstract playRemoteAudio(): void;\n\n /**\n * Pause remote audio.\n */\n abstract pauseRemoteAudio(): void;\n\n /**\n * Get the native RTC client object.\n * @returns The native client, or null if not connected\n */\n abstract getNativeClient(): unknown;\n\n /**\n * Add event listener.\n * @param event - Event name\n * @param handler - Event handler\n */\n on<K extends keyof RTCProviderEvents>(\n event: K,\n handler: RTCProviderEvents[K]\n ): void {\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler);\n }\n\n /**\n * Remove event listener.\n * @param event - Event name\n * @param handler - Event handler\n */\n off<K extends keyof RTCProviderEvents>(\n event: K,\n handler: RTCProviderEvents[K]\n ): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.delete(handler);\n }\n }\n\n /**\n * Emit event to all listeners.\n * @param event - Event name\n * @param args - Event arguments\n * @internal\n */\n protected emit<K extends keyof RTCProviderEvents>(\n event: K,\n ...args: Parameters<RTCProviderEvents[K]>\n ): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (handler as any)(...args);\n } catch (error) {\n logger.error(this.name, `Error in event handler for ${event}:`, error);\n }\n });\n }\n }\n\n /**\n * Update connection state and emit event.\n * @param state - New connection state\n * @internal\n */\n protected setConnectionState(state: string): void {\n if (this.connectionState !== state) {\n this.connectionState = state;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.emit('connection-state-changed', state as any);\n }\n }\n}\n"],"names":[],"mappings":";;;;AAqBO,MAAe,aAAoC;AAAA,EAAnD;AAKK;AAAA,2CAA0B;AAE1B;AAAA,6DAAgD,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4E1D,GACE,OACA,SACM;AACN,QAAI,CAAC,KAAK,cAAc,IAAI,KAAK,GAAG;AAClC,WAAK,cAAc,IAAI,OAAO,oBAAI,KAAK;AAAA,IACzC;AACA,SAAK,cAAc,IAAI,KAAK,EAAG,IAAI,OAAO;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IACE,OACA,SACM;AACN,UAAM,WAAW,KAAK,cAAc,IAAI,KAAK;AAC7C,QAAI,UAAU;AACZ,eAAS,OAAO,OAAO;AAAA,IACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,KACR,UACG,MACG;AACN,UAAM,WAAW,KAAK,cAAc,IAAI,KAAK;AAC7C,QAAI,UAAU;AACZ,eAAS,QAAQ,CAAC,YAAY;AAC5B,YAAI;AAED,kBAAgB,GAAG,IAAI;AAAA,QAC1B,SAAS,OAAO;AACd,iBAAO,MAAM,KAAK,MAAM,8BAA8B,KAAK,KAAK,KAAK;AAAA,QACvE;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOU,mBAAmB,OAAqB;AAChD,QAAI,KAAK,oBAAoB,OAAO;AAClC,WAAK,kBAAkB;AAEvB,WAAK,KAAK,4BAA4B,KAAY;AAAA,IACpD;AAAA,EACF;AACF;"}
|
package/dist/index11.js
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
import { logger } from "./index7.js";
|
|
5
|
+
const PacketFlags = {
|
|
6
|
+
/** Idle packet - no payload */
|
|
7
|
+
Idle: 1,
|
|
8
|
+
/** First frame of animation session */
|
|
9
|
+
Start: 2,
|
|
10
|
+
/** Last frame of animation session */
|
|
11
|
+
End: 4,
|
|
12
|
+
/** Payload is zlib compressed */
|
|
13
|
+
Gzipped: 8,
|
|
14
|
+
/** Transition from idle to animation */
|
|
15
|
+
Transition: 16,
|
|
16
|
+
/** Transition from animation back to idle */
|
|
17
|
+
TransitionEnd: 32
|
|
18
|
+
};
|
|
19
|
+
const SEI_HEADER_SIZE = 5;
|
|
20
|
+
const FRAME_SEQ_SIZE = 4;
|
|
21
|
+
const DEFAULT_TRANSITION_START_FRAMES = 8;
|
|
22
|
+
const DEFAULT_TRANSITION_END_FRAMES = 12;
|
|
23
|
+
class SEIExtractor {
|
|
24
|
+
constructor() {
|
|
25
|
+
/** @internal */
|
|
26
|
+
__publicField(this, "callbacks", null);
|
|
27
|
+
// Session state tracking
|
|
28
|
+
/** @internal */
|
|
29
|
+
__publicField(this, "lastWasIdle", true);
|
|
30
|
+
// Transition state tracking (to handle consecutive packets)
|
|
31
|
+
/** @internal */
|
|
32
|
+
__publicField(this, "isInStartTransition", false);
|
|
33
|
+
/** @internal */
|
|
34
|
+
__publicField(this, "isInEndTransition", false);
|
|
35
|
+
// Transition frame counts
|
|
36
|
+
/** @internal */
|
|
37
|
+
__publicField(this, "transitionStartFrameCount");
|
|
38
|
+
/** @internal */
|
|
39
|
+
__publicField(this, "transitionEndFrameCount");
|
|
40
|
+
// Statistics tracking
|
|
41
|
+
/** @internal */
|
|
42
|
+
__publicField(this, "totalFrameCount", 0);
|
|
43
|
+
/** @internal */
|
|
44
|
+
__publicField(this, "intervalFrameCount", 0);
|
|
45
|
+
/** @internal */
|
|
46
|
+
__publicField(this, "lastStatsTime", 0);
|
|
47
|
+
/** @internal */
|
|
48
|
+
__publicField(this, "statsInterval", null);
|
|
49
|
+
// Debug logging
|
|
50
|
+
/** @internal */
|
|
51
|
+
__publicField(this, "debugLogging", false);
|
|
52
|
+
this.transitionStartFrameCount = DEFAULT_TRANSITION_START_FRAMES;
|
|
53
|
+
this.transitionEndFrameCount = DEFAULT_TRANSITION_END_FRAMES;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Initialize the extractor with callbacks.
|
|
57
|
+
* @param callbacks - Callbacks to receive extracted data
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
initialize(callbacks) {
|
|
61
|
+
this.callbacks = callbacks;
|
|
62
|
+
this.startStatsInterval();
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Enable or disable debug logging.
|
|
66
|
+
* @param enabled - Whether to enable debug logging
|
|
67
|
+
* @internal
|
|
68
|
+
*/
|
|
69
|
+
setDebugLogging(enabled) {
|
|
70
|
+
this.debugLogging = enabled;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Handle SEI data from Agora.
|
|
74
|
+
* Wraps async processing to not block the event handler.
|
|
75
|
+
* @param seiData - Raw SEI payload
|
|
76
|
+
* @internal
|
|
77
|
+
*/
|
|
78
|
+
handleSEIData(seiData) {
|
|
79
|
+
this.processSEIData(seiData).catch((error) => {
|
|
80
|
+
logger.error("SEIExtractor", "Failed to process SEI data:", error);
|
|
81
|
+
this.logDebugInfo(seiData);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Convert EBSP (Encapsulated Byte Sequence Payload) to RBSP (Raw Byte Sequence Payload).
|
|
86
|
+
* This removes H.264 emulation prevention bytes (0x03) inserted after 00 00 sequences.
|
|
87
|
+
*
|
|
88
|
+
* H.264 spec: When encoding, 0x03 is inserted after 00 00 to prevent start code emulation:
|
|
89
|
+
* - 00 00 00 → 00 00 03 00
|
|
90
|
+
* - 00 00 01 → 00 00 03 01
|
|
91
|
+
* - 00 00 02 → 00 00 03 02
|
|
92
|
+
* - 00 00 03 → 00 00 03 03
|
|
93
|
+
*
|
|
94
|
+
* @param data - EBSP data
|
|
95
|
+
* @returns RBSP data
|
|
96
|
+
* @internal
|
|
97
|
+
*/
|
|
98
|
+
ebspToRbsp(data) {
|
|
99
|
+
if (data.length === 0) {
|
|
100
|
+
return data;
|
|
101
|
+
}
|
|
102
|
+
const result = [];
|
|
103
|
+
let zeroCount = 0;
|
|
104
|
+
for (let i = 0; i < data.length; i++) {
|
|
105
|
+
const byte = data[i];
|
|
106
|
+
if (zeroCount >= 2 && byte === 3) {
|
|
107
|
+
if (i + 1 < data.length) {
|
|
108
|
+
const nextByte = data[i + 1];
|
|
109
|
+
if (nextByte <= 3) {
|
|
110
|
+
zeroCount = 0;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (byte === 0) {
|
|
116
|
+
zeroCount++;
|
|
117
|
+
} else {
|
|
118
|
+
zeroCount = 0;
|
|
119
|
+
}
|
|
120
|
+
result.push(byte);
|
|
121
|
+
}
|
|
122
|
+
return new Uint8Array(result);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Unescape zero bytes that were escaped by the server.
|
|
126
|
+
* Escape scheme: 0x00 0xFF -> 0x00, 0xFF 0xFF -> 0xFF
|
|
127
|
+
* This reverses the escaping done to avoid H.264 emulation prevention issues.
|
|
128
|
+
*
|
|
129
|
+
* @param data - Escaped data
|
|
130
|
+
* @returns Unescaped data
|
|
131
|
+
* @internal
|
|
132
|
+
*/
|
|
133
|
+
unescapeZeroBytes(data) {
|
|
134
|
+
const result = [];
|
|
135
|
+
let i = 0;
|
|
136
|
+
while (i < data.length) {
|
|
137
|
+
if (i + 1 < data.length && data[i] === 0 && data[i + 1] === 255) {
|
|
138
|
+
result.push(0);
|
|
139
|
+
i += 2;
|
|
140
|
+
} else if (i + 1 < data.length && data[i] === 255 && data[i + 1] === 255) {
|
|
141
|
+
result.push(255);
|
|
142
|
+
i += 2;
|
|
143
|
+
} else {
|
|
144
|
+
result.push(data[i]);
|
|
145
|
+
i++;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return new Uint8Array(result);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Decompress zlib data using DecompressionStream API.
|
|
152
|
+
* Uses 'deflate' format (not gzip) to avoid H.264 emulation prevention issues
|
|
153
|
+
* with gzip's mtime field containing 00 00 00 00.
|
|
154
|
+
*
|
|
155
|
+
* @param data - Zlib compressed data
|
|
156
|
+
* @returns Decompressed data
|
|
157
|
+
* @internal
|
|
158
|
+
*/
|
|
159
|
+
async decompressZlib(data) {
|
|
160
|
+
const ds = new DecompressionStream("deflate");
|
|
161
|
+
const writer = ds.writable.getWriter();
|
|
162
|
+
const copy = new Uint8Array(data);
|
|
163
|
+
writer.write(copy);
|
|
164
|
+
writer.close();
|
|
165
|
+
const reader = ds.readable.getReader();
|
|
166
|
+
const chunks = [];
|
|
167
|
+
let totalLength = 0;
|
|
168
|
+
while (true) {
|
|
169
|
+
const { done, value } = await reader.read();
|
|
170
|
+
if (done) break;
|
|
171
|
+
chunks.push(value);
|
|
172
|
+
totalLength += value.length;
|
|
173
|
+
}
|
|
174
|
+
const result = new Uint8Array(totalLength);
|
|
175
|
+
let offset = 0;
|
|
176
|
+
for (const chunk of chunks) {
|
|
177
|
+
result.set(chunk, offset);
|
|
178
|
+
offset += chunk.length;
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Process SEI data asynchronously.
|
|
184
|
+
* @param seiData - Raw SEI payload
|
|
185
|
+
* @internal
|
|
186
|
+
*/
|
|
187
|
+
async processSEIData(seiData) {
|
|
188
|
+
var _a, _b, _c, _d;
|
|
189
|
+
if (!this.callbacks) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
this.totalFrameCount++;
|
|
193
|
+
this.intervalFrameCount++;
|
|
194
|
+
const cleanedData = this.ebspToRbsp(seiData);
|
|
195
|
+
if (cleanedData.length < SEI_HEADER_SIZE) {
|
|
196
|
+
logger.warn("SEIExtractor", `SEI data too short: ${cleanedData.length}`);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const flags = cleanedData[0];
|
|
200
|
+
const msgLen = new DataView(
|
|
201
|
+
cleanedData.buffer,
|
|
202
|
+
cleanedData.byteOffset + 1,
|
|
203
|
+
4
|
|
204
|
+
).getUint32(0, true);
|
|
205
|
+
if (this.debugLogging && (this.totalFrameCount <= 5 || this.totalFrameCount % 50 === 0)) {
|
|
206
|
+
logger.info(
|
|
207
|
+
"SEIExtractor",
|
|
208
|
+
`SEI packet #${this.totalFrameCount}: flags=0x${flags.toString(16)}, msgLen=${msgLen}, cleanedLen=${cleanedData.length}`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
const isIdle = (flags & PacketFlags.Idle) !== 0;
|
|
212
|
+
if (isIdle || msgLen === 0) {
|
|
213
|
+
if (!this.lastWasIdle) {
|
|
214
|
+
this.lastWasIdle = true;
|
|
215
|
+
this.isInStartTransition = false;
|
|
216
|
+
this.isInEndTransition = false;
|
|
217
|
+
this.callbacks.onIdleStart();
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const rawPayload = cleanedData.slice(SEI_HEADER_SIZE);
|
|
222
|
+
const compressedPayload = this.unescapeZeroBytes(rawPayload);
|
|
223
|
+
let payload;
|
|
224
|
+
if ((flags & PacketFlags.Gzipped) !== 0) {
|
|
225
|
+
payload = await this.decompressZlib(compressedPayload);
|
|
226
|
+
} else {
|
|
227
|
+
payload = compressedPayload;
|
|
228
|
+
}
|
|
229
|
+
const isTransition = (flags & PacketFlags.Transition) !== 0;
|
|
230
|
+
const isTransitionEnd = (flags & PacketFlags.TransitionEnd) !== 0;
|
|
231
|
+
const isStart = (flags & PacketFlags.Start) !== 0;
|
|
232
|
+
const isEnd = (flags & PacketFlags.End) !== 0;
|
|
233
|
+
let protobufData;
|
|
234
|
+
let frameSeq;
|
|
235
|
+
if (isTransition || isTransitionEnd) {
|
|
236
|
+
protobufData = new Uint8Array(payload).buffer;
|
|
237
|
+
} else {
|
|
238
|
+
if (payload.length < FRAME_SEQ_SIZE) {
|
|
239
|
+
logger.warn("SEIExtractor", "Payload too short for frame sequence");
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
frameSeq = new DataView(payload.buffer, payload.byteOffset, 4).getUint32(
|
|
243
|
+
0,
|
|
244
|
+
true
|
|
245
|
+
);
|
|
246
|
+
if (this.debugLogging && (this.totalFrameCount <= 5 || this.totalFrameCount % 50 === 0)) {
|
|
247
|
+
logger.info("SEIExtractor", `Animation frame seq=${frameSeq}`);
|
|
248
|
+
}
|
|
249
|
+
protobufData = new Uint8Array(
|
|
250
|
+
payload.subarray(FRAME_SEQ_SIZE)
|
|
251
|
+
).buffer;
|
|
252
|
+
}
|
|
253
|
+
if (isTransition) {
|
|
254
|
+
if (!this.isInStartTransition) {
|
|
255
|
+
this.isInStartTransition = true;
|
|
256
|
+
this.isInEndTransition = false;
|
|
257
|
+
if (this.debugLogging) {
|
|
258
|
+
logger.info("SEIExtractor", "Transition START (first packet)");
|
|
259
|
+
}
|
|
260
|
+
this.callbacks.onTransition(protobufData, this.transitionStartFrameCount);
|
|
261
|
+
}
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (isTransitionEnd) {
|
|
265
|
+
if (!this.isInEndTransition) {
|
|
266
|
+
this.isInEndTransition = true;
|
|
267
|
+
this.isInStartTransition = false;
|
|
268
|
+
if (this.debugLogging) {
|
|
269
|
+
logger.info("SEIExtractor", "Transition END (first packet)");
|
|
270
|
+
}
|
|
271
|
+
this.callbacks.onTransitionEnd(protobufData, this.transitionEndFrameCount);
|
|
272
|
+
}
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
this.isInStartTransition = false;
|
|
276
|
+
this.isInEndTransition = false;
|
|
277
|
+
const isFirstFrame = this.lastWasIdle || isStart;
|
|
278
|
+
this.lastWasIdle = false;
|
|
279
|
+
if (isFirstFrame) {
|
|
280
|
+
if (this.debugLogging) {
|
|
281
|
+
logger.info("SEIExtractor", "Session start");
|
|
282
|
+
}
|
|
283
|
+
(_b = (_a = this.callbacks).onSessionStart) == null ? void 0 : _b.call(_a);
|
|
284
|
+
}
|
|
285
|
+
this.callbacks.onAnimationData(protobufData, {
|
|
286
|
+
frameSeq,
|
|
287
|
+
isStart: isFirstFrame,
|
|
288
|
+
isEnd,
|
|
289
|
+
isIdle: false,
|
|
290
|
+
isRecovered: false
|
|
291
|
+
// Agora handles reliability internally
|
|
292
|
+
});
|
|
293
|
+
if (isEnd) {
|
|
294
|
+
if (this.debugLogging) {
|
|
295
|
+
logger.info("SEIExtractor", "Session end");
|
|
296
|
+
}
|
|
297
|
+
(_d = (_c = this.callbacks).onSessionEnd) == null ? void 0 : _d.call(_c);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Log debug information for failed SEI processing.
|
|
302
|
+
* @internal
|
|
303
|
+
*/
|
|
304
|
+
logDebugInfo(seiData) {
|
|
305
|
+
const totalLen = seiData.length;
|
|
306
|
+
logger.error("SEIExtractor", `Total SEI length: ${totalLen}`);
|
|
307
|
+
if (seiData.length >= 20) {
|
|
308
|
+
const hexBytes = Array.from(seiData.slice(0, 20)).map((b) => b.toString(16).padStart(2, "0")).join(" ");
|
|
309
|
+
logger.error("SEIExtractor", "First 20 bytes of SEI:", hexBytes);
|
|
310
|
+
}
|
|
311
|
+
if (seiData.length >= SEI_HEADER_SIZE) {
|
|
312
|
+
const flags = seiData[0];
|
|
313
|
+
const msgLen = new DataView(
|
|
314
|
+
seiData.buffer,
|
|
315
|
+
seiData.byteOffset + 1,
|
|
316
|
+
4
|
|
317
|
+
).getUint32(0, true);
|
|
318
|
+
logger.error(
|
|
319
|
+
"SEIExtractor",
|
|
320
|
+
`SEI header: flags=0x${flags.toString(16)}, msgLen=${msgLen}`
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Start the statistics reporting interval.
|
|
326
|
+
* @internal
|
|
327
|
+
*/
|
|
328
|
+
startStatsInterval() {
|
|
329
|
+
this.lastStatsTime = Date.now();
|
|
330
|
+
this.statsInterval = setInterval(() => {
|
|
331
|
+
const now = Date.now();
|
|
332
|
+
const elapsedSeconds = (now - this.lastStatsTime) / 1e3;
|
|
333
|
+
if (elapsedSeconds > 0 && this.callbacks) {
|
|
334
|
+
const fps = this.intervalFrameCount / elapsedSeconds;
|
|
335
|
+
const stats = {
|
|
336
|
+
framesPerSec: Math.round(fps * 10) / 10,
|
|
337
|
+
totalFrames: this.totalFrameCount,
|
|
338
|
+
framesSent: this.totalFrameCount,
|
|
339
|
+
framesLost: 0,
|
|
340
|
+
framesRecovered: 0,
|
|
341
|
+
framesDropped: 0,
|
|
342
|
+
framesOutOfOrder: 0,
|
|
343
|
+
framesDuplicate: 0,
|
|
344
|
+
lastRenderedSeq: -1
|
|
345
|
+
};
|
|
346
|
+
this.callbacks.onStreamStats(stats);
|
|
347
|
+
}
|
|
348
|
+
this.intervalFrameCount = 0;
|
|
349
|
+
this.lastStatsTime = now;
|
|
350
|
+
}, 1e3);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Stop the statistics reporting interval.
|
|
354
|
+
* @internal
|
|
355
|
+
*/
|
|
356
|
+
stopStatsInterval() {
|
|
357
|
+
if (this.statsInterval) {
|
|
358
|
+
clearInterval(this.statsInterval);
|
|
359
|
+
this.statsInterval = null;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Reset session state.
|
|
364
|
+
* Call this when reconnecting or starting a new session.
|
|
365
|
+
* @internal
|
|
366
|
+
*/
|
|
367
|
+
reset() {
|
|
368
|
+
this.lastWasIdle = true;
|
|
369
|
+
this.isInStartTransition = false;
|
|
370
|
+
this.isInEndTransition = false;
|
|
371
|
+
this.totalFrameCount = 0;
|
|
372
|
+
this.intervalFrameCount = 0;
|
|
373
|
+
this.lastStatsTime = Date.now();
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Dispose the extractor and release resources.
|
|
377
|
+
* @internal
|
|
378
|
+
*/
|
|
379
|
+
dispose() {
|
|
380
|
+
this.stopStatsInterval();
|
|
381
|
+
this.callbacks = null;
|
|
382
|
+
this.lastWasIdle = true;
|
|
383
|
+
this.isInStartTransition = false;
|
|
384
|
+
this.isInEndTransition = false;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
export {
|
|
388
|
+
SEIExtractor
|
|
389
|
+
};
|
|
390
|
+
//# sourceMappingURL=index11.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index11.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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"}
|