hls-streamer 2.0.0 β†’ 2.2.0

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 CHANGED
@@ -2,235 +2,193 @@
2
2
 
3
3
  [![npm version](https://badge.fury.io/js/hls-streamer.svg)](https://badge.fury.io/js/hls-streamer)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/)
5
+ [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](https://www.typescriptlang.org/)
6
6
 
7
- **HLS Streamer** is a lightweight npm package that creates and streams HLS (HTTP Live Streaming) from MP3 files on demand, without storing temporary files or requiring ffmpeg.
7
+ HLS Streamer turns any MP3 into an HTTP Live Streaming (HLS) playlist on the fly. It analyses the source audio in-memory, builds frame-aligned byte ranges, and streams them without temporary files, native bindings, or external binaries like ffmpeg.
8
8
 
9
- ## ✨ Features
9
+ ---
10
10
 
11
- - πŸš€ **Zero Dependencies** - No ffmpeg required
12
- - πŸ’Ύ **No Temporary Files** - Stream directly from source
13
- - ⚑ **Fast Startup** - Optional smaller initial segments for quick playback start
14
- - 🎯 **TypeScript Support** - Full type definitions included
15
- - πŸ”§ **Configurable** - Customizable segment sizes and naming
16
- - πŸ“± **Memory Efficient** - Byte-range streaming with minimal memory footprint
11
+ - [Why HLS Streamer?](#why-hls-streamer)
12
+ - [How It Works](#how-it-works)
13
+ - [Quick Start](#quick-start)
14
+ - [Serving Over HTTP](#serving-over-http)
15
+ - [Configuration Reference](#configuration-reference)
16
+ - [Playlist Anatomy](#playlist-anatomy)
17
+ - [Operational Tips](#operational-tips)
18
+ - [Development](#development)
19
+ - [Support](#support)
17
20
 
18
- ## πŸ“¦ Installation
21
+ ## Why HLS Streamer?
19
22
 
20
- ```bash
21
- npm install hls-streamer
22
- ```
23
+ - **Zero dependencies** – no shared libraries, no ffmpeg, no native compilation. Drop it into Docker, serverless, or edge runtimes.
24
+ - **Accurate segments** – real MP3 frame parsing provides true durations, `#EXTINF` metadata, and target durations that match playback.
25
+ - **Frame-aligned byte ranges** – every segment begins and ends on verified MP3 frame boundaries, preventing pops and clipped audio.
26
+ - **No temp files** – streams straight from the source MP3 using byte-range reads.
27
+ - **Fast-start aware** – optional smaller first segments improve startup latency for constrained networks.
28
+ - **TypeScript first** – authored in TypeScript with full type definitions for your tooling and IDEs.
23
29
 
24
- ```bash
25
- yarn add hls-streamer
26
- ```
30
+ ## How It Works
27
31
 
28
- ```bash
29
- pnpm add hls-streamer
30
- ```
32
+ 1. **Metadata analysis** – the package inspects ID3v2/ID3v1 tags, parses MP3 frame headers, and produces a frame table with offsets, durations, and bitrates.
33
+ 2. **Segment planning** – segment boundaries are calculated from the frame table so each segment contains whole frames while matching your target sizes.
34
+ 3. **Playlist generation** – `createM3U8()` emits an `#EXTM3U` playlist with accurate `#EXTINF` entries and `#EXT-X-TARGETDURATION` derived from the longest segment.
35
+ 4. **On-demand byte ranges** – `getFileBuffer(start, end)` streams the exact bytes for a segment without reading the entire file into memory.
31
36
 
32
- ## πŸš€ Quick Start
37
+ ```
38
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
39
+ β”‚ MP3 Source β”‚ ─────▢ β”‚ Frame Analyzer β”‚ ─────▢ β”‚ Segment Plannerβ”‚
40
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
41
+ β”‚
42
+ β–Ό
43
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
44
+ β”‚ HLS Playlist & Bytes β”‚
45
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
46
+ ```
33
47
 
34
- ### Basic Usage
48
+ ## Quick Start
35
49
 
36
- ```typescript
50
+ ```ts
37
51
  import { HlsStreamer } from 'hls-streamer';
38
52
 
39
- const hls = new HlsStreamer({
40
- filePath: 'path/to/audio.mp3',
53
+ const streamer = new HlsStreamer({
54
+ filePath: '/media/library/song.mp3',
41
55
  segmentSizeKB: 512,
42
- fileName: 'segment',
43
- baseUrl: 'segments/session-123',
44
- enableFastStart: true
56
+ fileName: 'track',
57
+ baseUrl: 'audio/stream/session-42',
58
+ enableFastStart: true,
45
59
  });
46
60
 
47
- // Generate M3U8 playlist
48
- const playlist = await hls.createM3U8();
61
+ const playlist = await streamer.createM3U8();
62
+ console.log(playlist);
49
63
 
50
- // Get file buffer for specific byte range
51
- const buffer = await hls.getFileBuffer(startByte, endByte);
64
+ const firstSegmentBuffer = await streamer.getFileBuffer(0, 512 * 1024);
52
65
  ```
53
66
 
54
- ### Express.js Integration
67
+ ## Serving Over HTTP
55
68
 
56
- ```typescript
69
+ Create playlists and segment endpoints with any HTTP framework. The example below shows an Express setup:
70
+
71
+ ```ts
57
72
  import express from 'express';
58
73
  import { HlsStreamer } from 'hls-streamer';
59
74
 
60
75
  const app = express();
61
76
 
62
- // Serve M3U8 playlist
63
- app.get('/stream/:sessionId/playlist.m3u8', async (req, res) => {
64
- const hls = new HlsStreamer({
65
- filePath: getFilePath(req.params.sessionId),
66
- baseUrl: `stream/${req.params.sessionId}`,
67
- enableFastStart: true
68
- });
77
+ app.get('/streams/:id/playlist.m3u8', async (req, res, next) => {
78
+ try {
79
+ const streamer = new HlsStreamer({
80
+ filePath: resolveAudioPath(req.params.id),
81
+ baseUrl: `streams/${req.params.id}`,
82
+ enableFastStart: true,
83
+ });
69
84
 
70
- res.setHeader('Content-Type', 'application/vnd.apple.mpegurl');
71
- res.send(await hls.createM3U8());
85
+ res.type('application/vnd.apple.mpegurl');
86
+ res.send(await streamer.createM3U8());
87
+ } catch (error) {
88
+ next(error);
89
+ }
72
90
  });
73
91
 
74
- // Serve segment files
75
- app.get('/stream/:sessionId/:start/:end/:filename', async (req, res) => {
76
- const hls = new HlsStreamer({
77
- filePath: getFilePath(req.params.sessionId),
78
- baseUrl: `stream/${req.params.sessionId}`
79
- });
92
+ app.get('/streams/:id/:start/:end/:filename', async (req, res, next) => {
93
+ try {
94
+ const streamer = new HlsStreamer({
95
+ filePath: resolveAudioPath(req.params.id),
96
+ baseUrl: `streams/${req.params.id}`,
97
+ });
80
98
 
81
- const start = parseInt(req.params.start);
82
- const end = parseInt(req.params.end);
99
+ const start = Number(req.params.start);
100
+ const end = Number(req.params.end);
83
101
 
84
- res.setHeader('Content-Type', 'audio/mpeg');
85
- res.setHeader('Accept-Ranges', 'bytes');
86
- res.send(await hls.getFileBuffer(start, end));
102
+ res.type('audio/mpeg');
103
+ res.set('Accept-Ranges', 'bytes');
104
+ res.send(await streamer.getFileBuffer(start, end));
105
+ } catch (error) {
106
+ next(error);
107
+ }
87
108
  });
88
109
  ```
89
110
 
90
- ## πŸ› οΈ API Reference
91
-
92
- ### HlsStreamer
93
-
94
- #### Constructor Options
95
-
96
- ```typescript
97
- interface HlsStreamerOptions {
98
- /** Path to the MP3 file */
99
- filePath: string;
100
- /** Segment size in KB (default: 512) */
101
- segmentSizeKB?: number;
102
- /** Base filename for segments (default: "file") */
103
- fileName?: string;
104
- /** Base URL path for segment URLs */
105
- baseUrl?: string;
106
- /** Enable smaller initial segments for faster startup */
107
- enableFastStart?: boolean;
108
- }
109
- ```
110
-
111
- #### Methods
112
-
113
- ##### `createM3U8(): Promise<string>`
114
- Generates an HLS M3U8 playlist file content.
115
-
116
- **Returns:** M3U8 playlist as a string
117
-
118
- ##### `getFileBuffer(startByte: number, endByte: number): Promise<Buffer>`
119
- Retrieves a specific byte range from the MP3 file.
120
-
121
- **Parameters:**
122
- - `startByte` - Starting byte position (inclusive)
123
- - `endByte` - Ending byte position (exclusive)
124
-
125
- **Returns:** Buffer containing the requested byte range
111
+ ### Segment URL Contract
126
112
 
127
- ##### `getSegmentDuration(segmentIndex: number): Promise<number>`
128
- Gets the accurate duration of a specific segment.
113
+ Generated playlists follow the pattern below:
129
114
 
130
- **Parameters:**
131
- - `segmentIndex` - Zero-based segment index
132
-
133
- **Returns:** Duration in seconds
134
-
135
- ### Error Handling
136
-
137
- The package includes custom error types for better error handling:
138
-
139
- ```typescript
140
- import {
141
- FileNotFoundError,
142
- InvalidFileError,
143
- InvalidRangeError,
144
- InvalidParameterError
145
- } from 'hls-streamer';
146
-
147
- try {
148
- const hls = new HlsStreamer({ filePath: 'nonexistent.mp3' });
149
- } catch (error) {
150
- if (error instanceof FileNotFoundError) {
151
- console.error('File not found:', error.message);
152
- }
153
- }
154
115
  ```
155
-
156
- ## πŸ“‹ Configuration Options
157
-
158
- | Option | Type | Default | Description |
159
- |--------|------|---------|-------------|
160
- | `filePath` | `string` | **required** | Path to the MP3 file |
161
- | `segmentSizeKB` | `number` | `512` | Size of each segment in KB |
162
- | `fileName` | `string` | `"file"` | Base name for segment files |
163
- | `baseUrl` | `string` | `""` | Base URL path for segment URLs |
164
- | `enableFastStart` | `boolean` | `false` | Use smaller initial segments for faster startup |
165
-
166
- ## 🎯 Use Cases
167
-
168
- - **Audio Streaming Services** - Stream music without pre-processing
169
- - **Podcast Platforms** - On-demand episode streaming
170
- - **Educational Platforms** - Stream lecture recordings
171
- - **Voice Message Systems** - Real-time audio message playback
172
- - **Audio Books** - Chapter-based streaming
173
-
174
- ## πŸ”§ Advanced Examples
175
-
176
- ### Custom Segment Sizing
177
-
178
- ```typescript
179
- const hls = new HlsStreamer({
180
- filePath: 'large-audio-file.mp3',
181
- segmentSizeKB: 1024, // 1MB segments for better quality
182
- enableFastStart: true // First segments will be 256KB and 512KB
183
- });
116
+ /{baseUrl}/{startByte}/{endByte}/{fileName}{index}.mp3
184
117
  ```
185
118
 
186
- ### Dynamic File Paths
187
-
188
- ```typescript
189
- class AudioStreamer {
190
- async streamAudio(userId: string, audioId: string) {
191
- const filePath = await this.getAudioPath(userId, audioId);
119
+ - `startByte` is inclusive, `endByte` is exclusive.
120
+ - `index` is zero-padded to three digits (`000`, `001`, ...).
121
+ - Use the provided byte range as-is when serving `audio/mpeg` responses.
122
+
123
+ ## Configuration Reference
124
+
125
+ | Option | Type | Default | Description |
126
+ | ------------------- | --------- | ------- | ----------- |
127
+ | `filePath` | `string` | β€” | Absolute or relative path to the MP3 file. |
128
+ | `segmentSizeKB` | `number` | `512` | Target segment size in kilobytes. Fast-start mode splits the first two segments into quarters/halves of this value. |
129
+ | `fileName` | `string` | `"file"` | Base name used in generated segment URLs (the index is appended automatically). |
130
+ | `baseUrl` | `string` | `""` | URL prefix inserted before each segment path. Useful when mounting under a route or CDN prefix. |
131
+ | `enableFastStart` | `boolean` | `false` | When true, the first two segments are smaller to reduce initial buffering time. |
132
+
133
+ ### API Surface
134
+
135
+ - `createM3U8(): Promise<string>` – Returns a full playlist with frame-accurate durations.
136
+ - `getFileBuffer(start: number, end: number): Promise<Buffer>` – Streams a byte range from the MP3.
137
+ - `getSegmentDuration(index: number): Promise<number>` – Reads the cached segment table to return the duration of a segment in seconds.
138
+
139
+ Custom error classes are exported to help with error handling: `FileNotFoundError`, `InvalidFileError`, `InvalidRangeError`, and `InvalidParameterError`.
140
+
141
+ ## Playlist Anatomy
142
+
143
+ ```m3u8
144
+ #EXTM3U
145
+ #EXT-X-VERSION:6
146
+ #EXT-X-PLAYLIST-TYPE:VOD
147
+ #EXT-X-TARGETDURATION:6
148
+ #EXT-X-MEDIA-SEQUENCE:0
149
+ #EXTINF:5.973,
150
+ /audio/session/0/260736/track000.mp3
151
+ #EXTINF:5.994,
152
+ /audio/session/260736/521472/track001.mp3
153
+ ...
154
+ #EXT-X-ENDLIST
155
+ ```
192
156
 
193
- const hls = new HlsStreamer({
194
- filePath,
195
- baseUrl: `audio/${userId}/${audioId}`,
196
- fileName: `audio-${audioId}`,
197
- segmentSizeKB: 256 // Smaller segments for mobile
198
- });
157
+ - `#EXT-X-TARGETDURATION` is rounded up from the longest real segment duration.
158
+ - `#EXTINF` entries retain millisecond precision for smooth playback on strict clients.
159
+ - Segment paths directly encode the byte ranges your route must return.
199
160
 
200
- return hls.createM3U8();
201
- }
202
- }
203
- ```
161
+ ## Operational Tips
204
162
 
205
- ## πŸ§ͺ Testing
163
+ - **Caching** – Construct the streamer once per unique MP3 and reuse it. Segment planning caches the metadata, so repeated calls to `createM3U8()` or `getSegmentDuration()` are cheap.
164
+ - **CDN friendliness** – Because segment URLs are deterministic byte ranges, edge caches can serve them efficiently. Configure consistent caching headers (e.g. `Cache-Control: public, max-age=86400`).
165
+ - **Serverless** – The zero-dependency design works well in Lambda/Cloud Functions. For large MP3s, prefer streaming reads (`getFileBuffer`) instead of loading entire files into memory.
166
+ - **Monitoring** – Log segment `start`/`end` pairs and durations to correlate playback issues with specific byte ranges or frame parsing warnings.
167
+ - **Troubleshooting** – For corrupted MP3s, inspect `FileLib.analyzeMP3File()` (available internally) to review parsing warnings and ID3 metadata.
206
168
 
207
- ```bash
208
- npm test
209
- npm run test:watch
210
- npm run test:coverage
211
- ```
169
+ ## Development
212
170
 
213
- ## πŸ—οΈ Building
171
+ Clone the repo, install dependencies, and run the usual scripts:
214
172
 
215
173
  ```bash
216
- npm run build # Build both ESM and CJS
217
- npm run build:esm # Build ES modules
218
- npm run build:cjs # Build CommonJS
174
+ npm install
175
+ npm test -- --runInBand --watchman=false
176
+ npm run build
219
177
  ```
220
178
 
221
- ## πŸ“„ License
179
+ The Jest flag `--watchman=false` avoids macOS sandbox issues when running in restricted environments.
222
180
 
223
- MIT License - see [LICENSE](LICENSE) file for details.
181
+ To explore the example playlist generator, see `example/test-hls-generation.js` and the bundled `example/sample.mp3` fixture.
224
182
 
225
- ## 🀝 Contributing
183
+ ## Support
226
184
 
227
- Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
185
+ - πŸ› Bug reports: [GitHub Issues](https://github.com/LordVersA/hls-streamer/issues)
186
+ - πŸ’¬ Questions & ideas: [GitHub Discussions](https://github.com/LordVersA/hls-streamer/discussions)
187
+ - πŸ“¦ npm registry: [hls-streamer](https://www.npmjs.com/package/hls-streamer)
228
188
 
229
- ## πŸ“ž Support
189
+ ## Contributing
230
190
 
231
- - πŸ› **Bug Reports:** [GitHub Issues](https://github.com/LordVersA/hls-streamer/issues)
232
- - πŸ’¬ **Questions:** [GitHub Discussions](https://github.com/LordVersA/hls-streamer/discussions)
233
- - πŸ“¦ **NPM:** [hls-streamer](https://www.npmjs.com/package/hls-streamer)
191
+ Contributions are welcome! Please open an issue to discuss substantial changes before submitting a pull request. Make sure `npm test -- --runInBand --watchman=false` and `npm run build` pass prior to filing the PR.
234
192
 
235
193
  ---
236
194
 
@@ -10,8 +10,25 @@ export interface SegmentInfo {
10
10
  end: number;
11
11
  duration: number;
12
12
  }
13
+ export interface Mp3FrameInfo {
14
+ index: number;
15
+ offset: number;
16
+ length: number;
17
+ duration: number;
18
+ samples: number;
19
+ sampleRate: number;
20
+ bitrate: number;
21
+ padding: 0 | 1;
22
+ }
13
23
  export interface Mp3FileInfo {
14
24
  size: number;
15
25
  duration: number;
26
+ audioDataSize: number;
27
+ sampleRate?: number;
28
+ averageBitrate?: number;
29
+ id3v2Size?: number;
30
+ id3v1Size?: number;
31
+ frames: Mp3FrameInfo[];
32
+ warnings?: string[];
16
33
  }
17
34
  //# sourceMappingURL=HlsStreamer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"HlsStreamer.d.ts","sourceRoot":"","sources":["../../src/Interfaces/HlsStreamer.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,kBAAkB;IAEjC,QAAQ,EAAE,MAAM,CAAC;IAEjB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAKD,MAAM,WAAW,WAAW;IAE1B,KAAK,EAAE,MAAM,CAAC;IAEd,GAAG,EAAE,MAAM,CAAC;IAEZ,QAAQ,EAAE,MAAM,CAAC;CAClB;AAKD,MAAM,WAAW,WAAW;IAE1B,IAAI,EAAE,MAAM,CAAC;IAEb,QAAQ,EAAE,MAAM,CAAC;CAClB"}
1
+ {"version":3,"file":"HlsStreamer.d.ts","sourceRoot":"","sources":["../../src/Interfaces/HlsStreamer.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,kBAAkB;IAEjC,QAAQ,EAAE,MAAM,CAAC;IAEjB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAKD,MAAM,WAAW,WAAW;IAE1B,KAAK,EAAE,MAAM,CAAC;IAEd,GAAG,EAAE,MAAM,CAAC;IAEZ,QAAQ,EAAE,MAAM,CAAC;CAClB;AAQD,MAAM,WAAW,YAAY;IAI3B,KAAK,EAAE,MAAM,CAAC;IAEd,MAAM,EAAE,MAAM,CAAC;IAEf,MAAM,EAAE,MAAM,CAAC;IAEf,QAAQ,EAAE,MAAM,CAAC;IAEjB,OAAO,EAAE,MAAM,CAAC;IAEhB,UAAU,EAAE,MAAM,CAAC;IAEnB,OAAO,EAAE,MAAM,CAAC;IAEhB,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;CAChB;AAKD,MAAM,WAAW,WAAW;IAE1B,IAAI,EAAE,MAAM,CAAC;IAEb,QAAQ,EAAE,MAAM,CAAC;IAEjB,aAAa,EAAE,MAAM,CAAC;IAEtB,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,MAAM,EAAE,YAAY,EAAE,CAAC;IAEvB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB"}
@@ -1,5 +1,20 @@
1
+ import { Mp3FileInfo } from '../Interfaces/HlsStreamer';
1
2
  export declare class FileLib {
3
+ private static readonly BITRATE_INDEX;
4
+ private static readonly SAMPLE_RATE_INDEX;
2
5
  static getFileSizeInBytes(buffer: Buffer): number;
3
6
  static getMP3DurationFromBuffer(mp3Buffer: Buffer): Promise<number>;
7
+ static analyzeMP3Buffer(buffer: Buffer, opts?: {
8
+ fileSize?: number;
9
+ }): Mp3FileInfo;
10
+ static analyzeMP3File(filePath: string): Promise<Mp3FileInfo>;
11
+ private static calculateFrameLength;
12
+ private static getId3Offsets;
13
+ private static syncSafeInteger;
14
+ private static isFrameSync;
15
+ private static parseFrameHeader;
16
+ private static decodeVersion;
17
+ private static decodeLayer;
18
+ private static getSamplesPerFrame;
4
19
  }
5
20
  //# sourceMappingURL=FileLib.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FileLib.d.ts","sourceRoot":"","sources":["../../src/Libs/FileLib.ts"],"names":[],"mappings":"AAKA,qBAAa,OAAO;IAIlB,MAAM,CAAC,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAWjD,MAAM,CAAC,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAWpE"}
1
+ {"version":3,"file":"FileLib.d.ts","sourceRoot":"","sources":["../../src/Libs/FileLib.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAgB,MAAM,2BAA2B,CAAC;AAqBtE,qBAAa,OAAO;IAClB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAgBnC;IAEF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAIvC;IAKF,MAAM,CAAC,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;WAWpC,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQzE,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,WAAW;WA6HzE,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IASnE,OAAO,CAAC,MAAM,CAAC,oBAAoB;IASnC,OAAO,CAAC,MAAM,CAAC,aAAa;IA8B5B,OAAO,CAAC,MAAM,CAAC,eAAe;IAO9B,OAAO,CAAC,MAAM,CAAC,WAAW;IAW1B,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAqC/B,OAAO,CAAC,MAAM,CAAC,aAAa;IAa5B,OAAO,CAAC,MAAM,CAAC,WAAW;IAa1B,OAAO,CAAC,MAAM,CAAC,kBAAkB;CAWlC"}