hls.js 1.5.5 → 1.5.6-0.canary.10001

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 (68) hide show
  1. package/README.md +1 -0
  2. package/dist/hls-demo.js +10 -0
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +2075 -1166
  5. package/dist/hls.js.d.ts +65 -50
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1148 -859
  8. package/dist/hls.light.js.map +1 -1
  9. package/dist/hls.light.min.js +1 -1
  10. package/dist/hls.light.min.js.map +1 -1
  11. package/dist/hls.light.mjs +984 -696
  12. package/dist/hls.light.mjs.map +1 -1
  13. package/dist/hls.min.js +1 -1
  14. package/dist/hls.min.js.map +1 -1
  15. package/dist/hls.mjs +1757 -863
  16. package/dist/hls.mjs.map +1 -1
  17. package/dist/hls.worker.js +1 -1
  18. package/dist/hls.worker.js.map +1 -1
  19. package/package.json +20 -20
  20. package/src/config.ts +3 -2
  21. package/src/controller/abr-controller.ts +21 -20
  22. package/src/controller/audio-stream-controller.ts +15 -16
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +20 -8
  25. package/src/controller/base-stream-controller.ts +149 -33
  26. package/src/controller/buffer-controller.ts +11 -11
  27. package/src/controller/cap-level-controller.ts +1 -2
  28. package/src/controller/cmcd-controller.ts +27 -6
  29. package/src/controller/content-steering-controller.ts +8 -6
  30. package/src/controller/eme-controller.ts +9 -22
  31. package/src/controller/error-controller.ts +6 -8
  32. package/src/controller/fps-controller.ts +2 -3
  33. package/src/controller/gap-controller.ts +43 -16
  34. package/src/controller/latency-controller.ts +9 -11
  35. package/src/controller/level-controller.ts +12 -18
  36. package/src/controller/stream-controller.ts +25 -32
  37. package/src/controller/subtitle-stream-controller.ts +13 -14
  38. package/src/controller/subtitle-track-controller.ts +5 -3
  39. package/src/controller/timeline-controller.ts +23 -30
  40. package/src/crypt/aes-crypto.ts +21 -2
  41. package/src/crypt/decrypter-aes-mode.ts +4 -0
  42. package/src/crypt/decrypter.ts +32 -18
  43. package/src/crypt/fast-aes-key.ts +24 -5
  44. package/src/demux/audio/adts.ts +9 -4
  45. package/src/demux/sample-aes.ts +2 -0
  46. package/src/demux/transmuxer-interface.ts +4 -12
  47. package/src/demux/transmuxer-worker.ts +4 -4
  48. package/src/demux/transmuxer.ts +16 -3
  49. package/src/demux/tsdemuxer.ts +71 -37
  50. package/src/demux/video/avc-video-parser.ts +208 -119
  51. package/src/demux/video/base-video-parser.ts +134 -2
  52. package/src/demux/video/exp-golomb.ts +0 -208
  53. package/src/demux/video/hevc-video-parser.ts +746 -0
  54. package/src/events.ts +7 -0
  55. package/src/hls.ts +42 -34
  56. package/src/loader/fragment-loader.ts +9 -2
  57. package/src/loader/key-loader.ts +2 -0
  58. package/src/loader/level-key.ts +10 -9
  59. package/src/loader/playlist-loader.ts +4 -5
  60. package/src/remux/mp4-generator.ts +196 -1
  61. package/src/remux/mp4-remuxer.ts +23 -7
  62. package/src/task-loop.ts +5 -2
  63. package/src/types/component-api.ts +2 -0
  64. package/src/types/demuxer.ts +3 -0
  65. package/src/types/events.ts +4 -0
  66. package/src/utils/codecs.ts +33 -4
  67. package/src/utils/encryption-methods-util.ts +21 -0
  68. package/src/utils/logger.ts +54 -24
package/src/events.ts CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  ManifestLoadingData,
4
4
  MediaAttachedData,
5
5
  MediaAttachingData,
6
+ MediaEndedData,
6
7
  LevelLoadingData,
7
8
  LevelLoadedData,
8
9
  ManifestParsedData,
@@ -60,6 +61,8 @@ export enum Events {
60
61
  MEDIA_DETACHING = 'hlsMediaDetaching',
61
62
  // Fired when MediaSource has been detached from media element
62
63
  MEDIA_DETACHED = 'hlsMediaDetached',
64
+ // Fired when HTMLMediaElement dispatches "ended" event, or stalls at end of VOD program
65
+ MEDIA_ENDED = 'hlsMediaEnded',
63
66
  // Fired when the buffer is going to be reset
64
67
  BUFFER_RESET = 'hlsBufferReset',
65
68
  // Fired when we know about the codecs that we need buffers for to push into - data: {tracks : { container, codec, levelCodec, initSegment, metadata }}
@@ -184,6 +187,10 @@ export interface HlsListeners {
184
187
  ) => void;
185
188
  [Events.MEDIA_DETACHING]: (event: Events.MEDIA_DETACHING) => void;
186
189
  [Events.MEDIA_DETACHED]: (event: Events.MEDIA_DETACHED) => void;
190
+ [Events.MEDIA_ENDED]: (
191
+ event: Events.MEDIA_ENDED,
192
+ data: MediaEndedData,
193
+ ) => void;
187
194
  [Events.BUFFER_RESET]: (event: Events.BUFFER_RESET) => void;
188
195
  [Events.BUFFER_CODECS]: (
189
196
  event: Events.BUFFER_CODECS,
package/src/hls.ts CHANGED
@@ -8,7 +8,7 @@ import KeyLoader from './loader/key-loader';
8
8
  import StreamController from './controller/stream-controller';
9
9
  import { isMSESupported, isSupported } from './is-supported';
10
10
  import { getMediaSource } from './utils/mediasource-helper';
11
- import { logger, enableLogs } from './utils/logger';
11
+ import { enableLogs, type ILogger } from './utils/logger';
12
12
  import { enableStreamingMode, hlsDefaultConfig, mergeConfig } from './config';
13
13
  import { EventEmitter } from 'eventemitter3';
14
14
  import { Events } from './events';
@@ -59,9 +59,13 @@ export default class Hls implements HlsEventEmitter {
59
59
  */
60
60
  public readonly userConfig: Partial<HlsConfig>;
61
61
 
62
+ /**
63
+ * The logger functions used by this player instance, configured on player instantiation.
64
+ */
65
+ public readonly logger: ILogger;
66
+
62
67
  private coreComponents: ComponentAPI[];
63
68
  private networkControllers: NetworkComponentAPI[];
64
- private started: boolean = false;
65
69
  private _emitter: HlsEventEmitter = new EventEmitter();
66
70
  private _autoLevelCapping: number = -1;
67
71
  private _maxHdcpLevel: HdcpLevel = null;
@@ -142,12 +146,19 @@ export default class Hls implements HlsEventEmitter {
142
146
  * @param userConfig - Configuration options applied over `Hls.DefaultConfig`
143
147
  */
144
148
  constructor(userConfig: Partial<HlsConfig> = {}) {
145
- enableLogs(userConfig.debug || false, 'Hls instance');
146
- const config = (this.config = mergeConfig(Hls.DefaultConfig, userConfig));
149
+ const logger = (this.logger = enableLogs(
150
+ userConfig.debug || false,
151
+ 'Hls instance',
152
+ ));
153
+ const config = (this.config = mergeConfig(
154
+ Hls.DefaultConfig,
155
+ userConfig,
156
+ logger,
157
+ ));
147
158
  this.userConfig = userConfig;
148
159
 
149
160
  if (config.progressive) {
150
- enableStreamingMode(config);
161
+ enableStreamingMode(config, logger);
151
162
  }
152
163
 
153
164
  // core controllers and network loaders
@@ -320,7 +331,7 @@ export default class Hls implements HlsEventEmitter {
320
331
  try {
321
332
  return this.emit(event, event, eventObject);
322
333
  } catch (error) {
323
- logger.error(
334
+ this.logger.error(
324
335
  'An internal error happened while handling event ' +
325
336
  event +
326
337
  '. Error message: "' +
@@ -354,7 +365,7 @@ export default class Hls implements HlsEventEmitter {
354
365
  * Dispose of the instance
355
366
  */
356
367
  destroy() {
357
- logger.log('destroy');
368
+ this.logger.log('destroy');
358
369
  this.trigger(Events.DESTROYING, undefined);
359
370
  this.detachMedia();
360
371
  this.removeAllListeners();
@@ -377,7 +388,7 @@ export default class Hls implements HlsEventEmitter {
377
388
  * Attaches Hls.js to a media element
378
389
  */
379
390
  attachMedia(media: HTMLMediaElement) {
380
- logger.log('attachMedia');
391
+ this.logger.log('attachMedia');
381
392
  this._media = media;
382
393
  this.trigger(Events.MEDIA_ATTACHING, { media: media });
383
394
  }
@@ -386,7 +397,7 @@ export default class Hls implements HlsEventEmitter {
386
397
  * Detach Hls.js from the media
387
398
  */
388
399
  detachMedia() {
389
- logger.log('detachMedia');
400
+ this.logger.log('detachMedia');
390
401
  this.trigger(Events.MEDIA_DETACHING, undefined);
391
402
  this._media = null;
392
403
  }
@@ -407,7 +418,7 @@ export default class Hls implements HlsEventEmitter {
407
418
  ));
408
419
  this._autoLevelCapping = -1;
409
420
  this._maxHdcpLevel = null;
410
- logger.log(`loadSource:${loadingSource}`);
421
+ this.logger.log(`loadSource:${loadingSource}`);
411
422
  if (
412
423
  media &&
413
424
  loadedSource &&
@@ -428,8 +439,7 @@ export default class Hls implements HlsEventEmitter {
428
439
  * Defaults to -1 (None: starts from earliest point)
429
440
  */
430
441
  startLoad(startPosition: number = -1) {
431
- logger.log(`startLoad(${startPosition})`);
432
- this.started = true;
442
+ this.logger.log(`startLoad(${startPosition})`);
433
443
  this.networkControllers.forEach((controller) => {
434
444
  controller.startLoad(startPosition);
435
445
  });
@@ -439,34 +449,31 @@ export default class Hls implements HlsEventEmitter {
439
449
  * Stop loading of any stream data.
440
450
  */
441
451
  stopLoad() {
442
- logger.log('stopLoad');
443
- this.started = false;
452
+ this.logger.log('stopLoad');
444
453
  this.networkControllers.forEach((controller) => {
445
454
  controller.stopLoad();
446
455
  });
447
456
  }
448
457
 
449
458
  /**
450
- * Resumes stream controller segment loading if previously started.
459
+ * Resumes stream controller segment loading after `pauseBuffering` has been called.
451
460
  */
452
461
  resumeBuffering() {
453
- if (this.started) {
454
- this.networkControllers.forEach((controller) => {
455
- if ('fragmentLoader' in controller) {
456
- controller.startLoad(-1);
457
- }
458
- });
459
- }
462
+ this.networkControllers.forEach((controller) => {
463
+ if (controller.resumeBuffering) {
464
+ controller.resumeBuffering();
465
+ }
466
+ });
460
467
  }
461
468
 
462
469
  /**
463
- * Stops stream controller segment loading without changing 'started' state like stopLoad().
470
+ * Prevents stream controller from loading new segments until `resumeBuffering` is called.
464
471
  * This allows for media buffering to be paused without interupting playlist loading.
465
472
  */
466
473
  pauseBuffering() {
467
474
  this.networkControllers.forEach((controller) => {
468
- if ('fragmentLoader' in controller) {
469
- controller.stopLoad();
475
+ if (controller.pauseBuffering) {
476
+ controller.pauseBuffering();
470
477
  }
471
478
  });
472
479
  }
@@ -475,7 +482,7 @@ export default class Hls implements HlsEventEmitter {
475
482
  * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
476
483
  */
477
484
  swapAudioCodec() {
478
- logger.log('swapAudioCodec');
485
+ this.logger.log('swapAudioCodec');
479
486
  this.streamController.swapAudioCodec();
480
487
  }
481
488
 
@@ -486,7 +493,7 @@ export default class Hls implements HlsEventEmitter {
486
493
  * Automatic recovery of media-errors by this process is configurable.
487
494
  */
488
495
  recoverMediaError() {
489
- logger.log('recoverMediaError');
496
+ this.logger.log('recoverMediaError');
490
497
  const media = this._media;
491
498
  this.detachMedia();
492
499
  if (media) {
@@ -517,7 +524,7 @@ export default class Hls implements HlsEventEmitter {
517
524
  * Set quality level index immediately. This will flush the current buffer to replace the quality asap. That means playback will interrupt at least shortly to re-buffer and re-sync eventually. Set to -1 for automatic level selection.
518
525
  */
519
526
  set currentLevel(newLevel: number) {
520
- logger.log(`set currentLevel:${newLevel}`);
527
+ this.logger.log(`set currentLevel:${newLevel}`);
521
528
  this.levelController.manualLevel = newLevel;
522
529
  this.streamController.immediateLevelSwitch();
523
530
  }
@@ -536,7 +543,7 @@ export default class Hls implements HlsEventEmitter {
536
543
  * @param newLevel - Pass -1 for automatic level selection
537
544
  */
538
545
  set nextLevel(newLevel: number) {
539
- logger.log(`set nextLevel:${newLevel}`);
546
+ this.logger.log(`set nextLevel:${newLevel}`);
540
547
  this.levelController.manualLevel = newLevel;
541
548
  this.streamController.nextLevelSwitch();
542
549
  }
@@ -555,7 +562,7 @@ export default class Hls implements HlsEventEmitter {
555
562
  * @param newLevel - Pass -1 for automatic level selection
556
563
  */
557
564
  set loadLevel(newLevel: number) {
558
- logger.log(`set loadLevel:${newLevel}`);
565
+ this.logger.log(`set loadLevel:${newLevel}`);
559
566
  this.levelController.manualLevel = newLevel;
560
567
  }
561
568
 
@@ -586,7 +593,7 @@ export default class Hls implements HlsEventEmitter {
586
593
  * Sets "first-level", see getter.
587
594
  */
588
595
  set firstLevel(newLevel: number) {
589
- logger.log(`set firstLevel:${newLevel}`);
596
+ this.logger.log(`set firstLevel:${newLevel}`);
590
597
  this.levelController.firstLevel = newLevel;
591
598
  }
592
599
 
@@ -611,7 +618,7 @@ export default class Hls implements HlsEventEmitter {
611
618
  * (determined from download of first segment)
612
619
  */
613
620
  set startLevel(newLevel: number) {
614
- logger.log(`set startLevel:${newLevel}`);
621
+ this.logger.log(`set startLevel:${newLevel}`);
615
622
  // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
616
623
  if (newLevel !== -1) {
617
624
  newLevel = Math.max(newLevel, this.minAutoLevel);
@@ -686,7 +693,7 @@ export default class Hls implements HlsEventEmitter {
686
693
  */
687
694
  set autoLevelCapping(newLevel: number) {
688
695
  if (this._autoLevelCapping !== newLevel) {
689
- logger.log(`set autoLevelCapping:${newLevel}`);
696
+ this.logger.log(`set autoLevelCapping:${newLevel}`);
690
697
  this._autoLevelCapping = newLevel;
691
698
  this.levelController.checkMaxAutoUpdated();
692
699
  }
@@ -1032,7 +1039,7 @@ export type {
1032
1039
  TSDemuxerConfig,
1033
1040
  } from './config';
1034
1041
  export type { MediaKeySessionContext } from './controller/eme-controller';
1035
- export type { ILogger } from './utils/logger';
1042
+ export type { ILogger, Logger } from './utils/logger';
1036
1043
  export type {
1037
1044
  PathwayClone,
1038
1045
  SteeringManifest,
@@ -1147,6 +1154,7 @@ export type {
1147
1154
  ManifestParsedData,
1148
1155
  MediaAttachedData,
1149
1156
  MediaAttachingData,
1157
+ MediaEndedData,
1150
1158
  NonNativeTextTrack,
1151
1159
  NonNativeTextTracksData,
1152
1160
  SteeringManifestLoadedData,
@@ -336,8 +336,11 @@ function createLoaderContext(
336
336
  if (Number.isFinite(start) && Number.isFinite(end)) {
337
337
  let byteRangeStart = start;
338
338
  let byteRangeEnd = end;
339
- if (frag.sn === 'initSegment' && frag.decryptdata?.method === 'AES-128') {
340
- // MAP segment encrypted with method 'AES-128', when served with HTTP Range,
339
+ if (
340
+ frag.sn === 'initSegment' &&
341
+ isMethodFullSegmentAesCbc(frag.decryptdata?.method)
342
+ ) {
343
+ // MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
341
344
  // has the unencrypted size specified in the range.
342
345
  // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
343
346
  const fragmentLen = end - start;
@@ -372,6 +375,10 @@ function createGapLoadError(frag: Fragment, part?: Part): LoadError {
372
375
  return new LoadError(errorData);
373
376
  }
374
377
 
378
+ function isMethodFullSegmentAesCbc(method) {
379
+ return method === 'AES-128' || method === 'AES-256';
380
+ }
381
+
375
382
  export class LoadError extends Error {
376
383
  public readonly data: FragLoadFailResult;
377
384
  constructor(data: FragLoadFailResult) {
@@ -194,6 +194,8 @@ export default class KeyLoader implements ComponentAPI {
194
194
  }
195
195
  return this.loadKeyEME(keyInfo, frag);
196
196
  case 'AES-128':
197
+ case 'AES-256':
198
+ case 'AES-256-CTR':
197
199
  return this.loadKeyHTTP(keyInfo, frag);
198
200
  default:
199
201
  return Promise.reject(
@@ -2,6 +2,7 @@ import {
2
2
  changeEndianness,
3
3
  convertDataUriToArrayBytes,
4
4
  } from '../utils/keysystem-util';
5
+ import { isFullSegmentEncryption } from '../utils/encryption-methods-util';
5
6
  import { KeySystemFormats } from '../utils/mediakeys-helper';
6
7
  import { mp4pssh } from '../utils/mp4-tools';
7
8
  import { logger } from '../utils/logger';
@@ -51,13 +52,14 @@ export class LevelKey implements DecryptData {
51
52
  this.keyFormatVersions = formatversions;
52
53
  this.iv = iv;
53
54
  this.encrypted = method ? method !== 'NONE' : false;
54
- this.isCommonEncryption = this.encrypted && method !== 'AES-128';
55
+ this.isCommonEncryption =
56
+ this.encrypted && !isFullSegmentEncryption(method);
55
57
  }
56
58
 
57
59
  public isSupported(): boolean {
58
60
  // If it's Segment encryption or No encryption, just select that key system
59
61
  if (this.method) {
60
- if (this.method === 'AES-128' || this.method === 'NONE') {
62
+ if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
61
63
  return true;
62
64
  }
63
65
  if (this.keyFormat === 'identity') {
@@ -88,16 +90,15 @@ export class LevelKey implements DecryptData {
88
90
  return null;
89
91
  }
90
92
 
91
- if (this.method === 'AES-128' && this.uri && !this.iv) {
93
+ if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
92
94
  if (typeof sn !== 'number') {
93
95
  // We are fetching decryption data for a initialization segment
94
- // If the segment was encrypted with AES-128
96
+ // If the segment was encrypted with AES-128/256
95
97
  // It must have an IV defined. We cannot substitute the Segment Number in.
96
- if (this.method === 'AES-128' && !this.iv) {
97
- logger.warn(
98
- `missing IV for initialization segment with method="${this.method}" - compliance issue`,
99
- );
100
- }
98
+ logger.warn(
99
+ `missing IV for initialization segment with method="${this.method}" - compliance issue`,
100
+ );
101
+
101
102
  // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
102
103
  sn = 0;
103
104
  }
@@ -8,7 +8,6 @@
8
8
 
9
9
  import { Events } from '../events';
10
10
  import { ErrorDetails, ErrorTypes } from '../errors';
11
- import { logger } from '../utils/logger';
12
11
  import M3U8Parser from './m3u8-parser';
13
12
  import type { LevelParsed, VariableMap } from '../types/level';
14
13
  import type {
@@ -221,10 +220,10 @@ class PlaylistLoader implements NetworkComponentAPI {
221
220
  loaderContext.level === context.level
222
221
  ) {
223
222
  // same URL can't overlap
224
- logger.trace('[playlist-loader]: playlist request ongoing');
223
+ this.hls.logger.trace('[playlist-loader]: playlist request ongoing');
225
224
  return;
226
225
  }
227
- logger.log(
226
+ this.hls.logger.log(
228
227
  `[playlist-loader]: aborting previous loader for type: ${context.type}`,
229
228
  );
230
229
  loader.abort();
@@ -408,7 +407,7 @@ class PlaylistLoader implements NetworkComponentAPI {
408
407
  levels[0].audioCodec &&
409
408
  !levels[0].attrs.AUDIO
410
409
  ) {
411
- logger.log(
410
+ this.hls.logger.log(
412
411
  '[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one',
413
412
  );
414
413
  audioTracks.unshift({
@@ -555,7 +554,7 @@ class PlaylistLoader implements NetworkComponentAPI {
555
554
  message += ` id: ${context.id} group-id: "${context.groupId}"`;
556
555
  }
557
556
  const error = new Error(message);
558
- logger.warn(`[playlist-loader]: ${message}`);
557
+ this.hls.logger.warn(`[playlist-loader]: ${message}`);
559
558
  let details = ErrorDetails.UNKNOWN;
560
559
  let fatal = false;
561
560
 
@@ -28,6 +28,8 @@ class MP4 {
28
28
  MP4.types = {
29
29
  avc1: [], // codingname
30
30
  avcC: [],
31
+ hvc1: [],
32
+ hvcC: [],
31
33
  btrt: [],
32
34
  dinf: [],
33
35
  dref: [],
@@ -839,8 +841,10 @@ class MP4 {
839
841
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
840
842
  }
841
843
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
842
- } else {
844
+ } else if (track.segmentCodec === 'avc') {
843
845
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
846
+ } else {
847
+ return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
844
848
  }
845
849
  }
846
850
 
@@ -1123,6 +1127,197 @@ class MP4 {
1123
1127
  const result = appendUint8Array(MP4.FTYP, movie);
1124
1128
  return result;
1125
1129
  }
1130
+
1131
+ static hvc1(track) {
1132
+ const ps = track.params;
1133
+ const units = [track.vps, track.sps, track.pps];
1134
+ const NALuLengthSize = 4;
1135
+ const config = new Uint8Array([
1136
+ 0x01,
1137
+ (ps.general_profile_space << 6) |
1138
+ (ps.general_tier_flag ? 32 : 0) |
1139
+ ps.general_profile_idc,
1140
+ ps.general_profile_compatibility_flags[0],
1141
+ ps.general_profile_compatibility_flags[1],
1142
+ ps.general_profile_compatibility_flags[2],
1143
+ ps.general_profile_compatibility_flags[3],
1144
+ ps.general_constraint_indicator_flags[0],
1145
+ ps.general_constraint_indicator_flags[1],
1146
+ ps.general_constraint_indicator_flags[2],
1147
+ ps.general_constraint_indicator_flags[3],
1148
+ ps.general_constraint_indicator_flags[4],
1149
+ ps.general_constraint_indicator_flags[5],
1150
+ ps.general_level_idc,
1151
+ 240 | (ps.min_spatial_segmentation_idc >> 8),
1152
+ 255 & ps.min_spatial_segmentation_idc,
1153
+ 252 | ps.parallelismType,
1154
+ 252 | ps.chroma_format_idc,
1155
+ 248 | ps.bit_depth_luma_minus8,
1156
+ 248 | ps.bit_depth_chroma_minus8,
1157
+ 0x00,
1158
+ parseInt(ps.frame_rate.fps),
1159
+ (NALuLengthSize - 1) |
1160
+ (ps.temporal_id_nested << 2) |
1161
+ (ps.num_temporal_layers << 3) |
1162
+ (ps.frame_rate.fixed ? 64 : 0),
1163
+ units.length,
1164
+ ]);
1165
+
1166
+ // compute hvcC size in bytes
1167
+ let length = config.length;
1168
+ for (let i = 0; i < units.length; i += 1) {
1169
+ length += 3;
1170
+ for (let j = 0; j < units[i].length; j += 1) {
1171
+ length += 2 + units[i][j].length;
1172
+ }
1173
+ }
1174
+
1175
+ const hvcC = new Uint8Array(length);
1176
+ hvcC.set(config, 0);
1177
+ length = config.length;
1178
+ // append parameter set units: one vps, one or more sps and pps
1179
+ const iMax = units.length - 1;
1180
+ for (let i = 0; i < units.length; i += 1) {
1181
+ hvcC.set(
1182
+ new Uint8Array([
1183
+ (32 + i) | (i === iMax ? 128 : 0),
1184
+ 0x00,
1185
+ units[i].length,
1186
+ ]),
1187
+ length,
1188
+ );
1189
+ length += 3;
1190
+ for (let j = 0; j < units[i].length; j += 1) {
1191
+ hvcC.set(
1192
+ new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]),
1193
+ length,
1194
+ );
1195
+ length += 2;
1196
+ hvcC.set(units[i][j], length);
1197
+ length += units[i][j].length;
1198
+ }
1199
+ }
1200
+ const hvcc = MP4.box(MP4.types.hvcC, hvcC);
1201
+ const width = track.width;
1202
+ const height = track.height;
1203
+ const hSpacing = track.pixelRatio[0];
1204
+ const vSpacing = track.pixelRatio[1];
1205
+
1206
+ return MP4.box(
1207
+ MP4.types.hvc1,
1208
+ new Uint8Array([
1209
+ 0x00,
1210
+ 0x00,
1211
+ 0x00, // reserved
1212
+ 0x00,
1213
+ 0x00,
1214
+ 0x00, // reserved
1215
+ 0x00,
1216
+ 0x01, // data_reference_index
1217
+ 0x00,
1218
+ 0x00, // pre_defined
1219
+ 0x00,
1220
+ 0x00, // reserved
1221
+ 0x00,
1222
+ 0x00,
1223
+ 0x00,
1224
+ 0x00,
1225
+ 0x00,
1226
+ 0x00,
1227
+ 0x00,
1228
+ 0x00,
1229
+ 0x00,
1230
+ 0x00,
1231
+ 0x00,
1232
+ 0x00, // pre_defined
1233
+ (width >> 8) & 0xff,
1234
+ width & 0xff, // width
1235
+ (height >> 8) & 0xff,
1236
+ height & 0xff, // height
1237
+ 0x00,
1238
+ 0x48,
1239
+ 0x00,
1240
+ 0x00, // horizresolution
1241
+ 0x00,
1242
+ 0x48,
1243
+ 0x00,
1244
+ 0x00, // vertresolution
1245
+ 0x00,
1246
+ 0x00,
1247
+ 0x00,
1248
+ 0x00, // reserved
1249
+ 0x00,
1250
+ 0x01, // frame_count
1251
+ 0x12,
1252
+ 0x64,
1253
+ 0x61,
1254
+ 0x69,
1255
+ 0x6c, // dailymotion/hls.js
1256
+ 0x79,
1257
+ 0x6d,
1258
+ 0x6f,
1259
+ 0x74,
1260
+ 0x69,
1261
+ 0x6f,
1262
+ 0x6e,
1263
+ 0x2f,
1264
+ 0x68,
1265
+ 0x6c,
1266
+ 0x73,
1267
+ 0x2e,
1268
+ 0x6a,
1269
+ 0x73,
1270
+ 0x00,
1271
+ 0x00,
1272
+ 0x00,
1273
+ 0x00,
1274
+ 0x00,
1275
+ 0x00,
1276
+ 0x00,
1277
+ 0x00,
1278
+ 0x00,
1279
+ 0x00,
1280
+ 0x00,
1281
+ 0x00,
1282
+ 0x00, // compressorname
1283
+ 0x00,
1284
+ 0x18, // depth = 24
1285
+ 0x11,
1286
+ 0x11,
1287
+ ]), // pre_defined = -1
1288
+ hvcc,
1289
+ MP4.box(
1290
+ MP4.types.btrt,
1291
+ new Uint8Array([
1292
+ 0x00,
1293
+ 0x1c,
1294
+ 0x9c,
1295
+ 0x80, // bufferSizeDB
1296
+ 0x00,
1297
+ 0x2d,
1298
+ 0xc6,
1299
+ 0xc0, // maxBitrate
1300
+ 0x00,
1301
+ 0x2d,
1302
+ 0xc6,
1303
+ 0xc0,
1304
+ ]),
1305
+ ), // avgBitrate
1306
+ MP4.box(
1307
+ MP4.types.pasp,
1308
+ new Uint8Array([
1309
+ hSpacing >> 24, // hSpacing
1310
+ (hSpacing >> 16) & 0xff,
1311
+ (hSpacing >> 8) & 0xff,
1312
+ hSpacing & 0xff,
1313
+ vSpacing >> 24, // vSpacing
1314
+ (vSpacing >> 16) & 0xff,
1315
+ (vSpacing >> 8) & 0xff,
1316
+ vSpacing & 0xff,
1317
+ ]),
1318
+ ),
1319
+ );
1320
+ }
1126
1321
  }
1127
1322
 
1128
1323
  export default MP4;
@@ -29,6 +29,7 @@ import type { TrackSet } from '../types/track';
29
29
  import type { SourceBufferName } from '../types/buffer';
30
30
  import type { Fragment } from '../loader/fragment';
31
31
  import type { HlsConfig } from '../config';
32
+ import type { TypeSupported } from '../utils/codecs';
32
33
 
33
34
  const MAX_SILENT_FRAME_DURATION = 10 * 1000; // 10 seconds
34
35
  const AAC_SAMPLES_PER_FRAME = 1024;
@@ -41,7 +42,7 @@ let safariWebkitVersion: number | null = null;
41
42
  export default class MP4Remuxer implements Remuxer {
42
43
  private observer: HlsEventEmitter;
43
44
  private config: HlsConfig;
44
- private typeSupported: any;
45
+ private typeSupported: TypeSupported;
45
46
  private ISGenerated: boolean = false;
46
47
  private _initPTS: RationalTimestamp | null = null;
47
48
  private _initDTS: RationalTimestamp | null = null;
@@ -515,7 +516,7 @@ export default class MP4Remuxer implements Remuxer {
515
516
  if (foundHole || foundOverlap) {
516
517
  if (foundHole) {
517
518
  logger.warn(
518
- `AVC: ${toMsFromMpegTsClock(
519
+ `${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(
519
520
  delta,
520
521
  true,
521
522
  )} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(
@@ -524,7 +525,7 @@ export default class MP4Remuxer implements Remuxer {
524
525
  );
525
526
  } else {
526
527
  logger.warn(
527
- `AVC: ${toMsFromMpegTsClock(
528
+ `${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(
528
529
  -delta,
529
530
  true,
530
531
  )} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(
@@ -543,12 +544,27 @@ export default class MP4Remuxer implements Remuxer {
543
544
  inputSamples[0].dts = firstDTS;
544
545
  inputSamples[0].pts = firstPTS;
545
546
  } else {
547
+ let isPTSOrderRetained = true;
546
548
  for (let i = 0; i < inputSamples.length; i++) {
547
- if (inputSamples[i].dts > firstPTS) {
549
+ if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
548
550
  break;
549
551
  }
552
+
553
+ const prevPTS = inputSamples[i].pts;
550
554
  inputSamples[i].dts -= delta;
551
555
  inputSamples[i].pts -= delta;
556
+
557
+ // check to see if this sample's PTS order has changed
558
+ // relative to the next one
559
+ if (i < inputSamples.length - 1) {
560
+ const nextSamplePTS = inputSamples[i + 1].pts;
561
+ const currentSamplePTS = inputSamples[i].pts;
562
+
563
+ const currentOrder = nextSamplePTS <= currentSamplePTS;
564
+ const prevOrder = nextSamplePTS <= prevPTS;
565
+
566
+ isPTSOrderRetained = currentOrder == prevOrder;
567
+ }
552
568
  }
553
569
  }
554
570
  logger.log(
@@ -743,7 +759,7 @@ export default class MP4Remuxer implements Remuxer {
743
759
  }
744
760
  }
745
761
  }
746
- // next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
762
+ // next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
747
763
  mp4SampleDuration =
748
764
  stretchedLastFrame || !mp4SampleDuration
749
765
  ? averageSampleDuration
@@ -927,7 +943,7 @@ export default class MP4Remuxer implements Remuxer {
927
943
  for (let j = 0; j < missing; j++) {
928
944
  const newStamp = Math.max(nextPts as number, 0);
929
945
  let fillFrame = AAC.getSilentFrame(
930
- track.manifestCodec || track.codec,
946
+ track.parsedCodec || track.manifestCodec || track.codec,
931
947
  track.channelCount,
932
948
  );
933
949
  if (!fillFrame) {
@@ -1077,7 +1093,7 @@ export default class MP4Remuxer implements Remuxer {
1077
1093
  const nbSamples: number = Math.ceil((endDTS - startDTS) / frameDuration);
1078
1094
  // silent frame
1079
1095
  const silentFrame: Uint8Array | undefined = AAC.getSilentFrame(
1080
- track.manifestCodec || track.codec,
1096
+ track.parsedCodec || track.manifestCodec || track.codec,
1081
1097
  track.channelCount,
1082
1098
  );
1083
1099