hls.js 1.5.10-0.canary.10328 → 1.5.10
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 +3 -4
- package/dist/hls-demo.js +38 -41
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +2194 -3476
- package/dist/hls.js.d.ts +85 -108
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +3137 -3784
- 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 +1256 -1928
- 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 +4182 -5487
- 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 +36 -36
- 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 +10 -27
- package/src/controller/base-stream-controller.ts +38 -160
- package/src/controller/buffer-controller.ts +92 -230
- package/src/controller/buffer-operation-queue.ts +19 -16
- package/src/controller/cap-level-controller.ts +2 -3
- package/src/controller/cmcd-controller.ts +14 -51
- package/src/controller/content-steering-controller.ts +15 -29
- package/src/controller/eme-controller.ts +23 -10
- package/src/controller/error-controller.ts +8 -6
- package/src/controller/fps-controller.ts +3 -8
- package/src/controller/fragment-tracker.ts +11 -15
- package/src/controller/gap-controller.ts +16 -43
- package/src/controller/id3-track-controller.ts +7 -7
- package/src/controller/latency-controller.ts +11 -9
- package/src/controller/level-controller.ts +19 -37
- package/src/controller/stream-controller.ts +32 -37
- package/src/controller/subtitle-stream-controller.ts +40 -28
- package/src/controller/subtitle-track-controller.ts +3 -5
- package/src/controller/timeline-controller.ts +21 -19
- package/src/crypt/aes-crypto.ts +2 -21
- package/src/crypt/decrypter.ts +16 -32
- package/src/crypt/fast-aes-key.ts +5 -28
- package/src/demux/audio/aacdemuxer.ts +2 -2
- package/src/demux/audio/ac3-demuxer.ts +3 -4
- package/src/demux/audio/adts.ts +4 -9
- package/src/demux/audio/base-audio-demuxer.ts +14 -16
- package/src/demux/audio/mp3demuxer.ts +3 -4
- package/src/demux/audio/mpegaudio.ts +1 -1
- package/src/demux/id3.ts +411 -0
- package/src/demux/mp4demuxer.ts +7 -7
- 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 +38 -75
- package/src/demux/video/avc-video-parser.ts +119 -208
- package/src/demux/video/base-video-parser.ts +18 -147
- package/src/demux/video/exp-golomb.ts +208 -0
- package/src/events.ts +1 -8
- package/src/exports-named.ts +1 -1
- package/src/hls.ts +38 -61
- package/src/loader/fragment-loader.ts +3 -10
- package/src/loader/key-loader.ts +1 -3
- 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 +8 -24
- package/src/task-loop.ts +2 -5
- package/src/types/component-api.ts +1 -3
- package/src/types/demuxer.ts +0 -4
- package/src/types/events.ts +0 -4
- package/src/types/remuxer.ts +1 -1
- package/src/utils/buffer-helper.ts +31 -12
- package/src/utils/cea-608-parser.ts +3 -1
- package/src/utils/codecs.ts +5 -34
- package/src/utils/fetch-loader.ts +1 -1
- package/src/utils/imsc1-ttml-parser.ts +1 -1
- package/src/utils/keysystem-util.ts +6 -1
- package/src/utils/logger.ts +23 -58
- package/src/utils/mp4-tools.ts +3 -5
- package/src/utils/webvtt-parser.ts +1 -1
- package/src/crypt/decrypter-aes-mode.ts +0 -4
- package/src/demux/video/hevc-video-parser.ts +0 -749
- package/src/utils/encryption-methods-util.ts +0 -21
- package/src/utils/utf8-utils.ts +0 -18
@@ -1,15 +1,15 @@
|
|
1
1
|
import { Events } from '../events';
|
2
|
-
import
|
2
|
+
import 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';
|
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
|
-
import type { Fragment
|
12
|
+
import type { Fragment } 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 =
|
84
|
+
this.onWaiting = this.onPlaying = 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, 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
|
|
@@ -209,11 +205,11 @@ export default class CMCDController implements ComponentAPI {
|
|
209
205
|
*/
|
210
206
|
private applyFragmentData = (context: FragmentLoaderContext) => {
|
211
207
|
try {
|
212
|
-
const
|
213
|
-
const level = this.hls.levels[
|
214
|
-
const ot = this.getObjectType(
|
208
|
+
const fragment = context.frag;
|
209
|
+
const level = this.hls.levels[fragment.level];
|
210
|
+
const ot = this.getObjectType(fragment);
|
215
211
|
const data: Cmcd = {
|
216
|
-
d:
|
212
|
+
d: fragment.duration * 1000,
|
217
213
|
ot,
|
218
214
|
};
|
219
215
|
|
@@ -227,45 +223,12 @@ export default class CMCDController implements ComponentAPI {
|
|
227
223
|
data.bl = this.getBufferLength(ot);
|
228
224
|
}
|
229
225
|
|
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
|
-
|
236
226
|
this.apply(context, data);
|
237
227
|
} catch (error) {
|
238
|
-
|
228
|
+
logger.warn('Could not generate segment CMCD data.', error);
|
239
229
|
}
|
240
230
|
};
|
241
231
|
|
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
|
-
|
269
232
|
/**
|
270
233
|
* The CMCD object type.
|
271
234
|
*/
|
@@ -324,7 +287,7 @@ export default class CMCDController implements ComponentAPI {
|
|
324
287
|
* Get the buffer length for a media type in milliseconds
|
325
288
|
*/
|
326
289
|
private getBufferLength(type: CmcdObjectType) {
|
327
|
-
const media = this.media;
|
290
|
+
const media = this.hls.media;
|
328
291
|
const buffer =
|
329
292
|
type === CmcdObjectType.AUDIO ? this.audioBuffer : this.videoBuffer;
|
330
293
|
|
@@ -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,15 +48,13 @@ 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 = '.';
|
59
|
-
private
|
57
|
+
private pathwayPriority: string[] | null = null;
|
60
58
|
private timeToLoad: number = 300;
|
61
59
|
private reloadTimer: number = -1;
|
62
60
|
private updated: number = 0;
|
@@ -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
|
|
@@ -92,23 +90,6 @@ export default class ContentSteeringController
|
|
92
90
|
hls.off(Events.ERROR, this.onError, this);
|
93
91
|
}
|
94
92
|
|
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
|
-
|
112
93
|
startLoad() {
|
113
94
|
this.started = true;
|
114
95
|
this.clearTimeout();
|
@@ -195,7 +176,7 @@ export default class ContentSteeringController
|
|
195
176
|
errorAction.flags === ErrorActionFlags.MoveAllAlternatesMatchingHost
|
196
177
|
) {
|
197
178
|
const levels = this.levels;
|
198
|
-
let pathwayPriority = this.
|
179
|
+
let pathwayPriority = this.pathwayPriority;
|
199
180
|
let errorPathway = this.pathwayId;
|
200
181
|
if (data.context) {
|
201
182
|
const { groupId, pathwayId, type } = data.context;
|
@@ -210,14 +191,19 @@ export default class ContentSteeringController
|
|
210
191
|
}
|
211
192
|
if (!pathwayPriority && levels) {
|
212
193
|
// If PATHWAY-PRIORITY was not provided, list pathways for error handling
|
213
|
-
pathwayPriority =
|
194
|
+
pathwayPriority = levels.reduce((pathways, level) => {
|
195
|
+
if (pathways.indexOf(level.pathwayId) === -1) {
|
196
|
+
pathways.push(level.pathwayId);
|
197
|
+
}
|
198
|
+
return pathways;
|
199
|
+
}, [] as string[]);
|
214
200
|
}
|
215
201
|
if (pathwayPriority && pathwayPriority.length > 1) {
|
216
202
|
this.updatePathwayPriority(pathwayPriority);
|
217
203
|
errorAction.resolved = this.pathwayId !== errorPathway;
|
218
204
|
}
|
219
205
|
if (!errorAction.resolved) {
|
220
|
-
|
206
|
+
logger.warn(
|
221
207
|
`Could not resolve ${data.details} ("${
|
222
208
|
data.error.message
|
223
209
|
}") with content-steering for Pathway: ${errorPathway} levels: ${
|
@@ -259,7 +245,7 @@ export default class ContentSteeringController
|
|
259
245
|
}
|
260
246
|
|
261
247
|
private updatePathwayPriority(pathwayPriority: string[]) {
|
262
|
-
this.
|
248
|
+
this.pathwayPriority = pathwayPriority;
|
263
249
|
let levels: Level[] | undefined;
|
264
250
|
|
265
251
|
// Evaluate if we should remove the pathway from the penalized list
|
@@ -456,7 +442,7 @@ export default class ContentSteeringController
|
|
456
442
|
) => {
|
457
443
|
this.log(`Loaded steering manifest: "${url}"`);
|
458
444
|
const steeringData = response.data as SteeringManifest;
|
459
|
-
if (steeringData
|
445
|
+
if (steeringData.VERSION !== 1) {
|
460
446
|
this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
|
461
447
|
return;
|
462
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,
|
@@ -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/keysystem-util';
|
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,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';
|
@@ -27,12 +28,10 @@ class FPSController implements ComponentAPI {
|
|
27
28
|
|
28
29
|
protected registerListeners() {
|
29
30
|
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);
|
36
35
|
}
|
37
36
|
|
38
37
|
destroy() {
|
@@ -66,10 +65,6 @@ class FPSController implements ComponentAPI {
|
|
66
65
|
}
|
67
66
|
}
|
68
67
|
|
69
|
-
private onMediaDetaching() {
|
70
|
-
this.media = null;
|
71
|
-
}
|
72
|
-
|
73
68
|
checkFPS(
|
74
69
|
video: HTMLVideoElement,
|
75
70
|
decodedFrames: number,
|
@@ -89,13 +84,13 @@ class FPSController implements ComponentAPI {
|
|
89
84
|
totalDroppedFrames: droppedFrames,
|
90
85
|
});
|
91
86
|
if (droppedFPS > 0) {
|
92
|
-
//
|
87
|
+
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
93
88
|
if (
|
94
89
|
currentDropped >
|
95
90
|
hls.config.fpsDroppedMonitoringThreshold * currentDecoded
|
96
91
|
) {
|
97
92
|
let currentLevel = hls.currentLevel;
|
98
|
-
|
93
|
+
logger.warn(
|
99
94
|
'drop FPS ratio greater than max allowed value for currentLevel: ' +
|
100
95
|
currentLevel,
|
101
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?.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,
|
@@ -4,6 +4,7 @@ import {
|
|
4
4
|
clearCurrentCues,
|
5
5
|
removeCuesInRange,
|
6
6
|
} from '../utils/texttrack-utils';
|
7
|
+
import * as ID3 from '../demux/id3';
|
7
8
|
import {
|
8
9
|
DateRange,
|
9
10
|
isDateRangeCueAttribute,
|
@@ -18,8 +19,6 @@ import type {
|
|
18
19
|
} from '../types/events';
|
19
20
|
import type { ComponentAPI } from '../types/component-api';
|
20
21
|
import type Hls from '../hls';
|
21
|
-
import { getId3Frames } from '@svta/common-media-library/id3/getId3Frames';
|
22
|
-
import { isId3TimestampFrame } from '@svta/common-media-library/id3/isId3TimestampFrame';
|
23
22
|
|
24
23
|
declare global {
|
25
24
|
interface Window {
|
@@ -138,10 +137,11 @@ class ID3TrackController implements ComponentAPI {
|
|
138
137
|
}
|
139
138
|
|
140
139
|
protected onMediaDetaching(): void {
|
141
|
-
if (this.id3Track) {
|
142
|
-
|
143
|
-
this.id3Track = null;
|
140
|
+
if (!this.id3Track) {
|
141
|
+
return;
|
144
142
|
}
|
143
|
+
clearCurrentCues(this.id3Track);
|
144
|
+
this.id3Track = null;
|
145
145
|
this.media = null;
|
146
146
|
this.dateRangeCuesAppended = {};
|
147
147
|
}
|
@@ -211,7 +211,7 @@ class ID3TrackController implements ComponentAPI {
|
|
211
211
|
continue;
|
212
212
|
}
|
213
213
|
|
214
|
-
const frames =
|
214
|
+
const frames = ID3.getID3Frames(samples[i].data);
|
215
215
|
if (frames) {
|
216
216
|
const startTime = samples[i].pts;
|
217
217
|
let endTime: number = startTime + samples[i].duration;
|
@@ -228,7 +228,7 @@ class ID3TrackController implements ComponentAPI {
|
|
228
228
|
for (let j = 0; j < frames.length; j++) {
|
229
229
|
const frame = frames[j];
|
230
230
|
// Safari doesn't put the timestamp frame in the TextTrack
|
231
|
-
if (!
|
231
|
+
if (!ID3.isTimeStampFrame(frame)) {
|
232
232
|
// add a bounds to any unbounded cues
|
233
233
|
this.updateId3CueEnds(startTime, type);
|
234
234
|
const cue = createCueWithDataFields(
|