hls.js 1.5.13 → 1.5.14-0.canary.10415
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 +4 -3
- package/dist/hls-demo.js +41 -38
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +4211 -2666
- package/dist/hls.js.d.ts +179 -110
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +2841 -1921
- 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 +2569 -1639
- 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 +3572 -2017
- 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 +38 -38
- package/src/config.ts +5 -2
- package/src/controller/abr-controller.ts +39 -25
- package/src/controller/audio-stream-controller.ts +156 -136
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +27 -10
- package/src/controller/base-stream-controller.ts +234 -89
- package/src/controller/buffer-controller.ts +250 -97
- package/src/controller/buffer-operation-queue.ts +16 -19
- package/src/controller/cap-level-controller.ts +3 -2
- package/src/controller/cmcd-controller.ts +51 -14
- package/src/controller/content-steering-controller.ts +29 -15
- package/src/controller/eme-controller.ts +10 -23
- package/src/controller/error-controller.ts +28 -22
- package/src/controller/fps-controller.ts +8 -3
- package/src/controller/fragment-finders.ts +44 -16
- package/src/controller/fragment-tracker.ts +58 -25
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/id3-track-controller.ts +45 -35
- package/src/controller/latency-controller.ts +18 -13
- package/src/controller/level-controller.ts +37 -19
- package/src/controller/stream-controller.ts +100 -83
- package/src/controller/subtitle-stream-controller.ts +35 -47
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +20 -22
- package/src/crypt/aes-crypto.ts +21 -2
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +32 -16
- package/src/crypt/fast-aes-key.ts +28 -5
- package/src/demux/audio/aacdemuxer.ts +2 -2
- package/src/demux/audio/ac3-demuxer.ts +4 -3
- package/src/demux/audio/adts.ts +9 -4
- package/src/demux/audio/base-audio-demuxer.ts +16 -14
- package/src/demux/audio/mp3demuxer.ts +4 -3
- package/src/demux/audio/mpegaudio.ts +1 -1
- package/src/demux/mp4demuxer.ts +7 -7
- package/src/demux/sample-aes.ts +2 -0
- package/src/demux/transmuxer-interface.ts +8 -16
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +16 -3
- package/src/demux/tsdemuxer.ts +75 -38
- package/src/demux/video/avc-video-parser.ts +210 -121
- package/src/demux/video/base-video-parser.ts +135 -2
- package/src/demux/video/exp-golomb.ts +0 -208
- package/src/demux/video/hevc-video-parser.ts +749 -0
- package/src/events.ts +8 -1
- package/src/exports-named.ts +1 -1
- package/src/hls.ts +84 -47
- package/src/loader/date-range.ts +71 -5
- package/src/loader/fragment-loader.ts +23 -21
- package/src/loader/fragment.ts +8 -4
- package/src/loader/key-loader.ts +3 -1
- package/src/loader/level-details.ts +6 -6
- package/src/loader/level-key.ts +10 -9
- package/src/loader/m3u8-parser.ts +138 -144
- package/src/loader/playlist-loader.ts +5 -7
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +32 -62
- package/src/remux/passthrough-remuxer.ts +1 -1
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +3 -1
- package/src/types/demuxer.ts +3 -0
- package/src/types/events.ts +19 -6
- package/src/types/fragment-tracker.ts +2 -2
- package/src/types/media-playlist.ts +9 -1
- package/src/types/remuxer.ts +1 -1
- package/src/utils/attr-list.ts +96 -9
- package/src/utils/buffer-helper.ts +12 -31
- package/src/utils/cea-608-parser.ts +1 -3
- package/src/utils/codecs.ts +34 -5
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/fetch-loader.ts +1 -1
- package/src/utils/hash.ts +10 -0
- package/src/utils/hdr.ts +4 -7
- package/src/utils/imsc1-ttml-parser.ts +1 -1
- package/src/utils/keysystem-util.ts +1 -6
- package/src/utils/level-helper.ts +71 -44
- package/src/utils/logger.ts +58 -23
- package/src/utils/mp4-tools.ts +5 -3
- package/src/utils/rendition-helper.ts +100 -74
- package/src/utils/utf8-utils.ts +18 -0
- package/src/utils/variable-substitution.ts +0 -19
- package/src/utils/webvtt-parser.ts +2 -12
- package/src/demux/id3.ts +0 -411
- package/src/types/general.ts +0 -6
@@ -1,15 +1,15 @@
|
|
1
1
|
import { Events } from '../events';
|
2
|
-
import Hls from '../hls';
|
2
|
+
import type Hls from '../hls';
|
3
3
|
import { Cmcd } from '@svta/common-media-library/cmcd/Cmcd';
|
4
4
|
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
|
-
import { logger } from '../utils/logger';
|
11
11
|
import type { ComponentAPI } from '../types/component-api';
|
12
|
-
import type { Fragment } from '../loader/fragment';
|
12
|
+
import type { Fragment, Part } from '../loader/fragment';
|
13
13
|
import type { BufferCreatedData, MediaAttachedData } from '../types/events';
|
14
14
|
import type {
|
15
15
|
FragmentLoaderContext,
|
@@ -81,7 +81,7 @@ export default class CMCDController implements ComponentAPI {
|
|
81
81
|
// @ts-ignore
|
82
82
|
this.hls = this.config = this.audioBuffer = this.videoBuffer = null;
|
83
83
|
// @ts-ignore
|
84
|
-
this.onWaiting = this.onPlaying = null;
|
84
|
+
this.onWaiting = this.onPlaying = this.media = null;
|
85
85
|
}
|
86
86
|
|
87
87
|
private onMediaAttached(
|
@@ -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,
|
168
|
+
// TODO: Implement rtp, nrr, dl
|
169
169
|
|
170
170
|
const { includeKeys } = this;
|
171
171
|
if (includeKeys) {
|
@@ -175,14 +175,18 @@ export default class CMCDController implements ComponentAPI {
|
|
175
175
|
}, {});
|
176
176
|
}
|
177
177
|
|
178
|
+
const options: CmcdEncodeOptions = {
|
179
|
+
baseUrl: context.url,
|
180
|
+
};
|
181
|
+
|
178
182
|
if (this.useHeaders) {
|
179
183
|
if (!context.headers) {
|
180
184
|
context.headers = {};
|
181
185
|
}
|
182
186
|
|
183
|
-
appendCmcdHeaders(context.headers, data);
|
187
|
+
appendCmcdHeaders(context.headers, data, options);
|
184
188
|
} else {
|
185
|
-
context.url = appendCmcdQuery(context.url, data);
|
189
|
+
context.url = appendCmcdQuery(context.url, data, options);
|
186
190
|
}
|
187
191
|
}
|
188
192
|
|
@@ -196,7 +200,7 @@ export default class CMCDController implements ComponentAPI {
|
|
196
200
|
su: !this.initialized,
|
197
201
|
});
|
198
202
|
} catch (error) {
|
199
|
-
logger.warn('Could not generate manifest CMCD data.', error);
|
203
|
+
this.hls.logger.warn('Could not generate manifest CMCD data.', error);
|
200
204
|
}
|
201
205
|
};
|
202
206
|
|
@@ -205,11 +209,11 @@ export default class CMCDController implements ComponentAPI {
|
|
205
209
|
*/
|
206
210
|
private applyFragmentData = (context: FragmentLoaderContext) => {
|
207
211
|
try {
|
208
|
-
const
|
209
|
-
const level = this.hls.levels[
|
210
|
-
const ot = this.getObjectType(
|
212
|
+
const { frag, part } = context;
|
213
|
+
const level = this.hls.levels[frag.level];
|
214
|
+
const ot = this.getObjectType(frag);
|
211
215
|
const data: Cmcd = {
|
212
|
-
d:
|
216
|
+
d: (part || frag).duration * 1000,
|
213
217
|
ot,
|
214
218
|
};
|
215
219
|
|
@@ -223,12 +227,45 @@ export default class CMCDController implements ComponentAPI {
|
|
223
227
|
data.bl = this.getBufferLength(ot);
|
224
228
|
}
|
225
229
|
|
230
|
+
const next = part ? this.getNextPart(part) : this.getNextFrag(frag);
|
231
|
+
|
232
|
+
if (next?.url && next.url !== frag.url) {
|
233
|
+
data.nor = next.url;
|
234
|
+
}
|
235
|
+
|
226
236
|
this.apply(context, data);
|
227
237
|
} catch (error) {
|
228
|
-
logger.warn('Could not generate segment CMCD data.', error);
|
238
|
+
this.hls.logger.warn('Could not generate segment CMCD data.', error);
|
229
239
|
}
|
230
240
|
};
|
231
241
|
|
242
|
+
private getNextFrag(fragment: Fragment): Fragment | undefined {
|
243
|
+
const levelDetails = this.hls.levels[fragment.level]?.details;
|
244
|
+
if (levelDetails) {
|
245
|
+
const index = (fragment.sn as number) - levelDetails.startSN;
|
246
|
+
return levelDetails.fragments[index + 1];
|
247
|
+
}
|
248
|
+
|
249
|
+
return undefined;
|
250
|
+
}
|
251
|
+
|
252
|
+
private getNextPart(part: Part): Part | undefined {
|
253
|
+
const { index, fragment } = part;
|
254
|
+
const partList = this.hls.levels[fragment.level]?.details?.partList;
|
255
|
+
|
256
|
+
if (partList) {
|
257
|
+
const { sn } = fragment;
|
258
|
+
for (let i = partList.length - 1; i >= 0; i--) {
|
259
|
+
const p = partList[i];
|
260
|
+
if (p.index === index && p.fragment.sn === sn) {
|
261
|
+
return partList[i + 1];
|
262
|
+
}
|
263
|
+
}
|
264
|
+
}
|
265
|
+
|
266
|
+
return undefined;
|
267
|
+
}
|
268
|
+
|
232
269
|
/**
|
233
270
|
* The CMCD object type.
|
234
271
|
*/
|
@@ -287,7 +324,7 @@ export default class CMCDController implements ComponentAPI {
|
|
287
324
|
* Get the buffer length for a media type in milliseconds
|
288
325
|
*/
|
289
326
|
private getBufferLength(type: CmcdObjectType) {
|
290
|
-
const media = this.
|
327
|
+
const media = this.media;
|
291
328
|
const buffer =
|
292
329
|
type === CmcdObjectType.AUDIO ? this.audioBuffer : this.videoBuffer;
|
293
330
|
|
@@ -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,13 +48,15 @@ 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 = '.';
|
57
|
-
private
|
59
|
+
private _pathwayPriority: string[] | null = null;
|
58
60
|
private timeToLoad: number = 300;
|
59
61
|
private reloadTimer: number = -1;
|
60
62
|
private updated: number = 0;
|
@@ -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
|
|
@@ -90,6 +92,23 @@ export default class ContentSteeringController implements NetworkComponentAPI {
|
|
90
92
|
hls.off(Events.ERROR, this.onError, this);
|
91
93
|
}
|
92
94
|
|
95
|
+
pathways() {
|
96
|
+
return (this.levels || []).reduce((pathways, level) => {
|
97
|
+
if (pathways.indexOf(level.pathwayId) === -1) {
|
98
|
+
pathways.push(level.pathwayId);
|
99
|
+
}
|
100
|
+
return pathways;
|
101
|
+
}, [] as string[]);
|
102
|
+
}
|
103
|
+
|
104
|
+
get pathwayPriority(): string[] | null {
|
105
|
+
return this._pathwayPriority;
|
106
|
+
}
|
107
|
+
|
108
|
+
set pathwayPriority(pathwayPriority: string[]) {
|
109
|
+
this.updatePathwayPriority(pathwayPriority);
|
110
|
+
}
|
111
|
+
|
93
112
|
startLoad() {
|
94
113
|
this.started = true;
|
95
114
|
this.clearTimeout();
|
@@ -176,7 +195,7 @@ export default class ContentSteeringController implements NetworkComponentAPI {
|
|
176
195
|
errorAction.flags === ErrorActionFlags.MoveAllAlternatesMatchingHost
|
177
196
|
) {
|
178
197
|
const levels = this.levels;
|
179
|
-
let pathwayPriority = this.
|
198
|
+
let pathwayPriority = this._pathwayPriority;
|
180
199
|
let errorPathway = this.pathwayId;
|
181
200
|
if (data.context) {
|
182
201
|
const { groupId, pathwayId, type } = data.context;
|
@@ -191,19 +210,14 @@ export default class ContentSteeringController implements NetworkComponentAPI {
|
|
191
210
|
}
|
192
211
|
if (!pathwayPriority && levels) {
|
193
212
|
// If PATHWAY-PRIORITY was not provided, list pathways for error handling
|
194
|
-
pathwayPriority =
|
195
|
-
if (pathways.indexOf(level.pathwayId) === -1) {
|
196
|
-
pathways.push(level.pathwayId);
|
197
|
-
}
|
198
|
-
return pathways;
|
199
|
-
}, [] as string[]);
|
213
|
+
pathwayPriority = this.pathways();
|
200
214
|
}
|
201
215
|
if (pathwayPriority && pathwayPriority.length > 1) {
|
202
216
|
this.updatePathwayPriority(pathwayPriority);
|
203
217
|
errorAction.resolved = this.pathwayId !== errorPathway;
|
204
218
|
}
|
205
219
|
if (!errorAction.resolved) {
|
206
|
-
|
220
|
+
this.warn(
|
207
221
|
`Could not resolve ${data.details} ("${
|
208
222
|
data.error.message
|
209
223
|
}") with content-steering for Pathway: ${errorPathway} levels: ${
|
@@ -245,7 +259,7 @@ export default class ContentSteeringController implements NetworkComponentAPI {
|
|
245
259
|
}
|
246
260
|
|
247
261
|
private updatePathwayPriority(pathwayPriority: string[]) {
|
248
|
-
this.
|
262
|
+
this._pathwayPriority = pathwayPriority;
|
249
263
|
let levels: Level[] | undefined;
|
250
264
|
|
251
265
|
// Evaluate if we should remove the pathway from the penalized list
|
@@ -442,7 +456,7 @@ export default class ContentSteeringController implements NetworkComponentAPI {
|
|
442
456
|
) => {
|
443
457
|
this.log(`Loaded steering manifest: "${url}"`);
|
444
458
|
const steeringData = response.data as SteeringManifest;
|
445
|
-
if (steeringData
|
459
|
+
if (steeringData?.VERSION !== 1) {
|
446
460
|
this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
|
447
461
|
return;
|
448
462
|
}
|
@@ -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,
|
@@ -19,7 +19,7 @@ import {
|
|
19
19
|
KeySystems,
|
20
20
|
requestMediaKeySystemAccess,
|
21
21
|
} from '../utils/mediakeys-helper';
|
22
|
-
import { strToUtf8array } from '../utils/
|
22
|
+
import { strToUtf8array } from '../utils/utf8-utils';
|
23
23
|
import { base64Decode } from '../utils/numeric-encoding-utils';
|
24
24
|
import { DecryptData, LevelKey } from '../loader/level-key';
|
25
25
|
import Hex from '../utils/hex';
|
@@ -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,12 +8,12 @@ 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';
|
15
15
|
import type { ErrorData } from '../types/events';
|
16
|
-
import type { Fragment } from '../loader/fragment';
|
16
|
+
import type { Fragment, MediaFragment } from '../loader/fragment';
|
17
17
|
import type { LevelDetails } from '../loader/level-details';
|
18
18
|
|
19
19
|
export const enum NetworkErrorAction {
|
@@ -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
|
|
@@ -129,10 +127,7 @@ export default class ErrorController implements NetworkComponentAPI {
|
|
129
127
|
case ErrorDetails.FRAG_PARSING_ERROR:
|
130
128
|
// ignore empty segment errors marked as gap
|
131
129
|
if (data.frag?.gap) {
|
132
|
-
data.errorAction =
|
133
|
-
action: NetworkErrorAction.DoNothing,
|
134
|
-
flags: ErrorActionFlags.None,
|
135
|
-
};
|
130
|
+
data.errorAction = createDoNothingErrorAction();
|
136
131
|
return;
|
137
132
|
}
|
138
133
|
// falls through
|
@@ -220,10 +215,13 @@ export default class ErrorController implements NetworkComponentAPI {
|
|
220
215
|
case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
|
221
216
|
case ErrorDetails.REMUX_ALLOC_ERROR:
|
222
217
|
case ErrorDetails.BUFFER_APPEND_ERROR:
|
223
|
-
|
224
|
-
|
225
|
-
data.
|
226
|
-
|
218
|
+
// Buffer-controller can set errorAction when append errors can be ignored or resolved locally
|
219
|
+
if (!data.errorAction) {
|
220
|
+
data.errorAction = this.getLevelSwitchAction(
|
221
|
+
data,
|
222
|
+
data.level ?? hls.loadLevel,
|
223
|
+
);
|
224
|
+
}
|
227
225
|
return;
|
228
226
|
case ErrorDetails.INTERNAL_EXCEPTION:
|
229
227
|
case ErrorDetails.BUFFER_APPENDING_ERROR:
|
@@ -232,10 +230,7 @@ export default class ErrorController implements NetworkComponentAPI {
|
|
232
230
|
case ErrorDetails.BUFFER_STALLED_ERROR:
|
233
231
|
case ErrorDetails.BUFFER_SEEK_OVER_HOLE:
|
234
232
|
case ErrorDetails.BUFFER_NUDGE_ON_STALL:
|
235
|
-
data.errorAction =
|
236
|
-
action: NetworkErrorAction.DoNothing,
|
237
|
-
flags: ErrorActionFlags.None,
|
238
|
-
};
|
233
|
+
data.errorAction = createDoNothingErrorAction();
|
239
234
|
return;
|
240
235
|
}
|
241
236
|
|
@@ -389,7 +384,7 @@ export default class ErrorController implements NetworkComponentAPI {
|
|
389
384
|
const levelDetails = levels[candidate].details;
|
390
385
|
if (levelDetails) {
|
391
386
|
const fragCandidate = findFragmentByPTS(
|
392
|
-
data.frag,
|
387
|
+
data.frag as MediaFragment,
|
393
388
|
levelDetails.fragments,
|
394
389
|
data.frag.start,
|
395
390
|
);
|
@@ -513,3 +508,14 @@ export default class ErrorController implements NetworkComponentAPI {
|
|
513
508
|
}
|
514
509
|
}
|
515
510
|
}
|
511
|
+
|
512
|
+
export function createDoNothingErrorAction(resolved?: boolean): IErrorAction {
|
513
|
+
const errorAction: IErrorAction = {
|
514
|
+
action: NetworkErrorAction.DoNothing,
|
515
|
+
flags: ErrorActionFlags.None,
|
516
|
+
};
|
517
|
+
if (resolved) {
|
518
|
+
errorAction.resolved = true;
|
519
|
+
}
|
520
|
+
return errorAction;
|
521
|
+
}
|
@@ -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';
|
@@ -28,10 +27,12 @@ class FPSController implements ComponentAPI {
|
|
28
27
|
|
29
28
|
protected registerListeners() {
|
30
29
|
this.hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
|
30
|
+
this.hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
31
31
|
}
|
32
32
|
|
33
33
|
protected unregisterListeners() {
|
34
34
|
this.hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
|
35
|
+
this.hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
35
36
|
}
|
36
37
|
|
37
38
|
destroy() {
|
@@ -65,6 +66,10 @@ class FPSController implements ComponentAPI {
|
|
65
66
|
}
|
66
67
|
}
|
67
68
|
|
69
|
+
private onMediaDetaching() {
|
70
|
+
this.media = null;
|
71
|
+
}
|
72
|
+
|
68
73
|
checkFPS(
|
69
74
|
video: HTMLVideoElement,
|
70
75
|
decodedFrames: number,
|
@@ -84,13 +89,13 @@ class FPSController implements ComponentAPI {
|
|
84
89
|
totalDroppedFrames: droppedFrames,
|
85
90
|
});
|
86
91
|
if (droppedFPS > 0) {
|
87
|
-
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
92
|
+
// hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
88
93
|
if (
|
89
94
|
currentDropped >
|
90
95
|
hls.config.fpsDroppedMonitoringThreshold * currentDecoded
|
91
96
|
) {
|
92
97
|
let currentLevel = hls.currentLevel;
|
93
|
-
logger.warn(
|
98
|
+
hls.logger.warn(
|
94
99
|
'drop FPS ratio greater than max allowed value for currentLevel: ' +
|
95
100
|
currentLevel,
|
96
101
|
);
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import BinarySearch from '../utils/binary-search';
|
2
|
-
import { Fragment } from '../loader/fragment';
|
2
|
+
import type { Fragment, MediaFragment } from '../loader/fragment';
|
3
|
+
import type { LevelDetails } from '../loader/level-details';
|
3
4
|
|
4
5
|
/**
|
5
6
|
* Returns first fragment whose endPdt value exceeds the given PDT, or null.
|
@@ -8,10 +9,10 @@ import { Fragment } from '../loader/fragment';
|
|
8
9
|
* @param maxFragLookUpTolerance - The amount of time that a fragment's start/end can be within in order to be considered contiguous
|
9
10
|
*/
|
10
11
|
export function findFragmentByPDT(
|
11
|
-
fragments:
|
12
|
+
fragments: MediaFragment[],
|
12
13
|
PDTValue: number | null,
|
13
14
|
maxFragLookUpTolerance: number,
|
14
|
-
):
|
15
|
+
): MediaFragment | null {
|
15
16
|
if (
|
16
17
|
PDTValue === null ||
|
17
18
|
!Array.isArray(fragments) ||
|
@@ -54,23 +55,27 @@ export function findFragmentByPDT(
|
|
54
55
|
* @returns a matching fragment or null
|
55
56
|
*/
|
56
57
|
export function findFragmentByPTS(
|
57
|
-
fragPrevious:
|
58
|
-
fragments:
|
58
|
+
fragPrevious: MediaFragment | null,
|
59
|
+
fragments: MediaFragment[],
|
59
60
|
bufferEnd: number = 0,
|
60
61
|
maxFragLookUpTolerance: number = 0,
|
61
62
|
nextFragLookupTolerance: number = 0.005,
|
62
|
-
):
|
63
|
-
let fragNext:
|
63
|
+
): MediaFragment | null {
|
64
|
+
let fragNext: MediaFragment | null = null;
|
64
65
|
if (fragPrevious) {
|
65
|
-
fragNext =
|
66
|
-
fragments[
|
67
|
-
(fragPrevious.sn as number) - (fragments[0].sn as number) + 1
|
68
|
-
] || null;
|
66
|
+
fragNext = fragments[1 + fragPrevious.sn - fragments[0].sn] || null;
|
69
67
|
// check for buffer-end rounding error
|
70
|
-
const bufferEdgeError = fragPrevious.endDTS - bufferEnd;
|
68
|
+
const bufferEdgeError = (fragPrevious.endDTS as number) - bufferEnd;
|
71
69
|
if (bufferEdgeError > 0 && bufferEdgeError < 0.0000015) {
|
72
70
|
bufferEnd += 0.0000015;
|
73
71
|
}
|
72
|
+
if (
|
73
|
+
fragNext &&
|
74
|
+
fragPrevious.level !== fragNext.level &&
|
75
|
+
fragNext.end <= fragPrevious.end
|
76
|
+
) {
|
77
|
+
fragNext = fragments[2 + fragPrevious.sn - fragments[0].sn] || null;
|
78
|
+
}
|
74
79
|
} else if (bufferEnd === 0 && fragments[0].start === 0) {
|
75
80
|
fragNext = fragments[0];
|
76
81
|
}
|
@@ -135,7 +140,7 @@ function fragmentWithinFastStartSwitch(
|
|
135
140
|
export function fragmentWithinToleranceTest(
|
136
141
|
bufferEnd = 0,
|
137
142
|
maxFragLookUpTolerance = 0,
|
138
|
-
candidate:
|
143
|
+
candidate: MediaFragment,
|
139
144
|
) {
|
140
145
|
// eagerly accept an accurate match (no tolerance)
|
141
146
|
if (
|
@@ -189,7 +194,7 @@ export function fragmentWithinToleranceTest(
|
|
189
194
|
export function pdtWithinToleranceTest(
|
190
195
|
pdtBufferEnd: number,
|
191
196
|
maxFragLookUpTolerance: number,
|
192
|
-
candidate:
|
197
|
+
candidate: MediaFragment,
|
193
198
|
): boolean {
|
194
199
|
const candidateLookupTolerance =
|
195
200
|
Math.min(
|
@@ -203,9 +208,9 @@ export function pdtWithinToleranceTest(
|
|
203
208
|
}
|
204
209
|
|
205
210
|
export function findFragWithCC(
|
206
|
-
fragments:
|
211
|
+
fragments: MediaFragment[],
|
207
212
|
cc: number,
|
208
|
-
):
|
213
|
+
): MediaFragment | null {
|
209
214
|
return BinarySearch.search(fragments, (candidate) => {
|
210
215
|
if (candidate.cc < cc) {
|
211
216
|
return 1;
|
@@ -216,3 +221,26 @@ export function findFragWithCC(
|
|
216
221
|
}
|
217
222
|
});
|
218
223
|
}
|
224
|
+
|
225
|
+
export function findNearestWithCC(
|
226
|
+
details: LevelDetails | undefined,
|
227
|
+
cc: number,
|
228
|
+
fragment: MediaFragment,
|
229
|
+
): MediaFragment | null {
|
230
|
+
if (details) {
|
231
|
+
if (details.startCC <= cc && details.endCC >= cc) {
|
232
|
+
const start = fragment.start;
|
233
|
+
const end = fragment.end;
|
234
|
+
return BinarySearch.search(details.fragments, (candidate) => {
|
235
|
+
if (candidate.cc < cc || candidate.end <= start) {
|
236
|
+
return 1;
|
237
|
+
} else if (candidate.cc > cc || candidate.start >= end) {
|
238
|
+
return -1;
|
239
|
+
} else {
|
240
|
+
return 0;
|
241
|
+
}
|
242
|
+
});
|
243
|
+
}
|
244
|
+
}
|
245
|
+
return null;
|
246
|
+
}
|