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.
- package/README.md +1 -0
- package/dist/hls-demo.js +10 -0
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +1930 -1095
- package/dist/hls.js.d.ts +63 -50
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1609 -778
- package/dist/hls.light.js.map +1 -1
- package/dist/hls.light.min.js +1 -1
- package/dist/hls.light.min.js.map +1 -1
- package/dist/hls.light.mjs +1363 -542
- package/dist/hls.light.mjs.map +1 -1
- package/dist/hls.min.js +1 -1
- package/dist/hls.min.js.map +1 -1
- package/dist/hls.mjs +1635 -815
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/dist/hls.worker.js.map +1 -1
- package/package.json +18 -18
- package/src/config.ts +3 -2
- package/src/controller/abr-controller.ts +21 -20
- package/src/controller/audio-stream-controller.ts +15 -16
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +7 -7
- package/src/controller/base-stream-controller.ts +56 -29
- package/src/controller/buffer-controller.ts +11 -11
- package/src/controller/cap-level-controller.ts +1 -2
- package/src/controller/cmcd-controller.ts +25 -3
- package/src/controller/content-steering-controller.ts +8 -6
- package/src/controller/eme-controller.ts +9 -22
- package/src/controller/error-controller.ts +6 -8
- package/src/controller/fps-controller.ts +2 -3
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/latency-controller.ts +9 -11
- package/src/controller/level-controller.ts +5 -17
- package/src/controller/stream-controller.ts +25 -32
- package/src/controller/subtitle-stream-controller.ts +13 -14
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +23 -30
- package/src/crypt/aes-crypto.ts +21 -2
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +32 -18
- package/src/crypt/fast-aes-key.ts +24 -5
- package/src/demux/audio/adts.ts +9 -4
- package/src/demux/sample-aes.ts +2 -0
- package/src/demux/transmuxer-interface.ts +4 -12
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +16 -3
- package/src/demux/tsdemuxer.ts +63 -37
- package/src/demux/video/avc-video-parser.ts +208 -119
- package/src/demux/video/base-video-parser.ts +134 -2
- package/src/demux/video/exp-golomb.ts +0 -208
- package/src/demux/video/hevc-video-parser.ts +746 -0
- package/src/events.ts +7 -0
- package/src/hls.ts +42 -34
- package/src/loader/fragment-loader.ts +9 -2
- package/src/loader/key-loader.ts +2 -0
- package/src/loader/level-key.ts +10 -9
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +23 -7
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +2 -0
- package/src/types/demuxer.ts +3 -0
- package/src/types/events.ts +4 -0
- package/src/utils/codecs.ts +33 -4
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/logger.ts +53 -24
@@ -5,6 +5,7 @@ import { CmcdObjectType } from '@svta/common-media-library/cmcd/CmcdObjectType';
|
|
5
5
|
import { CmcdStreamingFormat } from '@svta/common-media-library/cmcd/CmcdStreamingFormat';
|
6
6
|
import { appendCmcdHeaders } from '@svta/common-media-library/cmcd/appendCmcdHeaders';
|
7
7
|
import { appendCmcdQuery } from '@svta/common-media-library/cmcd/appendCmcdQuery';
|
8
|
+
import type { CmcdEncodeOptions } from '@svta/common-media-library/cmcd/CmcdEncodeOptions';
|
8
9
|
import { uuid } from '@svta/common-media-library/utils/uuid';
|
9
10
|
import { BufferHelper } from '../utils/buffer-helper';
|
10
11
|
import { logger } from '../utils/logger';
|
@@ -165,7 +166,7 @@ export default class CMCDController implements ComponentAPI {
|
|
165
166
|
data.su = this.buffering;
|
166
167
|
}
|
167
168
|
|
168
|
-
// TODO: Implement rtp, nrr,
|
169
|
+
// TODO: Implement rtp, nrr, dl
|
169
170
|
|
170
171
|
const { includeKeys } = this;
|
171
172
|
if (includeKeys) {
|
@@ -175,14 +176,18 @@ export default class CMCDController implements ComponentAPI {
|
|
175
176
|
}, {});
|
176
177
|
}
|
177
178
|
|
179
|
+
const options: CmcdEncodeOptions = {
|
180
|
+
baseUrl: context.url,
|
181
|
+
};
|
182
|
+
|
178
183
|
if (this.useHeaders) {
|
179
184
|
if (!context.headers) {
|
180
185
|
context.headers = {};
|
181
186
|
}
|
182
187
|
|
183
|
-
appendCmcdHeaders(context.headers, data);
|
188
|
+
appendCmcdHeaders(context.headers, data, options);
|
184
189
|
} else {
|
185
|
-
context.url = appendCmcdQuery(context.url, data);
|
190
|
+
context.url = appendCmcdQuery(context.url, data, options);
|
186
191
|
}
|
187
192
|
}
|
188
193
|
|
@@ -223,12 +228,29 @@ export default class CMCDController implements ComponentAPI {
|
|
223
228
|
data.bl = this.getBufferLength(ot);
|
224
229
|
}
|
225
230
|
|
231
|
+
const next = this.getNextFrag(fragment);
|
232
|
+
if (next) {
|
233
|
+
if (next.url && next.url !== fragment.url) {
|
234
|
+
data.nor = next.url;
|
235
|
+
}
|
236
|
+
}
|
237
|
+
|
226
238
|
this.apply(context, data);
|
227
239
|
} catch (error) {
|
228
240
|
logger.warn('Could not generate segment CMCD data.', error);
|
229
241
|
}
|
230
242
|
};
|
231
243
|
|
244
|
+
private getNextFrag(fragment: Fragment): Fragment | undefined {
|
245
|
+
const levelDetails = this.hls.levels[fragment.level]?.details;
|
246
|
+
if (levelDetails) {
|
247
|
+
const index = (fragment.sn as number) - levelDetails.startSN;
|
248
|
+
return levelDetails.fragments[index + 1];
|
249
|
+
}
|
250
|
+
|
251
|
+
return undefined;
|
252
|
+
}
|
253
|
+
|
232
254
|
/**
|
233
255
|
* The CMCD object type.
|
234
256
|
*/
|
@@ -3,7 +3,7 @@ import { Level } from '../types/level';
|
|
3
3
|
import { reassignFragmentLevelIndexes } from '../utils/level-helper';
|
4
4
|
import { AttrList } from '../utils/attr-list';
|
5
5
|
import { ErrorActionFlags, NetworkErrorAction } from './error-controller';
|
6
|
-
import {
|
6
|
+
import { Logger } from '../utils/logger';
|
7
7
|
import {
|
8
8
|
PlaylistContextType,
|
9
9
|
type Loader,
|
@@ -48,9 +48,11 @@ export type UriReplacement = {
|
|
48
48
|
|
49
49
|
const PATHWAY_PENALTY_DURATION_MS = 300000;
|
50
50
|
|
51
|
-
export default class ContentSteeringController
|
51
|
+
export default class ContentSteeringController
|
52
|
+
extends Logger
|
53
|
+
implements NetworkComponentAPI
|
54
|
+
{
|
52
55
|
private readonly hls: Hls;
|
53
|
-
private log: (msg: any) => void;
|
54
56
|
private loader: Loader<LoaderContext> | null = null;
|
55
57
|
private uri: string | null = null;
|
56
58
|
private pathwayId: string = '.';
|
@@ -66,8 +68,8 @@ export default class ContentSteeringController implements NetworkComponentAPI {
|
|
66
68
|
private penalizedPathways: { [pathwayId: string]: number } = {};
|
67
69
|
|
68
70
|
constructor(hls: Hls) {
|
71
|
+
super('content-steering', hls.logger);
|
69
72
|
this.hls = hls;
|
70
|
-
this.log = logger.log.bind(logger, `[content-steering]:`);
|
71
73
|
this.registerListeners();
|
72
74
|
}
|
73
75
|
|
@@ -203,7 +205,7 @@ export default class ContentSteeringController implements NetworkComponentAPI {
|
|
203
205
|
errorAction.resolved = this.pathwayId !== errorPathway;
|
204
206
|
}
|
205
207
|
if (!errorAction.resolved) {
|
206
|
-
|
208
|
+
this.warn(
|
207
209
|
`Could not resolve ${data.details} ("${
|
208
210
|
data.error.message
|
209
211
|
}") with content-steering for Pathway: ${errorPathway} levels: ${
|
@@ -442,7 +444,7 @@ export default class ContentSteeringController implements NetworkComponentAPI {
|
|
442
444
|
) => {
|
443
445
|
this.log(`Loaded steering manifest: "${url}"`);
|
444
446
|
const steeringData = response.data as SteeringManifest;
|
445
|
-
if (steeringData
|
447
|
+
if (steeringData?.VERSION !== 1) {
|
446
448
|
this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
|
447
449
|
return;
|
448
450
|
}
|
@@ -5,7 +5,7 @@
|
|
5
5
|
*/
|
6
6
|
import { Events } from '../events';
|
7
7
|
import { ErrorTypes, ErrorDetails } from '../errors';
|
8
|
-
import {
|
8
|
+
import { Logger } from '../utils/logger';
|
9
9
|
import {
|
10
10
|
getKeySystemsForConfig,
|
11
11
|
getSupportedMediaKeySystemConfigurations,
|
@@ -41,9 +41,6 @@ import type {
|
|
41
41
|
LoaderConfiguration,
|
42
42
|
LoaderContext,
|
43
43
|
} from '../types/loader';
|
44
|
-
|
45
|
-
const LOGGER_PREFIX = '[eme]';
|
46
|
-
|
47
44
|
interface KeySystemAccessPromises {
|
48
45
|
keySystemAccess: Promise<MediaKeySystemAccess>;
|
49
46
|
mediaKeys?: Promise<MediaKeys>;
|
@@ -68,7 +65,7 @@ export interface MediaKeySessionContext {
|
|
68
65
|
* @class
|
69
66
|
* @constructor
|
70
67
|
*/
|
71
|
-
class EMEController implements ComponentAPI {
|
68
|
+
class EMEController extends Logger implements ComponentAPI {
|
72
69
|
public static CDMCleanupPromise: Promise<void> | void;
|
73
70
|
|
74
71
|
private readonly hls: Hls;
|
@@ -90,15 +87,9 @@ class EMEController implements ComponentAPI {
|
|
90
87
|
private setMediaKeysQueue: Promise<void>[] = EMEController.CDMCleanupPromise
|
91
88
|
? [EMEController.CDMCleanupPromise]
|
92
89
|
: [];
|
93
|
-
private onMediaEncrypted = this._onMediaEncrypted.bind(this);
|
94
|
-
private onWaitingForKey = this._onWaitingForKey.bind(this);
|
95
|
-
|
96
|
-
private debug: (msg: any) => void = logger.debug.bind(logger, LOGGER_PREFIX);
|
97
|
-
private log: (msg: any) => void = logger.log.bind(logger, LOGGER_PREFIX);
|
98
|
-
private warn: (msg: any) => void = logger.warn.bind(logger, LOGGER_PREFIX);
|
99
|
-
private error: (msg: any) => void = logger.error.bind(logger, LOGGER_PREFIX);
|
100
90
|
|
101
91
|
constructor(hls: Hls) {
|
92
|
+
super('eme', hls.logger);
|
102
93
|
this.hls = hls;
|
103
94
|
this.config = hls.config;
|
104
95
|
this.registerListeners();
|
@@ -113,13 +104,9 @@ class EMEController implements ComponentAPI {
|
|
113
104
|
config.licenseXhrSetup = config.licenseResponseCallback = undefined;
|
114
105
|
config.drmSystems = config.drmSystemOptions = {};
|
115
106
|
// @ts-ignore
|
116
|
-
this.hls =
|
117
|
-
this.onMediaEncrypted =
|
118
|
-
this.onWaitingForKey =
|
119
|
-
this.keyIdToKeySessionPromise =
|
120
|
-
null as any;
|
107
|
+
this.hls = this.config = this.keyIdToKeySessionPromise = null;
|
121
108
|
// @ts-ignore
|
122
|
-
this.
|
109
|
+
this.onMediaEncrypted = this.onWaitingForKey = null;
|
123
110
|
}
|
124
111
|
|
125
112
|
private registerListeners() {
|
@@ -523,7 +510,7 @@ class EMEController implements ComponentAPI {
|
|
523
510
|
return this.attemptKeySystemAccess(keySystemsToAttempt);
|
524
511
|
}
|
525
512
|
|
526
|
-
private
|
513
|
+
private onMediaEncrypted = (event: MediaEncryptedEvent) => {
|
527
514
|
const { initDataType, initData } = event;
|
528
515
|
this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
|
529
516
|
|
@@ -639,11 +626,11 @@ class EMEController implements ComponentAPI {
|
|
639
626
|
);
|
640
627
|
}
|
641
628
|
keySessionContextPromise.catch((error) => this.handleError(error));
|
642
|
-
}
|
629
|
+
};
|
643
630
|
|
644
|
-
private
|
631
|
+
private onWaitingForKey = (event: Event) => {
|
645
632
|
this.log(`"${event.type}" event`);
|
646
|
-
}
|
633
|
+
};
|
647
634
|
|
648
635
|
private attemptSetMediaKeys(
|
649
636
|
keySystem: KeySystems,
|
@@ -8,7 +8,7 @@ import {
|
|
8
8
|
} from '../utils/error-helper';
|
9
9
|
import { findFragmentByPTS } from './fragment-finders';
|
10
10
|
import { HdcpLevel, HdcpLevels } from '../types/level';
|
11
|
-
import {
|
11
|
+
import { Logger } from '../utils/logger';
|
12
12
|
import type Hls from '../hls';
|
13
13
|
import type { RetryConfig } from '../config';
|
14
14
|
import type { NetworkComponentAPI } from '../types/component-api';
|
@@ -50,19 +50,17 @@ type PenalizedRendition = {
|
|
50
50
|
|
51
51
|
type PenalizedRenditions = { [key: number]: PenalizedRendition };
|
52
52
|
|
53
|
-
export default class ErrorController
|
53
|
+
export default class ErrorController
|
54
|
+
extends Logger
|
55
|
+
implements NetworkComponentAPI
|
56
|
+
{
|
54
57
|
private readonly hls: Hls;
|
55
58
|
private playlistError: number = 0;
|
56
59
|
private penalizedRenditions: PenalizedRenditions = {};
|
57
|
-
private log: (msg: any) => void;
|
58
|
-
private warn: (msg: any) => void;
|
59
|
-
private error: (msg: any) => void;
|
60
60
|
|
61
61
|
constructor(hls: Hls) {
|
62
|
+
super('error-controller', hls.logger);
|
62
63
|
this.hls = hls;
|
63
|
-
this.log = logger.log.bind(logger, `[info]:`);
|
64
|
-
this.warn = logger.warn.bind(logger, `[warning]:`);
|
65
|
-
this.error = logger.error.bind(logger, `[error]:`);
|
66
64
|
this.registerListeners();
|
67
65
|
}
|
68
66
|
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import { Events } from '../events';
|
2
|
-
import { logger } from '../utils/logger';
|
3
2
|
import type { ComponentAPI } from '../types/component-api';
|
4
3
|
import type Hls from '../hls';
|
5
4
|
import type { MediaAttachingData } from '../types/events';
|
@@ -84,13 +83,13 @@ class FPSController implements ComponentAPI {
|
|
84
83
|
totalDroppedFrames: droppedFrames,
|
85
84
|
});
|
86
85
|
if (droppedFPS > 0) {
|
87
|
-
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
86
|
+
// hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
88
87
|
if (
|
89
88
|
currentDropped >
|
90
89
|
hls.config.fpsDroppedMonitoringThreshold * currentDecoded
|
91
90
|
) {
|
92
91
|
let currentLevel = hls.currentLevel;
|
93
|
-
logger.warn(
|
92
|
+
hls.logger.warn(
|
94
93
|
'drop FPS ratio greater than max allowed value for currentLevel: ' +
|
95
94
|
currentLevel,
|
96
95
|
);
|
@@ -1,20 +1,22 @@
|
|
1
|
-
import
|
1
|
+
import { State } from './base-stream-controller';
|
2
2
|
import { BufferHelper } from '../utils/buffer-helper';
|
3
3
|
import { ErrorTypes, ErrorDetails } from '../errors';
|
4
4
|
import { PlaylistLevelType } from '../types/loader';
|
5
5
|
import { Events } from '../events';
|
6
|
-
import {
|
6
|
+
import { Logger } from '../utils/logger';
|
7
7
|
import type Hls from '../hls';
|
8
|
+
import type { BufferInfo } from '../utils/buffer-helper';
|
8
9
|
import type { HlsConfig } from '../config';
|
9
10
|
import type { Fragment } from '../loader/fragment';
|
10
11
|
import type { FragmentTracker } from './fragment-tracker';
|
12
|
+
import type { LevelDetails } from '../loader/level-details';
|
11
13
|
|
12
14
|
export const STALL_MINIMUM_DURATION_MS = 250;
|
13
15
|
export const MAX_START_GAP_JUMP = 2.0;
|
14
16
|
export const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
15
17
|
export const SKIP_BUFFER_RANGE_START = 0.05;
|
16
18
|
|
17
|
-
export default class GapController {
|
19
|
+
export default class GapController extends Logger {
|
18
20
|
private config: HlsConfig;
|
19
21
|
private media: HTMLMediaElement | null = null;
|
20
22
|
private fragmentTracker: FragmentTracker;
|
@@ -24,8 +26,15 @@ export default class GapController {
|
|
24
26
|
private stalled: number | null = null;
|
25
27
|
private moved: boolean = false;
|
26
28
|
private seeking: boolean = false;
|
29
|
+
private ended: number = 0;
|
27
30
|
|
28
|
-
constructor(
|
31
|
+
constructor(
|
32
|
+
config: HlsConfig,
|
33
|
+
media: HTMLMediaElement,
|
34
|
+
fragmentTracker: FragmentTracker,
|
35
|
+
hls: Hls,
|
36
|
+
) {
|
37
|
+
super('gap-controller', hls.logger);
|
29
38
|
this.config = config;
|
30
39
|
this.media = media;
|
31
40
|
this.fragmentTracker = fragmentTracker;
|
@@ -44,7 +53,12 @@ export default class GapController {
|
|
44
53
|
*
|
45
54
|
* @param lastCurrentTime - Previously read playhead position
|
46
55
|
*/
|
47
|
-
public poll(
|
56
|
+
public poll(
|
57
|
+
lastCurrentTime: number,
|
58
|
+
activeFrag: Fragment | null,
|
59
|
+
levelDetails: LevelDetails | undefined,
|
60
|
+
state: string,
|
61
|
+
) {
|
48
62
|
const { config, media, stalled } = this;
|
49
63
|
if (media === null) {
|
50
64
|
return;
|
@@ -57,6 +71,7 @@ export default class GapController {
|
|
57
71
|
|
58
72
|
// The playhead is moving, no-op
|
59
73
|
if (currentTime !== lastCurrentTime) {
|
74
|
+
this.ended = 0;
|
60
75
|
this.moved = true;
|
61
76
|
if (!seeking) {
|
62
77
|
this.nudgeRetry = 0;
|
@@ -65,7 +80,7 @@ export default class GapController {
|
|
65
80
|
// The playhead is now moving, but was previously stalled
|
66
81
|
if (this.stallReported) {
|
67
82
|
const stalledDuration = self.performance.now() - stalled;
|
68
|
-
|
83
|
+
this.warn(
|
69
84
|
`playback not stuck anymore @${currentTime}, after ${Math.round(
|
70
85
|
stalledDuration,
|
71
86
|
)}ms`,
|
@@ -128,12 +143,9 @@ export default class GapController {
|
|
128
143
|
// When joining a live stream with audio tracks, account for live playlist window sliding by allowing
|
129
144
|
// a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
|
130
145
|
// that begins over 1 target duration after the video start position.
|
131
|
-
const
|
132
|
-
? this.hls.levels[this.hls.currentLevel]
|
133
|
-
: null;
|
134
|
-
const isLive = level?.details?.live;
|
146
|
+
const isLive = !!levelDetails?.live;
|
135
147
|
const maxStartGapJump = isLive
|
136
|
-
?
|
148
|
+
? levelDetails!.targetduration * 2
|
137
149
|
: MAX_START_GAP_JUMP;
|
138
150
|
const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
|
139
151
|
if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
|
@@ -153,6 +165,21 @@ export default class GapController {
|
|
153
165
|
|
154
166
|
const stalledDuration = tnow - stalled;
|
155
167
|
if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
|
168
|
+
// Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
|
169
|
+
if (
|
170
|
+
state === State.ENDED &&
|
171
|
+
!(levelDetails && levelDetails.live) &&
|
172
|
+
Math.abs(currentTime - (levelDetails?.edge || 0)) < 1
|
173
|
+
) {
|
174
|
+
if (stalledDuration < 1000 || this.ended) {
|
175
|
+
return;
|
176
|
+
}
|
177
|
+
this.ended = currentTime;
|
178
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
179
|
+
stalled: true,
|
180
|
+
});
|
181
|
+
return;
|
182
|
+
}
|
156
183
|
// Report stalling after trying to fix
|
157
184
|
this._reportStall(bufferInfo);
|
158
185
|
if (!this.media) {
|
@@ -206,7 +233,7 @@ export default class GapController {
|
|
206
233
|
bufferInfo.nextStart - currentTime < config.maxBufferHole)) &&
|
207
234
|
stalledDurationMs > config.highBufferWatchdogPeriod * 1000
|
208
235
|
) {
|
209
|
-
|
236
|
+
this.warn('Trying to nudge playhead over buffer-hole');
|
210
237
|
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
211
238
|
// We only try to jump the hole if it's under the configured size
|
212
239
|
// Reset stalled so to rearm watchdog timer
|
@@ -230,7 +257,7 @@ export default class GapController {
|
|
230
257
|
media.currentTime
|
231
258
|
} due to low buffer (${JSON.stringify(bufferInfo)})`,
|
232
259
|
);
|
233
|
-
|
260
|
+
this.warn(error.message);
|
234
261
|
hls.trigger(Events.ERROR, {
|
235
262
|
type: ErrorTypes.MEDIA_ERROR,
|
236
263
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -305,7 +332,7 @@ export default class GapController {
|
|
305
332
|
startTime + SKIP_BUFFER_RANGE_START,
|
306
333
|
currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS,
|
307
334
|
);
|
308
|
-
|
335
|
+
this.warn(
|
309
336
|
`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`,
|
310
337
|
);
|
311
338
|
this.moved = true;
|
@@ -348,7 +375,7 @@ export default class GapController {
|
|
348
375
|
const error = new Error(
|
349
376
|
`Nudging 'currentTime' from ${currentTime} to ${targetTime}`,
|
350
377
|
);
|
351
|
-
|
378
|
+
this.warn(error.message);
|
352
379
|
media.currentTime = targetTime;
|
353
380
|
hls.trigger(Events.ERROR, {
|
354
381
|
type: ErrorTypes.MEDIA_ERROR,
|
@@ -360,7 +387,7 @@ export default class GapController {
|
|
360
387
|
const error = new Error(
|
361
388
|
`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`,
|
362
389
|
);
|
363
|
-
|
390
|
+
this.error(error.message);
|
364
391
|
hls.trigger(Events.ERROR, {
|
365
392
|
type: ErrorTypes.MEDIA_ERROR,
|
366
393
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -6,7 +6,6 @@ import type {
|
|
6
6
|
LevelUpdatedData,
|
7
7
|
MediaAttachingData,
|
8
8
|
} from '../types/events';
|
9
|
-
import { logger } from '../utils/logger';
|
10
9
|
import type { ComponentAPI } from '../types/component-api';
|
11
10
|
import type Hls from '../hls';
|
12
11
|
import type { HlsConfig } from '../config';
|
@@ -19,7 +18,6 @@ export default class LatencyController implements ComponentAPI {
|
|
19
18
|
private currentTime: number = 0;
|
20
19
|
private stallCount: number = 0;
|
21
20
|
private _latency: number | null = null;
|
22
|
-
private timeupdateHandler = () => this.timeupdate();
|
23
21
|
|
24
22
|
constructor(hls: Hls) {
|
25
23
|
this.hls = hls;
|
@@ -126,7 +124,7 @@ export default class LatencyController implements ComponentAPI {
|
|
126
124
|
this.onMediaDetaching();
|
127
125
|
this.levelDetails = null;
|
128
126
|
// @ts-ignore
|
129
|
-
this.hls =
|
127
|
+
this.hls = null;
|
130
128
|
}
|
131
129
|
|
132
130
|
private registerListeners() {
|
@@ -150,12 +148,12 @@ export default class LatencyController implements ComponentAPI {
|
|
150
148
|
data: MediaAttachingData,
|
151
149
|
) {
|
152
150
|
this.media = data.media;
|
153
|
-
this.media.addEventListener('timeupdate', this.
|
151
|
+
this.media.addEventListener('timeupdate', this.onTimeupdate);
|
154
152
|
}
|
155
153
|
|
156
154
|
private onMediaDetaching() {
|
157
155
|
if (this.media) {
|
158
|
-
this.media.removeEventListener('timeupdate', this.
|
156
|
+
this.media.removeEventListener('timeupdate', this.onTimeupdate);
|
159
157
|
this.media = null;
|
160
158
|
}
|
161
159
|
}
|
@@ -172,10 +170,10 @@ export default class LatencyController implements ComponentAPI {
|
|
172
170
|
) {
|
173
171
|
this.levelDetails = details;
|
174
172
|
if (details.advanced) {
|
175
|
-
this.
|
173
|
+
this.onTimeupdate();
|
176
174
|
}
|
177
175
|
if (!details.live && this.media) {
|
178
|
-
this.media.removeEventListener('timeupdate', this.
|
176
|
+
this.media.removeEventListener('timeupdate', this.onTimeupdate);
|
179
177
|
}
|
180
178
|
}
|
181
179
|
|
@@ -185,13 +183,13 @@ export default class LatencyController implements ComponentAPI {
|
|
185
183
|
}
|
186
184
|
this.stallCount++;
|
187
185
|
if (this.levelDetails?.live) {
|
188
|
-
logger.warn(
|
189
|
-
'[
|
186
|
+
this.hls.logger.warn(
|
187
|
+
'[latency-controller]: Stall detected, adjusting target latency',
|
190
188
|
);
|
191
189
|
}
|
192
190
|
}
|
193
191
|
|
194
|
-
private
|
192
|
+
private onTimeupdate = () => {
|
195
193
|
const { media, levelDetails } = this;
|
196
194
|
if (!media || !levelDetails) {
|
197
195
|
return;
|
@@ -242,7 +240,7 @@ export default class LatencyController implements ComponentAPI {
|
|
242
240
|
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
243
241
|
media.playbackRate = 1;
|
244
242
|
}
|
245
|
-
}
|
243
|
+
};
|
246
244
|
|
247
245
|
private estimateLiveEdge(): number | null {
|
248
246
|
const { levelDetails } = this;
|
@@ -27,8 +27,6 @@ import type Hls from '../hls';
|
|
27
27
|
import type { HlsUrlParameters, LevelParsed } from '../types/level';
|
28
28
|
import type { MediaPlaylist } from '../types/media-playlist';
|
29
29
|
|
30
|
-
let chromeOrFirefox: boolean;
|
31
|
-
|
32
30
|
export default class LevelController extends BasePlaylistController {
|
33
31
|
private _levels: Level[] = [];
|
34
32
|
private _firstLevel: number = -1;
|
@@ -45,7 +43,7 @@ export default class LevelController extends BasePlaylistController {
|
|
45
43
|
hls: Hls,
|
46
44
|
contentSteeringController: ContentSteeringController | null,
|
47
45
|
) {
|
48
|
-
super(hls, '
|
46
|
+
super(hls, 'level-controller');
|
49
47
|
this.steering = contentSteeringController;
|
50
48
|
this._registerListeners();
|
51
49
|
}
|
@@ -119,22 +117,12 @@ export default class LevelController extends BasePlaylistController {
|
|
119
117
|
|
120
118
|
data.levels.forEach((levelParsed: LevelParsed) => {
|
121
119
|
const attributes = levelParsed.attrs;
|
122
|
-
|
123
|
-
// erase audio codec info if browser does not support mp4a.40.34.
|
124
|
-
// demuxer will autodetect codec and fallback to mpeg/audio
|
125
120
|
let { audioCodec, videoCodec } = levelParsed;
|
126
|
-
if (audioCodec?.indexOf('mp4a.40.34') !== -1) {
|
127
|
-
chromeOrFirefox ||= /chrome|firefox/i.test(navigator.userAgent);
|
128
|
-
if (chromeOrFirefox) {
|
129
|
-
levelParsed.audioCodec = audioCodec = undefined;
|
130
|
-
}
|
131
|
-
}
|
132
|
-
|
133
121
|
if (audioCodec) {
|
134
|
-
|
135
|
-
|
136
|
-
preferManagedMediaSource
|
137
|
-
|
122
|
+
// Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
|
123
|
+
levelParsed.audioCodec = audioCodec =
|
124
|
+
getCodecCompatibleName(audioCodec, preferManagedMediaSource) ||
|
125
|
+
undefined;
|
138
126
|
}
|
139
127
|
|
140
128
|
if (videoCodec?.indexOf('avc1') === 0) {
|