hls.js 1.6.6-0.canary.11352 → 1.6.6-0.canary.11353
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.d.mts +1 -0
- package/dist/hls.d.ts +1 -0
- package/dist/hls.js +126 -72
- package/dist/hls.js.d.ts +1 -0
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +135 -89
- 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 +125 -80
- 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 +117 -68
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/package.json +1 -1
- package/src/controller/abr-controller.ts +21 -7
- package/src/utils/mediacapabilities-helper.ts +135 -81
@@ -8,7 +8,6 @@ import {
|
|
8
8
|
requiresMediaCapabilitiesDecodingInfo,
|
9
9
|
SUPPORTED_INFO_DEFAULT,
|
10
10
|
} from '../utils/mediacapabilities-helper';
|
11
|
-
import { isHEVC } from '../utils/mp4-tools';
|
12
11
|
import {
|
13
12
|
type AudioTracksByGroup,
|
14
13
|
type CodecSetTier,
|
@@ -47,6 +46,10 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
47
46
|
private partCurrent: Part | null = null;
|
48
47
|
private bitrateTestDelay: number = 0;
|
49
48
|
private rebufferNotice: number = -1;
|
49
|
+
private supportedCache: Record<
|
50
|
+
string,
|
51
|
+
Promise<MediaCapabilitiesDecodingInfo>
|
52
|
+
> = {};
|
50
53
|
|
51
54
|
public bwEstimator: EwmaBandWidthEstimator;
|
52
55
|
|
@@ -108,7 +111,7 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
108
111
|
this.unregisterListeners();
|
109
112
|
this.clearTimer();
|
110
113
|
// @ts-ignore
|
111
|
-
this.hls = this._abandonRulesCheck = null;
|
114
|
+
this.hls = this._abandonRulesCheck = this.supportedCache = null;
|
112
115
|
this.fragCurrent = this.partCurrent = null;
|
113
116
|
}
|
114
117
|
|
@@ -119,6 +122,7 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
119
122
|
this.lastLoadedFragLevel = -1;
|
120
123
|
this.firstSelection = -1;
|
121
124
|
this.lastLevelLoadSec = 0;
|
125
|
+
this.supportedCache = {};
|
122
126
|
this.fragCurrent = this.partCurrent = null;
|
123
127
|
this.onLevelsUpdated();
|
124
128
|
this.clearTimer();
|
@@ -826,20 +830,20 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
826
830
|
| undefined;
|
827
831
|
if (
|
828
832
|
typeof mediaCapabilities?.decodingInfo === 'function' &&
|
829
|
-
|
833
|
+
requiresMediaCapabilitiesDecodingInfo(
|
830
834
|
levelInfo,
|
831
835
|
audioTracksByGroup,
|
832
836
|
currentVideoRange,
|
833
837
|
currentFrameRate,
|
834
838
|
currentBw,
|
835
839
|
audioPreference,
|
836
|
-
)
|
837
|
-
isHEVC(levelInfo.videoCodec)) // Force media capabilities check for HEVC to avoid failure on Windows
|
840
|
+
)
|
838
841
|
) {
|
839
842
|
levelInfo.supportedPromise = getMediaDecodingInfoPromise(
|
840
843
|
levelInfo,
|
841
844
|
audioTracksByGroup,
|
842
845
|
mediaCapabilities,
|
846
|
+
this.supportedCache,
|
843
847
|
);
|
844
848
|
levelInfo.supportedPromise.then((decodingInfo) => {
|
845
849
|
if (!this.hls) {
|
@@ -867,6 +871,15 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
867
871
|
this.hls.nextLoadLevel = 0;
|
868
872
|
}
|
869
873
|
}
|
874
|
+
} else if (
|
875
|
+
decodingInfo.decodingInfoResults.some(
|
876
|
+
(info) =>
|
877
|
+
info.smooth === false || info.powerEfficient === false,
|
878
|
+
)
|
879
|
+
) {
|
880
|
+
this.log(
|
881
|
+
`MediaCapabilities decodingInfo for level ${index} not smooth or powerEfficient: ${stringify(decodingInfo)}`,
|
882
|
+
);
|
870
883
|
}
|
871
884
|
});
|
872
885
|
} else {
|
@@ -883,8 +896,9 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
883
896
|
(!upSwitch &&
|
884
897
|
currentFrameRate > 0 &&
|
885
898
|
currentFrameRate < levelInfo.frameRate) ||
|
886
|
-
|
887
|
-
|
899
|
+
levelInfo.supportedResult?.decodingInfoResults?.some(
|
900
|
+
(info) => info.smooth === false,
|
901
|
+
)
|
888
902
|
) {
|
889
903
|
if (!firstSelection || i !== minStartIndex) {
|
890
904
|
levelsSkipped.push(i);
|
@@ -16,8 +16,6 @@ export type MediaDecodingInfo = {
|
|
16
16
|
error?: Error;
|
17
17
|
};
|
18
18
|
|
19
|
-
type BaseVideoConfiguration = Omit<VideoConfiguration, 'contentType'>;
|
20
|
-
|
21
19
|
export const SUPPORTED_INFO_DEFAULT: MediaDecodingInfo = {
|
22
20
|
supported: true,
|
23
21
|
configurations: [] as MediaDecodingConfiguration[],
|
@@ -48,11 +46,6 @@ export function getUnsupportedResult(
|
|
48
46
|
};
|
49
47
|
}
|
50
48
|
|
51
|
-
export const SUPPORTED_INFO_CACHE: Record<
|
52
|
-
string,
|
53
|
-
Promise<MediaCapabilitiesDecodingInfo>
|
54
|
-
> = {};
|
55
|
-
|
56
49
|
export function requiresMediaCapabilitiesDecodingInfo(
|
57
50
|
level: Level,
|
58
51
|
audioTracksByGroup: AudioTracksByGroup,
|
@@ -62,6 +55,7 @@ export function requiresMediaCapabilitiesDecodingInfo(
|
|
62
55
|
audioPreference: AudioSelectionOption | undefined,
|
63
56
|
): boolean {
|
64
57
|
// Only test support when configuration is exceeds minimum options
|
58
|
+
const videoCodecs = level.videoCodec;
|
65
59
|
const audioGroups = level.audioCodec ? level.audioGroups : null;
|
66
60
|
const audioCodecPreference = audioPreference?.audioCodec;
|
67
61
|
const channelsPreference = audioPreference?.channels;
|
@@ -98,8 +92,10 @@ export function requiresMediaCapabilitiesDecodingInfo(
|
|
98
92
|
}
|
99
93
|
}
|
100
94
|
return (
|
101
|
-
(
|
102
|
-
|
95
|
+
(videoCodecs !== undefined &&
|
96
|
+
// Force media capabilities check for HEVC to avoid failure on Windows
|
97
|
+
(videoCodecs.split(',').some((videoCodec) => isHEVC(videoCodec)) ||
|
98
|
+
(level.width > 1920 && level.height > 1088) ||
|
103
99
|
(level.height > 1920 && level.width > 1088) ||
|
104
100
|
level.frameRate > Math.max(currentFrameRate, 30) ||
|
105
101
|
(level.videoRange !== 'SDR' &&
|
@@ -117,85 +113,56 @@ export function getMediaDecodingInfoPromise(
|
|
117
113
|
level: Level,
|
118
114
|
audioTracksByGroup: AudioTracksByGroup,
|
119
115
|
mediaCapabilities: MediaCapabilities | undefined,
|
116
|
+
cache: Record<string, Promise<MediaCapabilitiesDecodingInfo>> = {},
|
120
117
|
): Promise<MediaDecodingInfo> {
|
121
118
|
const videoCodecs = level.videoCodec;
|
122
|
-
|
123
|
-
if ((!videoCodecs && !audioCodecs) || !mediaCapabilities) {
|
119
|
+
if ((!videoCodecs && !level.audioCodec) || !mediaCapabilities) {
|
124
120
|
return Promise.resolve(SUPPORTED_INFO_DEFAULT);
|
125
121
|
}
|
126
122
|
|
127
123
|
const configurations: MediaDecodingConfiguration[] = [];
|
128
124
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
125
|
+
const videoDecodeList = makeVideoConfigurations(level);
|
126
|
+
const videoCount = videoDecodeList.length;
|
127
|
+
const audioDecodeList = makeAudioConfigurations(
|
128
|
+
level,
|
129
|
+
audioTracksByGroup,
|
130
|
+
videoCount > 0,
|
131
|
+
);
|
132
|
+
const audioCount = audioDecodeList.length;
|
133
|
+
for (let i = videoCount || 1 * audioCount || 1; i--; ) {
|
134
|
+
const configuration: MediaDecodingConfiguration = {
|
135
|
+
type: 'media-source',
|
136
136
|
};
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
137
|
+
if (videoCount) {
|
138
|
+
configuration.video = videoDecodeList[i % videoCount];
|
139
|
+
}
|
140
|
+
if (audioCount) {
|
141
|
+
configuration.audio = audioDecodeList[i % audioCount];
|
142
|
+
const audioBitrate = configuration.audio.bitrate;
|
143
|
+
if (configuration.video && audioBitrate) {
|
144
|
+
configuration.video.bitrate -= audioBitrate;
|
145
|
+
}
|
141
146
|
}
|
142
|
-
|
147
|
+
configurations.push(configuration);
|
148
|
+
}
|
149
|
+
|
150
|
+
if (videoCodecs) {
|
143
151
|
// Override Windows Firefox HEVC MediaCapabilities result (https://github.com/video-dev/hls.js/issues/7046)
|
144
152
|
const ua = navigator.userAgent;
|
145
153
|
if (
|
146
|
-
|
154
|
+
videoCodecs.split(',').some((videoCodec) => isHEVC(videoCodec)) &&
|
147
155
|
userAgentHevcSupportIsInaccurate()
|
148
156
|
) {
|
149
157
|
return Promise.resolve(
|
150
158
|
getUnsupportedResult(
|
151
159
|
new Error(
|
152
|
-
`Overriding Windows Firefox HEVC MediaCapabilities result based on user-agent
|
160
|
+
`Overriding Windows Firefox HEVC MediaCapabilities result based on user-agent string: (${ua})`,
|
153
161
|
),
|
154
162
|
configurations,
|
155
163
|
),
|
156
164
|
);
|
157
165
|
}
|
158
|
-
configurations.push.apply(
|
159
|
-
configurations,
|
160
|
-
videoCodecsArray.map((videoCodec) => ({
|
161
|
-
type: 'media-source',
|
162
|
-
video: {
|
163
|
-
...baseVideoConfiguration,
|
164
|
-
contentType: mimeTypeForCodec(
|
165
|
-
fillInMissingAV01Params(videoCodec),
|
166
|
-
'video',
|
167
|
-
),
|
168
|
-
},
|
169
|
-
})),
|
170
|
-
);
|
171
|
-
}
|
172
|
-
|
173
|
-
if (audioCodecs && level.audioGroups) {
|
174
|
-
level.audioGroups.forEach((audioGroupId) => {
|
175
|
-
if (!audioGroupId) {
|
176
|
-
return;
|
177
|
-
}
|
178
|
-
audioTracksByGroup.groups[audioGroupId]?.tracks.forEach((audioTrack) => {
|
179
|
-
if (audioTrack.groupId === audioGroupId) {
|
180
|
-
const channels = audioTrack.channels || '';
|
181
|
-
const channelsNumber = parseFloat(channels);
|
182
|
-
if (Number.isFinite(channelsNumber) && channelsNumber > 2) {
|
183
|
-
configurations.push.apply(
|
184
|
-
configurations,
|
185
|
-
audioCodecs.split(',').map((audioCodec) => ({
|
186
|
-
type: 'media-source',
|
187
|
-
audio: {
|
188
|
-
contentType: mimeTypeForCodec(audioCodec, 'audio'),
|
189
|
-
channels: '' + channelsNumber,
|
190
|
-
// spatialRendering:
|
191
|
-
// audioCodec === 'ec-3' && channels.indexOf('JOC'),
|
192
|
-
},
|
193
|
-
})),
|
194
|
-
);
|
195
|
-
}
|
196
|
-
}
|
197
|
-
});
|
198
|
-
});
|
199
166
|
}
|
200
167
|
|
201
168
|
return Promise.all(
|
@@ -203,9 +170,8 @@ export function getMediaDecodingInfoPromise(
|
|
203
170
|
// Cache MediaCapabilities promises
|
204
171
|
const decodingInfoKey = getMediaDecodingInfoKey(configuration);
|
205
172
|
return (
|
206
|
-
|
207
|
-
(
|
208
|
-
mediaCapabilities.decodingInfo(configuration))
|
173
|
+
cache[decodingInfoKey] ||
|
174
|
+
(cache[decodingInfoKey] = mediaCapabilities.decodingInfo(configuration))
|
209
175
|
);
|
210
176
|
}),
|
211
177
|
)
|
@@ -222,19 +188,107 @@ export function getMediaDecodingInfoPromise(
|
|
222
188
|
}));
|
223
189
|
}
|
224
190
|
|
191
|
+
function makeVideoConfigurations(level: Level): VideoConfiguration[] {
|
192
|
+
const videoCodecs = level.videoCodec?.split(',');
|
193
|
+
const bitrate = getVariantDecodingBitrate(level);
|
194
|
+
const width = level.width || 640;
|
195
|
+
const height = level.height || 480;
|
196
|
+
// Assume a framerate of 30fps since MediaCapabilities will not accept Level default of 0.
|
197
|
+
const framerate = level.frameRate || 30;
|
198
|
+
const videoRange = level.videoRange.toLowerCase() as 'sdr' | 'pq' | 'hlg';
|
199
|
+
return videoCodecs
|
200
|
+
? videoCodecs.map((videoCodec: string) => {
|
201
|
+
const videoConfiguration: VideoConfiguration = {
|
202
|
+
contentType: mimeTypeForCodec(
|
203
|
+
fillInMissingAV01Params(videoCodec),
|
204
|
+
'video',
|
205
|
+
),
|
206
|
+
width,
|
207
|
+
height,
|
208
|
+
bitrate,
|
209
|
+
framerate,
|
210
|
+
};
|
211
|
+
if (videoRange !== 'sdr') {
|
212
|
+
videoConfiguration.transferFunction = videoRange as TransferFunction;
|
213
|
+
}
|
214
|
+
return videoConfiguration;
|
215
|
+
})
|
216
|
+
: [];
|
217
|
+
}
|
218
|
+
|
219
|
+
function makeAudioConfigurations(
|
220
|
+
level: Level,
|
221
|
+
audioTracksByGroup: AudioTracksByGroup,
|
222
|
+
hasVideo: boolean,
|
223
|
+
): AudioConfiguration[] {
|
224
|
+
const audioCodecs = level.audioCodec?.split(',');
|
225
|
+
const combinedBitrate = getVariantDecodingBitrate(level);
|
226
|
+
if (audioCodecs && level.audioGroups) {
|
227
|
+
return level.audioGroups.reduce((configurations, audioGroupId) => {
|
228
|
+
const tracks = audioGroupId
|
229
|
+
? audioTracksByGroup.groups[audioGroupId]?.tracks
|
230
|
+
: null;
|
231
|
+
if (tracks) {
|
232
|
+
return tracks.reduce((configs, audioTrack) => {
|
233
|
+
if (audioTrack.groupId === audioGroupId) {
|
234
|
+
const channelsNumber = parseFloat(audioTrack.channels || '');
|
235
|
+
audioCodecs.forEach((audioCodec) => {
|
236
|
+
const audioConfiguration: AudioConfiguration = {
|
237
|
+
contentType: mimeTypeForCodec(audioCodec, 'audio'),
|
238
|
+
bitrate: hasVideo
|
239
|
+
? estimatedAudioBitrate(audioCodec, combinedBitrate)
|
240
|
+
: combinedBitrate,
|
241
|
+
};
|
242
|
+
if (channelsNumber) {
|
243
|
+
audioConfiguration.channels = '' + channelsNumber;
|
244
|
+
}
|
245
|
+
configs.push(audioConfiguration);
|
246
|
+
});
|
247
|
+
}
|
248
|
+
return configs;
|
249
|
+
}, configurations);
|
250
|
+
}
|
251
|
+
return configurations;
|
252
|
+
}, [] as AudioConfiguration[]);
|
253
|
+
}
|
254
|
+
return [];
|
255
|
+
}
|
256
|
+
|
257
|
+
function estimatedAudioBitrate(
|
258
|
+
audioCodec: string,
|
259
|
+
levelBitrate: number,
|
260
|
+
): number {
|
261
|
+
if (levelBitrate <= 1) {
|
262
|
+
return 1;
|
263
|
+
}
|
264
|
+
let audioBitrate = 128000;
|
265
|
+
if (audioCodec === 'ec-3') {
|
266
|
+
audioBitrate = 768000;
|
267
|
+
} else if (audioCodec === 'ac-3') {
|
268
|
+
audioBitrate = 640000;
|
269
|
+
}
|
270
|
+
return Math.min(levelBitrate / 2, audioBitrate); // Don't exceed some % of level bitrate
|
271
|
+
}
|
272
|
+
|
273
|
+
function getVariantDecodingBitrate(level: Level): number {
|
274
|
+
return (
|
275
|
+
Math.ceil(Math.max(level.bitrate * 0.9, level.averageBitrate) / 1000) *
|
276
|
+
1000 || 1
|
277
|
+
);
|
278
|
+
}
|
279
|
+
|
225
280
|
function getMediaDecodingInfoKey(config: MediaDecodingConfiguration): string {
|
281
|
+
let key = '';
|
226
282
|
const { audio, video } = config;
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
return `c${audio.channels}${audio.spatialRendering ? 's' : 'n'}_${codec}`;
|
237
|
-
}
|
283
|
+
if (video) {
|
284
|
+
const codec = getCodecsForMimeType(video.contentType);
|
285
|
+
key += `${codec}_r${video.height}x${video.width}f${Math.ceil(video.framerate)}${
|
286
|
+
video.transferFunction || 'sd'
|
287
|
+
}_${Math.ceil(video.bitrate / 1e5)}`;
|
288
|
+
}
|
289
|
+
if (audio) {
|
290
|
+
const codec = getCodecsForMimeType(audio.contentType);
|
291
|
+
key += `${video ? '_' : ''}${codec}_c${audio.channels}`;
|
238
292
|
}
|
239
|
-
return
|
293
|
+
return key;
|
240
294
|
}
|