| id | plex-client-compatibility-matrix | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| type | reference | ||||||||
| status | stable | ||||||||
| tags |
|
This page defines tier-1 Plex clients for Live TV validation, how IptvTunerr classifies them when IPTV_TUNERR_CLIENT_ADAPT is enabled, what stream path to expect, and a repeatable procedure to record pass/fail evidence.
Work breakdown: memory-bank/work_breakdown.md HR-003 (matrix + procedure). Related: HR-004 sticky WebSafe fallback, HR-001 WebSafe startup / IDR gate, HR-002 Plex Web regression evidence — plex-livetv-http-tuning.
Use this set first when validating cross-client behavior; extend the table later (e.g. Apple TV, Roku) without changing the classification rules below.
| Priority | Client | Notes |
|---|---|---|
| 1 | Plex Web | Test Firefox and Chrome separately (codec and DASH timing differ). |
| 2 | LG webOS | Plex app on TV (browser engine differs from desktop web). |
| 3 | iPhone / iOS | Plex app (native iOS player stack). |
| 4 | NVIDIA Shield TV | Plex app — proxy for Android TV-class behavior when Apple TV / other TVs are unavailable. |
Default testing stance (work breakdown): run what is physically available, keep the matrix extensible, and record gaps explicitly in results.
| Requirement | Why |
|---|---|
IPTV_TUNERR_CLIENT_ADAPT=true (or 1 / yes) |
Enables session-aware adaptation (see cli-and-env-reference). |
IPTV_TUNERR_PMS_URL + IPTV_TUNERR_PMS_TOKEN (or PLEX_HOST / PLEX_TOKEN) |
Tunerr calls /status/sessions to match X-Plex-Session-Identifier / X-Plex-Client-Identifier from the live request. Without this, clients are often treated as unknown → WebSafe. |
| Known-good channel and DVR | Same channel ID across runs so results are comparable. |
| Access to Tunerr logs and optionally PMS logs | Correlate gateway: adaptation lines with Plex startup errors (dash_init_404, start.mpd timeouts, etc.). |
Tunerr maps each resolved Plex Player to a client class (internal/tuner/gateway_adapt.go: plexClientClass). Autopilot keys memory by dna_id + client class.
| Class | Detection (heuristic) | With CLIENT_ADAPT on: typical adaptation outcome |
|---|---|---|
web |
Product or platform string matches browser-like tokens (plex web, web, browser, firefox, chrome, safari, …). |
Transcode on, profile plexsafe — reasons such as resolved-web-client, unknown-client-websafe (unresolved session), resolve-error-websafe, sticky-fallback-websafe (HR-004). |
internal |
PMS / Lavf / ffmpeg fetcher patterns (lavf, plex media server, segmenter, ffmpeg, …). |
Transcode on, plexsafe — internal-fetcher-websafe. |
native |
Everything else that resolves from /status/sessions. |
Transcode off for “full” path when adaptation chooses remux/direct — reason resolved-nonweb-client (profile empty; still subject to STREAM_TRANSCODE / overrides / ffprobe). |
unknown |
Session not resolved (no match, or PMS unreachable). | Transcode on, plexsafe — unknown-client-websafe (safe default). |
Caveat: web detection is substring-based; unusual product strings containing web can be misclassified — if in doubt, grep PMS /status/sessions XML for the live session’s Player fields.
| Tier-1 client | Expected class (typical) | Expected adaptation reason (typical) | Notes |
|---|---|---|---|
| Plex Web (Firefox/Chrome) | web |
resolved-web-client |
Plex uses DASH from MPEG-TS; WebSafe / startup tuning matters (runbook §6). |
| LG webOS Plex | native |
resolved-nonweb-client |
TV app — often remux-first unless overrides or upstream forces transcode. |
| iPhone Plex | native |
resolved-nonweb-client |
Same broad expectation as other native apps. |
| Shield / Android TV Plex | native |
resolved-nonweb-client |
Validate at least one HLS-heavy channel if your lineup mixes HLS and TS. |
After a native path hits hard failure (all_upstreams_failed or upstream_concurrency_limited), HR-004 may register sticky WebSafe for that channel + Plex session/client id — look for sticky-fallback-websafe on the next tune.
Run adaptation unit tests whenever you touch gateway_adapt*.go:
go test -count=1 -run 'TestGateway_requestAdaptation_|TestGateway_adaptSticky_' ./internal/tunerThese cover: query profile, resolved web vs non-web, internal fetcher, unknown default, Autopilot precedence, and HR-004 sticky registration.
Full CI-equivalent: ./scripts/verify.
For Plex Web startup without clicking in a browser, some deployments use a Python probe that drives PMS Live TV APIs and fetches start.mpd. That script is not vendored in this repo; it often lives alongside cluster tooling (e.g. plex/scripts/plex-web-livetv-probe.py). Typical invocation shape:
# Example only — adjust path, kube context, DVR id, and channel selector to your environment.
python3 /path/to/plex/scripts/plex-web-livetv-probe.py --dvr <id> --channel-id <id> --json-out /tmp/probe.jsonEvidence bundle: save probe JSON, the matching Tunerr log slice (gateway: lines for the same wall time), and optionally /debug/stream-attempts.json. See known_issues for probe/tuner correlation caveats (wrong log file inference on multi-instance deployments).
If you already use live-race-harness.sh, set:
export PWPROBE_SCRIPT=/path/to/plex-web-livetv-probe.py
export PWPROBE_ARGS='--dvr 138 --channel-id 112'The harness will capture plex-web-probe.json, plex-web-probe.log, and exit code in the same bundle, and live-race-harness-report.py will summarize the probe result when present.
If the same harness run also has Plex API access (PMS_URL + PMS_TOKEN, or IPTV_TUNERR_PMS_URL + IPTV_TUNERR_PMS_TOKEN / PLEX_HOST + PLEX_TOKEN), it now snapshots /status/sessions during the run window and summarizes observed Player titles/products/platforms plus live session IDs. That makes it easier to prove whether Tunerr resolved the same class of Plex client you thought you were testing.
Goal: reproducible proof that Plex Web can complete start.mpd / DASH init on a small agreed channel set through Tunerr’s direct WebSafe path (real XMLTV + deduped lineup as deployed), with logs that explain startup.
Template (fill in for your deployment):
| Field | Example (replace) |
|---|---|
| DVR | 138 (lab HDHR-style DVR id in PMS) |
| Channels | 2–3 channel_id values: one historically “easy”, one HLS-heavy, one former startmpd1_0 offender |
| Tunerr instance | Which pod/service name actually receives GET /stream/<id> for that DVR (critical for grep) |
| Env posture | IPTV_TUNERR_CLIENT_ADAPT=true, PMS URL/token set, WebSafe transcode defaults documented in cli-and-env-reference |
Pass criteria (automated probe): probe exits OK (no start.mpd hang / timeout at the harness threshold); JSON output shows DASH init + media segment progress per the script’s schema.
Pass criteria (tunerr logs): for the matching request id / wall time, startup-gate buffered=... release=min-bytes-idr-aac-ready (or another release= you document as acceptable for that channel). On failure, capture release=, adapt ... reason=, and /debug/stream-attempts.json.
PMS logs: retain a slice around dash_init, TranscodeSession, or Failed to find consumer when the probe fails — correlate timestamps with Tunerr.
Use one row per client session.
| Date | Client (incl. browser version / app version) | Channel ID | Tune OK? | Playback stable ≥ 60s? | Tunerr adapt ... reason= |
Notes / failures |
|---|
During the run, capture:
- Tunerr:
grep 'gateway: channel=\|adapt transcode=\|adapt inherit\|sticky websafe\|startup-gate buffered'on the instance that serves that DVR (checkrelease=on WebSafe transcodes — HR-001). - Optional:
curl -s "$BASE/debug/stream-attempts.json?limit=5"for structured final status. - Plex: if Web fails, grab PMS
Plex Media Server.logslice fordash_init,TranscodeSession, or consumer errors.
Pass (pragmatic): tune succeeds, video+audio play without user-visible failure for the soak window, and Tunerr shows ok (or an explained, non-regression mux / 429 path) in stream-attempts for that request.
- plex-livetv-http-tuning — HTTP pools, mux concurrency, HR-004 sticky fallback, HR-007 overrides.
- transcode-profiles —
plexsafe,?profile=, mux query modes. - cli-and-env-reference —
IPTV_TUNERR_CLIENT_ADAPT*, PMS URL/token. - iptvtunerr-troubleshooting — startup race profile, stream-compare harness,
stream-attempts.json.