@invintusmedia/tomp4 1.0.9 → 1.1.1

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.
@@ -69,7 +69,7 @@ export class TSParser {
69
69
  this.videoHeight = null;
70
70
  this.debug = { packets: 0, patFound: false, pmtFound: false };
71
71
  }
72
-
72
+
73
73
  /**
74
74
  * Parse MPEG-TS data
75
75
  * @param {Uint8Array} data - MPEG-TS data
@@ -79,7 +79,7 @@ export class TSParser {
79
79
  // Find first sync byte
80
80
  while (offset < data.byteLength && data[offset] !== TS_SYNC_BYTE) offset++;
81
81
  if (offset > 0) this.debug.skippedBytes = offset;
82
-
82
+
83
83
  // Parse all packets
84
84
  while (offset + TS_PACKET_SIZE <= data.byteLength) {
85
85
  if (data[offset] !== TS_SYNC_BYTE) {
@@ -94,7 +94,7 @@ export class TSParser {
94
94
  offset += TS_PACKET_SIZE;
95
95
  }
96
96
  }
97
-
97
+
98
98
  parsePacket(packet) {
99
99
  const pid = ((packet[1] & 0x1F) << 8) | packet[2];
100
100
  const payloadStart = (packet[1] & 0x40) !== 0;
@@ -107,23 +107,23 @@ export class TSParser {
107
107
  }
108
108
  if (adaptationField === 2) return;
109
109
  if (payloadOffset >= packet.length) return;
110
-
110
+
111
111
  const payload = packet.subarray(payloadOffset);
112
112
  if (payload.length === 0) return;
113
-
113
+
114
114
  if (pid === PAT_PID) this.parsePAT(payload);
115
115
  else if (pid === this.pmtPid) this.parsePMT(payload);
116
116
  else if (pid === this.videoPid) this.collectPES(payload, payloadStart, 'video');
117
117
  else if (pid === this.audioPid) this.collectPES(payload, payloadStart, 'audio');
118
118
  }
119
-
119
+
120
120
  parsePAT(payload) {
121
121
  if (payload.length < 12) return;
122
122
  let offset = payload[0] + 1;
123
123
  if (offset + 8 > payload.length) return;
124
-
124
+
125
125
  offset += 8;
126
-
126
+
127
127
  while (offset + 4 <= payload.length - 4) {
128
128
  const programNum = (payload[offset] << 8) | payload[offset + 1];
129
129
  const pmtPid = ((payload[offset + 2] & 0x1F) << 8) | payload[offset + 3];
@@ -135,29 +135,29 @@ export class TSParser {
135
135
  offset += 4;
136
136
  }
137
137
  }
138
-
138
+
139
139
  parsePMT(payload) {
140
140
  if (payload.length < 16) return;
141
141
  let offset = payload[0] + 1;
142
142
  if (offset + 12 > payload.length) return;
143
-
143
+
144
144
  offset++;
145
145
  const sectionLength = ((payload[offset] & 0x0F) << 8) | payload[offset + 1];
146
146
  offset += 2;
147
147
  offset += 5;
148
148
  offset += 2;
149
-
149
+
150
150
  if (offset + 2 > payload.length) return;
151
151
  const programInfoLength = ((payload[offset] & 0x0F) << 8) | payload[offset + 1];
152
152
  offset += 2 + programInfoLength;
153
-
153
+
154
154
  const sectionEnd = Math.min(payload.length - 4, 1 + payload[0] + 3 + sectionLength - 4);
155
-
155
+
156
156
  while (offset + 5 <= sectionEnd) {
157
157
  const streamType = payload[offset];
158
158
  const elementaryPid = ((payload[offset + 1] & 0x1F) << 8) | payload[offset + 2];
159
159
  const esInfoLength = ((payload[offset + 3] & 0x0F) << 8) | payload[offset + 4];
160
-
160
+
161
161
  if (!this.videoPid && (streamType === 0x01 || streamType === 0x02 || streamType === 0x1B || streamType === 0x24)) {
162
162
  this.videoPid = elementaryPid;
163
163
  this.videoStreamType = streamType;
@@ -167,11 +167,11 @@ export class TSParser {
167
167
  this.audioPid = elementaryPid;
168
168
  this.audioStreamType = streamType;
169
169
  }
170
-
170
+
171
171
  offset += 5 + esInfoLength;
172
172
  }
173
173
  }
174
-
174
+
175
175
  collectPES(payload, isStart, type) {
176
176
  const buffer = type === 'video' ? this.videoPesBuffer : this.audioPesBuffer;
177
177
  if (isStart) {
@@ -181,7 +181,7 @@ export class TSParser {
181
181
  }
182
182
  buffer.push(payload.slice());
183
183
  }
184
-
184
+
185
185
  processPES(pesData, type) {
186
186
  if (pesData.length < 9) return;
187
187
  if (pesData[0] !== 0 || pesData[1] !== 0 || pesData[2] !== 1) return;
@@ -194,7 +194,7 @@ export class TSParser {
194
194
  if (type === 'video') this.processVideoPayload(payload, pts, dts);
195
195
  else this.processAudioPayload(payload, pts);
196
196
  }
197
-
197
+
198
198
  parsePTS(data, offset) {
199
199
  return ((data[offset] & 0x0E) << 29) |
200
200
  ((data[offset + 1]) << 22) |
@@ -202,7 +202,7 @@ export class TSParser {
202
202
  ((data[offset + 3]) << 7) |
203
203
  ((data[offset + 4] & 0xFE) >> 1);
204
204
  }
205
-
205
+
206
206
  processVideoPayload(payload, pts, dts) {
207
207
  const nalUnits = this.extractNALUnits(payload);
208
208
  if (nalUnits.length > 0 && pts !== null) {
@@ -211,7 +211,7 @@ export class TSParser {
211
211
  this.videoDts.push(dts !== null ? dts : pts);
212
212
  }
213
213
  }
214
-
214
+
215
215
  extractNALUnits(data) {
216
216
  const nalUnits = [];
217
217
  let i = 0;
@@ -223,8 +223,8 @@ export class TSParser {
223
223
  if (startCodeLen > 0) {
224
224
  let end = i + startCodeLen;
225
225
  while (end < data.length - 2) {
226
- if (data[end] === 0 && data[end + 1] === 0 &&
227
- (data[end + 2] === 1 || (data[end + 2] === 0 && end + 3 < data.length && data[end + 3] === 1))) break;
226
+ if (data[end] === 0 && data[end + 1] === 0 &&
227
+ (data[end + 2] === 1 || (data[end + 2] === 0 && end + 3 < data.length && data[end + 3] === 1))) break;
228
228
  end++;
229
229
  }
230
230
  if (end >= data.length - 2) end = data.length;
@@ -238,13 +238,13 @@ export class TSParser {
238
238
  }
239
239
  return nalUnits;
240
240
  }
241
-
241
+
242
242
  processAudioPayload(payload, pts) {
243
243
  const frames = this.extractADTSFrames(payload);
244
-
244
+
245
245
  this.debug.audioPesCount = (this.debug.audioPesCount || 0) + 1;
246
246
  this.debug.audioFramesInPes = (this.debug.audioFramesInPes || 0) + frames.length;
247
-
247
+
248
248
  if (pts !== null) {
249
249
  this.lastAudioPts = pts;
250
250
  } else if (this.lastAudioPts !== null) {
@@ -253,10 +253,10 @@ export class TSParser {
253
253
  this.debug.audioSkipped = (this.debug.audioSkipped || 0) + frames.length;
254
254
  return;
255
255
  }
256
-
256
+
257
257
  const sampleRate = this.audioSampleRate || 48000;
258
258
  const ptsIncrement = Math.round(1024 * 90000 / sampleRate);
259
-
259
+
260
260
  for (const frame of frames) {
261
261
  this.audioAccessUnits.push({ data: frame.data, pts });
262
262
  this.audioPts.push(pts);
@@ -264,13 +264,13 @@ export class TSParser {
264
264
  this.lastAudioPts = pts;
265
265
  }
266
266
  }
267
-
267
+
268
268
  extractADTSFrames(data) {
269
269
  const SAMPLE_RATES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
270
-
270
+
271
271
  const frames = [];
272
272
  let i = 0;
273
-
273
+
274
274
  if (this.adtsPartial && this.adtsPartial.length > 0) {
275
275
  const combined = new Uint8Array(this.adtsPartial.length + data.length);
276
276
  combined.set(this.adtsPartial);
@@ -278,12 +278,12 @@ export class TSParser {
278
278
  data = combined;
279
279
  this.adtsPartial = null;
280
280
  }
281
-
281
+
282
282
  while (i < data.length - 7) {
283
283
  if (data[i] === 0xFF && (data[i + 1] & 0xF0) === 0xF0) {
284
284
  const protectionAbsent = data[i + 1] & 0x01;
285
285
  const frameLength = ((data[i + 3] & 0x03) << 11) | (data[i + 4] << 3) | ((data[i + 5] & 0xE0) >> 5);
286
-
286
+
287
287
  if (!this.audioSampleRate && frameLength > 0) {
288
288
  const samplingFreqIndex = ((data[i + 2] & 0x3C) >> 2);
289
289
  const channelConfig = ((data[i + 2] & 0x01) << 2) | ((data[i + 3] & 0xC0) >> 6);
@@ -292,7 +292,7 @@ export class TSParser {
292
292
  this.audioChannels = channelConfig;
293
293
  }
294
294
  }
295
-
295
+
296
296
  if (frameLength > 0) {
297
297
  if (i + frameLength <= data.length) {
298
298
  const headerSize = protectionAbsent ? 7 : 9;
@@ -309,7 +309,7 @@ export class TSParser {
309
309
  }
310
310
  return frames;
311
311
  }
312
-
312
+
313
313
  concatenateBuffers(buffers) {
314
314
  const totalLength = buffers.reduce((sum, b) => sum + b.length, 0);
315
315
  const result = new Uint8Array(totalLength);
@@ -317,29 +317,29 @@ export class TSParser {
317
317
  for (const buf of buffers) { result.set(buf, offset); offset += buf.length; }
318
318
  return result;
319
319
  }
320
-
320
+
321
321
  /**
322
322
  * Finalize parsing - process remaining buffers and normalize timestamps
323
323
  */
324
324
  finalize() {
325
325
  if (this.videoPesBuffer.length > 0) this.processPES(this.concatenateBuffers(this.videoPesBuffer), 'video');
326
326
  if (this.audioPesBuffer.length > 0) this.processPES(this.concatenateBuffers(this.audioPesBuffer), 'audio');
327
-
327
+
328
328
  this.normalizeTimestamps();
329
329
  }
330
-
330
+
331
331
  normalizeTimestamps() {
332
332
  let minPts = Infinity;
333
-
333
+
334
334
  if (this.videoPts.length > 0) {
335
335
  minPts = Math.min(minPts, Math.min(...this.videoPts));
336
336
  }
337
337
  if (this.audioPts.length > 0) {
338
338
  minPts = Math.min(minPts, Math.min(...this.audioPts));
339
339
  }
340
-
340
+
341
341
  if (minPts === Infinity || minPts === 0) return;
342
-
342
+
343
343
  for (let i = 0; i < this.videoPts.length; i++) {
344
344
  this.videoPts[i] -= minPts;
345
345
  }
@@ -349,7 +349,7 @@ export class TSParser {
349
349
  for (let i = 0; i < this.audioPts.length; i++) {
350
350
  this.audioPts[i] -= minPts;
351
351
  }
352
-
352
+
353
353
  for (const au of this.videoAccessUnits) {
354
354
  au.pts -= minPts;
355
355
  au.dts -= minPts;
@@ -357,7 +357,7 @@ export class TSParser {
357
357
  for (const au of this.audioAccessUnits) {
358
358
  au.pts -= minPts;
359
359
  }
360
-
360
+
361
361
  this.debug.timestampOffset = minPts;
362
362
  this.debug.timestampNormalized = true;
363
363
  }
@@ -42,7 +42,7 @@ const MAX_TAIL_SIZE = 2 * 1024 * 1024; // 2MB for moov at end
42
42
  async function fetchWithTimeout(url, options = {}) {
43
43
  const controller = new AbortController();
44
44
  const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
45
-
45
+
46
46
  try {
47
47
  const response = await fetch(url, {
48
48
  ...options,
@@ -79,10 +79,10 @@ async function fetchFileSize(url) {
79
79
  // ============================================================================
80
80
 
81
81
  function wrapADTS(aacData, sampleRate, channels) {
82
- const sampleRateIndex = [96000, 88200, 64000, 48000, 44100, 32000, 24000,
83
- 22050, 16000, 12000, 11025, 8000, 7350].indexOf(sampleRate);
82
+ const sampleRateIndex = [96000, 88200, 64000, 48000, 44100, 32000, 24000,
83
+ 22050, 16000, 12000, 11025, 8000, 7350].indexOf(sampleRate);
84
84
  const frameLength = aacData.length + 7;
85
-
85
+
86
86
  const adts = new Uint8Array(7 + aacData.length);
87
87
  adts[0] = 0xFF;
88
88
  adts[1] = 0xF1;
@@ -92,7 +92,7 @@ function wrapADTS(aacData, sampleRate, channels) {
92
92
  adts[5] = ((frameLength & 0x07) << 5) | 0x1F;
93
93
  adts[6] = 0xFC;
94
94
  adts.set(aacData, 7);
95
-
95
+
96
96
  return adts;
97
97
  }
98
98
 
@@ -117,12 +117,12 @@ export class RemoteMp4 {
117
117
  await instance._init();
118
118
  return instance;
119
119
  }
120
-
120
+
121
121
  constructor(url, options = {}) {
122
122
  this.url = url;
123
123
  this.segmentDuration = options.segmentDuration || DEFAULT_SEGMENT_DURATION;
124
- this.onProgress = options.onProgress || (() => {});
125
-
124
+ this.onProgress = options.onProgress || (() => { });
125
+
126
126
  // Populated by _init()
127
127
  this.fileSize = 0;
128
128
  this.moov = null;
@@ -131,7 +131,7 @@ export class RemoteMp4 {
131
131
  this.videoSamples = [];
132
132
  this.audioSamples = [];
133
133
  this.segments = [];
134
-
134
+
135
135
  // Computed properties
136
136
  this.duration = 0;
137
137
  this.width = 0;
@@ -139,22 +139,23 @@ export class RemoteMp4 {
139
139
  this.hasAudio = false;
140
140
  this.hasBframes = false;
141
141
  }
142
-
142
+
143
143
  async _init() {
144
144
  this.onProgress('Fetching metadata...');
145
-
145
+
146
146
  // Get file size
147
147
  this.fileSize = await fetchFileSize(this.url);
148
-
148
+
149
+
149
150
  // Find and fetch moov box
150
151
  this.moov = await this._findMoov();
151
-
152
+
152
153
  // Parse tracks using shared parser
153
154
  let trackOffset = 8;
154
155
  while (trackOffset < this.moov.length) {
155
156
  const trak = findBox(this.moov, 'trak', trackOffset);
156
157
  if (!trak) break;
157
-
158
+
158
159
  const track = analyzeTrack(this.moov, trak.offset, trak.size);
159
160
  if (track) {
160
161
  if (track.type === 'vide' && !this.videoTrack) {
@@ -172,29 +173,54 @@ export class RemoteMp4 {
172
173
  }
173
174
  trackOffset = trak.offset + trak.size;
174
175
  }
175
-
176
+
176
177
  if (!this.videoTrack) {
177
178
  throw new Error('No video track found');
178
179
  }
179
-
180
+
181
+ // Filter out samples that are beyond the file size (truncated file support)
182
+ const originalVideoCount = this.videoSamples.length;
183
+ this.videoSamples = this.videoSamples.filter(s => (s.offset + s.size) <= this.fileSize);
184
+
185
+ if (this.videoSamples.length < originalVideoCount) {
186
+ console.warn(`⚠️ File is truncated! content-length=${this.fileSize}. Keeping ${this.videoSamples.length}/${originalVideoCount} video samples.`);
187
+
188
+ // Update duration based on last available sample
189
+ if (this.videoSamples.length > 0) {
190
+ const lastSample = this.videoSamples[this.videoSamples.length - 1];
191
+ // sample.time and duration are in seconds (from mp4 parser)
192
+ this.duration = lastSample.time + (lastSample.duration || 0);
193
+ } else {
194
+ this.duration = 0;
195
+ }
196
+ }
197
+
198
+ // Filter audio samples
199
+ const originalAudioCount = this.audioSamples.length;
200
+ this.audioSamples = this.audioSamples.filter(s => (s.offset + s.size) <= this.fileSize);
201
+
202
+ if (this.audioSamples.length < originalAudioCount) {
203
+ console.warn(`⚠️ Audio truncated. Keeping ${this.audioSamples.length}/${originalAudioCount} samples.`);
204
+ }
205
+
180
206
  // Build segments
181
207
  this.segments = buildSegments(this.videoSamples, this.segmentDuration);
182
-
208
+
183
209
  this.onProgress(`Parsed: ${this.duration.toFixed(1)}s, ${this.segments.length} segments`);
184
210
  }
185
-
211
+
186
212
  async _findMoov() {
187
213
  const headerSize = Math.min(MAX_HEADER_SIZE, this.fileSize);
188
214
  const header = await fetchRange(this.url, 0, headerSize - 1);
189
-
215
+
190
216
  // Scan header for boxes
191
217
  let offset = 0;
192
218
  while (offset < header.length - 8) {
193
219
  const size = readUint32(header, offset);
194
220
  const type = boxType(header, offset + 4);
195
-
221
+
196
222
  if (size === 0 || size > this.fileSize) break;
197
-
223
+
198
224
  if (type === 'moov') {
199
225
  // moov in header - fetch complete if needed
200
226
  if (offset + size <= header.length) {
@@ -202,7 +228,7 @@ export class RemoteMp4 {
202
228
  }
203
229
  return fetchRange(this.url, offset, offset + size - 1);
204
230
  }
205
-
231
+
206
232
  if (type === 'mdat') {
207
233
  // mdat at start means moov is at end
208
234
  const moovOffset = offset + size;
@@ -214,39 +240,39 @@ export class RemoteMp4 {
214
240
  if (moov.size <= tail.length) {
215
241
  return tail.slice(moov.offset, moov.offset + moov.size);
216
242
  }
217
- return fetchRange(this.url, moovOffset + moov.offset,
218
- moovOffset + moov.offset + moov.size - 1);
243
+ return fetchRange(this.url, moovOffset + moov.offset,
244
+ moovOffset + moov.offset + moov.size - 1);
219
245
  }
220
246
  }
221
247
  break;
222
248
  }
223
-
249
+
224
250
  offset += size;
225
251
  }
226
-
252
+
227
253
  // Try end of file as fallback
228
254
  const tailSize = Math.min(MAX_TAIL_SIZE, this.fileSize);
229
255
  const tail = await fetchRange(this.url, this.fileSize - tailSize, this.fileSize - 1);
230
256
  const moov = findBox(tail, 'moov');
231
-
257
+
232
258
  if (moov) {
233
259
  const moovStart = this.fileSize - tailSize + moov.offset;
234
260
  return fetchRange(this.url, moovStart, moovStart + moov.size - 1);
235
261
  }
236
-
262
+
237
263
  // Check for fragmented MP4
238
264
  const moof = findBox(header, 'moof');
239
265
  if (moof) {
240
266
  throw new Error('Fragmented MP4 (fMP4) not supported');
241
267
  }
242
-
268
+
243
269
  throw new Error('Could not find moov box');
244
270
  }
245
-
271
+
246
272
  // ===========================================================================
247
273
  // Public API
248
274
  // ===========================================================================
249
-
275
+
250
276
  /**
251
277
  * Get source information
252
278
  */
@@ -265,7 +291,7 @@ export class RemoteMp4 {
265
291
  keyframeCount: this.videoTrack?.stss?.length || 0
266
292
  };
267
293
  }
268
-
294
+
269
295
  /**
270
296
  * Get segment definitions
271
297
  */
@@ -277,7 +303,7 @@ export class RemoteMp4 {
277
303
  duration: s.duration
278
304
  }));
279
305
  }
280
-
306
+
281
307
  /**
282
308
  * Generate HLS master playlist
283
309
  */
@@ -285,17 +311,17 @@ export class RemoteMp4 {
285
311
  const bandwidth = Math.round(
286
312
  (this.videoSamples.reduce((s, v) => s + v.size, 0) / this.duration) * 8
287
313
  );
288
-
289
- const resolution = this.width && this.height ?
314
+
315
+ const resolution = this.width && this.height ?
290
316
  `,RESOLUTION=${this.width}x${this.height}` : '';
291
-
317
+
292
318
  return `#EXTM3U
293
319
  #EXT-X-VERSION:3
294
320
  #EXT-X-STREAM-INF:BANDWIDTH=${bandwidth}${resolution}
295
321
  ${baseUrl}playlist.m3u8
296
322
  `;
297
323
  }
298
-
324
+
299
325
  /**
300
326
  * Generate HLS media playlist
301
327
  */
@@ -306,15 +332,15 @@ ${baseUrl}playlist.m3u8
306
332
  #EXT-X-MEDIA-SEQUENCE:0
307
333
  #EXT-X-PLAYLIST-TYPE:VOD
308
334
  `;
309
-
335
+
310
336
  for (const segment of this.segments) {
311
337
  playlist += `#EXTINF:${segment.duration.toFixed(6)},\n${baseUrl}segment${segment.index}.ts\n`;
312
338
  }
313
-
339
+
314
340
  playlist += '#EXT-X-ENDLIST\n';
315
341
  return playlist;
316
342
  }
317
-
343
+
318
344
  /**
319
345
  * Get a segment as MPEG-TS data
320
346
  * @param {number} index - Segment index
@@ -325,17 +351,17 @@ ${baseUrl}playlist.m3u8
325
351
  if (!segment) {
326
352
  throw new Error(`Segment ${index} not found`);
327
353
  }
328
-
354
+
329
355
  // Get samples for this segment
330
356
  const videoSamples = this.videoSamples.slice(segment.videoStart, segment.videoEnd);
331
357
  const audioSamples = this.audioSamples.filter(
332
358
  s => s.time >= segment.startTime && s.time < segment.endTime
333
359
  );
334
-
360
+
335
361
  // Fetch video data using byte ranges
336
362
  const videoRanges = calculateByteRanges(videoSamples);
337
363
  const videoData = await this._fetchRanges(videoRanges);
338
-
364
+
339
365
  // Map video sample data
340
366
  const parsedVideoSamples = videoSamples.map(sample => {
341
367
  const range = videoRanges.find(r => r.samples.includes(sample));
@@ -346,13 +372,13 @@ ${baseUrl}playlist.m3u8
346
372
  data: data.slice(relOffset, relOffset + sample.size)
347
373
  };
348
374
  });
349
-
375
+
350
376
  // Fetch and map audio data
351
377
  let parsedAudioSamples = [];
352
378
  if (audioSamples.length > 0) {
353
379
  const audioRanges = calculateByteRanges(audioSamples);
354
380
  const audioData = await this._fetchRanges(audioRanges);
355
-
381
+
356
382
  parsedAudioSamples = audioSamples.map(sample => {
357
383
  const range = audioRanges.find(r => r.samples.includes(sample));
358
384
  const data = audioData.get(range);
@@ -363,53 +389,53 @@ ${baseUrl}playlist.m3u8
363
389
  };
364
390
  });
365
391
  }
366
-
392
+
367
393
  // Build MPEG-TS segment
368
394
  return this._buildTsSegment(parsedVideoSamples, parsedAudioSamples);
369
395
  }
370
-
396
+
371
397
  async _fetchRanges(ranges) {
372
398
  const results = new Map();
373
-
399
+
374
400
  // Fetch ranges in parallel
375
401
  await Promise.all(ranges.map(async range => {
376
402
  const data = await fetchRange(this.url, range.start, range.end - 1);
377
403
  results.set(range, data);
378
404
  }));
379
-
405
+
380
406
  return results;
381
407
  }
382
-
408
+
383
409
  _buildTsSegment(videoSamples, audioSamples) {
384
410
  const muxer = new TSMuxer();
385
-
411
+
386
412
  if (this.videoTrack?.codecConfig) {
387
413
  muxer.setSpsPps(
388
414
  this.videoTrack.codecConfig.sps[0],
389
415
  this.videoTrack.codecConfig.pps[0]
390
416
  );
391
417
  }
392
-
418
+
393
419
  muxer.setHasAudio(audioSamples.length > 0);
394
-
420
+
395
421
  const PTS_PER_SECOND = 90000;
396
422
  const sampleRate = this.audioTrack?.audioConfig?.sampleRate || 44100;
397
423
  const channels = this.audioTrack?.audioConfig?.channels || 2;
398
-
424
+
399
425
  // Add audio samples
400
426
  for (const sample of audioSamples) {
401
427
  const dts90k = Math.round((sample.dts ?? sample.time) * PTS_PER_SECOND);
402
428
  const adts = wrapADTS(sample.data, sampleRate, channels);
403
429
  muxer.addAudioSample(adts, dts90k);
404
430
  }
405
-
431
+
406
432
  // Add video samples with PTS and DTS
407
433
  for (const sample of videoSamples) {
408
434
  const pts90k = Math.round((sample.pts ?? sample.time) * PTS_PER_SECOND);
409
435
  const dts90k = Math.round((sample.dts ?? sample.time) * PTS_PER_SECOND);
410
436
  muxer.addVideoSample(sample.data, sample.isKeyframe, pts90k, dts90k);
411
437
  }
412
-
438
+
413
439
  muxer.flush();
414
440
  return muxer.build();
415
441
  }