hls.js 1.5.8-0.canary.10170 → 1.5.8
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 +3 -12
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +2366 -3626
- package/dist/hls.js.d.ts +84 -98
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1643 -2278
- 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 +1258 -1903
- 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 +1531 -2794
- 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 +30 -30
- 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 +9 -30
- package/src/controller/content-steering-controller.ts +6 -8
- 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 -13
- 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 +31 -25
- 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/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 +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 +1 -8
- package/src/exports-named.ts +1 -1
- package/src/hls.ts +37 -49
- 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 -3
- 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/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,13 +1,13 @@
|
|
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
12
|
import type { Fragment } from '../loader/fragment';
|
13
13
|
import type { BufferCreatedData, MediaAttachedData } from '../types/events';
|
@@ -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
|
|
@@ -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
|
*/
|
@@ -308,7 +287,7 @@ export default class CMCDController implements ComponentAPI {
|
|
308
287
|
* Get the buffer length for a media type in milliseconds
|
309
288
|
*/
|
310
289
|
private getBufferLength(type: CmcdObjectType) {
|
311
|
-
const media = this.media;
|
290
|
+
const media = this.hls.media;
|
312
291
|
const buffer =
|
313
292
|
type === CmcdObjectType.AUDIO ? this.audioBuffer : this.videoBuffer;
|
314
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,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,
|
@@ -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(
|
@@ -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;
|