hls.js 1.5.4 → 1.5.5-0.canary.9977

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 (67) 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 +1930 -1095
  5. package/dist/hls.js.d.ts +63 -50
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1609 -778
  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 +1363 -542
  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 +1635 -815
  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 +18 -18
  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 +7 -7
  25. package/src/controller/base-stream-controller.ts +56 -29
  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 +25 -3
  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 +5 -17
  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 +63 -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/remux/mp4-generator.ts +196 -1
  60. package/src/remux/mp4-remuxer.ts +23 -7
  61. package/src/task-loop.ts +5 -2
  62. package/src/types/component-api.ts +2 -0
  63. package/src/types/demuxer.ts +3 -0
  64. package/src/types/events.ts +4 -0
  65. package/src/utils/codecs.ts +33 -4
  66. package/src/utils/encryption-methods-util.ts +21 -0
  67. package/src/utils/logger.ts +53 -24
package/package.json CHANGED
@@ -58,39 +58,39 @@
58
58
  "test:func:sauce": "SAUCE=1 UA=safari OS='OS X 10.15' BABEL_ENV=development mocha --require @babel/register tests/functional/auto/setup.js --timeout 40000 --exit",
59
59
  "type-check": "tsc --noEmit",
60
60
  "type-check:watch": "npm run type-check -- --watch",
61
- "prepare": "husky install"
61
+ "prepare": "husky"
62
62
  },
63
63
  "devDependencies": {
64
- "@babel/core": "7.23.7",
64
+ "@babel/core": "7.23.9",
65
65
  "@babel/helper-module-imports": "7.22.15",
66
66
  "@babel/plugin-proposal-class-properties": "7.18.6",
67
67
  "@babel/plugin-proposal-object-rest-spread": "7.20.7",
68
68
  "@babel/plugin-proposal-optional-chaining": "7.21.0",
69
69
  "@babel/plugin-transform-object-assign": "7.23.3",
70
- "@babel/preset-env": "7.23.7",
70
+ "@babel/preset-env": "7.23.9",
71
71
  "@babel/preset-typescript": "7.23.3",
72
72
  "@babel/register": "7.23.7",
73
- "@microsoft/api-documenter": "7.23.16",
74
- "@microsoft/api-extractor": "7.39.1",
73
+ "@microsoft/api-documenter": "7.23.20",
74
+ "@microsoft/api-extractor": "7.39.4",
75
75
  "@rollup/plugin-alias": "5.1.0",
76
76
  "@rollup/plugin-babel": "6.0.4",
77
77
  "@rollup/plugin-commonjs": "25.0.7",
78
78
  "@rollup/plugin-node-resolve": "15.2.3",
79
79
  "@rollup/plugin-replace": "5.0.5",
80
80
  "@rollup/plugin-terser": "0.4.4",
81
- "@rollup/plugin-typescript": "11.1.5",
82
- "@svta/common-media-library": "0.6.1",
81
+ "@rollup/plugin-typescript": "11.1.6",
82
+ "@svta/common-media-library": "0.6.2",
83
83
  "@types/chai": "4.3.11",
84
84
  "@types/chart.js": "2.9.41",
85
85
  "@types/mocha": "10.0.6",
86
86
  "@types/sinon-chai": "3.2.12",
87
- "@typescript-eslint/eslint-plugin": "6.17.0",
88
- "@typescript-eslint/parser": "6.17.0",
87
+ "@typescript-eslint/eslint-plugin": "6.21.0",
88
+ "@typescript-eslint/parser": "6.21.0",
89
89
  "babel-loader": "9.1.3",
90
90
  "babel-plugin-transform-remove-console": "6.9.4",
91
- "chai": "4.3.10",
91
+ "chai": "4.4.1",
92
92
  "chart.js": "2.9.4",
93
- "chromedriver": "120.0.1",
93
+ "chromedriver": "121.0.0",
94
94
  "doctoc": "2.2.1",
95
95
  "es-check": "7.1.1",
96
96
  "eslint": "8.56.0",
@@ -101,7 +101,7 @@
101
101
  "eslint-plugin-promise": "6.1.1",
102
102
  "eventemitter3": "5.0.1",
103
103
  "http-server": "14.1.1",
104
- "husky": "8.0.3",
104
+ "husky": "9.0.10",
105
105
  "jsonpack": "1.1.5",
106
106
  "karma": "6.4.2",
107
107
  "karma-chrome-launcher": "3.2.0",
@@ -116,19 +116,19 @@
116
116
  "micromatch": "4.0.5",
117
117
  "mocha": "10.2.0",
118
118
  "node-fetch": "3.3.2",
119
- "npm-run-all": "4.1.5",
120
- "prettier": "3.1.1",
119
+ "npm-run-all2": "5.0.2",
120
+ "prettier": "3.2.4",
121
121
  "promise-polyfill": "8.3.0",
122
- "rollup": "4.9.4",
122
+ "rollup": "4.9.6",
123
123
  "rollup-plugin-istanbul": "5.0.0",
124
124
  "sauce-connect-launcher": "1.3.2",
125
- "selenium-webdriver": "4.16.0",
125
+ "selenium-webdriver": "4.17.0",
126
126
  "semver": "7.5.4",
127
127
  "sinon": "17.0.1",
128
128
  "sinon-chai": "3.7.0",
129
129
  "typescript": "5.3.3",
130
130
  "url-toolkit": "2.2.5",
131
- "wrangler": "3.22.4"
131
+ "wrangler": "3.26.0"
132
132
  },
133
- "version": "1.5.4"
133
+ "version": "1.5.5-0.canary.9977"
134
134
  }
package/src/config.ts CHANGED
@@ -17,10 +17,10 @@ import XhrLoader from './utils/xhr-loader';
17
17
  import FetchLoader, { fetchSupported } from './utils/fetch-loader';
18
18
  import Cues from './utils/cues';
19
19
  import { requestMediaKeySystemAccess } from './utils/mediakeys-helper';
20
- import { ILogger, logger } from './utils/logger';
21
20
 
22
21
  import type Hls from './hls';
23
22
  import type { CuesInterface } from './utils/cues';
23
+ import type { ILogger } from './utils/logger';
24
24
  import type { MediaKeyFunc, KeySystems } from './utils/mediakeys-helper';
25
25
  import type {
26
26
  FragmentLoaderContext,
@@ -558,6 +558,7 @@ function timelineConfig(): TimelineControllerConfig {
558
558
  export function mergeConfig(
559
559
  defaultConfig: HlsConfig,
560
560
  userConfig: Partial<HlsConfig>,
561
+ logger: ILogger,
561
562
  ): HlsConfig {
562
563
  if (
563
564
  (userConfig.liveSyncDurationCount ||
@@ -664,7 +665,7 @@ function deepCpy(obj: any): any {
664
665
  /**
665
666
  * @ignore
666
667
  */
667
- export function enableStreamingMode(config) {
668
+ export function enableStreamingMode(config: HlsConfig, logger: ILogger) {
668
669
  const currentLoader = config.loader;
669
670
  if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
670
671
  // If a developer has configured their own loader, respect that choice
@@ -2,7 +2,7 @@ import EwmaBandWidthEstimator from '../utils/ewma-bandwidth-estimator';
2
2
  import { Events } from '../events';
3
3
  import { ErrorDetails } from '../errors';
4
4
  import { PlaylistLevelType } from '../types/loader';
5
- import { logger } from '../utils/logger';
5
+ import { Logger } from '../utils/logger';
6
6
  import {
7
7
  SUPPORTED_INFO_DEFAULT,
8
8
  getMediaDecodingInfoPromise,
@@ -31,7 +31,7 @@ import type {
31
31
  } from '../types/events';
32
32
  import type { AbrComponentAPI } from '../types/component-api';
33
33
 
34
- class AbrController implements AbrComponentAPI {
34
+ class AbrController extends Logger implements AbrComponentAPI {
35
35
  protected hls: Hls;
36
36
  private lastLevelLoadSec: number = 0;
37
37
  private lastLoadedFragLevel: number = -1;
@@ -48,6 +48,7 @@ class AbrController implements AbrComponentAPI {
48
48
  public bwEstimator: EwmaBandWidthEstimator;
49
49
 
50
50
  constructor(hls: Hls) {
51
+ super('abr', hls.logger);
51
52
  this.hls = hls;
52
53
  this.bwEstimator = this.initEstimator();
53
54
  this.registerListeners();
@@ -55,7 +56,7 @@ class AbrController implements AbrComponentAPI {
55
56
 
56
57
  public resetEstimator(abrEwmaDefaultEstimate?: number) {
57
58
  if (abrEwmaDefaultEstimate) {
58
- logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
59
+ this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
59
60
  this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
60
61
  }
61
62
  this.firstSelection = -1;
@@ -355,7 +356,7 @@ class AbrController implements AbrComponentAPI {
355
356
  }
356
357
 
357
358
  this.clearTimer();
358
- logger.warn(`[abr] Fragment ${frag.sn}${
359
+ this.warn(`Fragment ${frag.sn}${
359
360
  part ? ' part ' + part.index : ''
360
361
  } of level ${frag.level} is loading too slowly;
361
362
  Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
@@ -479,8 +480,8 @@ class AbrController implements AbrComponentAPI {
479
480
  }
480
481
  const firstLevel = this.hls.firstLevel;
481
482
  const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
482
- logger.warn(
483
- `[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`,
483
+ this.warn(
484
+ `Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`,
484
485
  );
485
486
  return clamped;
486
487
  }
@@ -591,8 +592,8 @@ class AbrController implements AbrComponentAPI {
591
592
  ? Math.min(currentFragDuration, config.maxLoadingDelay)
592
593
  : config.maxLoadingDelay;
593
594
  maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
594
- logger.info(
595
- `[abr] bitrate test took ${Math.round(
595
+ this.info(
596
+ `bitrate test took ${Math.round(
596
597
  1000 * bitrateTestDelay,
597
598
  )}ms, set first fragment max fetchDuration to ${Math.round(
598
599
  1000 * maxStarvationDelay,
@@ -611,8 +612,8 @@ class AbrController implements AbrComponentAPI {
611
612
  bwFactor,
612
613
  bwUpFactor,
613
614
  );
614
- logger.info(
615
- `[abr] ${
615
+ this.info(
616
+ `${
616
617
  bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'
617
618
  }, optimal quality level ${bestLevel}`,
618
619
  );
@@ -691,7 +692,7 @@ class AbrController implements AbrComponentAPI {
691
692
  : videoRanges[0];
692
693
  currentFrameRate = minFramerate;
693
694
  currentBw = Math.max(currentBw, minBitrate);
694
- logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
695
+ this.log(`picked start tier ${JSON.stringify(startTier)}`);
695
696
  } else {
696
697
  currentCodecSet = level?.codecSet;
697
698
  currentVideoRange = level?.videoRange;
@@ -741,19 +742,19 @@ class AbrController implements AbrComponentAPI {
741
742
  const levels = this.hls.levels;
742
743
  const index = levels.indexOf(levelInfo);
743
744
  if (decodingInfo.error) {
744
- logger.warn(
745
- `[abr] MediaCapabilities decodingInfo error: "${
745
+ this.warn(
746
+ `MediaCapabilities decodingInfo error: "${
746
747
  decodingInfo.error
747
748
  }" for level ${index} ${JSON.stringify(decodingInfo)}`,
748
749
  );
749
750
  } else if (!decodingInfo.supported) {
750
- logger.warn(
751
- `[abr] Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(
751
+ this.warn(
752
+ `Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(
752
753
  decodingInfo,
753
754
  )}`,
754
755
  );
755
756
  if (index > -1 && levels.length > 1) {
756
- logger.log(`[abr] Removing unsupported level ${index}`);
757
+ this.log(`Removing unsupported level ${index}`);
757
758
  this.hls.removeLevel(index);
758
759
  }
759
760
  }
@@ -832,8 +833,8 @@ class AbrController implements AbrComponentAPI {
832
833
  (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)
833
834
  ) {
834
835
  if (levelsSkipped.length) {
835
- logger.trace(
836
- `[abr] Skipped level(s) ${levelsSkipped.join(
836
+ this.trace(
837
+ `Skipped level(s) ${levelsSkipped.join(
837
838
  ',',
838
839
  )} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${
839
840
  levels[levelsSkipped[0]].codecs
@@ -842,8 +843,8 @@ class AbrController implements AbrComponentAPI {
842
843
  }" ${currentVideoRange}`,
843
844
  );
844
845
  }
845
- logger.info(
846
- `[abr] switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(
846
+ this.info(
847
+ `switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(
847
848
  adjustedbw,
848
849
  )})-bitrate=${Math.round(
849
850
  adjustedbw - bitrate,
@@ -71,30 +71,27 @@ class AudioStreamController
71
71
  hls,
72
72
  fragmentTracker,
73
73
  keyLoader,
74
- '[audio-stream-controller]',
74
+ 'audio-stream-controller',
75
75
  PlaylistLevelType.AUDIO,
76
76
  );
77
- this._registerListeners();
77
+ this.registerListeners();
78
78
  }
79
79
 
80
80
  protected onHandlerDestroying() {
81
- this._unregisterListeners();
81
+ this.unregisterListeners();
82
82
  super.onHandlerDestroying();
83
83
  this.mainDetails = null;
84
84
  this.bufferedTrack = null;
85
85
  this.switchingTrack = null;
86
86
  }
87
87
 
88
- private _registerListeners() {
88
+ protected registerListeners() {
89
+ super.registerListeners();
89
90
  const { hls } = this;
90
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
91
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
92
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
93
91
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
94
92
  hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
95
93
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
96
94
  hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
97
- hls.on(Events.ERROR, this.onError, this);
98
95
  hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
99
96
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
100
97
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -103,16 +100,16 @@ class AudioStreamController
103
100
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
104
101
  }
105
102
 
106
- private _unregisterListeners() {
103
+ protected unregisterListeners() {
107
104
  const { hls } = this;
108
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
109
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
110
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
105
+ if (!hls) {
106
+ return;
107
+ }
108
+ super.unregisterListeners();
111
109
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
112
110
  hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
113
111
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
114
112
  hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
115
- hls.off(Events.ERROR, this.onError, this);
116
113
  hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
117
114
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
118
115
  hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -281,12 +278,14 @@ class AudioStreamController
281
278
  const { hls, levels, media, trackId } = this;
282
279
  const config = hls.config;
283
280
 
284
- // 1. if video not attached AND
281
+ // 1. if buffering is suspended
282
+ // 2. if video not attached AND
285
283
  // start fragment already requested OR start frag prefetch not enabled
286
- // 2. if tracks or track not loaded and selected
284
+ // 3. if tracks or track not loaded and selected
287
285
  // then exit loop
288
286
  // => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
289
287
  if (
288
+ !this.buffering ||
290
289
  (!media && (this.startFragRequested || !config.startFragPrefetch)) ||
291
290
  !levels?.[trackId]
292
291
  ) {
@@ -713,7 +712,7 @@ class AudioStreamController
713
712
  this.fragBufferedComplete(frag, part);
714
713
  }
715
714
 
716
- private onError(event: Events.ERROR, data: ErrorData) {
715
+ protected onError(event: Events.ERROR, data: ErrorData) {
717
716
  if (data.fatal) {
718
717
  this.state = State.ERROR;
719
718
  return;
@@ -33,7 +33,7 @@ class AudioTrackController extends BasePlaylistController {
33
33
  private selectDefaultTrack: boolean = true;
34
34
 
35
35
  constructor(hls: Hls) {
36
- super(hls, '[audio-track-controller]');
36
+ super(hls, 'audio-track-controller');
37
37
  this.registerListeners();
38
38
  }
39
39
 
@@ -5,7 +5,7 @@ import { computeReloadInterval, mergeDetails } from '../utils/level-helper';
5
5
  import { ErrorData } from '../types/events';
6
6
  import { getRetryDelay, isTimeoutError } from '../utils/error-helper';
7
7
  import { NetworkErrorAction } from './error-controller';
8
- import { logger } from '../utils/logger';
8
+ import { Logger } from '../utils/logger';
9
9
  import type { LevelDetails } from '../loader/level-details';
10
10
  import type { MediaPlaylist } from '../types/media-playlist';
11
11
  import type {
@@ -14,17 +14,17 @@ import type {
14
14
  TrackLoadedData,
15
15
  } from '../types/events';
16
16
 
17
- export default class BasePlaylistController implements NetworkComponentAPI {
17
+ export default class BasePlaylistController
18
+ extends Logger
19
+ implements NetworkComponentAPI
20
+ {
18
21
  protected hls: Hls;
19
22
  protected timer: number = -1;
20
23
  protected requestScheduled: number = -1;
21
24
  protected canLoad: boolean = false;
22
- protected log: (msg: any) => void;
23
- protected warn: (msg: any) => void;
24
25
 
25
26
  constructor(hls: Hls, logPrefix: string) {
26
- this.log = logger.log.bind(logger, `${logPrefix}:`);
27
- this.warn = logger.warn.bind(logger, `${logPrefix}:`);
27
+ super(logPrefix, hls.logger);
28
28
  this.hls = hls;
29
29
  }
30
30
 
@@ -65,7 +65,7 @@ export default class BasePlaylistController implements NetworkComponentAPI {
65
65
  try {
66
66
  uri = new self.URL(attr.URI, previous.url).href;
67
67
  } catch (error) {
68
- logger.warn(
68
+ this.warn(
69
69
  `Could not construct new URL for Rendition Report: ${error}`,
70
70
  );
71
71
  uri = attr.URI || '';
@@ -1,12 +1,15 @@
1
1
  import TaskLoop from '../task-loop';
2
2
  import { FragmentState } from './fragment-tracker';
3
3
  import { Bufferable, BufferHelper, BufferInfo } from '../utils/buffer-helper';
4
- import { logger } from '../utils/logger';
5
4
  import { Events } from '../events';
6
5
  import { ErrorDetails, ErrorTypes } from '../errors';
7
6
  import { ChunkMetadata } from '../types/transmuxer';
8
7
  import { appendUint8Array } from '../utils/mp4-tools';
9
8
  import { alignStream } from '../utils/discontinuities';
9
+ import {
10
+ isFullSegmentEncryption,
11
+ getAesModeFromFullSegmentMethod,
12
+ } from '../utils/encryption-methods-util';
10
13
  import {
11
14
  findFragmentByPDT,
12
15
  findFragmentByPTS,
@@ -97,12 +100,7 @@ export default class BaseStreamController
97
100
  protected startFragRequested: boolean = false;
98
101
  protected decrypter: Decrypter;
99
102
  protected initPTS: RationalTimestamp[] = [];
100
- protected onvseeking: EventListener | null = null;
101
- protected onvended: EventListener | null = null;
102
-
103
- private readonly logPrefix: string = '';
104
- protected log: (msg: any) => void;
105
- protected warn: (msg: any) => void;
103
+ protected buffering: boolean = true;
106
104
 
107
105
  constructor(
108
106
  hls: Hls,
@@ -111,18 +109,32 @@ export default class BaseStreamController
111
109
  logPrefix: string,
112
110
  playlistType: PlaylistLevelType,
113
111
  ) {
114
- super();
112
+ super(logPrefix, hls.logger);
115
113
  this.playlistType = playlistType;
116
- this.logPrefix = logPrefix;
117
- this.log = logger.log.bind(logger, `${logPrefix}:`);
118
- this.warn = logger.warn.bind(logger, `${logPrefix}:`);
119
114
  this.hls = hls;
120
115
  this.fragmentLoader = new FragmentLoader(hls.config);
121
116
  this.keyLoader = keyLoader;
122
117
  this.fragmentTracker = fragmentTracker;
123
118
  this.config = hls.config;
124
119
  this.decrypter = new Decrypter(hls.config);
120
+ }
121
+
122
+ protected registerListeners() {
123
+ const { hls } = this;
124
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
125
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
126
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
125
127
  hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
128
+ hls.on(Events.ERROR, this.onError, this);
129
+ }
130
+
131
+ protected unregisterListeners() {
132
+ const { hls } = this;
133
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
134
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
135
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
136
+ hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
137
+ hls.off(Events.ERROR, this.onError, this);
126
138
  }
127
139
 
128
140
  protected doTick() {
@@ -150,6 +162,14 @@ export default class BaseStreamController
150
162
  this.state = State.STOPPED;
151
163
  }
152
164
 
165
+ public pauseBuffering() {
166
+ this.buffering = false;
167
+ }
168
+
169
+ public resumeBuffering() {
170
+ this.buffering = true;
171
+ }
172
+
153
173
  protected _streamEnded(
154
174
  bufferInfo: BufferInfo,
155
175
  levelDetails: LevelDetails,
@@ -197,10 +217,8 @@ export default class BaseStreamController
197
217
  data: MediaAttachedData,
198
218
  ) {
199
219
  const media = (this.media = this.mediaBuffer = data.media);
200
- this.onvseeking = this.onMediaSeeking.bind(this) as EventListener;
201
- this.onvended = this.onMediaEnded.bind(this) as EventListener;
202
- media.addEventListener('seeking', this.onvseeking);
203
- media.addEventListener('ended', this.onvended);
220
+ media.addEventListener('seeking', this.onMediaSeeking);
221
+ media.addEventListener('ended', this.onMediaEnded);
204
222
  const config = this.config;
205
223
  if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
206
224
  this.startLoad(config.startPosition);
@@ -215,10 +233,9 @@ export default class BaseStreamController
215
233
  }
216
234
 
217
235
  // remove video listeners
218
- if (media && this.onvseeking && this.onvended) {
219
- media.removeEventListener('seeking', this.onvseeking);
220
- media.removeEventListener('ended', this.onvended);
221
- this.onvseeking = this.onvended = null;
236
+ if (media) {
237
+ media.removeEventListener('seeking', this.onMediaSeeking);
238
+ media.removeEventListener('ended', this.onMediaEnded);
222
239
  }
223
240
  if (this.keyLoader) {
224
241
  this.keyLoader.detach();
@@ -229,7 +246,11 @@ export default class BaseStreamController
229
246
  this.stopLoad();
230
247
  }
231
248
 
232
- protected onMediaSeeking() {
249
+ protected onManifestLoading() {}
250
+
251
+ protected onError(event: Events.ERROR, data: ErrorData) {}
252
+
253
+ protected onMediaSeeking = () => {
233
254
  const { config, fragCurrent, media, mediaBuffer, state } = this;
234
255
  const currentTime: number = media ? media.currentTime : 0;
235
256
  const bufferInfo = BufferHelper.bufferInfo(
@@ -292,12 +313,17 @@ export default class BaseStreamController
292
313
 
293
314
  // Async tick to speed up processing
294
315
  this.tickImmediate();
295
- }
316
+ };
296
317
 
297
- protected onMediaEnded() {
318
+ protected onMediaEnded = () => {
298
319
  // reset startPosition and lastCurrentTime to restart playback @ stream beginning
299
320
  this.startPosition = this.lastCurrentTime = 0;
300
- }
321
+ if (this.playlistType === PlaylistLevelType.MAIN) {
322
+ this.hls.trigger(Events.MEDIA_ENDED, {
323
+ stalled: false,
324
+ });
325
+ }
326
+ };
301
327
 
302
328
  protected onManifestLoaded(
303
329
  event: Events.MANIFEST_LOADED,
@@ -312,7 +338,7 @@ export default class BaseStreamController
312
338
  this.stopLoad();
313
339
  super.onHandlerDestroying();
314
340
  // @ts-ignore
315
- this.hls = null;
341
+ this.hls = this.onMediaSeeking = this.onMediaEnded = null;
316
342
  }
317
343
 
318
344
  protected onHandlerDestroyed() {
@@ -486,7 +512,7 @@ export default class BaseStreamController
486
512
  payload.byteLength > 0 &&
487
513
  decryptData?.key &&
488
514
  decryptData.iv &&
489
- decryptData.method === 'AES-128'
515
+ isFullSegmentEncryption(decryptData.method)
490
516
  ) {
491
517
  const startTime = self.performance.now();
492
518
  // decrypt init segment data
@@ -495,6 +521,7 @@ export default class BaseStreamController
495
521
  new Uint8Array(payload),
496
522
  decryptData.key.buffer,
497
523
  decryptData.iv.buffer,
524
+ getAesModeFromFullSegmentMethod(decryptData.method),
498
525
  )
499
526
  .catch((err) => {
500
527
  hls.trigger(Events.ERROR, {
@@ -649,7 +676,7 @@ export default class BaseStreamController
649
676
  if (frag.encrypted && !frag.decryptdata?.key) {
650
677
  this.log(
651
678
  `Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${
652
- this.logPrefix === '[stream-controller]' ? 'level' : 'track'
679
+ this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
653
680
  } ${frag.level}`,
654
681
  );
655
682
  this.state = State.KEY_LOADING;
@@ -689,7 +716,7 @@ export default class BaseStreamController
689
716
  } of playlist [${details.startSN}-${
690
717
  details.endSN
691
718
  }] parts [0-${partIndex}-${partList.length - 1}] ${
692
- this.logPrefix === '[stream-controller]' ? 'level' : 'track'
719
+ this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
693
720
  }: ${frag.level}, target: ${parseFloat(
694
721
  targetBufferTime.toFixed(3),
695
722
  )}`,
@@ -748,7 +775,7 @@ export default class BaseStreamController
748
775
  this.log(
749
776
  `Loading fragment ${frag.sn} cc: ${frag.cc} ${
750
777
  details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''
751
- }${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${
778
+ }${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${
752
779
  frag.level
753
780
  }, target: ${parseFloat(targetBufferTime.toFixed(3))}`,
754
781
  );
@@ -1550,7 +1577,7 @@ export default class BaseStreamController
1550
1577
  errorAction.resolved = true;
1551
1578
  }
1552
1579
  } else {
1553
- logger.warn(
1580
+ this.warn(
1554
1581
  `${data.details} reached or exceeded max retry (${retryCount})`,
1555
1582
  );
1556
1583
  return;
@@ -1,5 +1,5 @@
1
1
  import { Events } from '../events';
2
- import { logger } from '../utils/logger';
2
+ import { Logger } from '../utils/logger';
3
3
  import { ErrorDetails, ErrorTypes } from '../errors';
4
4
  import { BufferHelper } from '../utils/buffer-helper';
5
5
  import {
@@ -42,7 +42,7 @@ interface BufferedChangeEvent extends Event {
42
42
  readonly removedRanges?: TimeRanges;
43
43
  }
44
44
 
45
- export default class BufferController implements ComponentAPI {
45
+ export default class BufferController extends Logger implements ComponentAPI {
46
46
  // The level details used to determine duration, target-duration and live
47
47
  private details: LevelDetails | null = null;
48
48
  // cache the self generated object url to detect hijack of video tag
@@ -82,17 +82,10 @@ export default class BufferController implements ComponentAPI {
82
82
  public pendingTracks: TrackSet = {};
83
83
  public sourceBuffer!: SourceBuffers;
84
84
 
85
- protected log: (msg: any) => void;
86
- protected warn: (msg: any, obj?: any) => void;
87
- protected error: (msg: any, obj?: any) => void;
88
-
89
85
  constructor(hls: Hls) {
86
+ super('buffer-controller', hls.logger);
90
87
  this.hls = hls;
91
- const logPrefix = '[buffer-controller]';
92
88
  this.appendSource = hls.config.preferManagedMediaSource;
93
- this.log = logger.log.bind(logger, logPrefix);
94
- this.warn = logger.warn.bind(logger, logPrefix);
95
- this.error = logger.error.bind(logger, logPrefix);
96
89
  this._initSourceBuffer();
97
90
  this.registerListeners();
98
91
  }
@@ -110,6 +103,12 @@ export default class BufferController implements ComponentAPI {
110
103
  this.lastMpegAudioChunk = null;
111
104
  // @ts-ignore
112
105
  this.hls = null;
106
+ // @ts-ignore
107
+ this._onMediaSourceOpen = this._onMediaSourceClose = null;
108
+ // @ts-ignore
109
+ this._onMediaSourceEnded = null;
110
+ // @ts-ignore
111
+ this._onStartStreaming = this._onEndStreaming = null;
113
112
  }
114
113
 
115
114
  protected registerListeners() {
@@ -296,6 +295,7 @@ export default class BufferController implements ComponentAPI {
296
295
  this.resetBuffer(type);
297
296
  });
298
297
  this._initSourceBuffer();
298
+ this.hls.resumeBuffering();
299
299
  }
300
300
 
301
301
  private resetBuffer(type: SourceBufferName) {
@@ -1004,7 +1004,7 @@ export default class BufferController implements ComponentAPI {
1004
1004
  private _onMediaEmptied = () => {
1005
1005
  const { mediaSrc, _objectUrl } = this;
1006
1006
  if (mediaSrc !== _objectUrl) {
1007
- logger.error(
1007
+ this.error(
1008
1008
  `Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`,
1009
1009
  );
1010
1010
  }
@@ -12,7 +12,6 @@ import type {
12
12
  LevelsUpdatedData,
13
13
  } from '../types/events';
14
14
  import StreamController from './stream-controller';
15
- import { logger } from '../utils/logger';
16
15
  import type { ComponentAPI } from '../types/component-api';
17
16
  import type Hls from '../hls';
18
17
 
@@ -152,7 +151,7 @@ class CapLevelController implements ComponentAPI {
152
151
  const hls = this.hls;
153
152
  const maxLevel = this.getMaxLevel(levels.length - 1);
154
153
  if (maxLevel !== this.autoLevelCapping) {
155
- logger.log(
154
+ hls.logger.log(
156
155
  `Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`,
157
156
  );
158
157
  }