Summary
HLS.js v1.7.0 adds I-frame playlist support, improved playback of protected content, and CMCD v2 for standardized streaming analytics. It also delivers smoother audio-track switching, faster startup through parallel init-segment loading, and fixes across Low-Latency HLS, subtitles, interstitials, and live streaming.
Highlights to try in this release:
- I-frame playback — call
hls.createIFramePlayer()andloadMediaAt(time)to render frames from a stream's#EXT-X-I-FRAME-STREAM-INFvariants in a video element, or load an#EXT-X-I-FRAMES-ONLYmedia playlist directly. - Smoother audio switching — set
hls.nextAudioTrack(or pass{ flushImmediate: true }tosetAudioOptionfor an immediate one) to schedule a seamless audio-rendition change that avoids stalls and added live latency. - CMCD v2 — set
cmcd.version: 2to enable it, and configurecmcd.eventTargetsto send standardized event reports to an external analytics endpoint. Not all CMCD v2 events, fields, and HLS.js features are supported as of this release. - Faster startup — alternate audio and video init segments now load in parallel; try a stream with separate audio/video renditions.
- Improved support for looped video — a new
loopBackBufferFlushconfig option flushes the lower-quality back buffer on looped streams. - Tune level selection —
abrSwitchIntervalthrottles automatic quality switches, anderrorPenaltyExpireMslets a level penalized by a load error be re-elected instead of being avoided for the rest of the session. - Stop loading stale live streams — set
liveMaxUnchangedPlaylistRefreshto error out withPLAYLIST_UNCHANGED_ERRORafter a set number of unchanged playlist reloads instead of polling a dead playlist indefinitely. - Recover from broken appends — on live and VOD alike,
appendTimeoutbounds aSourceBufferappend that never completes, whileMEDIA_SOURCE_REQUIRES_RESETandBUFFER_APPEND_NO_PROGRESSreport a MediaSource that has to be reset and fragments that append without ever buffering.
Demo page
https://073606d7.hls-js-dev.pages.dev/demo/
Breaking changes
No public exports were removed and no runtime behavior changes are required to upgrade from v1.6 to v1.7. TypeScript consumers might see new compile errors where previously loose types have been narrowed. Each is listed with upgrade guidance in the migration guide:
https://github.com/video-dev/hls.js/blob/v1.7.0/MIGRATING.md#migrating-from-hlsjs-16-to-17
Some exported type dependencies ("eventemitter3", "@svta/cml-cmcd", "@svta/cml-utils", "@svta/cml-structured-field-values") have not been bundled with hls.d.ts. This will be addressed in an upcoming patch.
Changes since the last release
v1.6.18...v1.7.0, v1.7.0-rc.3...v1.7.0
Features
- HLS I-frame playlist support
#EXT-X-I-FRAME-STREAM-INFand#EXT-X-I-FRAMES-ONLY(#7757, #7791) @robwalch- Optimize I-frame parsing (#7794)
- "mjpg" image I-frame support (#7862)
- Parse I-frame multivariant tags that end at EOF without a trailing newline (#7851) @rudemateo
- Play single-keyframe fMP4 segments and parse
tfhddefault_sample_sizecorrectly (#7884) @rudemateo - Support remuxing of encrypted I-frame segments, including AES decryption of byte-range requests (#7896, #7892) @robwalch
- Force rendering of requested I-frames (#7934) @robwalch
- Fix I-frame trick-play rendering of stretched and backward-loaded frames (#7930) @rudemateo
- Support muxed audio+video fMP4 and multi-trun I-frame fragments (#7940) @rudemateo
- Consolidate the I-frame remux code into one function (#7945) @robwalch
- CMCD v2
- Upgrade Common Media Client Data reporting to the CMCD v2 specification (#7725) @littlespex
- Custom events, custom keys, and
ec(#7887) @cotid-qualabs - Implement rtp, lb, dl, pb, tpb, ttfb, and ttlb (#7895) @cotid-qualabs
- Smooth audio track switching
New API, error details, and config options
Nothing below is required to upgrade. Every addition is opt-in. Each entry links to its v1.7.0 API documentation.
Config options
abrSwitchInterval(0) minimum seconds between automatic ABR switchesappendTimeout(Infinity) milliseconds before anappendBuffer()operation is treated as timed outemsgKLVSchemaUri(undefined) URN matched when extracting MISB KLV metadata from CMAFemsgboxes, defaulting tourn:misb:KLV:bin:1910.1errorPenaltyExpireMs(0) milliseconds after which a level penalized for a load error can be re-electedhandleMpegTsVideoIntegrityErrors('process'or'skip') how corrupt MPEG-TS video is handlediframeCacheLimit(2 * 1024 * 1024) bytes of I-frame data retained by I-frame player instancesliveMaxUnchangedPlaylistRefresh(Infinity, minimum2) unchanged live reloads beforePLAYLIST_UNCHANGED_ERRORloopBackBufferFlush(undefined) flush the lower-quality back buffer on an upswitch whenmedia.loopistruenextAudioTrackBufferFlushForwardOffset(0.25) forward audio buffer retained on a scheduled audio switchskipBufferHolePadding(0.1) padding added to the buffer-hole skip targetloggerId(undefined) prefixes debug logs for anHlsinstance (assetPlayerIdstill fills in for it)cmcdsub-options for CMCD v2:version(1or2),eventTargets(v2 event report endpoints),rtpSafetyFactor(5),loader, andreporterCallback(receives aCmcdCustomReporterforupdateCustomData()andrecordCustomEvent()).includeKeysis retyped fromstring[]toCmcdKey[]- Controller overrides that can be replaced or disabled:
streamController,gapController,latencyController,id3TrackController, andiframeController
DRM callback arguments
drmSystems[KEY-SYSTEM].generateRequestreceiveskeyContext & { decryptdata: LevelKey, reason: LicenseRequestReason }and returnsGenerateRequestFilterResultlicenseXhrSetupandlicenseResponseCallbackreceivekeyContext & { decryptdata: LevelKey }, andlicenseXhrSetupcan now return or resolve astringas well as aUint8Array
Methods
hls.createIFramePlayer(configOverride?)returnsHlsIFramesOnly | null, a player that renders#EXT-X-I-FRAME-STREAM-INFvariants in a video elementhls.createImageIFramePlayer(configOverride?)returnsHlsImageIFramesOnly | null, a player for"mjpg"image I-frame variantsiframePlayer.loadMediaAt(time, options?)loads, appends, and seeks to the I-frame attime(optionsisPartial<LoadMediaAtOptions>)imagePlayer.attachImage(image)andimagePlayer.detachImage()attach or detach theHTMLImageElementused for renderinghls.setAudioOption(option, flushImmediate?)takes an optional second argument to switch immediately rather than on schedule
Getters and setters
hls.iframeVariantsgetLevelParsed[]of the multivariant playlist's I-frame variantshls.audioForwardBufferInfogetBufferInfo | nullfor the alternate audio bufferhls.nextAudioTrackget and set thenumberindex of the audio track scheduled for a seamless switch
Error details
This release adds three Hls.ErrorDetails members and no new Hls.Events members.
PLAYLIST_UNCHANGED_ERROR(NETWORK_ERROR, non-fatal) a live playlist hitliveMaxUnchangedPlaylistRefreshconsecutive unchanged reloadsBUFFER_APPEND_NO_PROGRESS(MEDIA_ERROR, non-fatal) an append cycle completed without increasing the fragment's buffered coverage, and addsappendsWithoutProgresstoErrorDataMEDIA_SOURCE_REQUIRES_RESET(MEDIA_ERROR) the MediaSource closed while attached or ended on an append error, withResetMediaSourceinerrorAction.flags
Event payload and behavior changes
iframeVariantsadded toMANIFEST_LOADEDandMANIFEST_PARSEDchunkMeta: ChunkMetadataadded toFRAG_PARSEDandFRAG_BUFFEREDsubtitleTrackandclosedCaptionsadded toCUES_PARSEDflushImmediateadded toAudioSelectionOptionandAUDIO_TRACK_SWITCHINGpreviousFragadded toFRAG_CHANGED,parttoSUBTITLE_FRAG_PROCESSEDandFRAG_DECRYPTED,timestampOffsetstoINIT_PTS_FOUND,start,end, anderrortoBUFFER_FLUSHED, andcontexttoLEVEL_LOADED,AUDIO_TRACK_LOADED, andSUBTITLE_TRACK_LOADEDnetworkDetailson playlist, fragment, asset-list, and error events is narrowed fromanytoNullableNetworkDetailsFRAG_PARSING_USERDATAis only emitted when text samples are foundMEDIA_DETACHEDis now triggered synchronously bydetachMedia()andtransferMedia()hls.loadingEnabledisfalseonce the stream controller has stopped, not just afterstopLoad()
Enhancements
Content protection (DRM)
- DRM and multi-key handling improvements for improved protected playback (#7517) @robwalch
- Resolve usable keys from the latest key session before requesting another, and resolve
setMediaKeysonce attached (#7879, #7615) @robwalch - Parse the Widevine key id from the
EXT-X-KEYPSSH (andencryptedevent initData) when HLS playlistKEYIDattribute is absent (#7775, #7415) @robwalch @yajin2021 - Support playback of elementary (containerless) audio encrypted with Sample-AES (#7479) @Yesterday17
- Load the key for the fragment of a selected LL-HLS part so encrypted parts decrypt correctly (#7874) @robwalch
Audio switching, live, and buffering
- Smooth audio track switching (#7575, #7795) @krseager
- Download init segments in parallel for a faster start (#7867) @krseager
- Add
hls.audioForwardBufferInfogetter for audio-specific forward buffer info (#7865) @robwalch - Flush the back buffer for looped streams (#7869) @dzianis-dashkevich
- Improve audio/video append synchronization (#7842) @robwalch
- Avoid an audio buffer flush on the first track switch (#7696)
- Reach the "ended" state reliably when a playlist ends with empty or gap segments (#7609)
- Better playlist alignment when PDT is inconsistent across renditions (#7482)
Stability and error handling
- Add
abrSwitchIntervalto throttle automatic level switches (#7669) @christriants - Re-elect penalized levels once
errorPenaltyExpireMshas passed (#7771) @Harshit661000143 - Error and stop streaming after max unchanged live updates (#7704) @dzianis-dashkevich
- SourceBuffer append operation timeout handling (#7670) @dzianis-dashkevich
- Detect and recover from a MediaSource that reports "ended" or "closed" while still attached, which fixes ManagedMediaSource cold starts failing on iOS 26.2 (#7697) @zalishchuk
- Surface the condition as the
MEDIA_SOURCE_REQUIRES_RESETerror so applications can handle it before automatic recovery (#7699) @christriants - Respond with a variant switch, a switch to SDR, and a MediaSource reset error action, so content that causes append errors no longer loops through recovery (#7702) @robwalch
- Limit recovery attempts when a media element decode error, rather than an append error, closed the MediaSource (#7707) @robwalch
- Surface the condition as the
Timed metadata and track management
- Extract MISB KLV timed metadata from MPEG-TS and CMAF streams (#7668) @jacorbello, including when audio is present (#7673) @jacorbello
- Add
subtitleTrackandclosedCaptionsto theCUES_PARSEDevent (#7843) @robwalch - Only emit
FRAG_PARSING_USERDATAwhen text samples are found (#7686) @XephyrKenny - Allow removing text tracks that HLS.js created (#7515) @xxoo
- (Uses removable DOM
<track>elements overHTMLMediaElement.addTextTrack()which does not allow removal)
- (Uses removable DOM
Networking and resilience
- fetch-loader retry logic for transient network failures (#7756) @dzianis-dashkevich
- Pass the loader context to
xhrSetupand export loader context types (#7831 @robwalch, #7762 @dzianis-dashkevich) - Verify byte-range responses and warn on a size mismatch (#7701) @robwalch
- Detect corrupt MPEG-TS video streams (#7094) @mstyura
Performance, memory, and lifecycle
- Cap the selected level to the player size with
ResizeObserver, and makenextLevelSwitchmore responsive (#7108) @robwalch - Clear the resolved fragment/part URL cache when done to reduce memory use (#7731)
- More reliable transmuxer teardown on
destroy()(#7863) - Keep inherited logger methods intact on
destroy()(#7864) - Improve M2TS init-segment handling and append-queue logging (#7695)
- Remove dead code and some unused exports for a smaller bundle (#7882)
- Faster parsing on low-powered devices: CEA-608 captions, Annex-B marker scanning in the tsdemuxer, and mp4 box parsing (#7949, #7955, #7956) @itsjamie
Bug fixes
Playback start, seeking, and timestamps
- Fix backward seeking that led to stalling or elevated buffering (#7577) @krseager
- Fix certain Tizen devices getting stuck seeking over buffer gaps (#7630) @krseager
- Fix an endless loop in the gap controller on bad streams (zero-duration segments) (#7661) @robwalch
- Restore playback after page restoration from bfcache in Safari (#7683) @christriants
- Restart loading from
recoverMediaError()whenautoStartLoadis disabled (#7693) @robwalch - Roll initPTS forward when needed by earlier segments (#7537) @robwalch
- Correct a
computeInitPtsrollover that caused a segment-0 loop (#7752) @christriants - Adjust initPTS when a level switch drops the opening GoP (#7700) @robwalch
- Do not update initPTS on small drift caused by playlist segment-duration rounding (#7792) @robwalch
Media parsing and codecs
- Work around Chrome silently evicting coded frames on PTS overlap (#7807) @robwalch
- Pad the initial PTS of remuxed MPEG-TS to avoid Chrome rendering glitches (#7805) @robwalch
- Drop bad MP4 data instead of failing the append (#7815) @robwalch
- HEVC: set sync samples from IRAP VCL NALs (#7861) @gemxx
- HEVC: correct access-unit sample boundaries in Safari (#7854) @gemxx
- Switch away from containerless EC-3 audio that HLS.js cannot demux (Safari) instead of stalling (#7873) @robwalch
- Handle implicit HE-AAC for AAC Main at low sample rates (#7835) @robwalch
- Fix unterminated emsg infinite loop and scheme parsing (#7968) @Jaybhade
Audio tracks
- Fix switching back to main (audio-only) after end-of-stream is buffered (#7645) @robwalch
- Fix AES-128 decryption failure on alternate audio when a shared key load is pending (#7834) @robwalch
- Fix audio TS segment duration calculation (#7646) @robwalch
- Stop
AUDIO_TRACK_LOAD_TIMEOUTfrom following theerrorRetrypolicy inplaylistLoadPolicy(#7506) @CongCong-1228
Content protection (DRM)
- Fix PlayReady on some devices (#7631) @KunXi-Fox
- Fix PlayReady key endianness (#7510) @ShubhamSharma2311
- Fix an eme-controller type error that depended on promise state (#7877) @bwallberg
- Fix
licenseXhrSetupreturn types and aselectKeySystemnested-Promise anti-pattern (#7888) @robwalch
Subtitles and captions
- Decrypt LL-HLS VTT AES parts per-part (#7881) @hongjun-bae
- Fix Low-Latency HLS VTT subtitle part loading, including segments where only the first parts were requested (#7626, #7893) @robwalch
- Align WebVTT segments missing an
X-TIMESTAMP-MAPheader with the media timeline (#7852) @robwalch - Fix WebVTT loading by disabling progressive loading for WebVTT (#7649) @krseager
- Cache initial cues added to a
TextTrackso the browser does not remove them (#7809) @robwalch - Fix CEA-608 cursor clamping so position stays within the column array bounds (#7666) @chelleccarlyle
Live, interstitials, and content steering
- Fix Low-Latency Live joins and resumption around interstitial prerolls and midrolls (#7846, #7908, #7913, #7935, #7944) @robwalch
_HLS_start_offsetmissing from asset-list requests on live join (#7846, #7944)- Live join with a
PRE,ONCEpreroll starting at the wrong position (#7908) - Preroll resume colliding with a midroll, and midroll buffering and playback (#7913)
- Fatal "init load aborted" when the init segment load context changes at preroll end (#7935)
- Fix interstitial live start with a short sliding window (#7799)
- Do not reset interstitials when seeking between assets (#7650)
- Fix an interstitial-controller exception on legacy engines where
media.play()returns no Promise (#7586) @malickyeu - Fix fallback to primary on live start (#7651) @robwalch
- Content Steering: apply
URI-REPLACEMENTHOSTto the hostname only (#7655) - Pathway Cloning: apply
PARAMSafterPER-VARIANT-URIS(#7710) - Fix "discontinuity sequence mismatch" for fragment hint with no parts (#7964)
- Preserve existing query string when adding HLS delivery directives (#7969) @CarlLindqvist
Live updates and playlist parsing
- Support an
EXT-X-DISCONTINUITY-SEQUENCEdeclaration after the first media segment (#7750) - Detect stream end after a live-to-VOD transition when
endListFragmentsis stale (#7754) @alchemyyy - Fix the initial reload time of a live playlist loaded with
hls.loadSource()(#7875) @robwalch - Defer playlist reload while the network is offline (#7926) @robwalch
- Fix
mapPartIntersectionloop bounds (#7859) @sugarzyc - Do not request a video buffer flush or EOS when the main playlist is audio-only (#7868)
- Fix "Missing format identifier #EXTM3U" playlist parsing errors (#7533)
- Multivariant playlist parsing fixes (#7523)
- Support
EXT-X-MEDIA-SEQUENCEdeclaration afterEXT-X-SKIP(#7905) @hongjun-bae
Buffering, memory, recovery, startup, and teardown
- Recover from
QuotaExceededErrorby evicting the back buffer and retrying (#7749) @alchemyyy - Limit buffering while paused outside the live sliding window (#7788) @robwalch
- Prevent loop-loading of segments whose appends were dropped (#7797)
- Forward transmuxer worker errors correctly (#7883)
- Make
part.gapwritable and handle part muxing errors as gaps (#7814) - Mark fragments as gaps after repeated appends without buffered range growth (#7941) @rudemateo
- Reported with the new
BUFFER_APPEND_NO_PROGRESSerror detail (see Error details above)
- Reported with the new
- Treat un-demuxable fragments as a gap to prevent infinite reloading (#7900) @hongjun-bae
- Fix selection with small samples (#7939) @robwalch
- Prevent loop loading for SourceBuffer mismatch append errors (#7965)
- Guard abandon-rules check after destroy (#7938) @AmPlace
Regressions in development and v1.7 beta
- Fix subtitle buffering up to or past media buffer (#7921) @robwalch
- Fix fatal KEY_SYSTEM_ERROR handling (#7920)
- Fix
autoStartLoad:falseand deferredstartPosition(#7903) - Fix uncaught init-segment abort errors (#7904)
- Fix key-status internal-error handling (#7963)
- Fix MPEG-TS Sample-AES decryption (#7959)
Build and CI
- Add minified ESM builds and a dist size budget check (#7970) @itsjamie
- Update minimum browser support documentation and build config (#7880) @robwalch
- Update Chrome configuration for macOS in build.yml (#7698)
- Increase minimum Windows test version for saucelabs (#7602)
- Use BrowserStack to run macOS CI tests (#7836)
- Update to TypeScript 5.9 (#7607)
- Type
networkDetailson playlist, fragment, and error events instead ofany(#7458) @ondreian - Migrate CI to Sauce Connect 5 (#7957) @itsjamie
- Add dev container support for contributors (#7758) @tjenkinson
- Use the GitHub
releaseenvironment for npm publish (#7544) @tjenkinson - Lint em-dash characters (#7899) @robwalch
- Correct interstitial env vars and content-steering condition (#7727) @zalishchuk
Documentation and README
- Update README supported features (#7953) @robwalch
- Update README Alternative setup for 2026 browsers (#7844)
- Document testing with BrowserStack and SauceLabs (#7780)
- Add missing config options to API.md (#7628) @krseager
- Create SECURITY.md (#7685) @pr4v33n7-sa
- Fix links for API Docs in README (#7637) @daniemo2
- Add Labra-Flex to 'They use HLS.js in production!' section (#7419) @aaskaoui
- Add Streamfizz to "They use HLS.js in production!" (#7927) @geosigno
- Fix HLS.js product name in issue templates (#7828) @karlhorky
Demo
- Fix TypeError on playlist load/reload inside of 3rd party library Chart.js for demo page (#7801) @krseager
- Add an
hlsjsConfigquery param to populate the demo editor (#7473) @quentinms
Special thanks
Beyond the code contributors credited above, thank you to the community members whose issue reports drove fixes and improvements in this release:
- @AndreasFurster (#4553), @gdplayerto (#4958), @kiran-dcunha (#4954), @CaseV7 (#5392), @byumark (#6191), @iamsyk (#6374), @fresleo (#6666), @Hyddan (#6680), @vaseala (#6711), @ShoobyD (#6776), @luwes (#6777), @qiushaocloud (#7023), @LalitAgrawala004 (#7142, #7738), @jiho-bae (#7420), @Yeongjin-Jeong (#7460), @hongfeih-es (#7508), @gugaio (#7518), @SangwonOh (#7522, #7885, #7962), @Tzipi-kaltura (#7524), @twilson90 (#7531), @knuthaug (#7535), @bmarshall (#7536), @AfLosada (#7541), @dchan-harmonicinc (#7554), @xtne6f (#7556), @mtaher1996 (#7564), @MosheMaorKaltura (#7572), @janpe (#7578), @matvp91 (#7641), @martystefan (#7646), @bes (#7656), @doublex (#7667), @greeble31 (#7674), @thexeos (#7675, #7677), @gjanblaszczyk (#7687), @wei-li-dwango (#7808), @foliovision (#7833), @philhar88 (#7845, #7906, #7911, #7912, #7931, #7942), @robybales3 (#7850), @ai-bughunter (#7856), @mrlika (#7876), and @dloftus (#7898)
- @flagoworld, whose PR #7453 informed the live media playlist reload fix (#7875)
- Contributors credited above who also filed the issues behind these fixes: @krseager (#7576, #7627, #7629, #7648), @KunXi-Fox (#7580, #7608, #7640), @hongjun-bae (#7385, #7557, #7777, #7871), @littlespex (#6090, #7724, #7858), @mstyura (#7441), @gemxx (#7853), @cotid-qualabs (#7886), and @AmPlace (#7937)
Feedback
Provide feedback in GitHub Issues. For more details on how to contribute to HLS.js, see our CONTRIBUTING guide.