Conversation
Filter a source HLS master to a single video codec family before it reaches HLSVod, so bandwidth-keyed variant selection never mixes codecs and the advertised CODECS always matches the served segments. - Add src/plugins/codec_filter.ts: parse the master with @eyevinn/m3u8, drop non-preferred video codec-family variants (keeping audio-only variants and EXT-X-MEDIA groups), and re-serialize. Single-codec masters pass through unchanged. If the preferred family is absent, fall back to the source's first-seen family so the master is never emptied. - Expose preference via OPTS_PREFERRED_VIDEO_CODEC (default: prefer avc1 over hvc1), surfaced from utils.ts. - Flip the #63 characterization test to assert the fixed behavior (advertised CODECS matches served-segment codec for every variant) and add tests for single-codec pass-through, inverted preference, and graceful fallback. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
birme
left a comment
There was a problem hiding this comment.
pr-reviewer verdict: CHANGES REQUESTED
Solid, well-documented, well-tested filter. The core codec_filter.ts logic is correct: it filters only video StreamItems, preserves audio-only STREAM-INF variants and #EXT-X-MEDIA groups, normalizes avc3/hev1 onto their families, and gracefully falls back to the first-seen family. I verified the audio-preservation behavior directly against @eyevinn/m3u8 and it holds. However there is a blocking gap: the fix is not wired into docker-fast's runtime path, so the bug is not actually fixed in the shipped product — only in an isolated unit test.
Deterministic proofs
- PROOF:
npm run lint-> 0 errors, 5 warnings (all pre-existing-styleno-explicit-anyon the untyped@eyevinn/m3u8objects). Acceptable. - PROOF:
npm run pretty-> All matched files use Prettier code style! - PROOF:
npm test-> Test Suites: 2 passed; Tests: 13 passed, 13 total. - PROOF:
npm run build-> tsc EXIT=0.
Acceptance criteria
- Repro test now passes with 0 mismatches — MET (in unit test); NOT met at runtime (see major finding below).
- Single-codec sources unaffected — MET.
filterMasterManifestByCodecreturnsundefinedand mutates nothing when<2video families; covered by a dedicated "no StreamItem removed" test. - No trademark/product names — MET. Only standard HLS CODECS prefixes (avc1/hvc1/av01/mp4a) in code/config; "AVC"/"HEVC" appear only as standard-body names in doc comments.
Findings
File: src/server.ts:45 & src/plugins/utils.ts:289 · Severity: major · Finding: createConfiguredMasterLoader() / getPreferredVideoCodecProfile() and env OPTS_PREFERRED_VIDEO_CODEC are never invoked in production. docker-fast runs new ChannelEngine(plugin.newAssetManager(), {...}); it never calls HLSVod.load() itself — channel-engine loads VODs internally, and the ChannelEngine options object exposes no master-manifest injector hook. So a multicodec source still reaches HLSVod unfiltered at runtime and the advertised-CODECS/segment mismatch persists in the packaged service. Acceptance criterion #1 is proven only by the unit test that calls the seam directly. · Recommendation: Either wire the loader into the actual VOD-load path (e.g. via the asset manager / whatever injection point channel-engine supports) so OPTS_PREFERRED_VIDEO_CODEC genuinely takes effect end-to-end, or — if channel-engine currently offers no such seam — say so explicitly in the PR and split this into (a) this library + tests and (b) a channel-engine change that exposes the injection point, and do not close #65 until the runtime path is covered. As-is the new public functions are dead code.
File: src/plugins/tests/fixtures/multicodec/master_no_avc.m3u8:10 · Severity: minor · Finding: References variant av1_720p.m3u8, which does not exist in the fixtures dir. Tests pass only because the av01 variant is filtered out (hvc1 chosen) before its media playlist is ever fetched — so the fallback test never actually exercises serving an av01 segment, and an inverted-preference test would break. Slightly overstates the graceful-fallback coverage. · Recommendation: Add the missing av1_720p.m3u8 fixture (mirroring the others) so the fixture is self-consistent and the fallback path can be exercised for av01 too.
File: package.json · Severity: minor · Finding: @eyevinn/m3u8 is used via require() but is a transitive dependency (resolved to v0.5.8 through eyevinn-channel-engine/@eyevinn/hls-vodtolive), not declared in docker-fast's own dependencies. This works today via hoisting but is fragile: a future channel-engine bump could drop/relocate it and break docker-fast at runtime with no lockfile signal. · Recommendation: Declare @eyevinn/m3u8 as a direct dependency (pinned to the resolved range) in a follow-up so the runtime contract is explicit. Not blocking on its own.
File: src/plugins/codec_filter.ts:189 · Severity: suggestion · Finding: fetch(source) sends the master URL with no timeout and no request options; non-ok status is handled and stream errors propagate via out.destroy(err), which is good. No unsanitized user input reaches the URL here (the URI comes from the asset manager). · Recommendation: Consider a fetch timeout / abort to avoid a hung loader on a stalled origin, consistent with other fetch call sites. Optional.
Verdict
CHANGES — the filter itself is correct and merge-quality, but because it is not connected to the runtime path the PR does not actually fix #65 in the shipped product. Wire it in (or clearly descope + keep #65 open) before merging. The missing av1 fixture and the undeclared transitive dep should be cleaned up as well.
… loader blocked-on-upstream The codec-preference filter cannot be wired into runtime: the installed eyevinn-channel-engine calls HLSVod.load() internally with no master-manifest injector, and neither ChannelEngineOpts nor VodResponse exposes a loader seam. Document createConfiguredMasterLoader as blocked-on-upstream instead of leaving it as dead code implying a runtime fix. - Declare @eyevinn/m3u8 as a direct dependency (was transitive-only via require). - Add missing av1_720p.m3u8 media fixture referenced by master_no_avc.m3u8 and a test that prefers av01 so the fixture set is self-consistent and exercised. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Closing unmerged: the codec-preference filter here is correct and tested, but its runtime integration is blocked on an upstream channel-engine master-manifest loader hook (see #65). Merging as-is would land dead code and prematurely close #65. Branch |
Summary
Implements the codec-preference filter from #65 — filtering a multicodec (HEVC+AVC) source master down to a single video codec family so advertised CODECS matches served segments. The filter logic is complete and tested.
Important
Runtime integration is BLOCKED ON UPSTREAM — #65 is NOT yet fixed in the packaged service. See "Runtime status" below. This PR lands the tested filter utility + honest wiring shape; it does not (and cannot, with the currently installed engine) filter a real source at request time.
Changes
src/plugins/codec_filter.ts(new):filterMasterManifestByCodec()drops non-preferred video variants while keeping audio-only variants and#EXT-X-MEDIAgroups;createCodecFilteringMasterLoader()builds a master-manifest injector shaped forHLSVod.load(_injectMasterManifest, …).src/plugins/utils.ts:getPreferredVideoCodecProfile()resolves the preference fromOPTS_PREFERRED_VIDEO_CODEC;createConfiguredMasterLoader()builds the loader. Its doc comment now clearly marks it blocked on upstream (not live runtime wiring) and names the exact hook the engine would need.@eyevinn/m3u8declared as a direct dependency (^0.5.8, the already-resolved transitive version) — it was used viarequire()but only present transitively. One-line lockfile change.src/plugins/__tests__/fixtures/multicodec/av1_720p.m3u8media playlist thatmaster_no_avc.m3u8references, and added a test that prefersav01so the fixture is exercised end to end (fixture set is now self-consistent).Config
OPTS_PREFERRED_VIDEO_CODEC— comma-separated HLS codec-family prefixes (e.g.hvc1,avc1). Defaultavc1,hvc1(prefer AVC over HEVC). Single-codec sources pass through unchanged.Runtime status (why #65 is not yet fixed in the service)
docker-fast never calls
HLSVod.load()—eyevinn-channel-engine@5.0.0does, from insideSession:dist/engine/session.jsL1737 / L1972:new HLSVod(vodResponse.uri, ...)thencurrentVod.load()(L1756/L1760) with no_injectMasterManifestargument, soHLSVodself-fetches the master (@eyevinn/hls-vodtolive@4.1.5index.js L490).ChannelEngineOpts(dist/engine/server.d.tsL1-37) exposes no master-manifest injector/loader option.VodResponse(server.d.tsL59-71) carries onlyuri: string— no loader/stream field.So the only lever docker-fast has over a source is the
uristring.HLSVod.load()does support an_injectMasterManifestseam (index.js L198, L489-503), but the engine does not surface it. Filtering via a self-hosted proxy would require a new HTTP route plus absolute-URL rewriting of every (currently relative) variant URI — a separate subsystem, out of scope here.Upstream hook needed to unblock:
eyevinn-channel-engineshould thread a caller-supplied master-manifest loader intoHLSVod.load(...)— e.g. amasterManifestLoaderonChannelEngineOpts, or aVodResponse.masterManifestLoadercallback returned fromIAssetManager.getNextVod. Once that exists,createConfiguredMasterLoader()wires straight in.Verification
npm run lint-> 0 errors (5 pre-existingno-explicit-anywarnings on untyped@eyevinn/m3u8objects)npm run pretty-> All matched files use Prettier code style!npm test-> Test Suites: 2 passed, Tests: 14 passednpm run build-> tsc EXIT=0Relates to #65 (filter + tests landed; runtime fix blocked on the upstream hook above).