Skip to content

feat: proactive MPEG-TS codec probe for automatic transcoding fallback#119

Merged
cbulock merged 6 commits intomainfrom
copilot/transcode-hls-stream
Apr 11, 2026
Merged

feat: proactive MPEG-TS codec probe for automatic transcoding fallback#119
cbulock merged 6 commits intomainfrom
copilot/transcode-hls-stream

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 11, 2026

The admin UI player would silently show a black screen for streams carrying MPEG-2 video or AC-3 audio — mpegts.js accepts the data but drops incompatible frames without emitting an error. The old HDHomeRun-specific Content-Type probe couldn't distinguish codec compatibility within the MPEG-TS container.

Server: GET /api/stream-probe/:source/:name

Fetches the first 64 KB of a channel's upstream and parses the MPEG-TS PAT → PMT chain to extract actual elementary stream types. Returns:

{ "container": "mpeg-ts", "browserCompatible": false, "videoStreamType": 2, "audioStreamType": 129 }
  • Incompatible video: 0x01 MPEG-1, 0x02 MPEG-2, 0x80 DigiCipher II
  • Incompatible audio: 0x03/0x04 MPEG-1/2, 0x81 AC-3, 0x87 E-AC-3
  • For HDHomeRun channels, forwards ?streamMode=hls to match what the browser actually receives (modern firmware returns HLS at that URL; older firmware returns MPEG-TS regardless)
  • Returns browserCompatible: true when PAT/PMT can't be parsed — biases toward false negatives over false-positive transcoding

Admin UI: unified codec probe in setupVideoPlayer()

Replaces the HDHomeRun-only HEAD/GET Content-Type probe with a single codec probe that runs for all channels:

  • Fires in parallel with HLS.js — no latency added on the happy path
  • Pre-empts HLS.js if incompatible MPEG-TS is identified before HLS.js errors
  • Defers the fallback decision when HLS.js fires MANIFEST_PARSING_ERROR before the probe returns — then routes to setupTranscodePlayer() (incompatible) or setupMpegtsPlayer() (compatible) based on actual codecs
  • Stale-check guards prevent async callbacks from affecting a subsequently switched channel

Tests

14 new tests covering: MPEG-2/AC-3 detection, H.264/AAC pass-through, HLS content-type short-circuit, ?streamMode=hls forwarding, upstream unreachable (502), PAT/PMT-absent fallback, and sync-boundary alignment with a null-packet prefix.

Copilot AI and others added 4 commits April 11, 2026 03:38
…anual button click

Agent-Logs-Url: https://github.com/cbulock/iptv-proxy/sessions/11e04321-61e7-453c-a7bd-b3b61344dbe8

Co-authored-by: cbulock <176519+cbulock@users.noreply.github.com>
Add GET /api/stream-probe/:source/:name server endpoint that fetches the
first 64 KB of a channel's upstream stream and parses the MPEG-TS PAT/PMT
tables to identify video/audio stream types.  Returns whether the codecs
are compatible with browser MSE.

Update admin UI setupVideoPlayer() to run the probe in parallel with HLS.js
for ALL channels (not just HDHomeRun):
- If probe detects MPEG-2 video (0x02), MPEG-1 (0x01), AC-3 (0x81), or
  other browser-incompatible codecs before HLS.js succeeds, pre-empt and
  switch to server-side transcoding via ffmpeg automatically.
- If HLS.js fires MANIFEST_PARSING_ERROR (raw MPEG-TS response), await the
  probe result: incompatible → setupTranscodePlayer(), compatible → mpegts.js.
- For HDHomeRun channels the probe URL includes ?streamMode=hls so the
  server probes the same content the browser will receive.

Remove the old HDHomeRun-specific HEAD/GET content-type probe which only
detected MPEG-TS vs HLS at the container level without inspecting codecs.

Add 14 integration + unit tests covering MPEG-2/AC-3 detection, H.264/AAC
pass-through, HLS detection, ?streamMode=hls forwarding, upstream errors,
and PAT/PMT-absent fallback (assumes compatible to avoid false positives).

Agent-Logs-Url: https://github.com/cbulock/iptv-proxy/sessions/34e5832a-aa46-4669-bce2-fbcfc736b3a8

Co-authored-by: cbulock <176519+cbulock@users.noreply.github.com>
… log transcode failures

Agent-Logs-Url: https://github.com/cbulock/iptv-proxy/sessions/34e5832a-aa46-4669-bce2-fbcfc736b3a8

Co-authored-by: cbulock <176519+cbulock@users.noreply.github.com>
@cbulock cbulock marked this pull request as ready for review April 11, 2026 04:15
Copilot AI review requested due to automatic review settings April 11, 2026 04:15
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a proactive stream codec probe (PAT→PMT parsing for MPEG-TS) so the admin UI can automatically fall back to server-side transcoding when the upstream stream carries browser-incompatible codecs (e.g., MPEG-2 video / AC-3 audio), preventing “black screen” playback.

Changes:

  • Introduces GET /api/stream-probe/:source/:name to fetch a small upstream byte range and infer container + codec compatibility.
  • Updates the admin UI video player setup flow to run the probe in parallel with HLS.js and choose between HLS.js, mpegts.js, and transcoding based on probe results (with stale-guarding).
  • Adds integration/unit tests for MPEG-TS codec detection and the new probe route behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
server/stream-probe.js New server route and MPEG-TS PAT/PMT parser used to infer codec compatibility.
test/integration/stream-probe.test.js New unit/integration coverage for codec parsing and /api/stream-probe responses.
index.js Registers the new stream-probe route in the server startup.
admin/src/App.vue Replaces the old HDHomeRun-only Content-Type probe with a unified codec probe to drive fallback behavior.

Comment on lines +211 to +226
before(async () => {
await fs.mkdir(path.dirname(channelsFile), { recursive: true });
try {
originalChannels = await fs.readFile(channelsFile, 'utf8');
hadOriginalChannelsFile = true;
} catch (err) {
if (err.code !== 'ENOENT') throw err;
}

await fs.writeFile(channelsFile, JSON.stringify(testChannels), 'utf8');
await initChannelsCache();

const app = express();
setupStreamProbeRoutes(app);
app.use(errorHandler);

Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This integration test mounts setupStreamProbeRoutes() directly but does not isolate CONFIG_PATH/app.yaml or provide session middleware. If a developer has admin_auth configured locally, requireAuth becomes active and these requests will return 401, making the test environment-dependent. Consider setting process.env.CONFIG_PATH to a temp dir with an empty app.yaml (or stubbing req.session.authenticated) within this suite, similar to other integration tests that isolate auth config.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e5f1fb3. The before() block now creates a temp dir, writes an empty app.yaml into it, sets process.env.CONFIG_PATH to that dir, and calls invalidateAuthCache() — matching the pattern used in guide.test.js and mcp.test.js. The after() block restores CONFIG_PATH (deleting it if it was originally unset) and calls invalidateAuthCache() again to clean up.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
… auth 401s

Point process.env.CONFIG_PATH at a temp dir containing an empty app.yaml
before the suite runs, then restore + invalidateAuthCache() in after().
This ensures requireAuth always passes through regardless of a developer's
local config/app.yaml containing admin_auth credentials.

Agent-Logs-Url: https://github.com/cbulock/iptv-proxy/sessions/50c4cbde-0224-4fc0-9b46-e6faa64bb32c

Co-authored-by: cbulock <176519+cbulock@users.noreply.github.com>
Copilot AI requested a review from cbulock April 11, 2026 04:25
@cbulock cbulock merged commit 0f5fd11 into main Apr 11, 2026
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants