@zenvor/hls.js 1.0.0
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/LICENSE +28 -0
- package/README.md +472 -0
- package/dist/hls-demo.js +26995 -0
- package/dist/hls-demo.js.map +1 -0
- package/dist/hls.d.mts +4204 -0
- package/dist/hls.d.ts +4204 -0
- package/dist/hls.js +40050 -0
- package/dist/hls.js.d.ts +4204 -0
- package/dist/hls.js.map +1 -0
- package/dist/hls.light.js +27145 -0
- package/dist/hls.light.js.map +1 -0
- package/dist/hls.light.min.js +2 -0
- package/dist/hls.light.min.js.map +1 -0
- package/dist/hls.light.mjs +26392 -0
- package/dist/hls.light.mjs.map +1 -0
- package/dist/hls.min.js +2 -0
- package/dist/hls.min.js.map +1 -0
- package/dist/hls.mjs +38956 -0
- package/dist/hls.mjs.map +1 -0
- package/dist/hls.worker.js +2 -0
- package/dist/hls.worker.js.map +1 -0
- package/package.json +143 -0
- package/src/config.ts +794 -0
- package/src/controller/abr-controller.ts +1019 -0
- package/src/controller/algo-data-controller.ts +794 -0
- package/src/controller/audio-stream-controller.ts +1099 -0
- package/src/controller/audio-track-controller.ts +454 -0
- package/src/controller/base-playlist-controller.ts +438 -0
- package/src/controller/base-stream-controller.ts +2526 -0
- package/src/controller/buffer-controller.ts +2015 -0
- package/src/controller/buffer-operation-queue.ts +159 -0
- package/src/controller/cap-level-controller.ts +367 -0
- package/src/controller/cmcd-controller.ts +422 -0
- package/src/controller/content-steering-controller.ts +622 -0
- package/src/controller/eme-controller.ts +1617 -0
- package/src/controller/error-controller.ts +627 -0
- package/src/controller/fps-controller.ts +146 -0
- package/src/controller/fragment-finders.ts +256 -0
- package/src/controller/fragment-tracker.ts +567 -0
- package/src/controller/gap-controller.ts +719 -0
- package/src/controller/id3-track-controller.ts +488 -0
- package/src/controller/interstitial-player.ts +302 -0
- package/src/controller/interstitials-controller.ts +2895 -0
- package/src/controller/interstitials-schedule.ts +698 -0
- package/src/controller/latency-controller.ts +294 -0
- package/src/controller/level-controller.ts +776 -0
- package/src/controller/stream-controller.ts +1597 -0
- package/src/controller/subtitle-stream-controller.ts +508 -0
- package/src/controller/subtitle-track-controller.ts +617 -0
- package/src/controller/timeline-controller.ts +677 -0
- package/src/crypt/aes-crypto.ts +36 -0
- package/src/crypt/aes-decryptor.ts +339 -0
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +225 -0
- package/src/crypt/fast-aes-key.ts +39 -0
- package/src/define-plugin.d.ts +17 -0
- package/src/demux/audio/aacdemuxer.ts +126 -0
- package/src/demux/audio/ac3-demuxer.ts +170 -0
- package/src/demux/audio/adts.ts +249 -0
- package/src/demux/audio/base-audio-demuxer.ts +205 -0
- package/src/demux/audio/dolby.ts +21 -0
- package/src/demux/audio/mp3demuxer.ts +85 -0
- package/src/demux/audio/mpegaudio.ts +177 -0
- package/src/demux/chunk-cache.ts +42 -0
- package/src/demux/dummy-demuxed-track.ts +13 -0
- package/src/demux/inject-worker.ts +75 -0
- package/src/demux/mp4demuxer.ts +234 -0
- package/src/demux/sample-aes.ts +198 -0
- package/src/demux/transmuxer-interface.ts +449 -0
- package/src/demux/transmuxer-worker.ts +221 -0
- package/src/demux/transmuxer.ts +560 -0
- package/src/demux/tsdemuxer.ts +1256 -0
- package/src/demux/video/avc-video-parser.ts +401 -0
- package/src/demux/video/base-video-parser.ts +198 -0
- package/src/demux/video/exp-golomb.ts +153 -0
- package/src/demux/video/hevc-video-parser.ts +736 -0
- package/src/empty-es.js +5 -0
- package/src/empty.js +3 -0
- package/src/errors.ts +107 -0
- package/src/events.ts +548 -0
- package/src/exports-default.ts +3 -0
- package/src/exports-named.ts +81 -0
- package/src/hls.ts +1613 -0
- package/src/is-supported.ts +54 -0
- package/src/loader/date-range.ts +207 -0
- package/src/loader/fragment-loader.ts +403 -0
- package/src/loader/fragment.ts +487 -0
- package/src/loader/interstitial-asset-list.ts +162 -0
- package/src/loader/interstitial-event.ts +337 -0
- package/src/loader/key-loader.ts +439 -0
- package/src/loader/level-details.ts +203 -0
- package/src/loader/level-key.ts +259 -0
- package/src/loader/load-stats.ts +17 -0
- package/src/loader/m3u8-parser.ts +1072 -0
- package/src/loader/playlist-loader.ts +839 -0
- package/src/polyfills/number.ts +15 -0
- package/src/remux/aac-helper.ts +81 -0
- package/src/remux/mp4-generator.ts +1380 -0
- package/src/remux/mp4-remuxer.ts +1261 -0
- package/src/remux/passthrough-remuxer.ts +434 -0
- package/src/task-loop.ts +130 -0
- package/src/types/algo.ts +44 -0
- package/src/types/buffer.ts +105 -0
- package/src/types/component-api.ts +20 -0
- package/src/types/demuxer.ts +208 -0
- package/src/types/events.ts +574 -0
- package/src/types/fragment-tracker.ts +23 -0
- package/src/types/level.ts +268 -0
- package/src/types/loader.ts +198 -0
- package/src/types/media-playlist.ts +92 -0
- package/src/types/network-details.ts +3 -0
- package/src/types/remuxer.ts +104 -0
- package/src/types/track.ts +12 -0
- package/src/types/transmuxer.ts +46 -0
- package/src/types/tuples.ts +6 -0
- package/src/types/vtt.ts +11 -0
- package/src/utils/arrays.ts +22 -0
- package/src/utils/attr-list.ts +192 -0
- package/src/utils/binary-search.ts +46 -0
- package/src/utils/buffer-helper.ts +173 -0
- package/src/utils/cea-608-parser.ts +1413 -0
- package/src/utils/chunker.ts +41 -0
- package/src/utils/codecs.ts +314 -0
- package/src/utils/cues.ts +96 -0
- package/src/utils/discontinuities.ts +174 -0
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/error-helper.ts +95 -0
- package/src/utils/event-listener-helper.ts +16 -0
- package/src/utils/ewma-bandwidth-estimator.ts +97 -0
- package/src/utils/ewma.ts +43 -0
- package/src/utils/fetch-loader.ts +331 -0
- package/src/utils/global.ts +2 -0
- package/src/utils/hash.ts +10 -0
- package/src/utils/hdr.ts +67 -0
- package/src/utils/hex.ts +32 -0
- package/src/utils/imsc1-ttml-parser.ts +261 -0
- package/src/utils/keysystem-util.ts +45 -0
- package/src/utils/level-helper.ts +629 -0
- package/src/utils/logger.ts +120 -0
- package/src/utils/media-option-attributes.ts +49 -0
- package/src/utils/mediacapabilities-helper.ts +301 -0
- package/src/utils/mediakeys-helper.ts +210 -0
- package/src/utils/mediasource-helper.ts +37 -0
- package/src/utils/mp4-tools.ts +1473 -0
- package/src/utils/number.ts +3 -0
- package/src/utils/numeric-encoding-utils.ts +26 -0
- package/src/utils/output-filter.ts +46 -0
- package/src/utils/rendition-helper.ts +505 -0
- package/src/utils/safe-json-stringify.ts +22 -0
- package/src/utils/texttrack-utils.ts +164 -0
- package/src/utils/time-ranges.ts +17 -0
- package/src/utils/timescale-conversion.ts +46 -0
- package/src/utils/utf8-utils.ts +18 -0
- package/src/utils/variable-substitution.ts +105 -0
- package/src/utils/vttcue.ts +384 -0
- package/src/utils/vttparser.ts +497 -0
- package/src/utils/webvtt-parser.ts +166 -0
- package/src/utils/xhr-loader.ts +337 -0
- package/src/version.ts +1 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import { NetworkErrorAction } from './error-controller';
|
|
2
|
+
import { ErrorDetails, ErrorTypes } from '../errors';
|
|
3
|
+
import { Events } from '../events';
|
|
4
|
+
import {
|
|
5
|
+
getSkipValue,
|
|
6
|
+
HlsSkip,
|
|
7
|
+
HlsUrlParameters,
|
|
8
|
+
type Level,
|
|
9
|
+
} from '../types/level';
|
|
10
|
+
import { getRetryDelay, isTimeoutError } from '../utils/error-helper';
|
|
11
|
+
import { computeReloadInterval, mergeDetails } from '../utils/level-helper';
|
|
12
|
+
import { Logger } from '../utils/logger';
|
|
13
|
+
import type Hls from '../hls';
|
|
14
|
+
import type { LevelDetails } from '../loader/level-details';
|
|
15
|
+
import type { NetworkComponentAPI } from '../types/component-api';
|
|
16
|
+
import type { ErrorData } from '../types/events';
|
|
17
|
+
import type {
|
|
18
|
+
AudioTrackLoadedData,
|
|
19
|
+
LevelLoadedData,
|
|
20
|
+
TrackLoadedData,
|
|
21
|
+
} from '../types/events';
|
|
22
|
+
import type { MediaPlaylist } from '../types/media-playlist';
|
|
23
|
+
|
|
24
|
+
export default class BasePlaylistController
|
|
25
|
+
extends Logger
|
|
26
|
+
implements NetworkComponentAPI
|
|
27
|
+
{
|
|
28
|
+
protected hls: Hls;
|
|
29
|
+
protected canLoad: boolean = false;
|
|
30
|
+
private timer: number = -1;
|
|
31
|
+
|
|
32
|
+
constructor(hls: Hls, logPrefix: string) {
|
|
33
|
+
super(logPrefix, hls.logger);
|
|
34
|
+
this.hls = hls;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public destroy() {
|
|
38
|
+
this.clearTimer();
|
|
39
|
+
// @ts-ignore
|
|
40
|
+
this.hls = this.log = this.warn = null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private clearTimer() {
|
|
44
|
+
if (this.timer !== -1) {
|
|
45
|
+
self.clearTimeout(this.timer);
|
|
46
|
+
this.timer = -1;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public startLoad() {
|
|
51
|
+
this.canLoad = true;
|
|
52
|
+
this.loadPlaylist();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public stopLoad() {
|
|
56
|
+
this.canLoad = false;
|
|
57
|
+
this.clearTimer();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
protected switchParams(
|
|
61
|
+
playlistUri: string,
|
|
62
|
+
previous: LevelDetails | undefined,
|
|
63
|
+
current: LevelDetails | undefined,
|
|
64
|
+
): HlsUrlParameters | undefined {
|
|
65
|
+
const renditionReports = previous?.renditionReports;
|
|
66
|
+
if (renditionReports) {
|
|
67
|
+
let foundIndex = -1;
|
|
68
|
+
for (let i = 0; i < renditionReports.length; i++) {
|
|
69
|
+
const attr = renditionReports[i];
|
|
70
|
+
let uri: string;
|
|
71
|
+
try {
|
|
72
|
+
uri = new self.URL(attr.URI, previous.url).href;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
this.warn(
|
|
75
|
+
`Could not construct new URL for Rendition Report: ${error}`,
|
|
76
|
+
);
|
|
77
|
+
uri = attr.URI || '';
|
|
78
|
+
}
|
|
79
|
+
// Use exact match. Otherwise, the last partial match, if any, will be used
|
|
80
|
+
// (Playlist URI includes a query string that the Rendition Report does not)
|
|
81
|
+
if (uri === playlistUri) {
|
|
82
|
+
foundIndex = i;
|
|
83
|
+
break;
|
|
84
|
+
} else if (uri === playlistUri.substring(0, uri.length)) {
|
|
85
|
+
foundIndex = i;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (foundIndex !== -1) {
|
|
89
|
+
const attr = renditionReports[foundIndex];
|
|
90
|
+
const msn = parseInt(attr['LAST-MSN']) || previous.lastPartSn;
|
|
91
|
+
let part = parseInt(attr['LAST-PART']) || previous.lastPartIndex;
|
|
92
|
+
if (this.hls.config.lowLatencyMode) {
|
|
93
|
+
const currentGoal = Math.min(
|
|
94
|
+
previous.age - previous.partTarget,
|
|
95
|
+
previous.targetduration,
|
|
96
|
+
);
|
|
97
|
+
if (part >= 0 && currentGoal > previous.partTarget) {
|
|
98
|
+
part += 1;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const skip = current && getSkipValue(current);
|
|
102
|
+
return new HlsUrlParameters(msn, part >= 0 ? part : undefined, skip);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
protected loadPlaylist(hlsUrlParameters?: HlsUrlParameters) {
|
|
108
|
+
// Loading is handled by the subclasses
|
|
109
|
+
this.clearTimer();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
protected loadingPlaylist(
|
|
113
|
+
playlist: Level | MediaPlaylist,
|
|
114
|
+
hlsUrlParameters?: HlsUrlParameters,
|
|
115
|
+
) {
|
|
116
|
+
// Loading is handled by the subclasses
|
|
117
|
+
this.clearTimer();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
protected shouldLoadPlaylist(
|
|
121
|
+
playlist: Level | MediaPlaylist | null | undefined,
|
|
122
|
+
): playlist is Level | MediaPlaylist {
|
|
123
|
+
return (
|
|
124
|
+
this.canLoad &&
|
|
125
|
+
!!playlist &&
|
|
126
|
+
!!playlist.url &&
|
|
127
|
+
(!playlist.details || playlist.details.live)
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
protected getUrlWithDirectives(
|
|
132
|
+
uri: string,
|
|
133
|
+
hlsUrlParameters: HlsUrlParameters | undefined,
|
|
134
|
+
): string {
|
|
135
|
+
if (hlsUrlParameters) {
|
|
136
|
+
try {
|
|
137
|
+
return hlsUrlParameters.addDirectives(uri);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
this.warn(
|
|
140
|
+
`Could not construct new URL with HLS Delivery Directives: ${error}`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return uri;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
protected playlistLoaded(
|
|
148
|
+
index: number,
|
|
149
|
+
data: LevelLoadedData | AudioTrackLoadedData | TrackLoadedData,
|
|
150
|
+
previousDetails?: LevelDetails,
|
|
151
|
+
) {
|
|
152
|
+
const { details, stats } = data;
|
|
153
|
+
|
|
154
|
+
// Set last updated date-time
|
|
155
|
+
const now = self.performance.now();
|
|
156
|
+
const elapsed = stats.loading.first
|
|
157
|
+
? Math.max(0, now - stats.loading.first)
|
|
158
|
+
: 0;
|
|
159
|
+
details.advancedDateTime = Date.now() - elapsed;
|
|
160
|
+
|
|
161
|
+
// shift fragment starts with timelineOffset
|
|
162
|
+
const timelineOffset = this.hls.config.timelineOffset;
|
|
163
|
+
if (timelineOffset !== details.appliedTimelineOffset) {
|
|
164
|
+
const offset = Math.max(timelineOffset || 0, 0);
|
|
165
|
+
details.appliedTimelineOffset = offset;
|
|
166
|
+
details.fragments.forEach((frag) => {
|
|
167
|
+
frag.setStart(frag.playlistOffset + offset);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// if current playlist is a live playlist, arm a timer to reload it
|
|
172
|
+
if (details.live || previousDetails?.live) {
|
|
173
|
+
const levelOrTrack = 'levelInfo' in data ? data.levelInfo : data.track;
|
|
174
|
+
details.reloaded(previousDetails);
|
|
175
|
+
|
|
176
|
+
// TODO: consider a separate flow for low-latency blocking reload requests with delivery directives
|
|
177
|
+
if (details.misses >= this.hls.config.liveMaxUnchangedPlaylistRefresh) {
|
|
178
|
+
const error = new Error(
|
|
179
|
+
`levelOrTrack (${levelOrTrack.id}) hits max allowed unchanged reloads.`,
|
|
180
|
+
);
|
|
181
|
+
this.warn(error);
|
|
182
|
+
const { networkDetails, context } = data;
|
|
183
|
+
this.hls.trigger(Events.ERROR, {
|
|
184
|
+
type: ErrorTypes.NETWORK_ERROR,
|
|
185
|
+
details: ErrorDetails.PLAYLIST_UNCHANGED_ERROR,
|
|
186
|
+
fatal: false,
|
|
187
|
+
url: details.url,
|
|
188
|
+
error,
|
|
189
|
+
reason: error.message,
|
|
190
|
+
level: (data as LevelLoadedData).level ?? undefined,
|
|
191
|
+
parent: details.fragments[0]?.type,
|
|
192
|
+
context,
|
|
193
|
+
networkDetails,
|
|
194
|
+
stats,
|
|
195
|
+
});
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Merge live playlists to adjust fragment starts and fill in delta playlist skipped segments
|
|
200
|
+
if (previousDetails && details.fragments.length > 0) {
|
|
201
|
+
mergeDetails(previousDetails, details, this);
|
|
202
|
+
const error = details.playlistParsingError;
|
|
203
|
+
if (error) {
|
|
204
|
+
this.warn(error);
|
|
205
|
+
const hls = this.hls;
|
|
206
|
+
if (!hls.config.ignorePlaylistParsingErrors) {
|
|
207
|
+
const { networkDetails } = data;
|
|
208
|
+
hls.trigger(Events.ERROR, {
|
|
209
|
+
type: ErrorTypes.NETWORK_ERROR,
|
|
210
|
+
details: ErrorDetails.LEVEL_PARSING_ERROR,
|
|
211
|
+
fatal: false,
|
|
212
|
+
url: details.url,
|
|
213
|
+
error,
|
|
214
|
+
reason: error.message,
|
|
215
|
+
level: (data as any).level || undefined,
|
|
216
|
+
parent: details.fragments[0]?.type,
|
|
217
|
+
networkDetails,
|
|
218
|
+
stats,
|
|
219
|
+
});
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
details.playlistParsingError = null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (details.requestScheduled === -1) {
|
|
226
|
+
details.requestScheduled = stats.loading.start;
|
|
227
|
+
}
|
|
228
|
+
const bufferInfo = this.hls.mainForwardBufferInfo;
|
|
229
|
+
const position = bufferInfo ? bufferInfo.end - bufferInfo.len : 0;
|
|
230
|
+
const distanceToLiveEdgeMs = (details.edge - position) * 1000;
|
|
231
|
+
const reloadInterval = computeReloadInterval(
|
|
232
|
+
details,
|
|
233
|
+
distanceToLiveEdgeMs,
|
|
234
|
+
);
|
|
235
|
+
if (details.requestScheduled + reloadInterval < now) {
|
|
236
|
+
details.requestScheduled = now;
|
|
237
|
+
} else {
|
|
238
|
+
details.requestScheduled += reloadInterval;
|
|
239
|
+
}
|
|
240
|
+
this.log(
|
|
241
|
+
`live playlist ${index} ${
|
|
242
|
+
details.advanced
|
|
243
|
+
? 'REFRESHED ' + details.lastPartSn + '-' + details.lastPartIndex
|
|
244
|
+
: details.updated
|
|
245
|
+
? 'UPDATED'
|
|
246
|
+
: 'MISSED'
|
|
247
|
+
}`,
|
|
248
|
+
);
|
|
249
|
+
if (!this.canLoad || !details.live) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
let deliveryDirectives: HlsUrlParameters | undefined;
|
|
253
|
+
let msn: number | undefined = undefined;
|
|
254
|
+
let part: number | undefined = undefined;
|
|
255
|
+
if (details.canBlockReload && details.endSN && details.advanced) {
|
|
256
|
+
// Load level with LL-HLS delivery directives
|
|
257
|
+
const lowLatencyMode = this.hls.config.lowLatencyMode;
|
|
258
|
+
const lastPartSn = details.lastPartSn;
|
|
259
|
+
const endSn = details.endSN;
|
|
260
|
+
const lastPartIndex = details.lastPartIndex;
|
|
261
|
+
const hasParts = lastPartIndex !== -1;
|
|
262
|
+
const atLastPartOfSegment = lastPartSn === endSn;
|
|
263
|
+
if (hasParts) {
|
|
264
|
+
// When low latency mode is disabled, request the last part of the next segment
|
|
265
|
+
if (atLastPartOfSegment) {
|
|
266
|
+
msn = endSn + 1;
|
|
267
|
+
part = lowLatencyMode ? 0 : lastPartIndex;
|
|
268
|
+
} else {
|
|
269
|
+
msn = lastPartSn;
|
|
270
|
+
part = lowLatencyMode ? lastPartIndex + 1 : details.maxPartIndex;
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
msn = endSn + 1;
|
|
274
|
+
}
|
|
275
|
+
// Low-Latency CDN Tune-in: "age" header and time since load indicates we're behind by more than one part
|
|
276
|
+
// Update directives to obtain the Playlist that has the estimated additional duration of media
|
|
277
|
+
const lastAdvanced = details.age;
|
|
278
|
+
const cdnAge = lastAdvanced + details.ageHeader;
|
|
279
|
+
let currentGoal = Math.min(
|
|
280
|
+
cdnAge - details.partTarget,
|
|
281
|
+
details.targetduration * 1.5,
|
|
282
|
+
);
|
|
283
|
+
if (currentGoal > 0) {
|
|
284
|
+
if (cdnAge > details.targetduration * 3) {
|
|
285
|
+
// Omit segment and part directives when the last response was more than 3 target durations ago,
|
|
286
|
+
this.log(
|
|
287
|
+
`Playlist last advanced ${lastAdvanced.toFixed(
|
|
288
|
+
2,
|
|
289
|
+
)}s ago. Omitting segment and part directives.`,
|
|
290
|
+
);
|
|
291
|
+
msn = undefined;
|
|
292
|
+
part = undefined;
|
|
293
|
+
} else if (
|
|
294
|
+
previousDetails?.tuneInGoal &&
|
|
295
|
+
cdnAge - details.partTarget > previousDetails.tuneInGoal
|
|
296
|
+
) {
|
|
297
|
+
// If we attempted to get the next or latest playlist update, but currentGoal increased,
|
|
298
|
+
// then we either can't catchup, or the "age" header cannot be trusted.
|
|
299
|
+
this.warn(
|
|
300
|
+
`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`,
|
|
301
|
+
);
|
|
302
|
+
currentGoal = 0;
|
|
303
|
+
} else {
|
|
304
|
+
const segments = Math.floor(currentGoal / details.targetduration);
|
|
305
|
+
msn += segments;
|
|
306
|
+
if (part !== undefined) {
|
|
307
|
+
const parts = Math.round(
|
|
308
|
+
(currentGoal % details.targetduration) / details.partTarget,
|
|
309
|
+
);
|
|
310
|
+
part += parts;
|
|
311
|
+
}
|
|
312
|
+
this.log(
|
|
313
|
+
`CDN Tune-in age: ${
|
|
314
|
+
details.ageHeader
|
|
315
|
+
}s last advanced ${lastAdvanced.toFixed(
|
|
316
|
+
2,
|
|
317
|
+
)}s goal: ${currentGoal} skip sn ${segments} to part ${part}`,
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
details.tuneInGoal = currentGoal;
|
|
321
|
+
}
|
|
322
|
+
deliveryDirectives = this.getDeliveryDirectives(
|
|
323
|
+
details,
|
|
324
|
+
data.deliveryDirectives,
|
|
325
|
+
msn,
|
|
326
|
+
part,
|
|
327
|
+
);
|
|
328
|
+
if (lowLatencyMode || !atLastPartOfSegment) {
|
|
329
|
+
details.requestScheduled = now;
|
|
330
|
+
this.loadingPlaylist(levelOrTrack, deliveryDirectives);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
} else if (details.canBlockReload || details.canSkipUntil) {
|
|
334
|
+
deliveryDirectives = this.getDeliveryDirectives(
|
|
335
|
+
details,
|
|
336
|
+
data.deliveryDirectives,
|
|
337
|
+
msn,
|
|
338
|
+
part,
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
if (deliveryDirectives && msn !== undefined && details.canBlockReload) {
|
|
342
|
+
details.requestScheduled =
|
|
343
|
+
stats.loading.first +
|
|
344
|
+
Math.max(reloadInterval - elapsed * 2, reloadInterval / 2);
|
|
345
|
+
}
|
|
346
|
+
this.scheduleLoading(levelOrTrack, deliveryDirectives, details);
|
|
347
|
+
} else {
|
|
348
|
+
this.clearTimer();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
protected scheduleLoading(
|
|
353
|
+
levelOrTrack: Level | MediaPlaylist,
|
|
354
|
+
deliveryDirectives?: HlsUrlParameters,
|
|
355
|
+
updatedDetails?: LevelDetails,
|
|
356
|
+
) {
|
|
357
|
+
const details = updatedDetails || levelOrTrack.details;
|
|
358
|
+
if (!details) {
|
|
359
|
+
this.loadingPlaylist(levelOrTrack, deliveryDirectives);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const now = self.performance.now();
|
|
363
|
+
const requestScheduled = details.requestScheduled;
|
|
364
|
+
if (now >= requestScheduled) {
|
|
365
|
+
this.loadingPlaylist(levelOrTrack, deliveryDirectives);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const estimatedTimeUntilUpdate = requestScheduled - now;
|
|
370
|
+
this.log(
|
|
371
|
+
`reload live playlist ${levelOrTrack.name || levelOrTrack.bitrate + 'bps'} in ${Math.round(
|
|
372
|
+
estimatedTimeUntilUpdate,
|
|
373
|
+
)} ms`,
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
this.clearTimer();
|
|
377
|
+
this.timer = self.setTimeout(
|
|
378
|
+
() => this.loadingPlaylist(levelOrTrack, deliveryDirectives),
|
|
379
|
+
estimatedTimeUntilUpdate,
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private getDeliveryDirectives(
|
|
384
|
+
details: LevelDetails,
|
|
385
|
+
previousDeliveryDirectives: HlsUrlParameters | null,
|
|
386
|
+
msn?: number,
|
|
387
|
+
part?: number,
|
|
388
|
+
): HlsUrlParameters {
|
|
389
|
+
let skip = getSkipValue(details);
|
|
390
|
+
if (previousDeliveryDirectives?.skip && details.deltaUpdateFailed) {
|
|
391
|
+
msn = previousDeliveryDirectives.msn;
|
|
392
|
+
part = previousDeliveryDirectives.part;
|
|
393
|
+
skip = HlsSkip.No;
|
|
394
|
+
}
|
|
395
|
+
return new HlsUrlParameters(msn, part, skip);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
protected checkRetry(errorEvent: ErrorData): boolean {
|
|
399
|
+
const errorDetails = errorEvent.details;
|
|
400
|
+
const isTimeout = isTimeoutError(errorEvent);
|
|
401
|
+
const errorAction = errorEvent.errorAction;
|
|
402
|
+
const { action, retryCount = 0, retryConfig } = errorAction || {};
|
|
403
|
+
const retry =
|
|
404
|
+
!!errorAction &&
|
|
405
|
+
!!retryConfig &&
|
|
406
|
+
(action === NetworkErrorAction.RetryRequest ||
|
|
407
|
+
(!errorAction.resolved &&
|
|
408
|
+
action === NetworkErrorAction.SendAlternateToPenaltyBox));
|
|
409
|
+
if (retry) {
|
|
410
|
+
if (retryCount >= retryConfig.maxNumRetry) {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
if (isTimeout && errorEvent.context?.deliveryDirectives) {
|
|
414
|
+
// The LL-HLS request already timed out so retry immediately
|
|
415
|
+
this.warn(
|
|
416
|
+
`Retrying playlist loading ${retryCount + 1}/${
|
|
417
|
+
retryConfig.maxNumRetry
|
|
418
|
+
} after "${errorDetails}" without delivery-directives`,
|
|
419
|
+
);
|
|
420
|
+
this.loadPlaylist();
|
|
421
|
+
} else {
|
|
422
|
+
const delay = getRetryDelay(retryConfig, retryCount);
|
|
423
|
+
// Schedule level/track reload
|
|
424
|
+
this.clearTimer();
|
|
425
|
+
this.timer = self.setTimeout(() => this.loadPlaylist(), delay);
|
|
426
|
+
this.warn(
|
|
427
|
+
`Retrying playlist loading ${retryCount + 1}/${
|
|
428
|
+
retryConfig.maxNumRetry
|
|
429
|
+
} after "${errorDetails}" in ${delay}ms`,
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
// `levelRetry = true` used to inform other controllers that a retry is happening
|
|
433
|
+
errorEvent.levelRetry = true;
|
|
434
|
+
errorAction.resolved = true;
|
|
435
|
+
}
|
|
436
|
+
return retry;
|
|
437
|
+
}
|
|
438
|
+
}
|