hls.js 1.5.12-0.canary.10338 → 1.5.12-0.canary.10341
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/dist/hls.js +431 -288
- package/dist/hls.js.d.ts +49 -16
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +364 -178
- 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 +356 -179
- 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 +422 -288
- 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 +2 -2
- package/src/controller/audio-stream-controller.ts +13 -5
- package/src/controller/base-stream-controller.ts +28 -26
- package/src/controller/fragment-finders.ts +12 -14
- package/src/controller/fragment-tracker.ts +14 -14
- package/src/controller/id3-track-controller.ts +38 -28
- package/src/controller/stream-controller.ts +9 -4
- package/src/controller/subtitle-stream-controller.ts +3 -3
- package/src/demux/transmuxer-interface.ts +4 -4
- package/src/hls.ts +3 -1
- package/src/loader/date-range.ts +71 -5
- package/src/loader/fragment.ts +6 -2
- package/src/loader/level-details.ts +7 -6
- package/src/loader/m3u8-parser.ts +138 -144
- package/src/types/events.ts +2 -5
- package/src/types/fragment-tracker.ts +2 -2
- package/src/utils/attr-list.ts +96 -9
- package/src/utils/level-helper.ts +68 -40
- package/src/utils/variable-substitution.ts +0 -19
package/package.json
CHANGED
@@ -4,7 +4,12 @@ import { Bufferable, BufferHelper } from '../utils/buffer-helper';
|
|
4
4
|
import { FragmentState } from './fragment-tracker';
|
5
5
|
import { Level } from '../types/level';
|
6
6
|
import { PlaylistContextType, PlaylistLevelType } from '../types/loader';
|
7
|
-
import {
|
7
|
+
import {
|
8
|
+
Fragment,
|
9
|
+
ElementaryStreamTypes,
|
10
|
+
Part,
|
11
|
+
MediaFragment,
|
12
|
+
} from '../loader/fragment';
|
8
13
|
import ChunkCache from '../demux/chunk-cache';
|
9
14
|
import TransmuxerInterface from '../demux/transmuxer-interface';
|
10
15
|
import { ChunkMetadata } from '../types/transmuxer';
|
@@ -40,7 +45,7 @@ import type { MediaPlaylist } from '../types/media-playlist';
|
|
40
45
|
const TICK_INTERVAL = 100; // how often to tick in ms
|
41
46
|
|
42
47
|
type WaitingForPTSData = {
|
43
|
-
frag:
|
48
|
+
frag: MediaFragment;
|
44
49
|
part: Part | null;
|
45
50
|
cache: ChunkCache;
|
46
51
|
complete: boolean;
|
@@ -128,7 +133,9 @@ class AudioStreamController
|
|
128
133
|
if (id === 'main') {
|
129
134
|
const cc = frag.cc;
|
130
135
|
this.initPTS[frag.cc] = { baseTime: initPTS, timescale };
|
131
|
-
this.log(
|
136
|
+
this.log(
|
137
|
+
`InitPTS for cc: ${cc} found from main: ${initPTS}/${timescale}`,
|
138
|
+
);
|
132
139
|
this.videoTrackCC = cc;
|
133
140
|
// If we are waiting, tick immediately to unblock audio fragment transmuxing
|
134
141
|
if (this.state === State.WAITING_INIT_PTS) {
|
@@ -561,7 +568,8 @@ class AudioStreamController
|
|
561
568
|
}
|
562
569
|
|
563
570
|
_handleFragmentLoadProgress(data: FragLoadedData) {
|
564
|
-
const
|
571
|
+
const frag = data.frag as MediaFragment;
|
572
|
+
const { part, payload } = data;
|
565
573
|
const { config, trackId, levels } = this;
|
566
574
|
if (!levels) {
|
567
575
|
this.warn(
|
@@ -606,7 +614,7 @@ class AudioStreamController
|
|
606
614
|
const partial = partIndex !== -1;
|
607
615
|
const chunkMeta = new ChunkMetadata(
|
608
616
|
frag.level,
|
609
|
-
frag.sn
|
617
|
+
frag.sn,
|
610
618
|
frag.stats.chunkCount,
|
611
619
|
payload.byteLength,
|
612
620
|
partIndex,
|
@@ -22,7 +22,7 @@ import {
|
|
22
22
|
updateFragPTSDTS,
|
23
23
|
} from '../utils/level-helper';
|
24
24
|
import TransmuxerInterface from '../demux/transmuxer-interface';
|
25
|
-
import { Fragment, Part } from '../loader/fragment';
|
25
|
+
import { Fragment, MediaFragment, Part } from '../loader/fragment';
|
26
26
|
import FragmentLoader, {
|
27
27
|
FragmentLoadProgressCallback,
|
28
28
|
LoadError,
|
@@ -954,22 +954,24 @@ export default class BaseStreamController
|
|
954
954
|
part.stats.parsing.end = now;
|
955
955
|
}
|
956
956
|
// See if part loading should be disabled/enabled based on buffer and playback position.
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
this.loadingParts = shouldLoadParts;
|
969
|
-
}
|
957
|
+
const levelDetails = this.getLevelDetails();
|
958
|
+
const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
|
959
|
+
const shouldLoadParts =
|
960
|
+
loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
|
961
|
+
if (shouldLoadParts !== this.loadingParts) {
|
962
|
+
this.log(
|
963
|
+
`LL-Part loading ${
|
964
|
+
shouldLoadParts ? 'ON' : 'OFF'
|
965
|
+
} after parsing segment ending @${frag.end.toFixed(2)}`,
|
966
|
+
);
|
967
|
+
this.loadingParts = shouldLoadParts;
|
970
968
|
}
|
971
|
-
|
972
|
-
|
969
|
+
this.updateLevelTiming(
|
970
|
+
frag as MediaFragment,
|
971
|
+
part,
|
972
|
+
level,
|
973
|
+
chunkMeta.partial,
|
974
|
+
);
|
973
975
|
}
|
974
976
|
|
975
977
|
private shouldLoadParts(
|
@@ -999,7 +1001,7 @@ export default class BaseStreamController
|
|
999
1001
|
|
1000
1002
|
protected getCurrentContext(
|
1001
1003
|
chunkMeta: ChunkMetadata,
|
1002
|
-
): { frag:
|
1004
|
+
): { frag: MediaFragment; part: Part | null; level: Level } | null {
|
1003
1005
|
const { levels, fragCurrent } = this;
|
1004
1006
|
const { level: levelIndex, sn, part: partIndex } = chunkMeta;
|
1005
1007
|
if (!levels?.[levelIndex]) {
|
@@ -1183,7 +1185,7 @@ export default class BaseStreamController
|
|
1183
1185
|
const { config } = this;
|
1184
1186
|
const start = fragments[0].start;
|
1185
1187
|
const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
|
1186
|
-
let frag:
|
1188
|
+
let frag: MediaFragment | null = null;
|
1187
1189
|
|
1188
1190
|
if (levelDetails.live) {
|
1189
1191
|
const initialLiveManifestSize = config.initialLiveManifestSize;
|
@@ -1326,10 +1328,10 @@ export default class BaseStreamController
|
|
1326
1328
|
*/
|
1327
1329
|
protected getInitialLiveFragment(
|
1328
1330
|
levelDetails: LevelDetails,
|
1329
|
-
fragments:
|
1330
|
-
):
|
1331
|
+
fragments: MediaFragment[],
|
1332
|
+
): MediaFragment | null {
|
1331
1333
|
const fragPrevious = this.fragPrevious;
|
1332
|
-
let frag:
|
1334
|
+
let frag: MediaFragment | null = null;
|
1333
1335
|
if (fragPrevious) {
|
1334
1336
|
if (levelDetails.hasProgramDateTime) {
|
1335
1337
|
// Prefer using PDT, because it can be accurate enough to choose the correct fragment without knowing the level sliding
|
@@ -1393,7 +1395,7 @@ export default class BaseStreamController
|
|
1393
1395
|
bufferEnd: number,
|
1394
1396
|
end: number,
|
1395
1397
|
levelDetails: LevelDetails,
|
1396
|
-
):
|
1398
|
+
): MediaFragment | null {
|
1397
1399
|
const { config } = this;
|
1398
1400
|
let { fragPrevious } = this;
|
1399
1401
|
let { fragments, endSN } = levelDetails;
|
@@ -1409,10 +1411,10 @@ export default class BaseStreamController
|
|
1409
1411
|
if (loadingParts && fragmentHint && !this.bitrateTest) {
|
1410
1412
|
// Include incomplete fragment with parts at end
|
1411
1413
|
fragments = fragments.concat(fragmentHint);
|
1412
|
-
endSN = fragmentHint.sn
|
1414
|
+
endSN = fragmentHint.sn;
|
1413
1415
|
}
|
1414
1416
|
|
1415
|
-
let frag;
|
1417
|
+
let frag: MediaFragment | null;
|
1416
1418
|
if (bufferEnd < end) {
|
1417
1419
|
const lookupTolerance =
|
1418
1420
|
bufferEnd > end - maxFragLookUpTolerance ? 0 : maxFragLookUpTolerance;
|
@@ -1649,7 +1651,7 @@ export default class BaseStreamController
|
|
1649
1651
|
}
|
1650
1652
|
const gapTagEncountered = data.details === ErrorDetails.FRAG_GAP;
|
1651
1653
|
if (gapTagEncountered) {
|
1652
|
-
this.fragmentTracker.fragBuffered(frag, true);
|
1654
|
+
this.fragmentTracker.fragBuffered(frag as MediaFragment, true);
|
1653
1655
|
}
|
1654
1656
|
// keep retrying until the limit will be reached
|
1655
1657
|
const errorAction = data.errorAction;
|
@@ -1813,7 +1815,7 @@ export default class BaseStreamController
|
|
1813
1815
|
}
|
1814
1816
|
|
1815
1817
|
private updateLevelTiming(
|
1816
|
-
frag:
|
1818
|
+
frag: MediaFragment,
|
1817
1819
|
part: Part | null,
|
1818
1820
|
level: Level,
|
1819
1821
|
partial: boolean,
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import BinarySearch from '../utils/binary-search';
|
2
|
-
import { Fragment } from '../loader/fragment';
|
2
|
+
import type { Fragment, MediaFragment } from '../loader/fragment';
|
3
3
|
|
4
4
|
/**
|
5
5
|
* Returns first fragment whose endPdt value exceeds the given PDT, or null.
|
@@ -8,10 +8,10 @@ import { Fragment } from '../loader/fragment';
|
|
8
8
|
* @param maxFragLookUpTolerance - The amount of time that a fragment's start/end can be within in order to be considered contiguous
|
9
9
|
*/
|
10
10
|
export function findFragmentByPDT(
|
11
|
-
fragments:
|
11
|
+
fragments: MediaFragment[],
|
12
12
|
PDTValue: number | null,
|
13
13
|
maxFragLookUpTolerance: number,
|
14
|
-
):
|
14
|
+
): MediaFragment | null {
|
15
15
|
if (
|
16
16
|
PDTValue === null ||
|
17
17
|
!Array.isArray(fragments) ||
|
@@ -55,19 +55,17 @@ export function findFragmentByPDT(
|
|
55
55
|
*/
|
56
56
|
export function findFragmentByPTS(
|
57
57
|
fragPrevious: Fragment | null,
|
58
|
-
fragments:
|
58
|
+
fragments: MediaFragment[],
|
59
59
|
bufferEnd: number = 0,
|
60
60
|
maxFragLookUpTolerance: number = 0,
|
61
61
|
nextFragLookupTolerance: number = 0.005,
|
62
|
-
):
|
63
|
-
let fragNext:
|
62
|
+
): MediaFragment | null {
|
63
|
+
let fragNext: MediaFragment | null = null;
|
64
64
|
if (fragPrevious) {
|
65
65
|
fragNext =
|
66
|
-
fragments[
|
67
|
-
(fragPrevious.sn as number) - (fragments[0].sn as number) + 1
|
68
|
-
] || null;
|
66
|
+
fragments[(fragPrevious.sn as number) - fragments[0].sn + 1] || 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
|
}
|
@@ -135,7 +133,7 @@ function fragmentWithinFastStartSwitch(
|
|
135
133
|
export function fragmentWithinToleranceTest(
|
136
134
|
bufferEnd = 0,
|
137
135
|
maxFragLookUpTolerance = 0,
|
138
|
-
candidate:
|
136
|
+
candidate: MediaFragment,
|
139
137
|
) {
|
140
138
|
// eagerly accept an accurate match (no tolerance)
|
141
139
|
if (
|
@@ -189,7 +187,7 @@ export function fragmentWithinToleranceTest(
|
|
189
187
|
export function pdtWithinToleranceTest(
|
190
188
|
pdtBufferEnd: number,
|
191
189
|
maxFragLookUpTolerance: number,
|
192
|
-
candidate:
|
190
|
+
candidate: MediaFragment,
|
193
191
|
): boolean {
|
194
192
|
const candidateLookupTolerance =
|
195
193
|
Math.min(
|
@@ -203,9 +201,9 @@ export function pdtWithinToleranceTest(
|
|
203
201
|
}
|
204
202
|
|
205
203
|
export function findFragWithCC(
|
206
|
-
fragments:
|
204
|
+
fragments: MediaFragment[],
|
207
205
|
cc: number,
|
208
|
-
):
|
206
|
+
): MediaFragment | null {
|
209
207
|
return BinarySearch.search(fragments, (candidate) => {
|
210
208
|
if (candidate.cc < cc) {
|
211
209
|
return 1;
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Events } from '../events';
|
2
|
-
import { Fragment, Part } from '../loader/fragment';
|
2
|
+
import { Fragment, MediaFragment, Part } from '../loader/fragment';
|
3
3
|
import { PlaylistLevelType } from '../types/loader';
|
4
4
|
import type { SourceBufferName } from '../types/buffer';
|
5
5
|
import type {
|
@@ -107,7 +107,7 @@ export class FragmentTracker implements ComponentAPI {
|
|
107
107
|
public getBufferedFrag(
|
108
108
|
position: number,
|
109
109
|
levelType: PlaylistLevelType,
|
110
|
-
):
|
110
|
+
): MediaFragment | null {
|
111
111
|
return this.getFragAtPos(position, levelType, true);
|
112
112
|
}
|
113
113
|
|
@@ -115,7 +115,7 @@ export class FragmentTracker implements ComponentAPI {
|
|
115
115
|
position: number,
|
116
116
|
levelType: PlaylistLevelType,
|
117
117
|
buffered?: boolean,
|
118
|
-
):
|
118
|
+
): MediaFragment | null {
|
119
119
|
const { fragments } = this;
|
120
120
|
const keys = Object.keys(fragments);
|
121
121
|
for (let i = keys.length; i--; ) {
|
@@ -149,13 +149,13 @@ export class FragmentTracker implements ComponentAPI {
|
|
149
149
|
}
|
150
150
|
// Check if any flagged fragments have been unloaded
|
151
151
|
// excluding anything newer than appendedPartSn
|
152
|
-
const appendedPartSn =
|
152
|
+
const appendedPartSn = appendedPart?.fragment.sn || -1;
|
153
153
|
Object.keys(this.fragments).forEach((key) => {
|
154
154
|
const fragmentEntity = this.fragments[key];
|
155
155
|
if (!fragmentEntity) {
|
156
156
|
return;
|
157
157
|
}
|
158
|
-
if (appendedPartSn >=
|
158
|
+
if (appendedPartSn >= fragmentEntity.body.sn) {
|
159
159
|
return;
|
160
160
|
}
|
161
161
|
if (!fragmentEntity.buffered && !fragmentEntity.loaded) {
|
@@ -189,11 +189,11 @@ export class FragmentTracker implements ComponentAPI {
|
|
189
189
|
*/
|
190
190
|
public detectPartialFragments(data: FragBufferedData) {
|
191
191
|
const timeRanges = this.timeRanges;
|
192
|
-
|
193
|
-
if (!timeRanges || frag.sn === 'initSegment') {
|
192
|
+
if (!timeRanges || data.frag.sn === 'initSegment') {
|
194
193
|
return;
|
195
194
|
}
|
196
195
|
|
196
|
+
const frag = data.frag as MediaFragment;
|
197
197
|
const fragKey = getFragmentKey(frag);
|
198
198
|
const fragmentEntity = this.fragments[fragKey];
|
199
199
|
if (!fragmentEntity || (fragmentEntity.buffered && frag.gap)) {
|
@@ -209,7 +209,7 @@ export class FragmentTracker implements ComponentAPI {
|
|
209
209
|
const partial = isFragHint || streamInfo.partial === true;
|
210
210
|
fragmentEntity.range[elementaryStream] = this.getBufferedTimes(
|
211
211
|
frag,
|
212
|
-
part,
|
212
|
+
data.part,
|
213
213
|
partial,
|
214
214
|
timeRange,
|
215
215
|
);
|
@@ -224,7 +224,7 @@ export class FragmentTracker implements ComponentAPI {
|
|
224
224
|
}
|
225
225
|
if (!isPartial(fragmentEntity)) {
|
226
226
|
// Remove older fragment parts from lookup after frag is tracked as buffered
|
227
|
-
this.removeParts(
|
227
|
+
this.removeParts(frag.sn - 1, frag.type);
|
228
228
|
}
|
229
229
|
} else {
|
230
230
|
// remove fragment if nothing was appended
|
@@ -238,11 +238,11 @@ export class FragmentTracker implements ComponentAPI {
|
|
238
238
|
return;
|
239
239
|
}
|
240
240
|
this.activePartLists[levelType] = activeParts.filter(
|
241
|
-
(part) =>
|
241
|
+
(part) => part.fragment.sn >= snToKeep,
|
242
242
|
);
|
243
243
|
}
|
244
244
|
|
245
|
-
public fragBuffered(frag:
|
245
|
+
public fragBuffered(frag: MediaFragment, force?: true) {
|
246
246
|
const fragKey = getFragmentKey(frag);
|
247
247
|
let fragmentEntity = this.fragments[fragKey];
|
248
248
|
if (!fragmentEntity && force) {
|
@@ -388,15 +388,15 @@ export class FragmentTracker implements ComponentAPI {
|
|
388
388
|
}
|
389
389
|
|
390
390
|
private onFragLoaded(event: Events.FRAG_LOADED, data: FragLoadedData) {
|
391
|
-
const { frag, part } = data;
|
392
391
|
// don't track initsegment (for which sn is not a number)
|
393
392
|
// don't track frags used for bitrateTest, they're irrelevant.
|
394
|
-
if (frag.sn === 'initSegment' || frag.bitrateTest) {
|
393
|
+
if (data.frag.sn === 'initSegment' || data.frag.bitrateTest) {
|
395
394
|
return;
|
396
395
|
}
|
397
396
|
|
397
|
+
const frag = data.frag as MediaFragment;
|
398
398
|
// Fragment entity `loaded` FragLoadedData is null when loading parts
|
399
|
-
const loaded = part ? null : data;
|
399
|
+
const loaded = data.part ? null : data;
|
400
400
|
|
401
401
|
const fragKey = getFragmentKey(frag);
|
402
402
|
this.fragments[fragKey] = {
|
@@ -9,10 +9,12 @@ import {
|
|
9
9
|
isDateRangeCueAttribute,
|
10
10
|
isSCTE35Attribute,
|
11
11
|
} from '../loader/date-range';
|
12
|
+
import { LevelDetails } from '../loader/level-details';
|
12
13
|
import { MetadataSchema } from '../types/demuxer';
|
13
14
|
import type {
|
14
15
|
BufferFlushingData,
|
15
16
|
FragParsingMetadataData,
|
17
|
+
LevelPTSUpdatedData,
|
16
18
|
LevelUpdatedData,
|
17
19
|
MediaAttachedData,
|
18
20
|
} from '../types/events';
|
@@ -69,10 +71,6 @@ const MAX_CUE_ENDTIME = (() => {
|
|
69
71
|
return Number.POSITIVE_INFINITY;
|
70
72
|
})();
|
71
73
|
|
72
|
-
function dateRangeDateToTimelineSeconds(date: Date, offset: number): number {
|
73
|
-
return date.getTime() / 1000 - offset;
|
74
|
-
}
|
75
|
-
|
76
74
|
function hexToArrayBuffer(str): ArrayBuffer {
|
77
75
|
return Uint8Array.from(
|
78
76
|
str
|
@@ -100,7 +98,7 @@ class ID3TrackController implements ComponentAPI {
|
|
100
98
|
this._registerListeners();
|
101
99
|
}
|
102
100
|
|
103
|
-
destroy() {
|
101
|
+
public destroy() {
|
104
102
|
this._unregisterListeners();
|
105
103
|
this.id3Track = null;
|
106
104
|
this.media = null;
|
@@ -117,6 +115,7 @@ class ID3TrackController implements ComponentAPI {
|
|
117
115
|
hls.on(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this);
|
118
116
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
119
117
|
hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
|
118
|
+
hls.on(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this);
|
120
119
|
}
|
121
120
|
|
122
121
|
private _unregisterListeners() {
|
@@ -127,17 +126,18 @@ class ID3TrackController implements ComponentAPI {
|
|
127
126
|
hls.off(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this);
|
128
127
|
hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
129
128
|
hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
|
129
|
+
hls.off(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this);
|
130
130
|
}
|
131
131
|
|
132
132
|
// Add ID3 metatadata text track.
|
133
|
-
|
133
|
+
private onMediaAttached(
|
134
134
|
event: Events.MEDIA_ATTACHED,
|
135
135
|
data: MediaAttachedData,
|
136
136
|
): void {
|
137
137
|
this.media = data.media;
|
138
138
|
}
|
139
139
|
|
140
|
-
|
140
|
+
private onMediaDetaching(): void {
|
141
141
|
if (this.id3Track) {
|
142
142
|
clearCurrentCues(this.id3Track);
|
143
143
|
this.id3Track = null;
|
@@ -150,13 +150,13 @@ class ID3TrackController implements ComponentAPI {
|
|
150
150
|
this.dateRangeCuesAppended = {};
|
151
151
|
}
|
152
152
|
|
153
|
-
createTrack(media: HTMLMediaElement): TextTrack {
|
153
|
+
private createTrack(media: HTMLMediaElement): TextTrack {
|
154
154
|
const track = this.getID3Track(media.textTracks) as TextTrack;
|
155
155
|
track.mode = 'hidden';
|
156
156
|
return track;
|
157
157
|
}
|
158
158
|
|
159
|
-
getID3Track(textTracks: TextTrackList): TextTrack | void {
|
159
|
+
private getID3Track(textTracks: TextTrackList): TextTrack | void {
|
160
160
|
if (!this.media) {
|
161
161
|
return;
|
162
162
|
}
|
@@ -173,7 +173,7 @@ class ID3TrackController implements ComponentAPI {
|
|
173
173
|
return this.media.addTextTrack('metadata', 'id3');
|
174
174
|
}
|
175
175
|
|
176
|
-
onFragParsingMetadata(
|
176
|
+
private onFragParsingMetadata(
|
177
177
|
event: Events.FRAG_PARSING_METADATA,
|
178
178
|
data: FragParsingMetadataData,
|
179
179
|
) {
|
@@ -247,7 +247,7 @@ class ID3TrackController implements ComponentAPI {
|
|
247
247
|
}
|
248
248
|
}
|
249
249
|
|
250
|
-
updateId3CueEnds(startTime: number, type: MetadataSchema) {
|
250
|
+
private updateId3CueEnds(startTime: number, type: MetadataSchema) {
|
251
251
|
const cues = this.id3Track?.cues;
|
252
252
|
if (cues) {
|
253
253
|
for (let i = cues.length; i--; ) {
|
@@ -263,7 +263,7 @@ class ID3TrackController implements ComponentAPI {
|
|
263
263
|
}
|
264
264
|
}
|
265
265
|
|
266
|
-
onBufferFlushing(
|
266
|
+
private onBufferFlushing(
|
267
267
|
event: Events.BUFFER_FLUSHING,
|
268
268
|
{ startOffset, endOffset, type }: BufferFlushingData,
|
269
269
|
) {
|
@@ -295,7 +295,23 @@ class ID3TrackController implements ComponentAPI {
|
|
295
295
|
}
|
296
296
|
}
|
297
297
|
|
298
|
-
onLevelUpdated(
|
298
|
+
private onLevelUpdated(
|
299
|
+
event: Events.LEVEL_UPDATED,
|
300
|
+
{ details }: LevelUpdatedData,
|
301
|
+
) {
|
302
|
+
this.updateDateRangeCues(details, true);
|
303
|
+
}
|
304
|
+
|
305
|
+
private onLevelPtsUpdated(
|
306
|
+
event: Events.LEVEL_PTS_UPDATED,
|
307
|
+
data: LevelPTSUpdatedData,
|
308
|
+
) {
|
309
|
+
if (Math.abs(data.drift) > 0.01) {
|
310
|
+
this.updateDateRangeCues(data.details);
|
311
|
+
}
|
312
|
+
}
|
313
|
+
|
314
|
+
private updateDateRangeCues(details: LevelDetails, removeOldCues?: true) {
|
299
315
|
if (
|
300
316
|
!this.media ||
|
301
317
|
!details.hasProgramDateTime ||
|
@@ -307,7 +323,7 @@ class ID3TrackController implements ComponentAPI {
|
|
307
323
|
const { dateRanges } = details;
|
308
324
|
const ids = Object.keys(dateRanges);
|
309
325
|
// Remove cues from track not found in details.dateRanges
|
310
|
-
if (id3Track) {
|
326
|
+
if (id3Track && removeOldCues) {
|
311
327
|
const idsToRemove = Object.keys(dateRangeCuesAppended).filter(
|
312
328
|
(id) => !ids.includes(id),
|
313
329
|
);
|
@@ -329,26 +345,20 @@ class ID3TrackController implements ComponentAPI {
|
|
329
345
|
this.id3Track = this.createTrack(this.media);
|
330
346
|
}
|
331
347
|
|
332
|
-
const dateTimeOffset =
|
333
|
-
(lastFragment.programDateTime as number) / 1000 - lastFragment.start;
|
334
348
|
const Cue = getCueClass();
|
335
|
-
|
336
349
|
for (let i = 0; i < ids.length; i++) {
|
337
350
|
const id = ids[i];
|
338
351
|
const dateRange = dateRanges[id];
|
339
|
-
const startTime =
|
340
|
-
dateRange.startDate,
|
341
|
-
dateTimeOffset,
|
342
|
-
);
|
352
|
+
const startTime = dateRange.startTime;
|
343
353
|
|
344
354
|
// Process DateRanges to determine end-time (known DURATION, END-DATE, or END-ON-NEXT)
|
345
355
|
const appendedDateRangeCues = dateRangeCuesAppended[id];
|
346
356
|
const cues = appendedDateRangeCues?.cues || {};
|
347
357
|
let durationKnown = appendedDateRangeCues?.durationKnown || false;
|
348
358
|
let endTime = MAX_CUE_ENDTIME;
|
349
|
-
const endDate = dateRange
|
350
|
-
if (endDate) {
|
351
|
-
endTime =
|
359
|
+
const { duration, endDate } = dateRange;
|
360
|
+
if (endDate && duration !== null) {
|
361
|
+
endTime = startTime + duration;
|
352
362
|
durationKnown = true;
|
353
363
|
} else if (dateRange.endOnNext && !durationKnown) {
|
354
364
|
const nextDateRangeWithSameClass = ids.reduce(
|
@@ -369,10 +379,7 @@ class ID3TrackController implements ComponentAPI {
|
|
369
379
|
null,
|
370
380
|
);
|
371
381
|
if (nextDateRangeWithSameClass) {
|
372
|
-
endTime =
|
373
|
-
nextDateRangeWithSameClass.startDate,
|
374
|
-
dateTimeOffset,
|
375
|
-
);
|
382
|
+
endTime = nextDateRangeWithSameClass.startTime;
|
376
383
|
durationKnown = true;
|
377
384
|
}
|
378
385
|
}
|
@@ -389,6 +396,9 @@ class ID3TrackController implements ComponentAPI {
|
|
389
396
|
if (cue) {
|
390
397
|
if (durationKnown && !appendedDateRangeCues.durationKnown) {
|
391
398
|
cue.endTime = endTime;
|
399
|
+
} else if (Math.abs(cue.startTime - startTime) > 0.01) {
|
400
|
+
cue.startTime = startTime;
|
401
|
+
cue.endTime = endTime;
|
392
402
|
}
|
393
403
|
} else if (Cue) {
|
394
404
|
let data = dateRange.attr[key];
|
@@ -4,7 +4,11 @@ import { Events } from '../events';
|
|
4
4
|
import { BufferHelper, BufferInfo } from '../utils/buffer-helper';
|
5
5
|
import { FragmentState } from './fragment-tracker';
|
6
6
|
import { PlaylistContextType, PlaylistLevelType } from '../types/loader';
|
7
|
-
import {
|
7
|
+
import {
|
8
|
+
ElementaryStreamTypes,
|
9
|
+
Fragment,
|
10
|
+
MediaFragment,
|
11
|
+
} from '../loader/fragment';
|
8
12
|
import TransmuxerInterface from '../demux/transmuxer-interface';
|
9
13
|
import { ChunkMetadata } from '../types/transmuxer';
|
10
14
|
import GapController, { MAX_START_GAP_JUMP } from './gap-controller';
|
@@ -691,7 +695,8 @@ export default class StreamController
|
|
691
695
|
}
|
692
696
|
|
693
697
|
protected _handleFragmentLoadProgress(data: FragLoadedData) {
|
694
|
-
const
|
698
|
+
const frag = data.frag as MediaFragment;
|
699
|
+
const { part, payload } = data;
|
695
700
|
const { levels } = this;
|
696
701
|
if (!levels) {
|
697
702
|
this.warn(
|
@@ -729,7 +734,7 @@ export default class StreamController
|
|
729
734
|
const partial = partIndex !== -1;
|
730
735
|
const chunkMeta = new ChunkMetadata(
|
731
736
|
frag.level,
|
732
|
-
frag.sn
|
737
|
+
frag.sn,
|
733
738
|
frag.stats.chunkCount,
|
734
739
|
payload.byteLength,
|
735
740
|
partIndex,
|
@@ -1113,7 +1118,7 @@ export default class StreamController
|
|
1113
1118
|
}
|
1114
1119
|
|
1115
1120
|
// Avoid buffering if backtracking this fragment
|
1116
|
-
if (video && details
|
1121
|
+
if (video && details) {
|
1117
1122
|
const prevFrag = details.fragments[frag.sn - 1 - details.startSN];
|
1118
1123
|
const isFirstFragment = frag.sn === details.startSN;
|
1119
1124
|
const isFirstInDiscontinuity = !prevFrag || frag.cc > prevFrag.cc;
|
@@ -18,7 +18,7 @@ import type Hls from '../hls';
|
|
18
18
|
import type { FragmentTracker } from './fragment-tracker';
|
19
19
|
import type KeyLoader from '../loader/key-loader';
|
20
20
|
import type { LevelDetails } from '../loader/level-details';
|
21
|
-
import type { Fragment } from '../loader/fragment';
|
21
|
+
import type { Fragment, MediaFragment } from '../loader/fragment';
|
22
22
|
import type {
|
23
23
|
ErrorData,
|
24
24
|
FragLoadedData,
|
@@ -156,7 +156,7 @@ export class SubtitleStreamController
|
|
156
156
|
};
|
157
157
|
buffered.push(timeRange);
|
158
158
|
}
|
159
|
-
this.fragmentTracker.fragBuffered(frag);
|
159
|
+
this.fragmentTracker.fragBuffered(frag as MediaFragment);
|
160
160
|
this.fragBufferedComplete(frag, null);
|
161
161
|
}
|
162
162
|
|
@@ -206,7 +206,7 @@ export class SubtitleStreamController
|
|
206
206
|
|
207
207
|
if (frag?.type === PlaylistLevelType.SUBTITLE) {
|
208
208
|
if (data.details === ErrorDetails.FRAG_GAP) {
|
209
|
-
this.fragmentTracker.fragBuffered(frag, true);
|
209
|
+
this.fragmentTracker.fragBuffered(frag as MediaFragment, true);
|
210
210
|
}
|
211
211
|
if (this.fragCurrent) {
|
212
212
|
this.fragCurrent.abortRequests();
|
@@ -13,7 +13,7 @@ import Transmuxer, {
|
|
13
13
|
import { logger } from '../utils/logger';
|
14
14
|
import { ErrorTypes, ErrorDetails } from '../errors';
|
15
15
|
import { EventEmitter } from 'eventemitter3';
|
16
|
-
import {
|
16
|
+
import { MediaFragment, Part } from '../loader/fragment';
|
17
17
|
import { getM2TSSupportedAudioTypes } from '../utils/codecs';
|
18
18
|
import type { ChunkMetadata, TransmuxerResult } from '../types/transmuxer';
|
19
19
|
import type Hls from '../hls';
|
@@ -26,7 +26,7 @@ export default class TransmuxerInterface {
|
|
26
26
|
private hls: Hls;
|
27
27
|
private id: PlaylistLevelType;
|
28
28
|
private observer: HlsEventEmitter;
|
29
|
-
private frag:
|
29
|
+
private frag: MediaFragment | null = null;
|
30
30
|
private part: Part | null = null;
|
31
31
|
private useWorker: boolean;
|
32
32
|
private workerContext: WorkerContext | null = null;
|
@@ -173,7 +173,7 @@ export default class TransmuxerInterface {
|
|
173
173
|
initSegmentData: Uint8Array | undefined,
|
174
174
|
audioCodec: string | undefined,
|
175
175
|
videoCodec: string | undefined,
|
176
|
-
frag:
|
176
|
+
frag: MediaFragment,
|
177
177
|
part: Part | null,
|
178
178
|
duration: number,
|
179
179
|
accurateTimeOffset: boolean,
|
@@ -189,7 +189,7 @@ export default class TransmuxerInterface {
|
|
189
189
|
|
190
190
|
const discontinuity = !(lastFrag && frag.cc === lastFrag.cc);
|
191
191
|
const trackSwitch = !(lastFrag && chunkMeta.level === lastFrag.level);
|
192
|
-
const snDiff = lastFrag ? chunkMeta.sn -
|
192
|
+
const snDiff = lastFrag ? chunkMeta.sn - lastFrag.sn : -1;
|
193
193
|
const partDiff = this.part ? chunkMeta.part - this.part.index : -1;
|
194
194
|
const progressive =
|
195
195
|
snDiff === 0 &&
|
package/src/hls.ts
CHANGED
@@ -1068,7 +1068,7 @@ export type {
|
|
1068
1068
|
KeySystems,
|
1069
1069
|
KeySystemFormats,
|
1070
1070
|
} from './utils/mediakeys-helper';
|
1071
|
-
export type { DateRange } from './loader/date-range';
|
1071
|
+
export type { DateRange, DateRangeCue } from './loader/date-range';
|
1072
1072
|
export type { LoadStats } from './loader/load-stats';
|
1073
1073
|
export type { LevelKey } from './loader/level-key';
|
1074
1074
|
export type { LevelDetails } from './loader/level-details';
|
@@ -1118,6 +1118,7 @@ export type { ChunkMetadata } from './types/transmuxer';
|
|
1118
1118
|
export type {
|
1119
1119
|
BaseSegment,
|
1120
1120
|
Fragment,
|
1121
|
+
MediaFragment,
|
1121
1122
|
Part,
|
1122
1123
|
ElementaryStreams,
|
1123
1124
|
ElementaryStreamTypes,
|
@@ -1184,3 +1185,4 @@ export type {
|
|
1184
1185
|
IErrorAction,
|
1185
1186
|
} from './controller/error-controller';
|
1186
1187
|
export type { AttrList } from './utils/attr-list';
|
1188
|
+
export type { ParsedMultivariantPlaylist } from './loader/m3u8-parser';
|