Playlist reload reuse
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 16.9k
- Forks
- 2.8k
- Avg merge
- 1d 12h
- Merged PRs (30d)
- 27
Description
Is your feature request related to a problem? Please describe.
While working on the zoneless parse as UTC issue, arrived at the idea that we just shouldn't be parsing as much as we do on every playlist reload, and we can do that by doing a better job of reusing the prior playlists.
That would be a much better improvement to the library rather than spending ~1KB of JS to fast-parse a PDT. It helps in multiple ways. We'll create less garbage if we reuse object instances that we currently throwaway, which have to be cleaned up by the GC, thereby reducing memory pressure, and improving overall performance at playlist reload.
Describe the solution you'd like
Reuse of internal data structures across parses.
Additional context
The previous details are already there when we parse. onLevelLoading puts the Level or track on the context as levelOrTrack, so parseLevelPlaylist can take context.levelOrTrack?.details as a previous argument with no new plumbing. Audio and subtitle tracks carry their own details the same way, so reuse is always scoped to one level or track.
We already reuse instances in two places, so this is generalizing that pattern. Delta playlists fill EXT-X-SKIP slots with the old Fragment objects in mapFragmentIntersection. And updateFragPTSDTS puts the loaded fragment back into the array because "the playlist has been refreshed between frag loading and call to updateFragPTSDTS()". With reuse that line is a no-op whenever the fragment was carried over.
The issue is the URI is the last line of a segment's block, and the parser allocates the Fragment up front and applies EXTINF, BYTERANGE, PROGRAM-DATE-TIME, GAP, BITRATE, and every PART to it as the tags arrive. By the time we can check the URI, the main work and allocations to skip has already happened. Carrying the instance over at that point still deletes the copy pass and gives stable identity, but it saves very little allocation. To actually skip the tag work the scanner needs to defer applying segment tags until it sees the URI (hold the matches for the pending segment, apply them only when the segment is new), or match the whole block in one go so the URI is known first. KEY, MAP, DISCONTINUITY, and DATERANGE still have to be processed for every line since they feed running state. So the honest saving is new Fragment, the part AttrLists and Part objects, tagList, and the merge pass, and to do it would cost a reshuffle of the scan loop.
The matching can use the one mergeDetails already enforces: same sn, and the same URI after stripping the query, since signed tokens rotate. When only the query changed I'd keep the instance, set the new relurl and null the cached url. For dates I'd compare rawProgramDateTime as strings. A URI mismatch keeps raising media sequence mismatch like today. A date mismatch I'd treat as "don't reuse, build a fresh fragment", which is today's behaviour since we never compared dates. I don't think raising the parsing error here, or determining if it's a small change like .000Z becoming Z, or the sub-second drift overlap that rfc8216bis-17 6.2.1 says a server MAY introduce is worth the cost it would be. I think keeping the matcher stupid and fast is better.
On the base property the parser builds one base object per parse from the response URL and every fragment shares it. Content steering isn't a problem since a pathway switch produces a cloned level or track with details = undefined, so there's never a previous with a different base there. A redirect changing the response URL between reloads is the case to guard. The guard can't be a plain previous.url === baseurl though, because LL-HLS reloads carry _HLS_msn/_HLS_part in the request URL and that's what getResponseUrl returns, so the URL changes on every blocking reload, I think potentially compare with the query stripped, once per reload, and fall back to a full parse when it differs, but interested in any thoughts here.
Overall, there's probably aspect I'm missing, but in particular on the deferred tag creation in the parser, and the object instance lifecycle.. I wonder if anyone relies on the object not being the same.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/loader/playlist-loader.ts to trace levelOrTrack into parseLevelPlaylist, then read the existing reuse logic in src/utils/level-helper.ts, including mapFragmentIntersection and updateFragPTSDTS. The work is done when reload parsing safely reuses prior structures without changing playlist behavior, including URI, date, base URL, and object-identity edge cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- performance
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100