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/package.json CHANGED
@@ -130,5 +130,5 @@
130
130
  "url-toolkit": "2.2.5",
131
131
  "wrangler": "3.59.0"
132
132
  },
133
- "version": "1.5.12-0.canary.10351"
133
+ "version": "1.5.12-0.canary.10353"
134
134
  }
@@ -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 { codecSet, videoRanges, minFramerate, minBitrate, preferHDR } =
698
- startTier;
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
- levelsSkipped.push(i);
793
- continue;
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:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`,
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
- last: boolean,
17
+ endOfSegment: boolean,
18
18
  duration: number,
19
19
  ) {
20
- const units = this.parseNALu(track, pes.data, last);
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 (last && VideoSample) {
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
- last: boolean,
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.lastNalu;
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
- if (!last) {
182
- track.lastNalu = unit;
183
- // logger.log('store NALu to push it on next PES');
184
- } else {
185
- units.push(unit);
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.lastNalu;
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
- last: boolean,
19
+ endOfSegment: boolean,
20
20
  duration: number,
21
21
  ) {
22
- const units = this.parseNALu(track, pes.data, last);
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 (last && VideoSample) {
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
- this.subtitleTrackController?.setSubtitleOption(subtitleOption);
826
- return null;
825
+ return (
826
+ this.subtitleTrackController?.setSubtitleOption(subtitleOption) || null
827
+ );
827
828
  }
828
829
 
829
830
  /**
@@ -77,7 +77,6 @@ export interface DemuxedVideoTrackBase extends DemuxedTrack {
77
77
  pps?: Uint8Array[];
78
78
  sps?: Uint8Array[];
79
79
  naluState?: number;
80
- lastNalu?: VideoSampleUnit | null;
81
80
  segmentCodec?: string;
82
81
  manifestCodec?: string;
83
82
  samples: VideoSample[] | Uint8Array;
@@ -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 = true;
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 = tier.channels[2] > 0;
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] =