@volter/world-core 3.0.151 → 3.0.153

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.
Files changed (52) hide show
  1. package/dist/src/derived-core.d.ts +8 -0
  2. package/dist/src/derived-core.js +2 -0
  3. package/dist/src/derived.d.ts +4 -0
  4. package/dist/src/events.d.ts +4 -4
  5. package/dist/src/events.js +39 -15
  6. package/dist/src/index.d.ts +3 -1
  7. package/dist/src/index.js +2 -1
  8. package/dist/src/live/access.d.ts +22 -0
  9. package/dist/src/live/access.js +83 -0
  10. package/dist/src/live/amf0.d.ts +7 -0
  11. package/dist/src/live/amf0.js +122 -0
  12. package/dist/src/live/flv.d.ts +42 -0
  13. package/dist/src/live/flv.js +148 -0
  14. package/dist/src/live/fmp4.d.ts +25 -0
  15. package/dist/src/live/fmp4.js +96 -0
  16. package/dist/src/live/hls.d.ts +22 -0
  17. package/dist/src/live/hls.js +46 -0
  18. package/dist/src/live/index.d.ts +9 -0
  19. package/dist/src/live/index.js +8 -0
  20. package/dist/src/live/ingest.d.ts +25 -0
  21. package/dist/src/live/ingest.js +229 -0
  22. package/dist/src/live/rtmp.d.ts +35 -0
  23. package/dist/src/live/rtmp.js +456 -0
  24. package/dist/src/live/segmenter.d.ts +57 -0
  25. package/dist/src/live/segmenter.js +204 -0
  26. package/dist/src/live/types.d.ts +102 -0
  27. package/dist/src/live/types.js +5 -0
  28. package/dist/src/pack-fetch.d.ts +4 -0
  29. package/dist/src/pack-fetch.js +24 -2
  30. package/dist/src/packRegistry.d.ts +1 -0
  31. package/dist/src/serve-http.d.ts +7 -1
  32. package/dist/src/serve-http.js +36 -3
  33. package/dist/src/twin-fetch.d.ts +4 -1
  34. package/package.json +5 -1
  35. package/src/derived-core.ts +10 -0
  36. package/src/derived.ts +4 -0
  37. package/src/events.ts +40 -16
  38. package/src/index.ts +4 -1
  39. package/src/live/access.ts +89 -0
  40. package/src/live/amf0.ts +80 -0
  41. package/src/live/flv.ts +145 -0
  42. package/src/live/fmp4.ts +102 -0
  43. package/src/live/hls.ts +59 -0
  44. package/src/live/index.ts +13 -0
  45. package/src/live/ingest.ts +210 -0
  46. package/src/live/rtmp.ts +375 -0
  47. package/src/live/segmenter.ts +218 -0
  48. package/src/live/types.ts +105 -0
  49. package/src/pack-fetch.ts +28 -2
  50. package/src/packRegistry.ts +1 -1
  51. package/src/serve-http.ts +29 -4
  52. package/src/twin-fetch.ts +4 -2
@@ -0,0 +1,456 @@
1
+ import { decodeAmf0, encodeAmf0 } from "./amf0.js";
2
+ /** What a publisher's media can draw mid-publish: its `onStatus` error, then the publish ended and the connection closed. */
3
+ export class RtmpRefusal extends Error {
4
+ code;
5
+ constructor(code, description) {
6
+ super(description);
7
+ this.code = code;
8
+ }
9
+ }
10
+ const HANDSHAKE = 1536;
11
+ /** The largest message: the chunk header's 24-bit length, the wire's own ceiling. The largest video frame measured fits
12
+ * under it: 11,781,334 bytes, a 2160p frame of noise at CRF 1 (ffmpeg 8.1.2, libx264 ultrafast, `ffmpeg -f lavfi -i
13
+ * "testsrc2=size=3840x2160:rate=30,noise=alls=30:allf=t+u" -frames:v 6 -c:v libx264 -preset ultrafast -crf 1 -f flv`, the
14
+ * largest `ffprobe -show_entries packet=size`), beside 320,672 bytes for 1080p60 at a Twitch-like 8 Mbit/s CBR and
15
+ * 2,964,434 for 1080p at CRF 1; measured on Windows 11, twin-world 5c01b42b, nothing else encoding. */
16
+ const MAX_MESSAGE = 0xffffff;
17
+ /** Before a publish is admitted only commands come, a few hundred bytes each (ffmpeg's and librtmp's `connect` object):
18
+ * a message, and what a connection holds in reassembly, stay small until the dialect has admitted the stream key. */
19
+ const MAX_MESSAGE_UNADMITTED = 64 * 1024;
20
+ const MAX_HELD_UNADMITTED = 256 * 1024;
21
+ /** Once publishing: one frame reassembling while audio interleaves on its own chunk stream. */
22
+ const MAX_HELD = MAX_MESSAGE + 1024 * 1024;
23
+ /** Chunk streams a connection may open: ffmpeg uses 2, 3, 4, 6 and 8 (libavformat/rtmppkt.h, `RTMPChannel`), librtmp 2
24
+ * to 5 and 8 (rtmp.c's `m_nChannel`). */
25
+ const MAX_CHUNK_STREAMS = 64;
26
+ /** Whole messages waiting their turn: the listener pauses past a few megabytes (serveStream); a transport that cannot
27
+ * pause (a bridged WebSocket) is closed past this. */
28
+ const MAX_QUEUED = 64 * 1024 * 1024;
29
+ /** A connection that has not published this long after it opened is closed: one that never gets as far as a stream
30
+ * key holds its place no longer than an encoder needs to connect and be admitted. */
31
+ const PUBLISH_DEADLINE_MS = 30_000;
32
+ const WINDOW = 5_000_000;
33
+ const OUT_CHUNK = 4096;
34
+ const u24 = (b, at) => (b[at] << 16) | (b[at + 1] << 8) | b[at + 2];
35
+ const u32 = (b, at) => ((b[at] << 24) >>> 0) + ((b[at + 1] << 16) | (b[at + 2] << 8) | b[at + 3]);
36
+ const u32le = (b, at) => (b[at] | (b[at + 1] << 8) | (b[at + 2] << 16)) + ((b[at + 3] << 24) >>> 0);
37
+ const be32 = (n) => [(n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff];
38
+ function join(parts, length) {
39
+ if (parts.length === 1 && parts[0].length === length)
40
+ return parts[0];
41
+ const out = new Uint8Array(length);
42
+ let at = 0;
43
+ for (const p of parts) {
44
+ out.set(p, at);
45
+ at += p.length;
46
+ }
47
+ return out;
48
+ }
49
+ /** One publisher's connection: its bytes in, the server's answers out through the sink. */
50
+ export function rtmpSession(sink, publisher, log = () => { }) {
51
+ let pending = new Uint8Array(0);
52
+ let phase = 'c0c1';
53
+ let inChunk = 128;
54
+ let received = 0;
55
+ let acked = 0;
56
+ let peerWindow = 0;
57
+ let app = '';
58
+ let tcUrl;
59
+ let flashVer;
60
+ let nextStream = 1;
61
+ let publishing = false;
62
+ let publishStream = 0;
63
+ let ended = false;
64
+ const chunks = new Map();
65
+ /** a chunk whose header was read and whose payload is still arriving */
66
+ let current;
67
+ /** bytes held in messages being reassembled, and in whole messages waiting their turn */
68
+ let held = 0;
69
+ let queued = 0;
70
+ let chain = Promise.resolve();
71
+ const deadline = setTimeout(() => { if (!publishing && !ended)
72
+ overrun(`it did not publish within ${PUBLISH_DEADLINE_MS / 1000} s`); }, PUBLISH_DEADLINE_MS);
73
+ deadline.unref?.();
74
+ let outChunk = 128;
75
+ const write = (bytes) => { if (phase !== 'closed')
76
+ sink.write(bytes); };
77
+ /** A message as chunks of the server's chunk size: a type-0 header, then type-3 continuations. */
78
+ const send = (csid, type, streamId, payload, timestamp = 0) => {
79
+ const head = [csid & 0x3f, ...be32(Math.min(timestamp, 0xffffff)).slice(1), ...be32(payload.length).slice(1), type, streamId & 0xff, (streamId >> 8) & 0xff, (streamId >> 16) & 0xff, (streamId >>> 24) & 0xff];
80
+ const parts = head;
81
+ for (let at = 0; at < payload.length; at += outChunk) {
82
+ if (at > 0)
83
+ parts.push(0xc0 | (csid & 0x3f));
84
+ for (const b of payload.subarray(at, at + outChunk))
85
+ parts.push(b);
86
+ }
87
+ write(Uint8Array.from(parts));
88
+ };
89
+ const control = (type, body) => send(2, type, 0, Uint8Array.from(body));
90
+ const command = (csid, streamId, ...values) => send(csid, 20, streamId, encodeAmf0(...values));
91
+ const status = (streamId, level, code, description, details) => command(5, streamId, 'onStatus', 0, null, { level, code, description, ...(details !== undefined ? { details } : {}) });
92
+ const close = () => {
93
+ if (phase === 'closed')
94
+ return;
95
+ phase = 'closed';
96
+ clearTimeout(deadline);
97
+ pending = new Uint8Array(0);
98
+ chunks.clear();
99
+ current = undefined;
100
+ held = 0;
101
+ try {
102
+ sink.end();
103
+ }
104
+ catch { /* the peer is gone */ }
105
+ };
106
+ /** A connection past a ceiling: said once, and closed. */
107
+ const overrun = (what) => { log(`closing a connection: ${what}`); close(); };
108
+ const stop = async () => {
109
+ if (!publishing || ended)
110
+ return;
111
+ ended = true;
112
+ publishing = false;
113
+ await publisher.unpublish();
114
+ };
115
+ const onCommand = async (message, values) => {
116
+ const [name, txnValue, object, ...args] = values;
117
+ const txn = typeof txnValue === 'number' ? txnValue : 0;
118
+ switch (name) {
119
+ case 'connect': {
120
+ const cmd = (object && typeof object === 'object' && !Array.isArray(object) && !(object instanceof Date) ? object : {});
121
+ app = String(cmd.app ?? '').replace(/^\/+|\/+$/g, '');
122
+ tcUrl = typeof cmd.tcUrl === 'string' ? cmd.tcUrl : undefined;
123
+ flashVer = typeof cmd.flashVer === 'string' ? cmd.flashVer : undefined;
124
+ const refused = publisher.connect(app, cmd);
125
+ if (refused !== undefined) {
126
+ command(3, 0, '_error', txn, null, { level: 'error', code: 'NetConnection.Connect.Rejected', description: refused });
127
+ close();
128
+ return;
129
+ }
130
+ control(5, be32(WINDOW));
131
+ control(6, [...be32(WINDOW), 2]);
132
+ control(1, be32(OUT_CHUNK));
133
+ outChunk = OUT_CHUNK;
134
+ command(3, 0, '_result', txn, { fmsVer: 'FMS/3,0,1,123', capabilities: 31 }, { level: 'status', code: 'NetConnection.Connect.Success', description: 'Connection succeeded.', objectEncoding: 0 });
135
+ return;
136
+ }
137
+ case 'releaseStream':
138
+ case 'FCPublish':
139
+ case 'getStreamLength':
140
+ case '_checkbw':
141
+ if (txn > 0)
142
+ command(3, 0, '_result', txn, null, undefined);
143
+ return;
144
+ case 'createStream':
145
+ command(3, 0, '_result', txn, null, nextStream++);
146
+ return;
147
+ case 'publish': {
148
+ const stream = message.streamId;
149
+ const named = typeof args[0] === 'string' ? args[0] : '';
150
+ if (publishing) {
151
+ status(stream, 'error', 'NetStream.Publish.BadName', 'This connection is already publishing.', named);
152
+ return;
153
+ }
154
+ const type = typeof args[1] === 'string' ? args[1] : undefined;
155
+ let refusal;
156
+ try {
157
+ refusal = await publisher.publish({ app, name: named, ...(type !== undefined ? { type } : {}), ...(tcUrl !== undefined ? { tcUrl } : {}), ...(flashVer !== undefined ? { flashVer } : {}) });
158
+ }
159
+ catch (error) {
160
+ log(`publish failed: ${String(error?.message ?? error)}`);
161
+ refusal = { code: 'NetStream.Publish.Denied', description: 'The server could not take the stream.' };
162
+ }
163
+ if (refusal) {
164
+ status(stream, 'error', refusal.code, refusal.description, named);
165
+ close();
166
+ return;
167
+ }
168
+ publishing = true;
169
+ publishStream = stream;
170
+ // user control StreamBegin (event 0) for the publish's stream, then its start
171
+ control(4, [0, 0, ...be32(stream)]);
172
+ status(stream, 'status', 'NetStream.Publish.Start', `${named.split('?')[0]} is now published.`, named.split('?')[0]);
173
+ return;
174
+ }
175
+ case 'FCUnpublish':
176
+ case 'closeStream':
177
+ case 'deleteStream':
178
+ await stop();
179
+ return;
180
+ case 'play':
181
+ status(message.streamId, 'error', 'NetStream.Play.Failed', 'This server takes published streams; it plays none over RTMP.');
182
+ close();
183
+ return;
184
+ default:
185
+ if (txn > 0)
186
+ command(3, 0, '_error', txn, null, { level: 'error', code: 'NetConnection.Call.Failed', description: `${String(name)} is not a command this server answers.` });
187
+ }
188
+ };
189
+ /** What was published, handed on: a refusal of it ends the publish (its `onStatus` error first) and the connection. */
190
+ const media = async (hand) => {
191
+ try {
192
+ await hand();
193
+ }
194
+ catch (error) {
195
+ if (!(error instanceof RtmpRefusal))
196
+ throw error;
197
+ status(publishStream, 'error', error.code, error.message);
198
+ await stop();
199
+ close();
200
+ }
201
+ };
202
+ const onMessage = async (message) => {
203
+ const p = message.payload;
204
+ switch (message.type) {
205
+ case 3:
206
+ case 6: return;
207
+ case 4: {
208
+ const event = (p[0] << 8) | p[1];
209
+ // a ping request is answered with the same timestamp (event 7, PingResponse)
210
+ if (event === 6 && p.length >= 6)
211
+ control(4, [0, 7, ...Array.from(p.subarray(2, 6))]);
212
+ return;
213
+ }
214
+ case 5:
215
+ peerWindow = u32(p, 0);
216
+ return;
217
+ case 8:
218
+ if (publishing)
219
+ await media(() => publisher.audio(message.timestamp, p));
220
+ return;
221
+ case 9:
222
+ if (publishing)
223
+ await media(() => publisher.video(message.timestamp, p));
224
+ return;
225
+ case 15:
226
+ case 18: {
227
+ const values = decodeAmf0(message.type === 15 ? p.subarray(1) : p);
228
+ const meta = values[0] === '@setDataFrame' ? values[2] : values[0] === 'onMetaData' ? values[1] : undefined;
229
+ if (publishing && meta && typeof meta === 'object' && !Array.isArray(meta) && !(meta instanceof Date))
230
+ await media(() => publisher.metadata(meta));
231
+ return;
232
+ }
233
+ case 17:
234
+ case 20:
235
+ await onCommand(message, decodeAmf0(message.type === 17 ? p.subarray(1) : p));
236
+ return;
237
+ default: return;
238
+ }
239
+ };
240
+ /** A chunk's bytes taken into its message, within what the connection may hold; false when past it (closed). */
241
+ const absorb = (st, bytes) => {
242
+ if (bytes.length === 0)
243
+ return true;
244
+ const ceiling = publishing ? MAX_HELD : MAX_HELD_UNADMITTED;
245
+ if (held + bytes.length > ceiling) {
246
+ overrun(`it holds more than ${ceiling} bytes of unfinished messages`);
247
+ return false;
248
+ }
249
+ st.parts.push(bytes.slice());
250
+ st.received += bytes.length;
251
+ held += bytes.length;
252
+ return true;
253
+ };
254
+ /** A chunk ended: its message, when whole, handled; the chunk size and an abort take effect at once, before the next
255
+ * chunk is read (RTMP 1.0 §5.4.1, §5.4.2). */
256
+ const finish = (st) => {
257
+ if (st.received < st.length)
258
+ return;
259
+ const payload = join(st.parts, st.length);
260
+ held -= st.received;
261
+ st.parts = [];
262
+ st.received = 0;
263
+ if (st.type === 1) {
264
+ inChunk = Math.max(1, u32(payload, 0) & 0x7fffffff);
265
+ return;
266
+ }
267
+ if (st.type === 2) {
268
+ const aborted = chunks.get(u32(payload, 0));
269
+ if (aborted) {
270
+ held -= aborted.received;
271
+ aborted.parts = [];
272
+ aborted.received = 0;
273
+ if (current?.st === aborted)
274
+ current = undefined;
275
+ }
276
+ return;
277
+ }
278
+ const message = { type: st.type, streamId: st.streamId, timestamp: st.timestamp, payload };
279
+ queued += payload.length;
280
+ if (queued > MAX_QUEUED) {
281
+ overrun(`more than ${MAX_QUEUED} bytes of messages wait to be recorded`);
282
+ return;
283
+ }
284
+ chain = chain.then(() => (phase === 'closed' && message.type !== 20 ? undefined : onMessage(message))).catch((error) => {
285
+ log(`a message failed: ${String(error?.message ?? error)}`);
286
+ close();
287
+ }).finally(() => { queued -= payload.length; });
288
+ };
289
+ /** What the pending bytes give: the rest of a chunk under way, or a chunk's header and as much of its payload as came.
290
+ * How many bytes it took, or 0 when no more can be read yet. */
291
+ const readChunk = () => {
292
+ const b = pending;
293
+ if (current) {
294
+ const n = Math.min(current.left, b.length);
295
+ if (n === 0)
296
+ return 0;
297
+ const { st } = current;
298
+ if (!absorb(st, b.subarray(0, n)))
299
+ return b.length;
300
+ current.left -= n;
301
+ if (current.left === 0) {
302
+ current = undefined;
303
+ finish(st);
304
+ }
305
+ return n;
306
+ }
307
+ if (b.length < 1)
308
+ return 0;
309
+ const fmt = b[0] >> 6;
310
+ let csid = b[0] & 0x3f;
311
+ let at = 1;
312
+ if (csid === 0) {
313
+ if (b.length < 2)
314
+ return 0;
315
+ csid = 64 + b[1];
316
+ at = 2;
317
+ }
318
+ else if (csid === 1) {
319
+ if (b.length < 3)
320
+ return 0;
321
+ csid = 64 + b[1] + b[2] * 256;
322
+ at = 3;
323
+ }
324
+ const headerLength = [11, 7, 3, 0][fmt];
325
+ if (b.length < at + headerLength)
326
+ return 0;
327
+ const prior = chunks.get(csid);
328
+ if (!prior && chunks.size >= MAX_CHUNK_STREAMS) {
329
+ overrun(`it opened more than ${MAX_CHUNK_STREAMS} chunk streams`);
330
+ return b.length;
331
+ }
332
+ const st = prior ?? { timestamp: 0, tsField: 0, extended: false, length: 0, type: 0, streamId: 0, parts: [], received: 0 };
333
+ let tsField = st.tsField;
334
+ let length = st.length;
335
+ let type = st.type;
336
+ let streamId = st.streamId;
337
+ if (fmt <= 2)
338
+ tsField = u24(b, at);
339
+ if (fmt <= 1) {
340
+ length = u24(b, at + 3);
341
+ type = b[at + 6];
342
+ }
343
+ if (fmt === 0)
344
+ streamId = u32le(b, at + 7);
345
+ at += headerLength;
346
+ // the extended timestamp: after a header whose field is 0xffffff, and in each type-3 chunk of a chunk stream whose
347
+ // last header had one (RTMP 1.0 §5.3.1.3), as ffmpeg and librtmp send it
348
+ const extended = fmt === 3 ? st.extended : tsField === 0xffffff;
349
+ let ts = tsField;
350
+ if (extended) {
351
+ if (b.length < at + 4)
352
+ return 0;
353
+ ts = u32(b, at);
354
+ at += 4;
355
+ }
356
+ const starting = st.received === 0;
357
+ const largest = publishing ? MAX_MESSAGE : MAX_MESSAGE_UNADMITTED;
358
+ if (starting && length > largest) {
359
+ overrun(`a message of ${length} bytes is past the ${largest} it may send now`);
360
+ return b.length;
361
+ }
362
+ if (!starting && length < st.received) {
363
+ overrun('a chunk header shortened the message under way');
364
+ return b.length;
365
+ }
366
+ // the header is the chunk stream's now
367
+ if (starting) {
368
+ // a type-0 header's timestamp is absolute; every other is a delta on the chunk stream's last (a type-3 header's is
369
+ // the last header's field, as ffmpeg reads it)
370
+ st.timestamp = fmt === 0 ? ts : st.timestamp + ts;
371
+ }
372
+ if (fmt <= 2) {
373
+ st.tsField = tsField;
374
+ st.extended = extended;
375
+ }
376
+ st.length = length;
377
+ st.type = type;
378
+ st.streamId = streamId;
379
+ if (!prior)
380
+ chunks.set(csid, st);
381
+ const take = Math.min(inChunk, length - st.received);
382
+ const n = Math.min(take, b.length - at);
383
+ if (!absorb(st, b.subarray(at, at + n)))
384
+ return b.length;
385
+ if (n < take)
386
+ current = { st, left: take - n };
387
+ else
388
+ finish(st);
389
+ return at + n;
390
+ };
391
+ const pump = () => {
392
+ for (;;) {
393
+ if (phase === 'closed') {
394
+ pending = new Uint8Array(0);
395
+ return;
396
+ }
397
+ if (phase === 'c0c1') {
398
+ if (pending.length < 1 + HANDSHAKE)
399
+ return;
400
+ if (pending[0] !== 3) {
401
+ log(`RTMP version ${pending[0]} is not 3`);
402
+ close();
403
+ return;
404
+ }
405
+ const c1 = pending.subarray(1, 1 + HANDSHAKE);
406
+ // S0 (version 3), S1 (time 0, zero, filler), S2 (C1 echoed): the simple handshake
407
+ const s = new Uint8Array(1 + HANDSHAKE * 2);
408
+ s[0] = 3;
409
+ s.set(c1, 1 + HANDSHAKE);
410
+ write(s);
411
+ pending = pending.slice(1 + HANDSHAKE);
412
+ phase = 'c2';
413
+ continue;
414
+ }
415
+ if (phase === 'c2') {
416
+ if (pending.length < HANDSHAKE)
417
+ return;
418
+ pending = pending.slice(HANDSHAKE);
419
+ phase = 'chunks';
420
+ continue;
421
+ }
422
+ const took = readChunk();
423
+ if (took === 0)
424
+ return;
425
+ pending = pending.subarray(took);
426
+ }
427
+ };
428
+ return {
429
+ data(chunk) {
430
+ if (phase === 'closed')
431
+ return;
432
+ received += chunk.length;
433
+ // the acknowledgement the peer's window asks for (RTMP 1.0 §5.4.3)
434
+ if (peerWindow > 0 && received - acked >= peerWindow) {
435
+ acked = received;
436
+ control(3, be32(received >>> 0));
437
+ }
438
+ // what is left unread is at most a chunk header (a chunk's payload is taken as it comes), so the copy is small
439
+ const rest = pending;
440
+ pending = new Uint8Array(rest.length + chunk.length);
441
+ pending.set(rest, 0);
442
+ pending.set(chunk, rest.length);
443
+ pump();
444
+ },
445
+ settled: () => chain,
446
+ // what the twin's work has not reached: whole messages waiting and the few bytes not yet read. An unfinished
447
+ // message's bytes are not in it: only more bytes finish it, and what it may hold is bounded (MAX_HELD)
448
+ backlog: () => queued + pending.length,
449
+ close() {
450
+ clearTimeout(deadline);
451
+ // the connection ended: a publish still open ends with it, after every message already read
452
+ chain = chain.then(() => stop()).catch((error) => log(`the publish's end failed: ${String(error?.message ?? error)}`));
453
+ phase = 'closed';
454
+ },
455
+ };
456
+ }
@@ -0,0 +1,57 @@
1
+ import type { LiveAudio, LiveVideo } from './types.js';
2
+ import { type AacConfig, type AvcConfig } from './flv.js';
3
+ /** A segment closed: its bytes, its media start and duration (seconds), and, when it opens a new init, that init. */
4
+ export type Cut = {
5
+ /** the segment's number in its recording */
6
+ sequence: number;
7
+ bytes: Uint8Array;
8
+ start: number;
9
+ duration: number;
10
+ init?: {
11
+ bytes: Uint8Array;
12
+ video?: LiveVideo;
13
+ audio?: LiveAudio;
14
+ };
15
+ };
16
+ /** A stream the segmenter will not hold: its publish is ended with the reason (ingest.ts). */
17
+ export declare class LiveOverrun extends Error {
18
+ }
19
+ export declare class Segmenter {
20
+ private readonly options;
21
+ private avc?;
22
+ private aac?;
23
+ private meta;
24
+ private freshInit;
25
+ private video;
26
+ private audio;
27
+ private start?;
28
+ private keyed;
29
+ private audioNext?;
30
+ private lastVideoDuration;
31
+ private sequence;
32
+ /** what the segment under way holds, and since when video has come with no keyframe to start on */
33
+ private bytes;
34
+ private frames;
35
+ private unkeyedSince?;
36
+ constructor(options: {
37
+ targetMs: number;
38
+ offsetMs: number;
39
+ firstSequence: number;
40
+ });
41
+ /** The publisher's metadata (`onMetaData`): the size and rate a stream's SPS does not say, and whether video comes. */
42
+ metadata(meta: Record<string, unknown>): void;
43
+ videoConfig(config: AvcConfig): Cut[];
44
+ audioConfig(config: AacConfig): Cut[];
45
+ /** A video frame at its decode time: the segment it closes, if it closes one. */
46
+ videoFrame(timestamp: number, key: boolean, cts: number, data: Uint8Array): Cut[];
47
+ audioFrame(timestamp: number, data: Uint8Array): Cut[];
48
+ /** The segment under way, closed (the publish's end, or a configuration change). */
49
+ flush(): Cut[];
50
+ private keyframeCeiling;
51
+ private noKeyframe;
52
+ /** A frame the segment under way may still take, or the refusal past its ceilings. */
53
+ private within;
54
+ private expectsVideo;
55
+ private audioOnlyStarted;
56
+ private cut;
57
+ }