@alessmicrosystems/mpegts.js 1.8.1 → 1.8.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alessmicrosystems/mpegts.js",
3
- "version": "1.8.1",
3
+ "version": "1.8.2",
4
4
  "description": "HTML5 MPEG2-TS Stream Player",
5
5
  "main": "./dist/mpegts.js",
6
6
  "types": "./d.ts/mpegts.d.ts",
@@ -99,7 +99,7 @@ type AudioData = {
99
99
  type StashedAudioPayload = {
100
100
  codec: 'aac' | 'aac-loas' | 'ac-3' | 'ec-3' | 'opus' | 'mp3';
101
101
  data: Uint8Array;
102
- pts: number;
102
+ pts: number | undefined;
103
103
  };
104
104
 
105
105
  class TSDemuxer extends BaseDemuxer {
@@ -164,6 +164,10 @@ class TSDemuxer extends BaseDemuxer {
164
164
  private stashed_audio_before_video_init_: Array<StashedAudioPayload> = [];
165
165
  private _last_dispatch_block_reason_: string = '';
166
166
  private video_init_dispatch_time_: number = 0;
167
+ private audio_wrap_log_count_: number = 0;
168
+ private audio_stale_drop_log_count_: number = 0;
169
+ private audio_startup_align_log_count_: number = 0;
170
+ private audio_startup_drop_log_count_: number = 0;
167
171
  // The PID currently being decoded as the active audio stream.
168
172
  // 0 means "use whatever common_pids picks" (default first audio).
169
173
  private active_audio_pid_: number = 0;
@@ -1072,6 +1076,11 @@ class TSDemuxer extends BaseDemuxer {
1072
1076
  pmt.common_pids.eac3 = 0;
1073
1077
  this.selectAudioPid(fallback.pid);
1074
1078
  }
1079
+ } else if (is_new_pmt) {
1080
+ const cfgPref = this.config_?.preferredAudioPid;
1081
+ if (cfgPref && pmt.all_audio_pids.some(a => a.pid === cfgPref)) {
1082
+ this.selectAudioPid(cfgPref);
1083
+ }
1075
1084
  }
1076
1085
  // Emit available track info to the player layer
1077
1086
  if (is_new_pmt && this.onTracksUpdated && (pmt.all_audio_pids.length > 1 || pmt.subtitle_pids.length > 0)) {
@@ -1458,6 +1467,61 @@ class TSDemuxer extends BaseDemuxer {
1458
1467
  }
1459
1468
  }
1460
1469
 
1470
+ private normalizeIncomingAudioPtsMilliseconds(base_pts_ms: number | undefined): number | undefined {
1471
+ if (base_pts_ms == undefined || this.audio_last_sample_pts_ == undefined) {
1472
+ return base_pts_ms;
1473
+ }
1474
+
1475
+ const halfWrapMs = 0x100000000 / this.timescale_;
1476
+ const fullWrapMs = 0x200000000 / this.timescale_;
1477
+
1478
+ if (base_pts_ms + halfWrapMs < this.audio_last_sample_pts_) {
1479
+ const normalized = base_pts_ms + fullWrapMs;
1480
+ this.audio_wrap_log_count_++;
1481
+ if (this.audio_wrap_log_count_ <= 3 || this.audio_wrap_log_count_ % 25 === 0) {
1482
+ Log.w(this.TAG, `[muvie] Audio timestamp wrap normalized (#${this.audio_wrap_log_count_}): ${base_pts_ms}ms -> ${normalized}ms (last=${this.audio_last_sample_pts_}ms)`);
1483
+ }
1484
+ return normalized;
1485
+ }
1486
+
1487
+ if (base_pts_ms > this.audio_last_sample_pts_ + halfWrapMs) {
1488
+ this.audio_stale_drop_log_count_++;
1489
+ if (this.audio_stale_drop_log_count_ <= 3 || this.audio_stale_drop_log_count_ % 25 === 0) {
1490
+ Log.w(this.TAG, `[muvie] Dropping stale pre-wrap audio timestamp (#${this.audio_stale_drop_log_count_}) ${base_pts_ms}ms (last=${this.audio_last_sample_pts_}ms)`);
1491
+ }
1492
+ return undefined;
1493
+ }
1494
+
1495
+ return base_pts_ms;
1496
+ }
1497
+
1498
+ private getStartupAudioAnchorMilliseconds(): number | undefined {
1499
+ if (this.video_track_.samples.length > 0) {
1500
+ return this.video_track_.samples[0].dts;
1501
+ }
1502
+ return undefined;
1503
+ }
1504
+
1505
+ private normalizeStartupAudioPtsMilliseconds(base_pts_ms: number | undefined): number | undefined {
1506
+ if (base_pts_ms == undefined || this.audio_last_sample_pts_ != undefined) {
1507
+ return base_pts_ms;
1508
+ }
1509
+ const anchor = this.getStartupAudioAnchorMilliseconds();
1510
+ if (anchor == undefined) {
1511
+ return base_pts_ms;
1512
+ }
1513
+ const maxStartupDriftMs = 5000;
1514
+ const drift = base_pts_ms - anchor;
1515
+ if (Math.abs(drift) > maxStartupDriftMs) {
1516
+ this.audio_startup_align_log_count_++;
1517
+ if (this.audio_startup_align_log_count_ <= 3 || this.audio_startup_align_log_count_ % 25 === 0) {
1518
+ Log.w(this.TAG, `[muvie] Startup audio anchor normalize (#${this.audio_startup_align_log_count_}): ${base_pts_ms}ms -> ${anchor}ms (video=${anchor}ms drift=${Math.round(drift)}ms)`);
1519
+ }
1520
+ return anchor;
1521
+ }
1522
+ return base_pts_ms;
1523
+ }
1524
+
1461
1525
  private stashAudioBeforeVideoInit(codec: StashedAudioPayload['codec'], data: Uint8Array, pts: number) {
1462
1526
  this.stashed_audio_before_video_init_.push({codec, data, pts});
1463
1527
  }
@@ -1465,9 +1529,22 @@ class TSDemuxer extends BaseDemuxer {
1465
1529
  private dispatchAudioVideoMediaSegment() {
1466
1530
  // Flush any audio that was stashed before video init dispatched.
1467
1531
  if (this.video_init_segment_dispatched_ && this.stashed_audio_before_video_init_.length > 0) {
1468
- const count = this.stashed_audio_before_video_init_.length;
1532
+ const anchor = this.getStartupAudioAnchorMilliseconds();
1533
+ let stash = this.stashed_audio_before_video_init_;
1534
+ if (anchor != undefined) {
1535
+ const maxStartupLeadMs = 1500;
1536
+ const before = stash.length;
1537
+ stash = stash.filter((item) => item.pts == undefined || (item.pts / this.timescale_) >= (anchor - maxStartupLeadMs));
1538
+ const dropped = before - stash.length;
1539
+ if (dropped > 0) {
1540
+ this.audio_startup_drop_log_count_++;
1541
+ if (this.audio_startup_drop_log_count_ <= 3 || this.audio_startup_drop_log_count_ % 25 === 0) {
1542
+ Log.w(this.TAG, `[muvie] Dropped ${dropped} stale startup audio payload(s) before video anchor ${anchor}ms (#${this.audio_startup_drop_log_count_})`);
1543
+ }
1544
+ }
1545
+ }
1546
+ const count = stash.length;
1469
1547
  Log.v(this.TAG, `[muvie] Flushing ${count} stashed audio payload(s) now that video init is dispatched`);
1470
- const stash = this.stashed_audio_before_video_init_;
1471
1548
  this.stashed_audio_before_video_init_ = [];
1472
1549
  for (const item of stash) {
1473
1550
  switch (item.codec) {
@@ -1558,6 +1635,11 @@ class TSDemuxer extends BaseDemuxer {
1558
1635
 
1559
1636
  if (pts != undefined) {
1560
1637
  base_pts_ms = pts / this.timescale_;
1638
+ base_pts_ms = this.normalizeStartupAudioPtsMilliseconds(base_pts_ms);
1639
+ base_pts_ms = this.normalizeIncomingAudioPtsMilliseconds(base_pts_ms);
1640
+ if (base_pts_ms == undefined) {
1641
+ return;
1642
+ }
1561
1643
  }
1562
1644
  if (this.audio_metadata_.codec === 'aac') {
1563
1645
  if (pts == undefined && this.audio_last_sample_pts_ != undefined) {
@@ -1652,6 +1734,11 @@ class TSDemuxer extends BaseDemuxer {
1652
1734
 
1653
1735
  if (pts != undefined) {
1654
1736
  base_pts_ms = pts / this.timescale_;
1737
+ base_pts_ms = this.normalizeStartupAudioPtsMilliseconds(base_pts_ms);
1738
+ base_pts_ms = this.normalizeIncomingAudioPtsMilliseconds(base_pts_ms);
1739
+ if (base_pts_ms == undefined) {
1740
+ return;
1741
+ }
1655
1742
  }
1656
1743
  if (this.audio_metadata_.codec === 'aac') {
1657
1744
  if (pts == undefined && this.audio_last_sample_pts_ != undefined) {
@@ -1738,6 +1825,11 @@ class TSDemuxer extends BaseDemuxer {
1738
1825
 
1739
1826
  if (pts != undefined) {
1740
1827
  base_pts_ms = pts / this.timescale_;
1828
+ base_pts_ms = this.normalizeStartupAudioPtsMilliseconds(base_pts_ms);
1829
+ base_pts_ms = this.normalizeIncomingAudioPtsMilliseconds(base_pts_ms);
1830
+ if (base_pts_ms == undefined) {
1831
+ return;
1832
+ }
1741
1833
  }
1742
1834
 
1743
1835
  if (this.audio_metadata_.codec === 'ac-3') {
@@ -1811,6 +1903,11 @@ class TSDemuxer extends BaseDemuxer {
1811
1903
 
1812
1904
  if (pts != undefined) {
1813
1905
  base_pts_ms = pts / this.timescale_;
1906
+ base_pts_ms = this.normalizeStartupAudioPtsMilliseconds(base_pts_ms);
1907
+ base_pts_ms = this.normalizeIncomingAudioPtsMilliseconds(base_pts_ms);
1908
+ if (base_pts_ms == undefined) {
1909
+ return;
1910
+ }
1814
1911
  }
1815
1912
 
1816
1913
  if (this.audio_metadata_.codec === 'ec-3') {
@@ -1884,6 +1981,11 @@ class TSDemuxer extends BaseDemuxer {
1884
1981
 
1885
1982
  if (pts != undefined) {
1886
1983
  base_pts_ms = pts / this.timescale_;
1984
+ base_pts_ms = this.normalizeStartupAudioPtsMilliseconds(base_pts_ms);
1985
+ base_pts_ms = this.normalizeIncomingAudioPtsMilliseconds(base_pts_ms);
1986
+ if (base_pts_ms == undefined) {
1987
+ return;
1988
+ }
1887
1989
  }
1888
1990
  if (this.audio_metadata_.codec === 'opus') {
1889
1991
  if (pts == undefined && this.audio_last_sample_pts_ != undefined) {
@@ -1961,7 +2063,6 @@ class TSDemuxer extends BaseDemuxer {
1961
2063
  let bit_rate = 0;
1962
2064
  let object_type = 34; // Layer-3, listed in MPEG-4 Audio Object Types
1963
2065
 
1964
- let codec = 'mp3';
1965
2066
  switch (ver) {
1966
2067
  case 0: // MPEG 2.5
1967
2068
  sample_rate = _mpegAudioV25SampleRateTable[sampling_freq_index];
@@ -2005,6 +2106,16 @@ class TSDemuxer extends BaseDemuxer {
2005
2106
  data: sample
2006
2107
  } as const;
2007
2108
 
2109
+ let pts_ms: number | undefined;
2110
+ if (pts != undefined) {
2111
+ pts_ms = pts / this.timescale_;
2112
+ pts_ms = this.normalizeStartupAudioPtsMilliseconds(pts_ms);
2113
+ pts_ms = this.normalizeIncomingAudioPtsMilliseconds(pts_ms);
2114
+ if (pts_ms == undefined) {
2115
+ return;
2116
+ }
2117
+ }
2118
+
2008
2119
 
2009
2120
  if (this.audio_init_segment_dispatched_ == false) {
2010
2121
  this.audio_metadata_ = {
@@ -2024,8 +2135,8 @@ class TSDemuxer extends BaseDemuxer {
2024
2135
  let mp3_sample = {
2025
2136
  unit: data,
2026
2137
  length: data.byteLength,
2027
- pts: pts / this.timescale_,
2028
- dts: pts / this.timescale_
2138
+ pts: pts_ms,
2139
+ dts: pts_ms
2029
2140
  };
2030
2141
  this.audio_track_.samples.push(mp3_sample);
2031
2142
  this.audio_track_.length += data.byteLength;
@@ -65,6 +65,8 @@ class MP4Remuxer {
65
65
  this._mp3UseMpegAudio = !Browser.firefox;
66
66
 
67
67
  this._fillAudioTimestampGap = this._config.fixAudioTimestampGap;
68
+ this._audioDiscontinuityResetLogCount = 0;
69
+ this._audioGapFillLogCount = 0;
68
70
  }
69
71
 
70
72
  destroy() {
@@ -86,6 +88,11 @@ class MP4Remuxer {
86
88
  return this;
87
89
  }
88
90
 
91
+ _shouldLogLimited(counterName) {
92
+ this[counterName] = (this[counterName] || 0) + 1;
93
+ return this[counterName] <= 3 || this[counterName] % 25 === 0;
94
+ }
95
+
89
96
  /* prototype: function onInitSegment(type: string, initSegment: ArrayBuffer): void
90
97
  InitSegment: {
91
98
  type: string,
@@ -251,6 +258,10 @@ class MP4Remuxer {
251
258
  let dtsCorrection = undefined;
252
259
  let firstDts = -1, lastDts = -1, lastPts = -1;
253
260
  let refSampleDuration = this._audioMeta.refSampleDuration;
261
+ const hardDiscontinuityThreshold = Math.max(
262
+ 30000,
263
+ refSampleDuration != null ? refSampleDuration * 180 : 30000
264
+ );
254
265
 
255
266
  let mpegRawTrack = this._audioMeta.codec === 'mp3' && this._mp3UseMpegAudio;
256
267
  let firstSegmentAfterSeek = this._dtsBaseInited && this._audioNextDts === undefined;
@@ -376,19 +387,35 @@ class MP4Remuxer {
376
387
 
377
388
  dtsCorrection = originalDts - curRefDts;
378
389
  if (dtsCorrection <= -maxAudioFramesDrift * refSampleDuration) {
390
+ if (Math.abs(dtsCorrection) > hardDiscontinuityThreshold) {
391
+ if (this._shouldLogLimited('_audioDiscontinuityResetLogCount')) {
392
+ Log.w(this.TAG, `Audio discontinuity reset (#${this._audioDiscontinuityResetLogCount}): backward ${Math.round(dtsCorrection)} ms, codec=${this._audioMeta.codec}`);
393
+ }
394
+ dts = Math.floor(originalDts);
395
+ sampleDuration = Math.floor(refSampleDuration);
396
+ this._audioNextDts = dts + sampleDuration;
397
+ } else {
379
398
  // If we're overlapping by more than maxAudioFramesDrift number of frame, drop this sample
380
- Log.w(this.TAG, `Dropping 1 audio frame (originalDts: ${originalDts} ms ,curRefDts: ${curRefDts} ms) due to dtsCorrection: ${dtsCorrection} ms overlap.`);
381
- continue;
399
+ Log.w(this.TAG, `Dropping 1 audio frame (originalDts: ${originalDts} ms ,curRefDts: ${curRefDts} ms) due to dtsCorrection: ${dtsCorrection} ms overlap.`);
400
+ continue;
401
+ }
382
402
  }
383
403
  else if (dtsCorrection >= maxAudioFramesDrift * refSampleDuration && this._fillAudioTimestampGap && !Browser.safari) {
384
404
  // Silent frame generation, if large timestamp gap detected && config.fixAudioTimestampGap
405
+ let frameCount = Math.floor(dtsCorrection / refSampleDuration);
406
+ if (frameCount > 180 || dtsCorrection > hardDiscontinuityThreshold) {
407
+ if (this._shouldLogLimited('_audioDiscontinuityResetLogCount')) {
408
+ Log.w(this.TAG, `Audio discontinuity reset (#${this._audioDiscontinuityResetLogCount}): forward ${Math.round(dtsCorrection)} ms, frames=${frameCount}, codec=${this._audioMeta.codec}`);
409
+ }
410
+ dts = Math.floor(originalDts);
411
+ sampleDuration = Math.floor(refSampleDuration);
412
+ this._audioNextDts = dts + sampleDuration;
413
+ } else {
385
414
  needFillSilentFrames = true;
386
415
  // We need to insert silent frames to fill timestamp gap
387
- let frameCount = Math.floor(dtsCorrection / refSampleDuration);
388
- Log.w(this.TAG, 'Large audio timestamp gap detected, may cause AV sync to drift. ' +
389
- 'Silent frames will be generated to avoid unsync.\n' +
390
- `originalDts: ${originalDts} ms, curRefDts: ${curRefDts} ms, ` +
391
- `dtsCorrection: ${Math.round(dtsCorrection)} ms, generate: ${frameCount} frames`);
416
+ if (this._shouldLogLimited('_audioGapFillLogCount')) {
417
+ Log.w(this.TAG, `Audio gap fill (#${this._audioGapFillLogCount}): correction=${Math.round(dtsCorrection)} ms, frames=${frameCount}, codec=${this._audioMeta.codec}`);
418
+ }
392
419
 
393
420
 
394
421
  dts = Math.floor(curRefDts);
@@ -428,6 +455,7 @@ class MP4Remuxer {
428
455
  }
429
456
 
430
457
  this._audioNextDts = curRefDts + refSampleDuration;
458
+ }
431
459
 
432
460
  } else {
433
461
 
@@ -478,7 +506,9 @@ class MP4Remuxer {
478
506
 
479
507
  if (needFillSilentFrames) {
480
508
  // Silent frames should be inserted after wrong-duration frame
481
- mp4Samples.push.apply(mp4Samples, silentFrames);
509
+ for (let j = 0; j < silentFrames.length; j++) {
510
+ mp4Samples.push(silentFrames[j]);
511
+ }
482
512
  }
483
513
  }
484
514