hls.js 1.5.12-0.canary.10351 → 1.5.12-0.canary.10353
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 +60 -46
- package/dist/hls.js.d.ts +1 -0
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +57 -43
- 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 +55 -41
- 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 +58 -44
- 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 +1 -1
- package/src/controller/abr-controller.ts +15 -5
- package/src/demux/video/avc-video-parser.ts +3 -3
- package/src/demux/video/base-video-parser.ts +24 -20
- package/src/demux/video/hevc-video-parser.ts +3 -3
- package/src/hls.ts +4 -3
- package/src/types/demuxer.ts +0 -1
- package/src/types/media-playlist.ts +9 -1
- package/src/utils/hdr.ts +4 -7
- package/src/utils/rendition-helper.ts +26 -5
package/package.json
CHANGED
@@ -675,6 +675,7 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
675
675
|
const audioTracksByGroup =
|
676
676
|
this.audioTracksByGroup ||
|
677
677
|
(this.audioTracksByGroup = getAudioTracksByGroup(allAudioTracks));
|
678
|
+
let minStartIndex = -1;
|
678
679
|
if (firstSelection) {
|
679
680
|
if (this.firstSelection !== -1) {
|
680
681
|
return this.firstSelection;
|
@@ -694,8 +695,15 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
694
695
|
audioPreference,
|
695
696
|
videoPreference,
|
696
697
|
);
|
697
|
-
const {
|
698
|
-
|
698
|
+
const {
|
699
|
+
codecSet,
|
700
|
+
videoRanges,
|
701
|
+
minFramerate,
|
702
|
+
minBitrate,
|
703
|
+
minIndex,
|
704
|
+
preferHDR,
|
705
|
+
} = startTier;
|
706
|
+
minStartIndex = minIndex;
|
699
707
|
currentCodecSet = codecSet;
|
700
708
|
currentVideoRange = preferHDR
|
701
709
|
? videoRanges[videoRanges.length - 1]
|
@@ -789,8 +797,10 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
789
797
|
(levelInfo.supportedResult &&
|
790
798
|
!levelInfo.supportedResult.decodingInfoResults?.[0].smooth)
|
791
799
|
) {
|
792
|
-
|
793
|
-
|
800
|
+
if (firstSelection && i !== minStartIndex) {
|
801
|
+
levelsSkipped.push(i);
|
802
|
+
continue;
|
803
|
+
}
|
794
804
|
}
|
795
805
|
|
796
806
|
const levelDetails = levelInfo.details;
|
@@ -869,7 +879,7 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
869
879
|
1,
|
870
880
|
)} fetchDuration:${fetchDuration.toFixed(
|
871
881
|
1,
|
872
|
-
)} firstSelection:${firstSelection} codecSet:${
|
882
|
+
)} firstSelection:${firstSelection} codecSet:${level.codecSet} videoRange:${level.videoRange} hls.loadLevel:${loadLevel}`,
|
873
883
|
);
|
874
884
|
}
|
875
885
|
if (firstSelection) {
|
@@ -14,10 +14,10 @@ class AvcVideoParser extends BaseVideoParser {
|
|
14
14
|
track: DemuxedVideoTrack,
|
15
15
|
textTrack: DemuxedUserdataTrack,
|
16
16
|
pes: PES,
|
17
|
-
|
17
|
+
endOfSegment: boolean,
|
18
18
|
duration: number,
|
19
19
|
) {
|
20
|
-
const units = this.parseNALu(track, pes.data,
|
20
|
+
const units = this.parseNALu(track, pes.data, endOfSegment);
|
21
21
|
const debug = false;
|
22
22
|
let VideoSample = this.VideoSample;
|
23
23
|
let push: boolean;
|
@@ -206,7 +206,7 @@ class AvcVideoParser extends BaseVideoParser {
|
|
206
206
|
}
|
207
207
|
});
|
208
208
|
// if last PES packet, push samples
|
209
|
-
if (
|
209
|
+
if (endOfSegment && VideoSample) {
|
210
210
|
this.pushAccessUnit(VideoSample, track);
|
211
211
|
this.VideoSample = null;
|
212
212
|
}
|
@@ -29,6 +29,22 @@ abstract class BaseVideoParser {
|
|
29
29
|
};
|
30
30
|
}
|
31
31
|
|
32
|
+
protected getLastNalUnit(
|
33
|
+
samples: VideoSample[],
|
34
|
+
): VideoSampleUnit | undefined {
|
35
|
+
let VideoSample = this.VideoSample;
|
36
|
+
let lastUnit: VideoSampleUnit | undefined;
|
37
|
+
// try to fallback to previous sample if current one is empty
|
38
|
+
if (!VideoSample || VideoSample.units.length === 0) {
|
39
|
+
VideoSample = samples[samples.length - 1];
|
40
|
+
}
|
41
|
+
if (VideoSample?.units) {
|
42
|
+
const units = VideoSample.units;
|
43
|
+
lastUnit = units[units.length - 1];
|
44
|
+
}
|
45
|
+
return lastUnit;
|
46
|
+
}
|
47
|
+
|
32
48
|
protected pushAccessUnit(
|
33
49
|
VideoSample: ParsedVideoSample,
|
34
50
|
videoTrack: DemuxedVideoTrack,
|
@@ -70,7 +86,7 @@ abstract class BaseVideoParser {
|
|
70
86
|
protected parseNALu(
|
71
87
|
track: DemuxedVideoTrack,
|
72
88
|
array: Uint8Array,
|
73
|
-
|
89
|
+
endOfSegment: boolean,
|
74
90
|
): Array<{
|
75
91
|
data: Uint8Array;
|
76
92
|
type: number;
|
@@ -118,10 +134,6 @@ abstract class BaseVideoParser {
|
|
118
134
|
data: array.subarray(lastUnitStart, overflow),
|
119
135
|
type: lastUnitType,
|
120
136
|
};
|
121
|
-
if (track.lastNalu) {
|
122
|
-
units.push(track.lastNalu);
|
123
|
-
track.lastNalu = null;
|
124
|
-
}
|
125
137
|
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
126
138
|
units.push(unit);
|
127
139
|
} else {
|
@@ -129,7 +141,7 @@ abstract class BaseVideoParser {
|
|
129
141
|
// first check if start code delimiter is overlapping between 2 PES packets,
|
130
142
|
// ie it started in last packet (lastState not zero)
|
131
143
|
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
132
|
-
const lastUnit = track.
|
144
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
133
145
|
if (lastUnit) {
|
134
146
|
if (lastState && i <= 4 - lastState) {
|
135
147
|
// start delimiter overlapping between PES packets
|
@@ -152,8 +164,6 @@ abstract class BaseVideoParser {
|
|
152
164
|
array.subarray(0, overflow),
|
153
165
|
);
|
154
166
|
lastUnit.state = 0;
|
155
|
-
units.push(lastUnit);
|
156
|
-
track.lastNalu = null;
|
157
167
|
}
|
158
168
|
}
|
159
169
|
}
|
@@ -178,21 +188,15 @@ abstract class BaseVideoParser {
|
|
178
188
|
type: lastUnitType,
|
179
189
|
state: state,
|
180
190
|
};
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
// logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
|
187
|
-
}
|
188
|
-
} else if (units.length === 0) {
|
189
|
-
// no NALu found
|
191
|
+
units.push(unit);
|
192
|
+
// logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
|
193
|
+
}
|
194
|
+
// no NALu found
|
195
|
+
if (units.length === 0) {
|
190
196
|
// append pes.data to previous NAL unit
|
191
|
-
const lastUnit = track.
|
197
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
192
198
|
if (lastUnit) {
|
193
199
|
lastUnit.data = appendUint8Array(lastUnit.data, array);
|
194
|
-
units.push(lastUnit);
|
195
|
-
track.lastNalu = null;
|
196
200
|
}
|
197
201
|
}
|
198
202
|
track.naluState = state;
|
@@ -16,10 +16,10 @@ class HevcVideoParser extends BaseVideoParser {
|
|
16
16
|
track: DemuxedVideoTrack,
|
17
17
|
textTrack: DemuxedUserdataTrack,
|
18
18
|
pes: PES,
|
19
|
-
|
19
|
+
endOfSegment: boolean,
|
20
20
|
duration: number,
|
21
21
|
) {
|
22
|
-
const units = this.parseNALu(track, pes.data,
|
22
|
+
const units = this.parseNALu(track, pes.data, endOfSegment);
|
23
23
|
const debug = false;
|
24
24
|
let VideoSample = this.VideoSample;
|
25
25
|
let push: boolean;
|
@@ -244,7 +244,7 @@ class HevcVideoParser extends BaseVideoParser {
|
|
244
244
|
}
|
245
245
|
});
|
246
246
|
// if last PES packet, push samples
|
247
|
-
if (
|
247
|
+
if (endOfSegment && VideoSample) {
|
248
248
|
this.pushAccessUnit(VideoSample, track);
|
249
249
|
this.VideoSample = null;
|
250
250
|
}
|
package/src/hls.ts
CHANGED
@@ -813,7 +813,7 @@ export default class Hls implements HlsEventEmitter {
|
|
813
813
|
public setAudioOption(
|
814
814
|
audioOption: MediaPlaylist | AudioSelectionOption | undefined,
|
815
815
|
): MediaPlaylist | null {
|
816
|
-
return this.audioTrackController?.setAudioOption(audioOption);
|
816
|
+
return this.audioTrackController?.setAudioOption(audioOption) || null;
|
817
817
|
}
|
818
818
|
/**
|
819
819
|
* Find and select the best matching subtitle track, making a level switch when a Group change is necessary.
|
@@ -822,8 +822,9 @@ export default class Hls implements HlsEventEmitter {
|
|
822
822
|
public setSubtitleOption(
|
823
823
|
subtitleOption: MediaPlaylist | SubtitleSelectionOption | undefined,
|
824
824
|
): MediaPlaylist | null {
|
825
|
-
|
826
|
-
|
825
|
+
return (
|
826
|
+
this.subtitleTrackController?.setSubtitleOption(subtitleOption) || null
|
827
|
+
);
|
827
828
|
}
|
828
829
|
|
829
830
|
/**
|
package/src/types/demuxer.ts
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import type { AttrList } from '../utils/attr-list';
|
2
2
|
import type { LevelDetails } from '../loader/level-details';
|
3
|
-
import type { VideoRange } from './level';
|
3
|
+
import type { Level, VideoRange } from './level';
|
4
|
+
import type { PlaylistLevelType } from './loader';
|
4
5
|
|
5
6
|
export type AudioPlaylistType = 'AUDIO';
|
6
7
|
|
@@ -10,9 +11,16 @@ export type SubtitlePlaylistType = 'SUBTITLES' | 'CLOSED-CAPTIONS';
|
|
10
11
|
|
11
12
|
export type MediaPlaylistType = MainPlaylistType | SubtitlePlaylistType;
|
12
13
|
|
14
|
+
export type MediaSelection = {
|
15
|
+
[PlaylistLevelType.MAIN]: Level;
|
16
|
+
[PlaylistLevelType.AUDIO]?: MediaPlaylist;
|
17
|
+
[PlaylistLevelType.SUBTITLE]?: MediaPlaylist;
|
18
|
+
};
|
19
|
+
|
13
20
|
export type VideoSelectionOption = {
|
14
21
|
preferHDR?: boolean;
|
15
22
|
allowedVideoRanges?: Array<VideoRange>;
|
23
|
+
videoCodec?: string;
|
16
24
|
};
|
17
25
|
|
18
26
|
export type AudioSelectionOption = {
|
package/src/utils/hdr.ts
CHANGED
@@ -49,16 +49,13 @@ export function getVideoSelectionOptions(
|
|
49
49
|
if (videoPreference) {
|
50
50
|
allowedVideoRanges =
|
51
51
|
videoPreference.allowedVideoRanges || VideoRangeValues.slice(0);
|
52
|
+
const allowAutoPreferHDR =
|
53
|
+
allowedVideoRanges.join('') !== 'SDR' && !videoPreference.videoCodec;
|
52
54
|
preferHDR =
|
53
55
|
videoPreference.preferHDR !== undefined
|
54
56
|
? videoPreference.preferHDR
|
55
|
-
: isHdrSupported();
|
56
|
-
|
57
|
-
if (preferHDR) {
|
58
|
-
allowedVideoRanges = allowedVideoRanges.filter(
|
59
|
-
(range: VideoRange) => range !== 'SDR',
|
60
|
-
);
|
61
|
-
} else {
|
57
|
+
: allowAutoPreferHDR && isHdrSupported();
|
58
|
+
if (!preferHDR) {
|
62
59
|
allowedVideoRanges = ['SDR'];
|
63
60
|
}
|
64
61
|
}
|
@@ -13,6 +13,7 @@ export type CodecSetTier = {
|
|
13
13
|
minBitrate: number;
|
14
14
|
minHeight: number;
|
15
15
|
minFramerate: number;
|
16
|
+
minIndex: number;
|
16
17
|
maxScore: number;
|
17
18
|
videoRanges: Record<string, number>;
|
18
19
|
channels: Record<string, number>;
|
@@ -32,6 +33,7 @@ type StartParameters = {
|
|
32
33
|
preferHDR: boolean;
|
33
34
|
minFramerate: number;
|
34
35
|
minBitrate: number;
|
36
|
+
minIndex: number;
|
35
37
|
};
|
36
38
|
|
37
39
|
export function getStartCodecTier(
|
@@ -44,13 +46,15 @@ export function getStartCodecTier(
|
|
44
46
|
const codecSets = Object.keys(codecTiers);
|
45
47
|
const channelsPreference = audioPreference?.channels;
|
46
48
|
const audioCodecPreference = audioPreference?.audioCodec;
|
49
|
+
const videoCodecPreference = videoPreference?.videoCodec;
|
47
50
|
const preferStereo = channelsPreference && parseInt(channelsPreference) === 2;
|
48
51
|
// Use first level set to determine stereo, and minimum resolution and framerate
|
49
|
-
let hasStereo =
|
52
|
+
let hasStereo = false;
|
50
53
|
let hasCurrentVideoRange = false;
|
51
54
|
let minHeight = Infinity;
|
52
55
|
let minFramerate = Infinity;
|
53
56
|
let minBitrate = Infinity;
|
57
|
+
let minIndex = Infinity;
|
54
58
|
let selectedScore = 0;
|
55
59
|
let videoRanges: Array<VideoRange> = [];
|
56
60
|
|
@@ -61,7 +65,7 @@ export function getStartCodecTier(
|
|
61
65
|
|
62
66
|
for (let i = codecSets.length; i--; ) {
|
63
67
|
const tier = codecTiers[codecSets[i]];
|
64
|
-
hasStereo
|
68
|
+
hasStereo ||= tier.channels[2] > 0;
|
65
69
|
minHeight = Math.min(minHeight, tier.minHeight);
|
66
70
|
minFramerate = Math.min(minFramerate, tier.minFramerate);
|
67
71
|
minBitrate = Math.min(minBitrate, tier.minBitrate);
|
@@ -70,7 +74,6 @@ export function getStartCodecTier(
|
|
70
74
|
);
|
71
75
|
if (matchingVideoRanges.length > 0) {
|
72
76
|
hasCurrentVideoRange = true;
|
73
|
-
videoRanges = matchingVideoRanges;
|
74
77
|
}
|
75
78
|
}
|
76
79
|
minHeight = Number.isFinite(minHeight) ? minHeight : 0;
|
@@ -82,7 +85,6 @@ export function getStartCodecTier(
|
|
82
85
|
// If there are no variants with matching preference, set currentVideoRange to undefined
|
83
86
|
if (!hasCurrentVideoRange) {
|
84
87
|
currentVideoRange = undefined;
|
85
|
-
videoRanges = [];
|
86
88
|
}
|
87
89
|
const codecSet = codecSets.reduce(
|
88
90
|
(selected: string | undefined, candidate: string) => {
|
@@ -91,6 +93,11 @@ export function getStartCodecTier(
|
|
91
93
|
if (candidate === selected) {
|
92
94
|
return selected;
|
93
95
|
}
|
96
|
+
videoRanges = hasCurrentVideoRange
|
97
|
+
? allowedVideoRanges.filter(
|
98
|
+
(range) => candidateTier.videoRanges[range] > 0,
|
99
|
+
)
|
100
|
+
: [];
|
94
101
|
if (candidateTier.minBitrate > currentBw) {
|
95
102
|
logStartCodecCandidateIgnored(
|
96
103
|
candidate,
|
@@ -159,6 +166,16 @@ export function getStartCodecTier(
|
|
159
166
|
);
|
160
167
|
return selected;
|
161
168
|
}
|
169
|
+
if (
|
170
|
+
videoCodecPreference &&
|
171
|
+
candidate.indexOf(videoCodecPreference.substring(0, 4)) % 5 !== 0
|
172
|
+
) {
|
173
|
+
logStartCodecCandidateIgnored(
|
174
|
+
candidate,
|
175
|
+
`video codec preference "${videoCodecPreference}" not found`,
|
176
|
+
);
|
177
|
+
return selected;
|
178
|
+
}
|
162
179
|
if (candidateTier.maxScore < selectedScore) {
|
163
180
|
logStartCodecCandidateIgnored(
|
164
181
|
candidate,
|
@@ -175,6 +192,7 @@ export function getStartCodecTier(
|
|
175
192
|
) {
|
176
193
|
return selected;
|
177
194
|
}
|
195
|
+
minIndex = candidateTier.minIndex;
|
178
196
|
selectedScore = candidateTier.maxScore;
|
179
197
|
return candidate;
|
180
198
|
},
|
@@ -186,6 +204,7 @@ export function getStartCodecTier(
|
|
186
204
|
preferHDR,
|
187
205
|
minFramerate,
|
188
206
|
minBitrate,
|
207
|
+
minIndex,
|
189
208
|
};
|
190
209
|
}
|
191
210
|
|
@@ -243,7 +262,7 @@ export function getCodecTiers(
|
|
243
262
|
): Record<string, CodecSetTier> {
|
244
263
|
return levels
|
245
264
|
.slice(minAutoLevel, maxAutoLevel + 1)
|
246
|
-
.reduce((tiers: Record<string, CodecSetTier>, level) => {
|
265
|
+
.reduce((tiers: Record<string, CodecSetTier>, level, index) => {
|
247
266
|
if (!level.codecSet) {
|
248
267
|
return tiers;
|
249
268
|
}
|
@@ -254,6 +273,7 @@ export function getCodecTiers(
|
|
254
273
|
minBitrate: Infinity,
|
255
274
|
minHeight: Infinity,
|
256
275
|
minFramerate: Infinity,
|
276
|
+
minIndex: index,
|
257
277
|
maxScore: 0,
|
258
278
|
videoRanges: { SDR: 0 },
|
259
279
|
channels: { '2': 0 },
|
@@ -265,6 +285,7 @@ export function getCodecTiers(
|
|
265
285
|
const lesserWidthOrHeight = Math.min(level.height, level.width);
|
266
286
|
tier.minHeight = Math.min(tier.minHeight, lesserWidthOrHeight);
|
267
287
|
tier.minFramerate = Math.min(tier.minFramerate, level.frameRate);
|
288
|
+
tier.minIndex = Math.min(tier.minIndex, index);
|
268
289
|
tier.maxScore = Math.max(tier.maxScore, level.score);
|
269
290
|
tier.fragmentError += level.fragmentError;
|
270
291
|
tier.videoRanges[level.videoRange] =
|