fix(hls): don't refresh heartbeat on playlist-only polls (#2045) - #2065
Open
andyst-dev wants to merge 1 commit into
Open
fix(hls): don't refresh heartbeat on playlist-only polls (#2045)#2065andyst-dev wants to merge 1 commit into
andyst-dev wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2045
Problem
On a channel with more than one viewer, the HLS playlist window can stop advancing for everyone once a client stops requesting segments — a paused player, a backgrounded tab, a device that went to sleep. The departed client's last-requested (low/old) segment keeps holding the window open for every other viewer.
Root cause
In the fragment route (
server/src/api/streamApi.ts),session.recordHeartbeat(req.ip)ran for every file request — including a poll of the variant playlist (stream.m3u8). But the playlist poll takes an early return beforeonSegmentRequestedis ever called (the fragment route only updates_minByIpwhen segments are actually requested).So a client that only polls
stream.m3u8(paused player / backgrounded tab keeps the manifest alive) stays "alive" forever — heartbeat fresh, never stale-cleaned — while its_minByIpentry stays pinned at its last requested segment.minSegmentRequestedthen anchors the playlist window there for all viewers.Fix
Extract an
shouldRefreshHeartbeatForFragmenthelper: a variant-playlist poll in the HLS modes (stream.m3u8×hls/hls_direct_v2) does not refresh the heartbeat; segment, subtitle and other file requests do. A playlist-only client now goes stale afterstalenessMs,removeStaleConnectionsdrops it, and its pinned_minByIpentry is released — so the window resumes advancing for the surviving viewers.A client actively watching keeps refreshing its heartbeat via its (continuous) segment requests; the master-playlist handshake is untouched. Semantics: heartbeat = actively consuming media segments, a manifest-only poller is idle.
Test
server/src/api/streamApi-heartbeat.test.ts(4 cases): locks the invariant —stream.m3u8 × hls/hls_direct_v2→ no heartbeat; segments/subtitles/hls_slower→ heartbeat. Verified RED against currentmainand GREEN with the fix.vitest run src/api— 18 tests pass.pnpm lint-changedclean.Note for reviewers
Happy to switch to the alternative approach (re-anchoring
_minByIpon playlist poll instead of dropping the connection) if you'd prefer it over a disconnect-after-stalenessMs.