@invintusmedia/tomp4 1.4.1 → 1.4.3
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/tomp4.js +2 -2
- package/package.json +1 -1
- package/src/hls-clip.js +251 -107
- package/src/index.js +1 -1
package/dist/tomp4.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* toMp4.js v1.4.
|
|
2
|
+
* toMp4.js v1.4.3
|
|
3
3
|
* Convert MPEG-TS and fMP4 to standard MP4
|
|
4
4
|
* https://github.com/TVWIT/toMp4.js
|
|
5
5
|
* MIT License
|
|
@@ -1186,7 +1186,7 @@
|
|
|
1186
1186
|
toMp4.isMpegTs = isMpegTs;
|
|
1187
1187
|
toMp4.isFmp4 = isFmp4;
|
|
1188
1188
|
toMp4.isStandardMp4 = isStandardMp4;
|
|
1189
|
-
toMp4.version = '1.4.
|
|
1189
|
+
toMp4.version = '1.4.3';
|
|
1190
1190
|
|
|
1191
1191
|
return toMp4;
|
|
1192
1192
|
});
|
package/package.json
CHANGED
package/src/hls-clip.js
CHANGED
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
import { parseHls, isHlsUrl, parsePlaylistText, toAbsoluteUrl } from './hls.js';
|
|
24
24
|
import { TSParser, getCodecInfo } from './parsers/mpegts.js';
|
|
25
25
|
import { createInitSegment, createFragment } from './muxers/fmp4.js';
|
|
26
|
+
import { convertFmp4ToMp4 } from './fmp4/converter.js';
|
|
27
|
+
import { parseBoxes, findBox, parseChildBoxes, createBox } from './fmp4/utils.js';
|
|
26
28
|
import { smartRender } from './codecs/smart-render.js';
|
|
27
29
|
|
|
28
30
|
// ── constants ─────────────────────────────────────────────
|
|
@@ -112,7 +114,11 @@ function clipSegment(parser, startTime, endTime, options = {}) {
|
|
|
112
114
|
if (videoAUs[i].pts >= startPts) { targetIdx = i; break; }
|
|
113
115
|
}
|
|
114
116
|
|
|
115
|
-
|
|
117
|
+
// Smart rendering is available but currently disabled for HLS output
|
|
118
|
+
// because the JS H.264 encoder's CAVLC output has bugs at high resolutions
|
|
119
|
+
// (works at 288x160 but fails at 1080p). Fall back to keyframe-accurate.
|
|
120
|
+
// TODO: Fix CAVLC encoding for high-resolution frames to re-enable.
|
|
121
|
+
const needsSmartRender = false;
|
|
116
122
|
|
|
117
123
|
let clippedVideo, clippedAudio, startOffset;
|
|
118
124
|
|
|
@@ -245,16 +251,18 @@ class HlsClipResult {
|
|
|
245
251
|
// Pre-clipped boundary segments are already in memory
|
|
246
252
|
if (seg.data) return seg.data;
|
|
247
253
|
|
|
248
|
-
// Middle segment: fetch from CDN
|
|
254
|
+
// Middle segment: fetch from CDN
|
|
249
255
|
const resp = await fetch(seg.originalUrl);
|
|
250
256
|
if (!resp.ok) throw new Error(`Segment fetch failed: ${resp.status}`);
|
|
251
|
-
const
|
|
257
|
+
const rawData = new Uint8Array(await resp.arrayBuffer());
|
|
252
258
|
|
|
253
|
-
|
|
254
|
-
|
|
259
|
+
// fMP4 segments pass through unchanged (already correct format)
|
|
260
|
+
if (seg._sourceFormat === 'fmp4') return rawData;
|
|
261
|
+
|
|
262
|
+
// TS segments: remux to fMP4
|
|
263
|
+
const parser = parseTs(rawData);
|
|
264
|
+
const audioTimescale = seg._audioTimescale || parser.audioSampleRate || 48000;
|
|
255
265
|
|
|
256
|
-
// Normalize timestamps: subtract the segment's original start PTS,
|
|
257
|
-
// then add the segment's position in the clip timeline
|
|
258
266
|
const firstVideoPts = parser.videoAccessUnits[0]?.pts ?? 0;
|
|
259
267
|
for (const au of parser.videoAccessUnits) { au.pts -= firstVideoPts; au.dts -= firstVideoPts; }
|
|
260
268
|
for (const au of parser.audioAccessUnits) { au.pts -= firstVideoPts; }
|
|
@@ -262,12 +270,7 @@ class HlsClipResult {
|
|
|
262
270
|
const videoBaseTime = Math.round(seg.timelineOffset * PTS_PER_SECOND);
|
|
263
271
|
const audioBaseTime = Math.round(seg.timelineOffset * audioTimescale);
|
|
264
272
|
|
|
265
|
-
|
|
266
|
-
parser, segmentIndex + 1,
|
|
267
|
-
videoBaseTime, audioBaseTime, audioTimescale
|
|
268
|
-
);
|
|
269
|
-
|
|
270
|
-
return fragment;
|
|
273
|
+
return remuxToFragment(parser, segmentIndex + 1, videoBaseTime, audioBaseTime, audioTimescale);
|
|
271
274
|
}
|
|
272
275
|
|
|
273
276
|
/**
|
|
@@ -286,6 +289,202 @@ class HlsClipResult {
|
|
|
286
289
|
}
|
|
287
290
|
}
|
|
288
291
|
|
|
292
|
+
// ── format detection ──────────────────────────────────────
|
|
293
|
+
|
|
294
|
+
function _detectSegmentFormat(data) {
|
|
295
|
+
if (data.length < 8) return 'unknown';
|
|
296
|
+
// Check for TS sync byte
|
|
297
|
+
if (data[0] === 0x47) return 'ts';
|
|
298
|
+
for (let i = 0; i < Math.min(188, data.length); i++) {
|
|
299
|
+
if (data[i] === 0x47 && i + 188 < data.length && data[i + 188] === 0x47) return 'ts';
|
|
300
|
+
}
|
|
301
|
+
// Check for fMP4 (moof, styp, or ftyp box)
|
|
302
|
+
const type = String.fromCharCode(data[4], data[5], data[6], data[7]);
|
|
303
|
+
if (['moof', 'styp', 'ftyp', 'mdat'].includes(type)) return 'fmp4';
|
|
304
|
+
return 'unknown';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ── TS variant processing ─────────────────────────────────
|
|
308
|
+
|
|
309
|
+
function _processTsVariant({ firstSegData, lastSegData, overlapping, isSingleSegment, startTime, endTime, firstSeg, lastSeg, log }) {
|
|
310
|
+
const firstParser = parseTs(firstSegData);
|
|
311
|
+
const lastParser = !isSingleSegment && lastSegData ? parseTs(lastSegData) : null;
|
|
312
|
+
|
|
313
|
+
const { sps, pps } = extractCodecInfo(firstParser);
|
|
314
|
+
if (!sps || !pps) throw new Error('Could not extract SPS/PPS from video');
|
|
315
|
+
const audioSampleRate = firstParser.audioSampleRate || 48000;
|
|
316
|
+
const audioChannels = firstParser.audioChannels || 2;
|
|
317
|
+
const hasAudio = firstParser.audioAccessUnits.length > 0;
|
|
318
|
+
const audioTimescale = audioSampleRate;
|
|
319
|
+
|
|
320
|
+
const initSegment = createInitSegment({
|
|
321
|
+
sps, pps, audioSampleRate, audioChannels, hasAudio,
|
|
322
|
+
videoTimescale: PTS_PER_SECOND, audioTimescale,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const clipSegments = [];
|
|
326
|
+
let timelineOffset = 0;
|
|
327
|
+
|
|
328
|
+
// First segment (smart-rendered)
|
|
329
|
+
const firstRelStart = startTime - firstSeg.startTime;
|
|
330
|
+
const firstRelEnd = isSingleSegment ? endTime - firstSeg.startTime : undefined;
|
|
331
|
+
const firstClipped = clipSegment(firstParser, firstRelStart, firstRelEnd);
|
|
332
|
+
if (!firstClipped) throw new Error('First segment clip produced no samples');
|
|
333
|
+
|
|
334
|
+
const firstFragment = createFragment({
|
|
335
|
+
videoSamples: firstClipped.videoSamples,
|
|
336
|
+
audioSamples: firstClipped.audioSamples,
|
|
337
|
+
sequenceNumber: 1,
|
|
338
|
+
videoTimescale: PTS_PER_SECOND, audioTimescale,
|
|
339
|
+
videoBaseTime: 0, audioBaseTime: 0, audioSampleDuration: 1024,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
clipSegments.push({
|
|
343
|
+
duration: firstClipped.duration, data: firstFragment,
|
|
344
|
+
originalUrl: null, timelineOffset: 0, isBoundary: true,
|
|
345
|
+
});
|
|
346
|
+
timelineOffset += firstClipped.duration;
|
|
347
|
+
|
|
348
|
+
// Middle segments
|
|
349
|
+
for (let i = 1; i < overlapping.length - 1; i++) {
|
|
350
|
+
clipSegments.push({
|
|
351
|
+
duration: overlapping[i].duration, data: null,
|
|
352
|
+
originalUrl: overlapping[i].url, timelineOffset, isBoundary: false,
|
|
353
|
+
_sourceFormat: 'ts', _audioTimescale: audioTimescale,
|
|
354
|
+
});
|
|
355
|
+
timelineOffset += overlapping[i].duration;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Last segment
|
|
359
|
+
if (!isSingleSegment && lastParser) {
|
|
360
|
+
const lastRelEnd = endTime - lastSeg.startTime;
|
|
361
|
+
const lastClipped = clipSegment(lastParser, undefined, lastRelEnd);
|
|
362
|
+
if (lastClipped && lastClipped.videoSamples.length > 0) {
|
|
363
|
+
const lastFragment = createFragment({
|
|
364
|
+
videoSamples: lastClipped.videoSamples,
|
|
365
|
+
audioSamples: lastClipped.audioSamples,
|
|
366
|
+
sequenceNumber: overlapping.length,
|
|
367
|
+
videoTimescale: PTS_PER_SECOND, audioTimescale,
|
|
368
|
+
videoBaseTime: Math.round(timelineOffset * PTS_PER_SECOND),
|
|
369
|
+
audioBaseTime: Math.round(timelineOffset * audioTimescale),
|
|
370
|
+
audioSampleDuration: 1024,
|
|
371
|
+
});
|
|
372
|
+
clipSegments.push({
|
|
373
|
+
duration: lastClipped.duration, data: lastFragment,
|
|
374
|
+
originalUrl: null, timelineOffset, isBoundary: true,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return { initSegment, clipSegments, audioTimescale };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ── fMP4 variant processing ───────────────────────────────
|
|
383
|
+
|
|
384
|
+
function _processFmp4Variant({ firstSegData, lastSegData, fmp4Init, overlapping, isSingleSegment, startTime, endTime, firstSeg, lastSeg, log }) {
|
|
385
|
+
// For fMP4 sources: the init segment already has the moov with codec info.
|
|
386
|
+
// We pass it through as-is. Boundary segments are clipped using the fMP4
|
|
387
|
+
// converter. Middle segments pass through unchanged.
|
|
388
|
+
|
|
389
|
+
if (!fmp4Init) throw new Error('fMP4 source requires an init segment (#EXT-X-MAP)');
|
|
390
|
+
|
|
391
|
+
// Use the source init segment directly (it has the correct moov)
|
|
392
|
+
const initSegment = fmp4Init;
|
|
393
|
+
|
|
394
|
+
// Detect audio timescale from the init segment's moov
|
|
395
|
+
let audioTimescale = 48000;
|
|
396
|
+
try {
|
|
397
|
+
const boxes = parseBoxes(fmp4Init);
|
|
398
|
+
const moov = findBox(boxes, 'moov');
|
|
399
|
+
if (moov) {
|
|
400
|
+
const moovChildren = parseChildBoxes(moov);
|
|
401
|
+
for (const child of moovChildren) {
|
|
402
|
+
if (child.type === 'trak') {
|
|
403
|
+
const trakChildren = parseChildBoxes(child);
|
|
404
|
+
for (const tc of trakChildren) {
|
|
405
|
+
if (tc.type === 'mdia') {
|
|
406
|
+
const mdiaChildren = parseChildBoxes(tc);
|
|
407
|
+
let isSoun = false;
|
|
408
|
+
for (const mc of mdiaChildren) {
|
|
409
|
+
if (mc.type === 'hdlr' && mc.data.byteLength >= 20) {
|
|
410
|
+
const handler = String.fromCharCode(mc.data[16], mc.data[17], mc.data[18], mc.data[19]);
|
|
411
|
+
if (handler === 'soun') isSoun = true;
|
|
412
|
+
}
|
|
413
|
+
if (mc.type === 'mdhd' && isSoun) {
|
|
414
|
+
const v = new DataView(mc.data.buffer, mc.data.byteOffset, mc.data.byteLength);
|
|
415
|
+
audioTimescale = mc.data[8] === 0 ? v.getUint32(20) : v.getUint32(28);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
} catch (e) { /* use default */ }
|
|
424
|
+
|
|
425
|
+
const clipSegments = [];
|
|
426
|
+
let timelineOffset = 0;
|
|
427
|
+
|
|
428
|
+
// First segment: clip using fMP4 converter
|
|
429
|
+
const firstRelStart = startTime - firstSeg.startTime;
|
|
430
|
+
const firstRelEnd = isSingleSegment ? endTime - firstSeg.startTime : undefined;
|
|
431
|
+
|
|
432
|
+
// Combine init + first segment for the converter
|
|
433
|
+
const firstCombined = new Uint8Array(fmp4Init.byteLength + firstSegData.byteLength);
|
|
434
|
+
firstCombined.set(fmp4Init, 0);
|
|
435
|
+
firstCombined.set(firstSegData, fmp4Init.byteLength);
|
|
436
|
+
|
|
437
|
+
try {
|
|
438
|
+
// Use convertFmp4ToMp4 with clipping, then re-fragment
|
|
439
|
+
// Actually, we can just pass the raw fMP4 segment through — for boundary
|
|
440
|
+
// segments, we trim at the keyframe level (no smart rendering for fMP4 yet).
|
|
441
|
+
// The segment already starts at a keyframe (HLS requirement).
|
|
442
|
+
|
|
443
|
+
// For the first segment, just pass through — the startTime cut is at keyframe
|
|
444
|
+
// For frame accuracy with fMP4, we'd need to add edit lists to the init segment
|
|
445
|
+
// or do smart rendering. For now, keyframe-accurate is the fMP4 path.
|
|
446
|
+
clipSegments.push({
|
|
447
|
+
duration: (firstRelEnd || firstSeg.duration) - firstRelStart,
|
|
448
|
+
data: firstSegData, // pass through the fMP4 segment
|
|
449
|
+
originalUrl: null, timelineOffset: 0, isBoundary: true,
|
|
450
|
+
_sourceFormat: 'fmp4',
|
|
451
|
+
});
|
|
452
|
+
timelineOffset += clipSegments[0].duration;
|
|
453
|
+
} catch (e) {
|
|
454
|
+
log('fMP4 first segment processing error: ' + e.message);
|
|
455
|
+
// Fallback: pass through as-is
|
|
456
|
+
clipSegments.push({
|
|
457
|
+
duration: firstSeg.duration, data: firstSegData,
|
|
458
|
+
originalUrl: null, timelineOffset: 0, isBoundary: true,
|
|
459
|
+
_sourceFormat: 'fmp4',
|
|
460
|
+
});
|
|
461
|
+
timelineOffset += firstSeg.duration;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Middle segments: pass through unchanged (already fMP4!)
|
|
465
|
+
for (let i = 1; i < overlapping.length - 1; i++) {
|
|
466
|
+
clipSegments.push({
|
|
467
|
+
duration: overlapping[i].duration, data: null,
|
|
468
|
+
originalUrl: overlapping[i].url, timelineOffset, isBoundary: false,
|
|
469
|
+
_sourceFormat: 'fmp4',
|
|
470
|
+
});
|
|
471
|
+
timelineOffset += overlapping[i].duration;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Last segment: pass through (truncation at end is handled by player)
|
|
475
|
+
if (!isSingleSegment && lastSegData) {
|
|
476
|
+
const lastRelEnd = endTime - lastSeg.startTime;
|
|
477
|
+
clipSegments.push({
|
|
478
|
+
duration: Math.min(lastRelEnd, lastSeg.duration),
|
|
479
|
+
data: lastSegData,
|
|
480
|
+
originalUrl: null, timelineOffset, isBoundary: true,
|
|
481
|
+
_sourceFormat: 'fmp4',
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return { initSegment, clipSegments, audioTimescale };
|
|
486
|
+
}
|
|
487
|
+
|
|
289
488
|
// ── main function ─────────────────────────────────────────
|
|
290
489
|
|
|
291
490
|
/**
|
|
@@ -362,107 +561,52 @@ export async function clipHls(source, options = {}) {
|
|
|
362
561
|
|
|
363
562
|
log(`Segments: ${overlapping.length} (${firstSeg.startTime.toFixed(1)}s – ${lastSeg.endTime.toFixed(1)}s)`);
|
|
364
563
|
|
|
365
|
-
// Download
|
|
564
|
+
// Download first boundary segment to detect format
|
|
366
565
|
log('Downloading boundary segments...');
|
|
367
|
-
const
|
|
368
|
-
const firstParser = parseTs(firstTsData);
|
|
566
|
+
const firstSegData = new Uint8Array(await (await fetch(firstSeg.url)).arrayBuffer());
|
|
369
567
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
lastTsData = new Uint8Array(await (await fetch(lastSeg.url)).arrayBuffer());
|
|
374
|
-
lastParser = parseTs(lastTsData);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// Extract codec info from first segment
|
|
378
|
-
const { sps, pps } = extractCodecInfo(firstParser);
|
|
379
|
-
if (!sps || !pps) throw new Error('Could not extract SPS/PPS from video');
|
|
380
|
-
const audioSampleRate = firstParser.audioSampleRate || 48000;
|
|
381
|
-
const audioChannels = firstParser.audioChannels || 2;
|
|
382
|
-
const hasAudio = firstParser.audioAccessUnits.length > 0;
|
|
383
|
-
const audioTimescale = audioSampleRate;
|
|
384
|
-
|
|
385
|
-
// Create CMAF init segment
|
|
386
|
-
const initSegment = createInitSegment({
|
|
387
|
-
sps, pps, audioSampleRate, audioChannels, hasAudio,
|
|
388
|
-
videoTimescale: PTS_PER_SECOND,
|
|
389
|
-
audioTimescale,
|
|
390
|
-
});
|
|
568
|
+
// Detect source format: TS or fMP4
|
|
569
|
+
const sourceFormat = _detectSegmentFormat(firstSegData);
|
|
570
|
+
const isFmp4Source = sourceFormat === 'fmp4';
|
|
391
571
|
|
|
392
|
-
|
|
393
|
-
const clipSegments = [];
|
|
394
|
-
let timelineOffset = 0;
|
|
395
|
-
|
|
396
|
-
// ── First segment (clipped at start, possibly also at end) ──
|
|
397
|
-
// Convert absolute times to segment-relative times (TS PTS starts at ~0 per segment)
|
|
398
|
-
const firstRelStart = startTime - firstSeg.startTime;
|
|
399
|
-
const firstRelEnd = isSingleSegment ? endTime - firstSeg.startTime : undefined;
|
|
400
|
-
const firstClipped = clipSegment(firstParser, firstRelStart, firstRelEnd);
|
|
401
|
-
if (!firstClipped) throw new Error('First segment clip produced no samples');
|
|
402
|
-
|
|
403
|
-
const firstFragment = createFragment({
|
|
404
|
-
videoSamples: firstClipped.videoSamples,
|
|
405
|
-
audioSamples: firstClipped.audioSamples,
|
|
406
|
-
sequenceNumber: 1,
|
|
407
|
-
videoTimescale: PTS_PER_SECOND,
|
|
408
|
-
audioTimescale,
|
|
409
|
-
videoBaseTime: 0,
|
|
410
|
-
audioBaseTime: 0,
|
|
411
|
-
audioSampleDuration: 1024,
|
|
412
|
-
});
|
|
572
|
+
log(`Source format: ${isFmp4Source ? 'fMP4 (CMAF)' : 'MPEG-TS'}`);
|
|
413
573
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
574
|
+
// Download fMP4 init segment if needed
|
|
575
|
+
let fmp4Init = null;
|
|
576
|
+
if (isFmp4Source && initSegmentUrl) {
|
|
577
|
+
const initResp = await fetch(initSegmentUrl);
|
|
578
|
+
if (initResp.ok) {
|
|
579
|
+
fmp4Init = new Uint8Array(await initResp.arrayBuffer());
|
|
580
|
+
}
|
|
581
|
+
}
|
|
422
582
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
const segDuration = seg.duration;
|
|
427
|
-
clipSegments.push({
|
|
428
|
-
duration: segDuration,
|
|
429
|
-
data: null, // fetched on demand
|
|
430
|
-
originalUrl: seg.url,
|
|
431
|
-
timelineOffset,
|
|
432
|
-
isBoundary: false,
|
|
433
|
-
});
|
|
434
|
-
timelineOffset += segDuration;
|
|
583
|
+
let lastSegData = null;
|
|
584
|
+
if (!isSingleSegment) {
|
|
585
|
+
lastSegData = new Uint8Array(await (await fetch(lastSeg.url)).arrayBuffer());
|
|
435
586
|
}
|
|
436
587
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
duration: lastClipped.duration,
|
|
460
|
-
data: lastFragment,
|
|
461
|
-
originalUrl: null,
|
|
462
|
-
timelineOffset,
|
|
463
|
-
isBoundary: true,
|
|
464
|
-
});
|
|
465
|
-
}
|
|
588
|
+
let initSegment, clipSegments, audioTimescale;
|
|
589
|
+
|
|
590
|
+
if (isFmp4Source) {
|
|
591
|
+
// ── fMP4 source path ────────────────────────────────
|
|
592
|
+
const result = _processFmp4Variant({
|
|
593
|
+
firstSegData, lastSegData, fmp4Init,
|
|
594
|
+
overlapping, isSingleSegment,
|
|
595
|
+
startTime, endTime, firstSeg, lastSeg, log,
|
|
596
|
+
});
|
|
597
|
+
initSegment = result.initSegment;
|
|
598
|
+
clipSegments = result.clipSegments;
|
|
599
|
+
audioTimescale = result.audioTimescale;
|
|
600
|
+
} else {
|
|
601
|
+
// ── TS source path (existing smart-render pipeline) ──
|
|
602
|
+
const result = _processTsVariant({
|
|
603
|
+
firstSegData, lastSegData,
|
|
604
|
+
overlapping, isSingleSegment,
|
|
605
|
+
startTime, endTime, firstSeg, lastSeg, log,
|
|
606
|
+
});
|
|
607
|
+
initSegment = result.initSegment;
|
|
608
|
+
clipSegments = result.clipSegments;
|
|
609
|
+
audioTimescale = result.audioTimescale;
|
|
466
610
|
}
|
|
467
611
|
|
|
468
612
|
const totalDuration = clipSegments.reduce((sum, s) => sum + s.duration, 0);
|
package/src/index.js
CHANGED