recker 1.0.19 → 1.0.20-next.963881c

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.
@@ -0,0 +1,81 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import type { Transport } from '../types/index.js';
3
+ export interface MockHlsServerOptions {
4
+ mode?: 'vod' | 'live' | 'event';
5
+ segmentDuration?: number;
6
+ segmentCount?: number;
7
+ windowSize?: number;
8
+ startSequence?: number;
9
+ realtime?: boolean;
10
+ segmentInterval?: number;
11
+ multiQuality?: boolean;
12
+ variants?: MockHlsVariant[];
13
+ encrypted?: boolean;
14
+ baseUrl?: string;
15
+ delay?: number;
16
+ segmentDataGenerator?: (sequence: number, variant?: string) => Uint8Array;
17
+ }
18
+ export interface MockHlsVariant {
19
+ name: string;
20
+ bandwidth: number;
21
+ resolution?: string;
22
+ codecs?: string;
23
+ }
24
+ export interface MockHlsSegment {
25
+ sequence: number;
26
+ duration: number;
27
+ data: Uint8Array;
28
+ addedAt: number;
29
+ programDateTime?: Date;
30
+ discontinuity?: boolean;
31
+ }
32
+ export interface MockHlsStats {
33
+ playlistRequests: number;
34
+ segmentRequests: number;
35
+ segmentsServed: number;
36
+ bytesServed: number;
37
+ requestLog: Array<{
38
+ url: string;
39
+ timestamp: number;
40
+ }>;
41
+ }
42
+ export declare class MockHlsServer extends EventEmitter {
43
+ private options;
44
+ private segments;
45
+ private currentSequence;
46
+ private ended;
47
+ private started;
48
+ private realtimeInterval;
49
+ private startTime;
50
+ private stats;
51
+ constructor(options?: MockHlsServerOptions);
52
+ get isRunning(): boolean;
53
+ get isEnded(): boolean;
54
+ get manifestUrl(): string;
55
+ get segmentCount(): number;
56
+ get statistics(): MockHlsStats;
57
+ get transport(): Transport;
58
+ start(): Promise<void>;
59
+ stop(): Promise<void>;
60
+ endStream(): void;
61
+ reset(): void;
62
+ addSegment(variant?: string, options?: Partial<MockHlsSegment>): MockHlsSegment;
63
+ addDiscontinuity(variant?: string): void;
64
+ getSegments(variant?: string): MockHlsSegment[];
65
+ private handleRequest;
66
+ private handleMasterPlaylist;
67
+ private handleMediaPlaylist;
68
+ private handleSegment;
69
+ private handleKey;
70
+ private createResponse;
71
+ private initializeVodSegments;
72
+ private initializeLiveSegments;
73
+ private addSegmentToAllVariants;
74
+ private startRealtimeSegmentGeneration;
75
+ private defaultSegmentGenerator;
76
+ static create(options?: MockHlsServerOptions): Promise<MockHlsServer>;
77
+ }
78
+ export declare function createMockHlsVod(segmentCount?: number, options?: Omit<MockHlsServerOptions, 'mode' | 'segmentCount'>): Promise<MockHlsServer>;
79
+ export declare function createMockHlsLive(options?: Omit<MockHlsServerOptions, 'mode'>): Promise<MockHlsServer>;
80
+ export declare function createMockHlsMultiQuality(options?: Omit<MockHlsServerOptions, 'multiQuality'>): Promise<MockHlsServer>;
81
+ //# sourceMappingURL=mock-hls-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-hls-server.d.ts","sourceRoot":"","sources":["../../src/testing/mock-hls-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,381 @@
1
+ import { EventEmitter } from 'node:events';
2
+ const DEFAULT_VARIANTS = [
3
+ { name: '360p', bandwidth: 800_000, resolution: '640x360', codecs: 'avc1.4d401e,mp4a.40.2' },
4
+ { name: '480p', bandwidth: 1_400_000, resolution: '854x480', codecs: 'avc1.4d401e,mp4a.40.2' },
5
+ { name: '720p', bandwidth: 2_800_000, resolution: '1280x720', codecs: 'avc1.4d401f,mp4a.40.2' },
6
+ { name: '1080p', bandwidth: 5_000_000, resolution: '1920x1080', codecs: 'avc1.640028,mp4a.40.2' },
7
+ ];
8
+ export class MockHlsServer extends EventEmitter {
9
+ options;
10
+ segments = new Map();
11
+ currentSequence;
12
+ ended = false;
13
+ started = false;
14
+ realtimeInterval = null;
15
+ startTime = 0;
16
+ stats = {
17
+ playlistRequests: 0,
18
+ segmentRequests: 0,
19
+ segmentsServed: 0,
20
+ bytesServed: 0,
21
+ requestLog: [],
22
+ };
23
+ constructor(options = {}) {
24
+ super();
25
+ const segmentDuration = options.segmentDuration ?? 6;
26
+ this.options = {
27
+ mode: 'vod',
28
+ segmentDuration,
29
+ segmentCount: 10,
30
+ windowSize: 3,
31
+ startSequence: 0,
32
+ realtime: false,
33
+ segmentInterval: segmentDuration * 1000,
34
+ multiQuality: false,
35
+ variants: options.variants ?? DEFAULT_VARIANTS,
36
+ encrypted: false,
37
+ baseUrl: 'http://mock-hls-server',
38
+ delay: 0,
39
+ segmentDataGenerator: options.segmentDataGenerator ?? this.defaultSegmentGenerator.bind(this),
40
+ ...options,
41
+ };
42
+ this.currentSequence = this.options.startSequence;
43
+ }
44
+ get isRunning() {
45
+ return this.started && !this.ended;
46
+ }
47
+ get isEnded() {
48
+ return this.ended;
49
+ }
50
+ get manifestUrl() {
51
+ return this.options.multiQuality
52
+ ? `${this.options.baseUrl}/master.m3u8`
53
+ : `${this.options.baseUrl}/playlist.m3u8`;
54
+ }
55
+ get segmentCount() {
56
+ const variantKey = this.options.multiQuality ? this.options.variants[0].name : 'default';
57
+ return this.segments.get(variantKey)?.length ?? 0;
58
+ }
59
+ get statistics() {
60
+ return { ...this.stats };
61
+ }
62
+ get transport() {
63
+ return {
64
+ dispatch: async (req) => this.handleRequest(req),
65
+ };
66
+ }
67
+ async start() {
68
+ if (this.started) {
69
+ throw new Error('Server already started');
70
+ }
71
+ this.started = true;
72
+ this.startTime = Date.now();
73
+ if (this.options.mode === 'vod') {
74
+ this.initializeVodSegments();
75
+ }
76
+ else {
77
+ this.initializeLiveSegments();
78
+ if (this.options.realtime) {
79
+ this.startRealtimeSegmentGeneration();
80
+ }
81
+ }
82
+ this.emit('start');
83
+ }
84
+ async stop() {
85
+ if (!this.started)
86
+ return;
87
+ if (this.realtimeInterval) {
88
+ clearInterval(this.realtimeInterval);
89
+ this.realtimeInterval = null;
90
+ }
91
+ this.started = false;
92
+ this.emit('stop');
93
+ }
94
+ endStream() {
95
+ if (this.options.mode === 'vod')
96
+ return;
97
+ this.ended = true;
98
+ if (this.realtimeInterval) {
99
+ clearInterval(this.realtimeInterval);
100
+ this.realtimeInterval = null;
101
+ }
102
+ this.emit('ended');
103
+ }
104
+ reset() {
105
+ this.segments.clear();
106
+ this.currentSequence = this.options.startSequence;
107
+ this.ended = false;
108
+ this.stats = {
109
+ playlistRequests: 0,
110
+ segmentRequests: 0,
111
+ segmentsServed: 0,
112
+ bytesServed: 0,
113
+ requestLog: [],
114
+ };
115
+ if (this.started) {
116
+ if (this.options.mode === 'vod') {
117
+ this.initializeVodSegments();
118
+ }
119
+ else {
120
+ this.initializeLiveSegments();
121
+ }
122
+ }
123
+ this.emit('reset');
124
+ }
125
+ addSegment(variant = 'default', options = {}) {
126
+ const variantSegments = this.segments.get(variant) ?? [];
127
+ const segment = {
128
+ sequence: this.currentSequence,
129
+ duration: this.options.segmentDuration,
130
+ data: options.data ?? this.options.segmentDataGenerator(this.currentSequence, variant),
131
+ addedAt: Date.now(),
132
+ programDateTime: options.programDateTime,
133
+ discontinuity: options.discontinuity,
134
+ ...options,
135
+ };
136
+ variantSegments.push(segment);
137
+ this.segments.set(variant, variantSegments);
138
+ this.currentSequence++;
139
+ if (this.options.mode === 'live' && variantSegments.length > this.options.windowSize) {
140
+ variantSegments.shift();
141
+ }
142
+ this.emit('segment', segment, variant);
143
+ return segment;
144
+ }
145
+ addDiscontinuity(variant = 'default') {
146
+ this.addSegment(variant, { discontinuity: true });
147
+ }
148
+ getSegments(variant = 'default') {
149
+ return [...(this.segments.get(variant) ?? [])];
150
+ }
151
+ async handleRequest(req) {
152
+ const url = req.url;
153
+ this.stats.requestLog.push({ url, timestamp: Date.now() });
154
+ if (this.options.delay > 0) {
155
+ await new Promise((resolve) => setTimeout(resolve, this.options.delay));
156
+ }
157
+ if (url.includes('master.m3u8')) {
158
+ return this.handleMasterPlaylist();
159
+ }
160
+ if (url.includes('.m3u8')) {
161
+ return this.handleMediaPlaylist(url);
162
+ }
163
+ if (url.includes('.ts')) {
164
+ return this.handleSegment(url);
165
+ }
166
+ if (url.includes('.key')) {
167
+ return this.handleKey();
168
+ }
169
+ throw new Error(`Unknown request: ${url}`);
170
+ }
171
+ handleMasterPlaylist() {
172
+ this.stats.playlistRequests++;
173
+ const lines = ['#EXTM3U', '#EXT-X-VERSION:3'];
174
+ for (const variant of this.options.variants) {
175
+ const attrs = [
176
+ `BANDWIDTH=${variant.bandwidth}`,
177
+ variant.resolution ? `RESOLUTION=${variant.resolution}` : null,
178
+ variant.codecs ? `CODECS="${variant.codecs}"` : null,
179
+ `NAME="${variant.name}"`,
180
+ ]
181
+ .filter(Boolean)
182
+ .join(',');
183
+ lines.push(`#EXT-X-STREAM-INF:${attrs}`);
184
+ lines.push(`${variant.name}/playlist.m3u8`);
185
+ }
186
+ const body = lines.join('\n');
187
+ return this.createResponse(body, 'application/vnd.apple.mpegurl');
188
+ }
189
+ handleMediaPlaylist(url) {
190
+ this.stats.playlistRequests++;
191
+ let variant = 'default';
192
+ if (this.options.multiQuality) {
193
+ const match = url.match(/\/(\w+)\/playlist\.m3u8/);
194
+ variant = match?.[1] ?? this.options.variants[0].name;
195
+ }
196
+ if (this.options.mode === 'live' && !this.options.realtime && !this.ended) {
197
+ this.addSegmentToAllVariants();
198
+ }
199
+ const segments = this.segments.get(variant) ?? [];
200
+ const lines = [
201
+ '#EXTM3U',
202
+ '#EXT-X-VERSION:3',
203
+ `#EXT-X-TARGETDURATION:${Math.ceil(this.options.segmentDuration)}`,
204
+ ];
205
+ if (this.options.mode !== 'vod') {
206
+ const mediaSequence = segments.length > 0 ? segments[0].sequence : 0;
207
+ lines.push(`#EXT-X-MEDIA-SEQUENCE:${mediaSequence}`);
208
+ }
209
+ if (this.options.mode === 'event') {
210
+ lines.push('#EXT-X-PLAYLIST-TYPE:EVENT');
211
+ }
212
+ else if (this.options.mode === 'vod') {
213
+ lines.push('#EXT-X-PLAYLIST-TYPE:VOD');
214
+ }
215
+ if (this.options.encrypted) {
216
+ lines.push(`#EXT-X-KEY:METHOD=AES-128,URI="${this.options.baseUrl}/key.key",IV=0x00000000000000000000000000000001`);
217
+ }
218
+ for (const segment of segments) {
219
+ if (segment.discontinuity) {
220
+ lines.push('#EXT-X-DISCONTINUITY');
221
+ }
222
+ if (segment.programDateTime) {
223
+ lines.push(`#EXT-X-PROGRAM-DATE-TIME:${segment.programDateTime.toISOString()}`);
224
+ }
225
+ lines.push(`#EXTINF:${segment.duration.toFixed(3)},`);
226
+ const segmentPath = this.options.multiQuality
227
+ ? `${variant}/segment${segment.sequence}.ts`
228
+ : `segment${segment.sequence}.ts`;
229
+ lines.push(segmentPath);
230
+ }
231
+ if (this.options.mode === 'vod' || this.ended) {
232
+ lines.push('#EXT-X-ENDLIST');
233
+ }
234
+ const body = lines.join('\n');
235
+ return this.createResponse(body, 'application/vnd.apple.mpegurl');
236
+ }
237
+ handleSegment(url) {
238
+ this.stats.segmentRequests++;
239
+ const urlPath = url.replace(/^https?:\/\/[^/]+/, '');
240
+ const match = urlPath.match(/(?:\/(\w+))?\/segment(\d+)\.ts/);
241
+ if (!match) {
242
+ return this.createResponse('Not Found', 'text/plain', 404);
243
+ }
244
+ const variant = match[1] ?? 'default';
245
+ const sequence = parseInt(match[2], 10);
246
+ const segments = this.segments.get(variant) ?? [];
247
+ const segment = segments.find((s) => s.sequence === sequence);
248
+ if (!segment) {
249
+ return this.createResponse('Not Found', 'text/plain', 404);
250
+ }
251
+ this.stats.segmentsServed++;
252
+ this.stats.bytesServed += segment.data.byteLength;
253
+ this.emit('segmentServed', segment, variant);
254
+ return this.createResponse(segment.data, 'video/mp2t');
255
+ }
256
+ handleKey() {
257
+ const key = new Uint8Array(16);
258
+ for (let i = 0; i < 16; i++) {
259
+ key[i] = i;
260
+ }
261
+ return this.createResponse(key, 'application/octet-stream');
262
+ }
263
+ createResponse(body, contentType, status = 200) {
264
+ const isText = typeof body === 'string';
265
+ const blobData = isText ? body : new Uint8Array(body);
266
+ return {
267
+ ok: status >= 200 && status < 300,
268
+ status,
269
+ text: async () => (isText ? body : new TextDecoder().decode(body)),
270
+ blob: async () => new Blob([blobData]),
271
+ arrayBuffer: async () => (isText ? new TextEncoder().encode(body).buffer : body.buffer),
272
+ headers: new Headers({
273
+ 'content-type': contentType,
274
+ 'content-length': String(isText ? body.length : body.byteLength),
275
+ }),
276
+ };
277
+ }
278
+ initializeVodSegments() {
279
+ const variants = this.options.multiQuality
280
+ ? this.options.variants.map((v) => v.name)
281
+ : ['default'];
282
+ for (const variant of variants) {
283
+ this.segments.set(variant, []);
284
+ for (let i = 0; i < this.options.segmentCount; i++) {
285
+ const segment = {
286
+ sequence: this.currentSequence,
287
+ duration: this.options.segmentDuration,
288
+ data: this.options.segmentDataGenerator(this.currentSequence, variant),
289
+ addedAt: Date.now(),
290
+ };
291
+ this.segments.get(variant).push(segment);
292
+ this.currentSequence++;
293
+ }
294
+ if (this.options.multiQuality) {
295
+ this.currentSequence = this.options.startSequence;
296
+ }
297
+ }
298
+ this.currentSequence = this.options.startSequence + this.options.segmentCount;
299
+ }
300
+ initializeLiveSegments() {
301
+ const variants = this.options.multiQuality
302
+ ? this.options.variants.map((v) => v.name)
303
+ : ['default'];
304
+ for (const variant of variants) {
305
+ this.segments.set(variant, []);
306
+ for (let i = 0; i < this.options.windowSize; i++) {
307
+ const segment = {
308
+ sequence: this.currentSequence + i,
309
+ duration: this.options.segmentDuration,
310
+ data: this.options.segmentDataGenerator(this.currentSequence + i, variant),
311
+ addedAt: Date.now(),
312
+ };
313
+ this.segments.get(variant).push(segment);
314
+ }
315
+ }
316
+ this.currentSequence += this.options.windowSize;
317
+ }
318
+ addSegmentToAllVariants() {
319
+ const variants = this.options.multiQuality
320
+ ? this.options.variants.map((v) => v.name)
321
+ : ['default'];
322
+ for (const variant of variants) {
323
+ const variantSegments = this.segments.get(variant) ?? [];
324
+ const segment = {
325
+ sequence: this.currentSequence,
326
+ duration: this.options.segmentDuration,
327
+ data: this.options.segmentDataGenerator(this.currentSequence, variant),
328
+ addedAt: Date.now(),
329
+ };
330
+ variantSegments.push(segment);
331
+ if (this.options.mode === 'live' && variantSegments.length > this.options.windowSize) {
332
+ variantSegments.shift();
333
+ }
334
+ this.segments.set(variant, variantSegments);
335
+ this.emit('segment', segment, variant);
336
+ }
337
+ this.currentSequence++;
338
+ }
339
+ startRealtimeSegmentGeneration() {
340
+ this.realtimeInterval = setInterval(() => {
341
+ if (this.ended)
342
+ return;
343
+ this.addSegmentToAllVariants();
344
+ }, this.options.segmentInterval);
345
+ }
346
+ defaultSegmentGenerator(sequence, _variant) {
347
+ const size = 1024 + (sequence % 512);
348
+ const data = new Uint8Array(size);
349
+ for (let i = 0; i < size; i++) {
350
+ data[i] = (sequence * 17 + i * 13) % 256;
351
+ }
352
+ return data;
353
+ }
354
+ static async create(options = {}) {
355
+ const server = new MockHlsServer(options);
356
+ await server.start();
357
+ return server;
358
+ }
359
+ }
360
+ export async function createMockHlsVod(segmentCount = 10, options = {}) {
361
+ return MockHlsServer.create({
362
+ mode: 'vod',
363
+ segmentCount,
364
+ ...options,
365
+ });
366
+ }
367
+ export async function createMockHlsLive(options = {}) {
368
+ return MockHlsServer.create({
369
+ mode: 'live',
370
+ realtime: true,
371
+ segmentDuration: 2,
372
+ windowSize: 3,
373
+ ...options,
374
+ });
375
+ }
376
+ export async function createMockHlsMultiQuality(options = {}) {
377
+ return MockHlsServer.create({
378
+ multiQuality: true,
379
+ ...options,
380
+ });
381
+ }
@@ -0,0 +1,100 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { type IncomingMessage } from 'node:http';
3
+ export interface MockHttpServerOptions {
4
+ port?: number;
5
+ host?: string;
6
+ defaultResponse?: MockHttpResponse;
7
+ delay?: number;
8
+ cors?: boolean;
9
+ corsOrigin?: string;
10
+ }
11
+ export interface MockHttpResponse {
12
+ status?: number;
13
+ body?: any;
14
+ headers?: Record<string, string>;
15
+ delay?: number;
16
+ drop?: boolean;
17
+ stream?: {
18
+ chunks: (string | Buffer)[];
19
+ interval: number;
20
+ };
21
+ }
22
+ export interface MockHttpRequest {
23
+ method: string;
24
+ path: string;
25
+ query: Record<string, string>;
26
+ headers: Record<string, string | string[] | undefined>;
27
+ body: any;
28
+ raw: IncomingMessage;
29
+ }
30
+ export type MockHttpHandler = (req: MockHttpRequest) => MockHttpResponse | Promise<MockHttpResponse>;
31
+ export interface MockHttpStats {
32
+ totalRequests: number;
33
+ requestsByMethod: Record<string, number>;
34
+ requestsByPath: Record<string, number>;
35
+ requestLog: Array<{
36
+ method: string;
37
+ path: string;
38
+ status: number;
39
+ timestamp: number;
40
+ duration: number;
41
+ }>;
42
+ }
43
+ export declare class MockHttpServer extends EventEmitter {
44
+ private options;
45
+ private httpServer;
46
+ private routes;
47
+ private _port;
48
+ private _started;
49
+ private stats;
50
+ constructor(options?: MockHttpServerOptions);
51
+ get port(): number;
52
+ get address(): string;
53
+ get url(): string;
54
+ get isRunning(): boolean;
55
+ get statistics(): MockHttpStats;
56
+ get routeCount(): number;
57
+ start(): Promise<void>;
58
+ stop(): Promise<void>;
59
+ reset(): void;
60
+ route(method: string, path: string, handler: MockHttpResponse | MockHttpHandler, options?: {
61
+ times?: number;
62
+ }): this;
63
+ get(path: string, handler: MockHttpResponse | MockHttpHandler, options?: {
64
+ times?: number;
65
+ }): this;
66
+ post(path: string, handler: MockHttpResponse | MockHttpHandler, options?: {
67
+ times?: number;
68
+ }): this;
69
+ put(path: string, handler: MockHttpResponse | MockHttpHandler, options?: {
70
+ times?: number;
71
+ }): this;
72
+ patch(path: string, handler: MockHttpResponse | MockHttpHandler, options?: {
73
+ times?: number;
74
+ }): this;
75
+ delete(path: string, handler: MockHttpResponse | MockHttpHandler, options?: {
76
+ times?: number;
77
+ }): this;
78
+ head(path: string, handler: MockHttpResponse | MockHttpHandler, options?: {
79
+ times?: number;
80
+ }): this;
81
+ optionsRoute(path: string, handler: MockHttpResponse | MockHttpHandler, options?: {
82
+ times?: number;
83
+ }): this;
84
+ any(path: string, handler: MockHttpResponse | MockHttpHandler, options?: {
85
+ times?: number;
86
+ }): this;
87
+ removeRoute(method: string, path: string): boolean;
88
+ clearRoutes(): void;
89
+ private handleRequest;
90
+ private findRoute;
91
+ private sendResponse;
92
+ private sendCorsHeaders;
93
+ private parseBody;
94
+ private pathToRegex;
95
+ waitForRequests(count: number, timeout?: number): Promise<void>;
96
+ getCallCount(method: string, path: string): number;
97
+ static create(options?: MockHttpServerOptions): Promise<MockHttpServer>;
98
+ }
99
+ export declare function createMockHttpServer(routes?: Record<string, MockHttpResponse>, options?: MockHttpServerOptions): Promise<MockHttpServer>;
100
+ //# sourceMappingURL=mock-http-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-http-server.d.ts","sourceRoot":"","sources":["../../src/testing/mock-http-server.ts"],"names":[],"mappings":"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"}