hls.js 1.6.6-0.canary.11352 → 1.6.6-0.canary.11354
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 +136 -74
- 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 +127 -70
- 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 +21 -7
- package/src/controller/interstitials-controller.ts +17 -2
- package/src/demux/transmuxer-worker.ts +1 -1
- package/src/utils/mediacapabilities-helper.ts +135 -81
package/package.json
CHANGED
@@ -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);
|
@@ -1084,6 +1084,21 @@ export default class InterstitialsController
|
|
1084
1084
|
scheduleIndex: index,
|
1085
1085
|
player,
|
1086
1086
|
});
|
1087
|
+
if (currentItem !== this.playingItem) {
|
1088
|
+
// Schedule change occured on INTERSTITIAL_ASSET_ENDED
|
1089
|
+
if (
|
1090
|
+
this.itemsMatch(currentItem, this.playingItem) &&
|
1091
|
+
!this.playingAsset
|
1092
|
+
) {
|
1093
|
+
this.advanceAfterAssetEnded(
|
1094
|
+
interstitial,
|
1095
|
+
this.findItemIndex(this.playingItem),
|
1096
|
+
playingAssetListIndex,
|
1097
|
+
);
|
1098
|
+
}
|
1099
|
+
// Navigation occured on INTERSTITIAL_ASSET_ENDED
|
1100
|
+
return;
|
1101
|
+
}
|
1087
1102
|
this.retreiveMediaSource(assetId, scheduledItem);
|
1088
1103
|
if (player.media && !this.detachedData?.mediaSource) {
|
1089
1104
|
player.detachMedia();
|
@@ -1107,7 +1122,7 @@ export default class InterstitialsController
|
|
1107
1122
|
this.updateSchedule();
|
1108
1123
|
const items = this.schedule.items;
|
1109
1124
|
if (scheduledItem && items) {
|
1110
|
-
const updatedIndex = this.
|
1125
|
+
const updatedIndex = this.findItemIndex(scheduledItem);
|
1111
1126
|
this.advanceSchedule(
|
1112
1127
|
updatedIndex,
|
1113
1128
|
items,
|
@@ -2537,7 +2552,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
2537
2552
|
// If all assets in interstitial fail, mark the interstitial with an error
|
2538
2553
|
if (!interstitial.assetList.some((asset) => !asset.error)) {
|
2539
2554
|
interstitial.error = error;
|
2540
|
-
} else
|
2555
|
+
} else {
|
2541
2556
|
// Reset level details and reload/parse media playlists to align with updated schedule
|
2542
2557
|
for (let i = assetListIndex; i < interstitial.assetList.length; i++) {
|
2543
2558
|
this.resetAssetPlayer(interstitial.assetList[i].identifier);
|
@@ -2,7 +2,7 @@ import { EventEmitter } from 'eventemitter3';
|
|
2
2
|
import Transmuxer, { isPromise } from '../demux/transmuxer';
|
3
3
|
import { ErrorDetails, ErrorTypes } from '../errors';
|
4
4
|
import { Events } from '../events';
|
5
|
-
import { enableLogs, type
|
5
|
+
import { enableLogs, type ILogger } from '../utils/logger';
|
6
6
|
import type { RemuxedTrack, RemuxerResult } from '../types/remuxer';
|
7
7
|
import type { ChunkMetadata, TransmuxerResult } from '../types/transmuxer';
|
8
8
|
|
@@ -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
|
}
|