@spatialwalk/avatarkit-rtc 1.0.0-beta.6 → 1.0.0-beta.8
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/dist/assets/animation-worker-CdhDm7lL.js.map +1 -0
- package/dist/core/AvatarPlayer.d.ts.map +1 -1
- package/dist/index11.js +64 -346
- package/dist/index11.js.map +1 -1
- package/dist/index12.js +14 -104
- package/dist/index12.js.map +1 -1
- package/dist/index13.js +386 -14
- package/dist/index13.js.map +1 -1
- package/dist/index15.js +1 -1
- package/dist/index2.js +34 -2
- package/dist/index2.js.map +1 -1
- package/dist/index3.js +2 -2
- package/dist/index4.js +1 -1
- package/dist/index6.js +134 -17
- package/dist/index6.js.map +1 -1
- package/dist/providers/livekit/animation-worker.d.ts.map +1 -1
- package/package.json +4 -4
- package/dist/assets/animation-worker-CUXZycUw.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"animation-worker-CdhDm7lL.js","sources":["../src/providers/livekit/animation-worker.ts"],"sourcesContent":["/**\n * RTCRtpScriptTransform Worker for Animation Track (VP8 Video)\n *\n * This worker handles extracting animation data from VP8 video frames.\n * The server prepends a minimal VP8 header to trick the browser's depacketizer.\n *\n * Frame format after depacketization (VP8 descriptor is stripped by depacketizer):\n * [0-9] VP8 frame header (10 bytes) - minimal keyframe structure, skipped\n * [10] flags (uint8) - packet flags\n * [11-14] msgLen (little-endian u32) - length of protobuf message\n * [15..15+msgLen) protobuf Message binary\n */\n\n// Type declarations for RTCRtpScriptTransform API (experimental)\ndeclare interface RTCTransformEvent extends Event {\n transformer: {\n readable: ReadableStream;\n writable: WritableStream;\n options?: unknown;\n };\n}\n\n// Worker global scope with RTCRtpScriptTransform support\ndeclare const self: DedicatedWorkerGlobalScope & {\n onrtctransform?: ((event: RTCTransformEvent) => void) | null;\n};\n\n// VP8 header size (frame tag + sync code + dimensions, descriptor already stripped)\nconst VP8_FRAME_HEADER_SIZE = 10;\n\n// Protocol constants (must match egress-server/metadata_track.go)\nconst METADATA_FIXED_HEADER_SIZE = 5; // 1B flags + 4B length\n\n// Packet flags\nconst PACKET_FLAG_IDLE = 0x01;\nconst PACKET_FLAG_START = 0x02;\nconst PACKET_FLAG_END = 0x04;\nconst PACKET_FLAG_GZIPPED = 0x08;\nconst PACKET_FLAG_TRANSITION = 0x10; // Transition packet - contains target frame for generating transition from idle\nconst PACKET_FLAG_TRANSITION_END = 0x20; // Transition end packet - contains last frame for generating transition back to idle\nconst PACKET_FLAG_REDUNDANT = 0x40; // Packet contains redundant previous frame for ALR loss recovery\n\ninterface FrameMetadata {\n flags: number;\n protobufLength: number;\n protobufData: Uint8Array;\n // ALR (Application-Level Redundancy) data\n hasRedundant: boolean;\n redundantLength: number;\n redundantData: Uint8Array | null;\n}\n\ninterface TransformOptions {\n operation: 'receiver';\n}\n\n// State for receiver\nlet receiverMetaCount = 0;\nlet lastLogTime = 0;\nlet totalFrames = 0;\nlet framesWithMeta = 0;\nlet wasIdle = false; // Track idle state for transition detection\nlet isInStartTransition = false; // Dedup consecutive transition-start packets\nlet isInEndTransition = false; // Dedup consecutive transition-end packets\n\n// ALR (Application-Level Redundancy) state for loss detection\nlet lastReceivedTimestamp: number | null = null;\nlet framesRecovered = 0;\nlet framesLost = 0;\n// Expected timestamp increment per animation frame (90000 Hz * 40ms = 3600)\n// Animation is sent every 2nd audio frame (40ms intervals)\nconst EXPECTED_TIMESTAMP_INCREMENT = 3600;\n\n// Sequence tracking for out-of-order detection\nlet lastRenderedSeq: number = -1; // Last sequence number sent to main thread\nlet framesOutOfOrder = 0; // Count of out-of-order frames detected\nlet framesDuplicate = 0; // Count of duplicate frames skipped\nlet framesDropped = 0; // Count of frames that were permanently lost (gaps in sequence)\nlet framesSent = 0; // Total frames successfully sent to main thread\n\nfunction parseMetadataHeader(data: Uint8Array): { meta: FrameMetadata; headerSize: number } | null {\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); // little-endian\n\n // Calculate minimum size needed\n const headerSize = METADATA_FIXED_HEADER_SIZE + msgLen;\n if (data.byteLength < headerSize) return null;\n\n // The compressed payload (will be decompressed later)\n const compressedData = data.slice(METADATA_FIXED_HEADER_SIZE, METADATA_FIXED_HEADER_SIZE + msgLen);\n\n // Note: With the new ALR optimization, both frames are gzipped together\n // The format INSIDE the gzip (after decompression) is:\n // - ALR: [currentLen (4B)][currentData][prevLen (4B)][prevData]\n // - Non-ALR: just the raw protobuf data\n // We return the compressed data here; decompression and ALR parsing happens in receiverTransform\n\n const hasRedundant = (flags & PACKET_FLAG_REDUNDANT) !== 0;\n\n return {\n meta: {\n flags,\n protobufLength: msgLen,\n protobufData: compressedData, // This is still compressed at this point\n hasRedundant,\n redundantLength: 0, // Will be determined after decompression\n redundantData: null, // Will be extracted after decompression\n },\n headerSize,\n };\n}\n\nfunction isIdlePacket(flags: number): boolean {\n return (flags & PACKET_FLAG_IDLE) !== 0;\n}\n\nfunction isStartPacket(flags: number): boolean {\n return (flags & PACKET_FLAG_START) !== 0;\n}\n\nfunction isEndPacket(flags: number): boolean {\n return (flags & PACKET_FLAG_END) !== 0;\n}\n\nfunction isGzipped(flags: number): boolean {\n return (flags & PACKET_FLAG_GZIPPED) !== 0;\n}\n\nfunction isTransitionPacket(flags: number): boolean {\n return (flags & PACKET_FLAG_TRANSITION) !== 0;\n}\n\nfunction isTransitionEndPacket(flags: number): boolean {\n return (flags & PACKET_FLAG_TRANSITION_END) !== 0;\n}\n\n// Decompress gzipped data using DecompressionStream API\nasync function decompressGzip(data: Uint8Array): Promise<Uint8Array> {\n const ds = new DecompressionStream('gzip');\n const writer = ds.writable.getWriter();\n // Create a copy to ensure we have a plain ArrayBuffer, not SharedArrayBuffer\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// Compression stats for logging\nlet totalCompressedBytes = 0;\nlet totalUncompressedBytes = 0;\n\n// ALR payload structure after decompression\ninterface ALRPayload {\n frameSeq: number; // Sequence number of current frame\n currentData: Uint8Array; // Current frame (N)\n prev1Data: Uint8Array | null; // Previous frame (N-1)\n prev2Data: Uint8Array | null; // Frame before previous (N-2)\n}\n\n// Parse ALR payload after decompression\n// ALR format: [frameSeq (4B)][currentLen (4B)][currentData][prev1Len (4B)][prev1Data][prev2Len (4B)][prev2Data]\n// Non-ALR format: [frameSeq (4B)][raw protobuf data]\n// Returns { frameSeq, currentData, prev1Data, prev2Data } or null if parsing fails\nfunction parseALRPayload(decompressed: Uint8Array, hasRedundant: boolean): ALRPayload | null {\n if (decompressed.byteLength < 4) return null;\n\n const view = new DataView(decompressed.buffer, decompressed.byteOffset, decompressed.byteLength);\n let offset = 0;\n\n // Parse frame sequence number\n const frameSeq = view.getUint32(offset, true);\n offset += 4;\n\n if (!hasRedundant) {\n // Non-ALR: rest is raw protobuf\n const currentData = decompressed.slice(offset);\n return { frameSeq, currentData, prev1Data: null, prev2Data: null };\n }\n\n // ALR format: parse current frame\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\n // Parse prev1 (N-1) if available\n let prev1Data: Uint8Array | null = 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\n // Parse prev2 (N-2) if available\n let prev2Data: Uint8Array | null = 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\n return { frameSeq, currentData, prev1Data, prev2Data };\n}\n\n// Helper to send animation data to main thread with sequence tracking\n// Returns true if frame was sent, false if skipped (duplicate/out-of-order)\nfunction sendAnimationToMainThread(\n protobufData: Uint8Array,\n flags: number,\n frameSeq: number,\n isRecovered: boolean = false\n): boolean {\n const isIdle = isIdlePacket(flags);\n const isStart = isStartPacket(flags);\n const isEnd = isEndPacket(flags);\n\n // Check for out-of-order or duplicate frames\n if (frameSeq <= lastRenderedSeq && lastRenderedSeq !== -1 && !isStart) {\n if (frameSeq === lastRenderedSeq) {\n framesDuplicate++;\n } else {\n framesOutOfOrder++;\n }\n return false;\n }\n\n // Check for gap (frames we never received and couldn't recover) - these are DROPPED\n if (lastRenderedSeq !== -1 && frameSeq > lastRenderedSeq + 1 && !isStart) {\n const gap = frameSeq - lastRenderedSeq - 1;\n framesDropped += gap;\n }\n\n framesSent++;\n\n lastRenderedSeq = frameSeq;\n\n const protobufBuffer = new ArrayBuffer(protobufData.byteLength);\n new Uint8Array(protobufBuffer).set(protobufData);\n\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\n return true;\n}\n\n// Receiver transform: extract animation data from video frames\n// Uses ALR (Application-Level Redundancy) to recover lost frames\nfunction receiverTransform(frame: RTCEncodedVideoFrame, _controller: TransformStreamDefaultController<RTCEncodedVideoFrame>) {\n totalFrames++;\n const data = new Uint8Array(frame.data);\n const currentTimestamp = frame.timestamp;\n\n // Skip the VP8 frame header (10 bytes) - the VP8 descriptor is already stripped by depacketizer\n // Check if we have enough data for VP8 header + at least 1 byte of animation data\n if (data.length <= VP8_FRAME_HEADER_SIZE) {\n return; // Frame too small, skip\n }\n\n // Extract animation data after VP8 header\n const animData = data.subarray(VP8_FRAME_HEADER_SIZE);\n\n const parsed = parseMetadataHeader(animData);\n\n if (parsed) {\n const { meta } = parsed;\n framesWithMeta++;\n receiverMetaCount++;\n\n const isIdle = isIdlePacket(meta.flags);\n const isStart = isStartPacket(meta.flags);\n const isEnd = isEndPacket(meta.flags);\n\n // ALR: Detect frame loss and recover using redundant data\n // Check if we missed frames by looking at timestamp gap\n if (lastReceivedTimestamp !== null && !isIdle && !isStart) {\n const timestampDelta = currentTimestamp - lastReceivedTimestamp;\n // Allow some tolerance (1.5x expected increment) for timing variations\n if (timestampDelta > EXPECTED_TIMESTAMP_INCREMENT * 1.5) {\n const missedFrames = Math.round(timestampDelta / EXPECTED_TIMESTAMP_INCREMENT) - 1;\n framesLost += missedFrames;\n\n // Try to recover using redundant data from current packet\n // With ALR optimization, up to 3 frames are gzipped together\n if (meta.hasRedundant && isGzipped(meta.flags)) {\n // Decompress to get all frames, then recover\n totalCompressedBytes += meta.protobufData.byteLength;\n\n decompressGzip(meta.protobufData)\n .then((decompressed) => {\n totalUncompressedBytes += decompressed.byteLength;\n\n const parsed = parseALRPayload(decompressed, true);\n if (parsed) {\n const currentSeq = parsed.frameSeq;\n\n // Determine how many frames we can recover based on how many were lost\n // and how many redundant frames we have\n const framesToRecover: Array<{data: Uint8Array, seq: number}> = [];\n\n if (missedFrames >= 2 && parsed.prev2Data) {\n framesToRecover.push({ data: parsed.prev2Data, seq: currentSeq - 2 });\n }\n if (missedFrames >= 1 && parsed.prev1Data) {\n framesToRecover.push({ data: parsed.prev1Data, seq: currentSeq - 1 });\n }\n\n const recovered = framesToRecover.length;\n if (recovered > 0) {\n framesRecovered += recovered;\n\n // Send recovered frames in chronological order (oldest first)\n for (const frame of framesToRecover) {\n sendAnimationToMainThread(frame.data, meta.flags & ~PACKET_FLAG_REDUNDANT, frame.seq, true);\n }\n }\n\n // Then send current frame\n sendAnimationToMainThread(parsed.currentData, meta.flags & ~PACKET_FLAG_REDUNDANT, currentSeq, false);\n }\n })\n .catch((err) => {\n console.error(`[Animation Worker] ALR decompression error:`, err);\n });\n\n // Update timestamp and return early (we've handled this frame)\n lastReceivedTimestamp = currentTimestamp;\n return;\n }\n }\n }\n\n // Update last received timestamp (skip for idle packets to avoid false loss detection)\n if (!isIdle) {\n lastReceivedTimestamp = currentTimestamp;\n }\n\n // Reset state on session start\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\n const isTransition = isTransitionPacket(meta.flags);\n const isTransitionEnd = isTransitionEndPacket(meta.flags);\n\n if (isIdle) {\n // First idle packet - post idleStart event\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 // Transition packet - contains target frame for smooth transition from idle\n wasIdle = false;\n\n // Dedup: only emit first packet in a consecutive transition-start run.\n if (!isInStartTransition) {\n isInStartTransition = true;\n isInEndTransition = false;\n\n // Decompress if gzipped (transition packets are always gzipped)\n const gzipped = isGzipped(meta.flags);\n if (gzipped) {\n totalCompressedBytes += meta.protobufData.byteLength;\n\n decompressGzip(meta.protobufData).then((decompressed) => {\n totalUncompressedBytes += decompressed.byteLength;\n\n const protobufBuffer = new ArrayBuffer(decompressed.byteLength);\n new Uint8Array(protobufBuffer).set(decompressed);\n\n // Post transition event - main thread will generate transition frames\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\n self.postMessage({\n type: 'transition',\n flags: meta.flags,\n protobufData: protobufBuffer,\n }, { transfer: [protobufBuffer] });\n }\n }\n } else if (isTransitionEnd && meta.protobufLength > 0) {\n // Transition end packet - contains last frame for smooth transition back to idle\n\n // Dedup: only emit first packet in a consecutive transition-end run.\n if (!isInEndTransition) {\n isInEndTransition = true;\n isInStartTransition = false;\n\n // Decompress if gzipped (transition end packets are always gzipped)\n const gzipped = isGzipped(meta.flags);\n if (gzipped) {\n totalCompressedBytes += meta.protobufData.byteLength;\n\n decompressGzip(meta.protobufData).then((decompressed) => {\n totalUncompressedBytes += decompressed.byteLength;\n\n const protobufBuffer = new ArrayBuffer(decompressed.byteLength);\n new Uint8Array(protobufBuffer).set(decompressed);\n\n // Post transition end event - main thread will generate reverse transition frames\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\n self.postMessage({\n type: 'transitionEnd',\n flags: meta.flags,\n protobufData: protobufBuffer,\n }, { transfer: [protobufBuffer] });\n }\n }\n } else if (meta.protobufLength > 0) {\n // Normal animation packet\n // Transition from idle to streaming (if not already transitioned via transition packet)\n if (wasIdle) {\n wasIdle = false;\n }\n isInStartTransition = false;\n isInEndTransition = false;\n\n // Decompress if gzipped\n const gzipped = isGzipped(meta.flags);\n if (gzipped) {\n // Track compression stats\n totalCompressedBytes += meta.protobufData.byteLength;\n\n // Async decompression\n decompressGzip(meta.protobufData).then((decompressed) => {\n totalUncompressedBytes += decompressed.byteLength;\n\n // Parse ALR format to get frame sequence and data\n // Format: [frameSeq (4B)][currentLen (4B)][currentData]... or [frameSeq (4B)][raw protobuf]\n const parsed = parseALRPayload(decompressed, meta.hasRedundant);\n if (parsed) {\n sendAnimationToMainThread(parsed.currentData, meta.flags & ~PACKET_FLAG_REDUNDANT, parsed.frameSeq, false);\n }\n }).catch(() => {});\n } else {\n // Uncompressed - this shouldn't happen in normal operation but handle it\n const protobufBuffer = new ArrayBuffer(meta.protobufData.byteLength);\n new Uint8Array(protobufBuffer).set(meta.protobufData);\n\n self.postMessage({\n type: 'animation',\n flags: meta.flags,\n isIdle: false,\n isStart,\n isEnd,\n frameSeq: -1, // Unknown sequence\n protobufData: protobufBuffer,\n }, { transfer: [protobufBuffer] });\n }\n }\n\n // Report stats every second\n const now = performance.now();\n if (now - lastLogTime > 1000) {\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\n // For video, we don't enqueue the frame since it's not real video\n // The browser's VP8 decoder would fail on our custom data anyway\n // controller.enqueue(frame);\n}\n\n// Handle rtctransform event (RTCRtpScriptTransform)\nself.onrtctransform = (event: RTCTransformEvent) => {\n const transformer = event.transformer;\n const options = transformer.options as TransformOptions;\n\n try {\n if (options.operation === 'receiver') {\n // Reset state\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\n transformer.readable\n .pipeThrough(new TransformStream({ transform: receiverTransform }))\n .pipeTo(transformer.writable)\n .catch((err: unknown) => {\n console.error('[Animation Worker] Pipeline error:', err);\n self.postMessage({ type: 'error', 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({ type: 'error', error: `Animation transform setup error: ${err}` });\n }\n};\n\n// Handle message-based initialization for fallback\nself.onmessage = (event: MessageEvent) => {\n const { type } = event.data;\n if (type === 'init') {\n self.postMessage({ type: 'initialized' });\n }\n};\n\nexport {};\n"],"names":["currentData","parsed","frame"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AvatarPlayer.d.ts","sourceRoot":"","sources":["../../src/core/AvatarPlayer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAGpD,OAAO,EAA2B,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAGlE,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,wBAAwB,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAEpB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,qBAAa,YAAY;
|
|
1
|
+
{"version":3,"file":"AvatarPlayer.d.ts","sourceRoot":"","sources":["../../src/core/AvatarPlayer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAGpD,OAAO,EAA2B,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAGlE,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,wBAAwB,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAEpB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,qBAAa,YAAY;IAkCvB;;;;;OAKG;gBAED,QAAQ,EAAE,WAAW,EACrB,UAAU,EAAE,UAAU,EACtB,OAAO,CAAC,EAAE,mBAAmB;IA6B/B;;OAEG;IACH,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;;;OAIG;IACG,OAAO,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAczD;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBjC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACG,YAAY,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB1D;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IASrC;;;;;;;;;;;;;;OAcG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBtC;;;;;OAKG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAYrC;;;OAGG;IACH,kBAAkB,IAAI,MAAM;IAI5B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,eAAe,IAAI,OAAO;IAI1B;;;;OAIG;IACH,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI;IAa1C;;;;OAIG;IACH,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI;IAyB3C;;;;;;;;;;;;;OAaG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;CAkKjC"}
|
package/dist/index11.js
CHANGED
|
@@ -1,390 +1,108 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
import { createAnimationReceiverTransform } from "./index14.js";
|
|
4
5
|
import { logger } from "./index7.js";
|
|
5
|
-
|
|
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;
|
|
6
|
+
import WorkerWrapper from "./index15.js";
|
|
21
7
|
const DEFAULT_TRANSITION_START_FRAMES = 8;
|
|
22
8
|
const DEFAULT_TRANSITION_END_FRAMES = 12;
|
|
23
|
-
class
|
|
9
|
+
class VP8Extractor {
|
|
24
10
|
constructor() {
|
|
25
11
|
/** @internal */
|
|
26
12
|
__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
13
|
/** @internal */
|
|
48
|
-
__publicField(this, "
|
|
49
|
-
// Debug logging
|
|
14
|
+
__publicField(this, "receiver", null);
|
|
50
15
|
/** @internal */
|
|
51
|
-
__publicField(this, "
|
|
52
|
-
this.transitionStartFrameCount = DEFAULT_TRANSITION_START_FRAMES;
|
|
53
|
-
this.transitionEndFrameCount = DEFAULT_TRANSITION_END_FRAMES;
|
|
16
|
+
__publicField(this, "transform", null);
|
|
54
17
|
}
|
|
55
18
|
/**
|
|
56
|
-
* Initialize the extractor with callbacks.
|
|
19
|
+
* Initialize the extractor with a receiver and callbacks.
|
|
20
|
+
* @param receiver - RTCRtpReceiver to extract data from
|
|
57
21
|
* @param callbacks - Callbacks to receive extracted data
|
|
58
22
|
* @internal
|
|
59
23
|
*/
|
|
60
|
-
initialize(callbacks) {
|
|
61
|
-
this.
|
|
62
|
-
|
|
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;
|
|
24
|
+
async initialize(receiver, callbacks) {
|
|
25
|
+
if (this.receiver || this.transform) {
|
|
26
|
+
throw new Error("VP8Extractor already initialized");
|
|
252
27
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
}
|
|
28
|
+
this.receiver = receiver;
|
|
29
|
+
this.callbacks = callbacks;
|
|
30
|
+
if (receiver.transform) {
|
|
273
31
|
return;
|
|
274
32
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
}
|
|
33
|
+
const worker = new WorkerWrapper();
|
|
34
|
+
const onEvent = (evt) => {
|
|
35
|
+
this.handleTransformEvent(evt);
|
|
36
|
+
};
|
|
37
|
+
this.transform = createAnimationReceiverTransform(worker, onEvent);
|
|
38
|
+
receiver.transform = this.transform;
|
|
299
39
|
}
|
|
300
40
|
/**
|
|
301
|
-
*
|
|
41
|
+
* Handle events from the animation transform.
|
|
302
42
|
* @internal
|
|
303
43
|
*/
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
)
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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);
|
|
44
|
+
handleTransformEvent(evt) {
|
|
45
|
+
if (!this.callbacks) return;
|
|
46
|
+
if (evt.type === "metadata") {
|
|
47
|
+
this.callbacks.onStreamStats({
|
|
48
|
+
framesPerSec: evt.framesPerSec,
|
|
49
|
+
totalFrames: evt.totalFrames ?? 0,
|
|
50
|
+
framesSent: evt.framesSent ?? 0,
|
|
51
|
+
framesLost: evt.framesLost ?? 0,
|
|
52
|
+
framesRecovered: evt.framesRecovered ?? 0,
|
|
53
|
+
framesDropped: evt.framesDropped ?? 0,
|
|
54
|
+
framesOutOfOrder: evt.framesOutOfOrder ?? 0,
|
|
55
|
+
framesDuplicate: evt.framesDuplicate ?? 0,
|
|
56
|
+
lastRenderedSeq: evt.lastRenderedSeq ?? -1
|
|
57
|
+
});
|
|
58
|
+
} else if (evt.type === "transition") {
|
|
59
|
+
this.callbacks.onTransition(evt.protobufData, DEFAULT_TRANSITION_START_FRAMES);
|
|
60
|
+
} else if (evt.type === "transitionEnd") {
|
|
61
|
+
this.callbacks.onTransitionEnd(evt.protobufData, DEFAULT_TRANSITION_END_FRAMES);
|
|
62
|
+
} else if (evt.type === "animation") {
|
|
63
|
+
if (!evt.isIdle) {
|
|
64
|
+
this.callbacks.onAnimationData(evt.protobufData, {
|
|
65
|
+
frameSeq: evt.frameSeq,
|
|
66
|
+
isStart: evt.isStart,
|
|
67
|
+
isEnd: evt.isEnd,
|
|
68
|
+
isIdle: evt.isIdle,
|
|
69
|
+
isRecovered: evt.isRecovered
|
|
70
|
+
});
|
|
347
71
|
}
|
|
348
|
-
|
|
349
|
-
this.
|
|
350
|
-
}
|
|
72
|
+
} else if (evt.type === "idleStart") {
|
|
73
|
+
this.callbacks.onIdleStart();
|
|
74
|
+
} else if (evt.type === "error") {
|
|
75
|
+
logger.error("VP8Extractor", "Error:", evt.error);
|
|
76
|
+
}
|
|
351
77
|
}
|
|
352
78
|
/**
|
|
353
|
-
*
|
|
79
|
+
* Check if this extractor is connected to the given receiver.
|
|
80
|
+
* @param receiver - RTCRtpReceiver to check
|
|
81
|
+
* @returns true if connected to this receiver
|
|
354
82
|
* @internal
|
|
355
83
|
*/
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
clearInterval(this.statsInterval);
|
|
359
|
-
this.statsInterval = null;
|
|
360
|
-
}
|
|
84
|
+
isConnectedTo(receiver) {
|
|
85
|
+
return this.receiver === receiver;
|
|
361
86
|
}
|
|
362
87
|
/**
|
|
363
|
-
*
|
|
364
|
-
*
|
|
88
|
+
* Get the receiver this extractor is connected to.
|
|
89
|
+
* @returns The connected RTCRtpReceiver or null
|
|
365
90
|
* @internal
|
|
366
91
|
*/
|
|
367
|
-
|
|
368
|
-
this.
|
|
369
|
-
this.isInStartTransition = false;
|
|
370
|
-
this.isInEndTransition = false;
|
|
371
|
-
this.totalFrameCount = 0;
|
|
372
|
-
this.intervalFrameCount = 0;
|
|
373
|
-
this.lastStatsTime = Date.now();
|
|
92
|
+
getReceiver() {
|
|
93
|
+
return this.receiver;
|
|
374
94
|
}
|
|
375
95
|
/**
|
|
376
96
|
* Dispose the extractor and release resources.
|
|
377
97
|
* @internal
|
|
378
98
|
*/
|
|
379
99
|
dispose() {
|
|
380
|
-
this.
|
|
100
|
+
this.receiver = null;
|
|
101
|
+
this.transform = null;
|
|
381
102
|
this.callbacks = null;
|
|
382
|
-
this.lastWasIdle = true;
|
|
383
|
-
this.isInStartTransition = false;
|
|
384
|
-
this.isInEndTransition = false;
|
|
385
103
|
}
|
|
386
104
|
}
|
|
387
105
|
export {
|
|
388
|
-
|
|
106
|
+
VP8Extractor
|
|
389
107
|
};
|
|
390
108
|
//# sourceMappingURL=index11.js.map
|
package/dist/index11.js.map
CHANGED
|
@@ -1 +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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"}
|
|
1
|
+
{"version":3,"file":"index11.js","sources":["../src/providers/livekit/VP8Extractor.ts"],"sourcesContent":["/**\n * VP8 Data Extractor for LiveKit.\n *\n * Extracts animation data from VP8 video tracks using RTCRtpScriptTransform.\n *\n * @internal\n * @packageDocumentation\n */\n\nimport type { AnimationTrackCallbacks } from '../../core/types';\nimport type { AnimationTransformEvent } from './types';\nimport { createAnimationReceiverTransform } from './animation-transform';\nimport { logger } from '../../utils';\n\n// Import worker with inline - this bundles the worker code as a blob URL\nimport AnimationWorker from './animation-worker.ts?worker&inline';\n\n// Default transition frame counts (used when protocol doesn't specify)\nconst DEFAULT_TRANSITION_START_FRAMES = 8;\nconst DEFAULT_TRANSITION_END_FRAMES = 12;\n\n/**\n * VP8Extractor - Extracts animation data from VP8 video tracks.\n *\n * Uses RTCRtpScriptTransform and a Web Worker to parse VP8 frames\n * and extract embedded animation data.\n *\n * @internal\n */\nexport class VP8Extractor {\n /** @internal */\n private callbacks: AnimationTrackCallbacks | null = null;\n /** @internal */\n private receiver: RTCRtpReceiver | null = null;\n /** @internal */\n private transform: RTCRtpScriptTransform | null = null;\n\n /**\n * Initialize the extractor with a receiver and callbacks.\n * @param receiver - RTCRtpReceiver to extract data from\n * @param callbacks - Callbacks to receive extracted data\n * @internal\n */\n async initialize(\n receiver: RTCRtpReceiver,\n callbacks: AnimationTrackCallbacks\n ): Promise<void> {\n if (this.receiver || this.transform) {\n throw new Error('VP8Extractor already initialized');\n }\n\n this.receiver = receiver;\n this.callbacks = callbacks;\n\n // Check if transform is already set\n if (receiver.transform) {\n return;\n }\n\n // Create worker and transform\n const worker = new AnimationWorker();\n const onEvent = (evt: AnimationTransformEvent) => {\n this.handleTransformEvent(evt);\n };\n\n this.transform = createAnimationReceiverTransform(worker, onEvent);\n receiver.transform = this.transform;\n }\n\n /**\n * Handle events from the animation transform.\n * @internal\n */\n private handleTransformEvent(evt: AnimationTransformEvent): void {\n if (!this.callbacks) return;\n\n if (evt.type === 'metadata') {\n this.callbacks.onStreamStats({\n framesPerSec: evt.framesPerSec,\n totalFrames: evt.totalFrames ?? 0,\n framesSent: evt.framesSent ?? 0,\n framesLost: evt.framesLost ?? 0,\n framesRecovered: evt.framesRecovered ?? 0,\n framesDropped: evt.framesDropped ?? 0,\n framesOutOfOrder: evt.framesOutOfOrder ?? 0,\n framesDuplicate: evt.framesDuplicate ?? 0,\n lastRenderedSeq: evt.lastRenderedSeq ?? -1,\n });\n } else if (evt.type === 'transition') {\n this.callbacks.onTransition(evt.protobufData, DEFAULT_TRANSITION_START_FRAMES);\n } else if (evt.type === 'transitionEnd') {\n this.callbacks.onTransitionEnd(evt.protobufData, DEFAULT_TRANSITION_END_FRAMES);\n } else if (evt.type === 'animation') {\n if (!evt.isIdle) {\n this.callbacks.onAnimationData(evt.protobufData, {\n frameSeq: evt.frameSeq,\n isStart: evt.isStart,\n isEnd: evt.isEnd,\n isIdle: evt.isIdle,\n isRecovered: evt.isRecovered,\n });\n }\n } else if (evt.type === 'idleStart') {\n this.callbacks.onIdleStart();\n } else if (evt.type === 'error') {\n logger.error('VP8Extractor', 'Error:', evt.error);\n }\n }\n\n /**\n * Check if this extractor is connected to the given receiver.\n * @param receiver - RTCRtpReceiver to check\n * @returns true if connected to this receiver\n * @internal\n */\n isConnectedTo(receiver: RTCRtpReceiver): boolean {\n return this.receiver === receiver;\n }\n\n /**\n * Get the receiver this extractor is connected to.\n * @returns The connected RTCRtpReceiver or null\n * @internal\n */\n getReceiver(): RTCRtpReceiver | null {\n return this.receiver;\n }\n\n /**\n * Dispose the extractor and release resources.\n * @internal\n */\n dispose(): void {\n this.receiver = null;\n this.transform = null;\n this.callbacks = null;\n }\n}\n"],"names":["AnimationWorker"],"mappings":";;;;;;AAkBA,MAAM,kCAAkC;AACxC,MAAM,gCAAgC;AAU/B,MAAM,aAAa;AAAA,EAAnB;AAEG;AAAA,qCAA4C;AAE5C;AAAA,oCAAkC;AAElC;AAAA,qCAA0C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQlD,MAAM,WACJ,UACA,WACe;AACf,QAAI,KAAK,YAAY,KAAK,WAAW;AACnC,YAAM,IAAI,MAAM,kCAAkC;AAAA,IACpD;AAEA,SAAK,WAAW;AAChB,SAAK,YAAY;AAGjB,QAAI,SAAS,WAAW;AACtB;AAAA,IACF;AAGA,UAAM,SAAS,IAAIA,cAAA;AACnB,UAAM,UAAU,CAAC,QAAiC;AAChD,WAAK,qBAAqB,GAAG;AAAA,IAC/B;AAEA,SAAK,YAAY,iCAAiC,QAAQ,OAAO;AACjE,aAAS,YAAY,KAAK;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,qBAAqB,KAAoC;AAC/D,QAAI,CAAC,KAAK,UAAW;AAErB,QAAI,IAAI,SAAS,YAAY;AAC3B,WAAK,UAAU,cAAc;AAAA,QAC3B,cAAc,IAAI;AAAA,QAClB,aAAa,IAAI,eAAe;AAAA,QAChC,YAAY,IAAI,cAAc;AAAA,QAC9B,YAAY,IAAI,cAAc;AAAA,QAC9B,iBAAiB,IAAI,mBAAmB;AAAA,QACxC,eAAe,IAAI,iBAAiB;AAAA,QACpC,kBAAkB,IAAI,oBAAoB;AAAA,QAC1C,iBAAiB,IAAI,mBAAmB;AAAA,QACxC,iBAAiB,IAAI,mBAAmB;AAAA,MAAA,CACzC;AAAA,IACH,WAAW,IAAI,SAAS,cAAc;AACpC,WAAK,UAAU,aAAa,IAAI,cAAc,+BAA+B;AAAA,IAC/E,WAAW,IAAI,SAAS,iBAAiB;AACvC,WAAK,UAAU,gBAAgB,IAAI,cAAc,6BAA6B;AAAA,IAChF,WAAW,IAAI,SAAS,aAAa;AACnC,UAAI,CAAC,IAAI,QAAQ;AACf,aAAK,UAAU,gBAAgB,IAAI,cAAc;AAAA,UAC/C,UAAU,IAAI;AAAA,UACd,SAAS,IAAI;AAAA,UACb,OAAO,IAAI;AAAA,UACX,QAAQ,IAAI;AAAA,UACZ,aAAa,IAAI;AAAA,QAAA,CAClB;AAAA,MACH;AAAA,IACF,WAAW,IAAI,SAAS,aAAa;AACnC,WAAK,UAAU,YAAA;AAAA,IACjB,WAAW,IAAI,SAAS,SAAS;AAC/B,aAAO,MAAM,gBAAgB,UAAU,IAAI,KAAK;AAAA,IAClD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,cAAc,UAAmC;AAC/C,WAAO,KAAK,aAAa;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAqC;AACnC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAgB;AACd,SAAK,WAAW;AAChB,SAAK,YAAY;AACjB,SAAK,YAAY;AAAA,EACnB;AACF;"}
|