mirror of
https://github.com/video-dev/hls.js.git
synced 2026-06-16 13:34:44 +00:00
Update minimum browser support documentation and build config (#7880)
* Use Array.indexOf instead of Array.includes for legacy support * Update the compatibility section of the README along with @babel/preset-env, es-check, and eslint configs * Do not allow media timestamps (DTS) to map to negative values after timestampOffset is applied on remuxed TS (legacy browser support) Resolves #7876
This commit is contained in:
+38
-7
@@ -1,7 +1,38 @@
|
||||
{
|
||||
"ecmaVersion": "es5",
|
||||
"modules": "false",
|
||||
"files": [
|
||||
"./dist/**/*.js"
|
||||
]
|
||||
}
|
||||
[
|
||||
{
|
||||
"ecmaVersion": "es5",
|
||||
"module": false,
|
||||
"files": [
|
||||
"./dist/hls.js",
|
||||
"./dist/hls.min.js",
|
||||
"./dist/hls.light.js",
|
||||
"./dist/hls.light.min.js",
|
||||
"./dist/hls.worker.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ecmaVersion": "es2016",
|
||||
"module": false,
|
||||
"checkFeatures": true,
|
||||
"ignore": "globalThis,ErrorCause",
|
||||
"files": [
|
||||
"./dist/hls.light.js",
|
||||
"./dist/hls.light.min.js",
|
||||
"./dist/hls.worker.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ecmaVersion": "es2017",
|
||||
"module": false,
|
||||
"checkFeatures": true,
|
||||
"ignore": "globalThis,ErrorCause",
|
||||
"files": ["./dist/hls.js", "./dist/hls.min.js"]
|
||||
},
|
||||
{
|
||||
"ecmaVersion": "es2017",
|
||||
"module": true,
|
||||
"checkFeatures": true,
|
||||
"ignore": "globalThis,ErrorCause",
|
||||
"files": ["./dist/hls.mjs", "./dist/hls.light.mjs"]
|
||||
}
|
||||
]
|
||||
|
||||
+30
-30
@@ -2,12 +2,23 @@ const asyncKeywordConstraintMsg =
|
||||
'The async keyword adds a `regenerator` dependency in the hls.js ES5 output not allowed in v1 due to bundle size constraints.';
|
||||
const selfVsWindowGlobalMsg =
|
||||
'Use `self` instead of `window` to access the global context everywhere (including workers).';
|
||||
const arrayFindCompatibilityMsg =
|
||||
'Usage of Array find method is restricted for compatibility.';
|
||||
const arrayFindIndexCompatibilityMsg =
|
||||
'Usage of Array findIndex method is restricted for compatibility.';
|
||||
const arrayEveryCompatibilityMsg =
|
||||
'Usage of Array every method is restricted for compatibility. Use a negative Array some check instead.';
|
||||
const arrayIncludesCompatibilityMsg =
|
||||
'Usage of Array includes method is restricted for compatibility. Use Array indexOf instead. (For String includes, suppress this with an eslint-disable comment.)';
|
||||
|
||||
const baseRestrictedSyntax = [
|
||||
{
|
||||
selector: 'FunctionDeclaration[async=true]',
|
||||
message: asyncKeywordConstraintMsg,
|
||||
},
|
||||
{
|
||||
selector: 'ArrowFunctionExpression[async=true]',
|
||||
message: asyncKeywordConstraintMsg,
|
||||
},
|
||||
{
|
||||
selector: 'MethodDefinition[value.async=true]',
|
||||
message: asyncKeywordConstraintMsg,
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
env: { browser: true, commonjs: true, es6: true },
|
||||
@@ -31,6 +42,7 @@ module.exports = {
|
||||
// see https://github.com/standard/eslint-config-standard
|
||||
// 'prettier' (https://github.com/prettier/eslint-config-prettier) must be last
|
||||
extends: ['eslint:recommended', 'prettier'],
|
||||
ignorePatterns: ['dist/'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: { sourceType: 'module', project: './tsconfig.json' },
|
||||
plugins: ['@typescript-eslint', 'import', 'no-for-of-loops'],
|
||||
@@ -74,32 +86,11 @@ module.exports = {
|
||||
'no-use-before-define': 'off',
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
selector: 'FunctionDeclaration[async=true]',
|
||||
message: asyncKeywordConstraintMsg,
|
||||
},
|
||||
{
|
||||
selector: 'ArrowFunctionExpression[async=true]',
|
||||
message: asyncKeywordConstraintMsg,
|
||||
},
|
||||
{
|
||||
selector: 'MethodDefinition[value.async=true]',
|
||||
message: asyncKeywordConstraintMsg,
|
||||
},
|
||||
...baseRestrictedSyntax,
|
||||
{
|
||||
selector:
|
||||
'MemberExpression[property.name="find"][object.type="Identifier"]',
|
||||
message: arrayFindCompatibilityMsg,
|
||||
},
|
||||
{
|
||||
selector:
|
||||
'MemberExpression[property.name="findIndex"][object.type="Identifier"]',
|
||||
message: arrayFindIndexCompatibilityMsg,
|
||||
},
|
||||
{
|
||||
selector:
|
||||
'MemberExpression[property.name="every"][object.type="Identifier"]',
|
||||
message: arrayEveryCompatibilityMsg,
|
||||
'MemberExpression[property.name="includes"][object.type="Identifier"]',
|
||||
message: arrayIncludesCompatibilityMsg,
|
||||
},
|
||||
],
|
||||
'import/order': [
|
||||
@@ -137,5 +128,14 @@ module.exports = {
|
||||
'@typescript-eslint/no-misused-promises': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['tests/**/*.ts'],
|
||||
rules: {
|
||||
// Tests run only in modern browsers and aren't bundled, so the
|
||||
// Array.includes compatibility constraint that applies to src/ doesn't
|
||||
// apply here.
|
||||
'no-restricted-syntax': ['error', ...baseRestrictedSyntax],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -296,16 +296,27 @@ HLS.js is only compatible with browsers supporting MediaSource extensions (MSE)
|
||||
|
||||
HLS.js is supported on:
|
||||
|
||||
- Chrome 39+ for Android
|
||||
- Chrome 39+ for Desktop
|
||||
- Firefox 41+ for Android
|
||||
- Firefox 42+ for Desktop
|
||||
- Chrome 47+ for Desktop
|
||||
- Firefox 51+ for Desktop
|
||||
- Edge for Windows 10+
|
||||
- Safari 9+ for macOS 10.11+
|
||||
- Safari 10+ for macOS 10.11+
|
||||
- Safari for iPadOS 13+
|
||||
- Safari for iOS 17.1+ since HLS version [1.5.0](https://github.com/video-dev/hls.js/releases/tag/v1.5.0) using Managed Media Source (MMS) [WebKit blog](https://webkit.org/blog/14735/webkit-features-in-safari-17-1/)
|
||||
- Chrome for Android 5+
|
||||
- Firefox for Android 5+
|
||||
|
||||
A [Promise polyfill](https://github.com/taylorhakes/promise-polyfill) is required in browsers missing native promise support.
|
||||
These versions are the targets passed to [`@babel/preset-env`](https://babeljs.io/docs/babel-preset-env) when building the UMD bundles in `dist/`. They share an **ES2016 runtime baseline**: ES5-style syntax plus native ES2016 globals (`Map`, `Set`, `Promise`, `Array.from`, `Uint8Array.from`, `Array.prototype.includes`, etc.). To keep bundle size small, no `core-js` polyfills are bundled.
|
||||
|
||||
Optional features such as CMCD pull in ES2017 APIs (e.g. `Object.entries`), so the full UMD bundle effectively requires an ES2017-capable runtime. The `light` bundle excludes those features and stays at the ES2016 baseline.
|
||||
|
||||
The `dist/` folder ships two distribution variants:
|
||||
|
||||
- **UMD** (`dist/hls.js`, `dist/hls.min.js`, `dist/hls.light.js`, `dist/hls.light.min.js`) — embeddable directly via a `<script>` tag (exposes a global `Hls`) or resolved by `require('hls.js')` via `package.json`'s `main` field. Targets the browser list above. The companion `dist/hls.worker.js` is the bundled transmuxer Web Worker.
|
||||
- **ESM** (`dist/hls.mjs`, `dist/hls.light.mjs`) — resolved by `import 'hls.js'` via the `module` field. Built with `@babel/preset-env`'s `esmodules: true` target (≈ Chrome 61+, Firefox 60+, Safari 10.1+, Edge 16+) and intended to be consumed by a modern bundler. Uses ES2015+ syntax but stays below ES2019 (no `Array.prototype.flatMap`, `Object.fromEntries`, etc.).
|
||||
|
||||
If you import from `src/` directly or include any of our runtime dependencies untranspiled in your own build, you bypass this Babel pipeline and become responsible for transpilation; those source modules can reach for ES2019+ APIs that are tree-shaken out of the bundles we publish.
|
||||
|
||||
To run on browsers below this baseline, supply your own polyfills for any missing globals before HLS.js loads.
|
||||
|
||||
**Please note:**
|
||||
|
||||
|
||||
+8
-3
@@ -126,12 +126,17 @@ const workerFnFooter = '})(false);';
|
||||
|
||||
const extensions = ['.ts', '.js'];
|
||||
|
||||
// Browser baseline for the legacy UMD/IIFE bundles. Chosen so that all listed
|
||||
// targets natively support the ES2015 built-ins (Map, Set, Array.from,
|
||||
// Uint8Array.from, Promise, etc.) that source and bundled @svta/* deps rely
|
||||
// on — we intentionally do NOT bundle core-js polyfills, to keep size down.
|
||||
// Keep this list in sync with the "Compatibility" section in README.md.
|
||||
const babelPresetEnvTargets = {
|
||||
chrome: '47',
|
||||
firefox: '51',
|
||||
safari: '8',
|
||||
ios: '8',
|
||||
android: '4',
|
||||
safari: '10',
|
||||
ios: '10',
|
||||
android: '5',
|
||||
samsung: '5',
|
||||
edge: '14',
|
||||
};
|
||||
|
||||
@@ -1368,6 +1368,7 @@ class EMEController extends Logger implements ComponentAPI {
|
||||
null,
|
||||
new Uint16Array(licenseChallenge.buffer),
|
||||
);
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
if (!xmlString.includes('PlayReadyKeyMessage')) {
|
||||
// This does not appear to be a wrapped message as on Edge. Some
|
||||
// clients do not need this unwrapping, so we will assume this is one of
|
||||
|
||||
@@ -360,7 +360,7 @@ export class ID3TrackController implements ComponentAPI {
|
||||
if (id3Track && removeOldCues) {
|
||||
if (id3Track.track.cues?.length) {
|
||||
const idsToRemove = Object.keys(dateRangeCuesAppended).filter(
|
||||
(id) => !ids.includes(id),
|
||||
(id) => ids.indexOf(id) === -1,
|
||||
);
|
||||
for (let i = idsToRemove.length; i--; ) {
|
||||
const id = idsToRemove[i];
|
||||
|
||||
@@ -1789,7 +1789,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
||||
|
||||
if (
|
||||
this.isInterstitial(playingItem) &&
|
||||
removedIds.includes(playingItem.event.identifier)
|
||||
removedIds.indexOf(playingItem.event.identifier) > -1
|
||||
) {
|
||||
this.warn(
|
||||
`Interstitial "${playingItem.event.identifier}" removed while playing`,
|
||||
|
||||
@@ -258,7 +258,7 @@ export class InterstitialsSchedule extends Logger {
|
||||
const ids = Object.keys(dateRanges);
|
||||
const removedInterstitials = previousInterstitialEvents
|
||||
? previousInterstitialEvents.filter(
|
||||
(event) => !ids.includes(event.identifier),
|
||||
(event) => ids.indexOf(event.identifier) === -1,
|
||||
)
|
||||
: [];
|
||||
if (interstitialEvents.length) {
|
||||
|
||||
@@ -268,7 +268,7 @@ class SubtitleTrackController extends BasePlaylistController {
|
||||
? this.hls.config.enableIMSC1
|
||||
: this.hls.config.enableWebVTT
|
||||
) {
|
||||
if (!subtitleGroups || subtitleGroups.includes(track.groupId)) {
|
||||
if (!subtitleGroups || subtitleGroups.indexOf(track.groupId) > -1) {
|
||||
// track.id should match hls.subtitleTracks index
|
||||
track.id = subtitleTracks.length;
|
||||
subtitleTracks.push(track);
|
||||
|
||||
@@ -245,7 +245,7 @@ export default class MP4Remuxer extends Logger implements Remuxer {
|
||||
}
|
||||
|
||||
if (this.ISGenerated) {
|
||||
if (enoughAudioSamples && enoughVideoSamples) {
|
||||
if (enoughAudioSamples && enoughVideoSamples && timeOffset) {
|
||||
// timeOffset is expected to be the offset of the first timestamp of this fragment (first DTS)
|
||||
// if first audio DTS is not aligned with first video DTS then we need to take that into account
|
||||
// when providing timeOffset to remuxAudio / remuxVideo. if we don't do that, there might be a permanent / small
|
||||
@@ -482,7 +482,8 @@ export default class MP4Remuxer extends Logger implements Remuxer {
|
||||
(timeOffset === 0 && videoInitPTS < _initPTS.baseTime)
|
||||
) {
|
||||
initDTS = Math.min(initDTS as number, videoInitDTS);
|
||||
initPTS = Math.min(initPTS as number, videoInitPTS);
|
||||
// Bind initPTS to the lowest DTS across tracks to prevent negative timestamps
|
||||
initPTS = Math.min(initPTS as number, videoInitDTS);
|
||||
}
|
||||
}
|
||||
this.videoTrackConfig = {
|
||||
|
||||
Reference in New Issue
Block a user