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