doc 26 INC5: disable fMP4-HLS client-side + AV1 force-transcode
Two parallel fixes for MNS S1E4 black-video bug. Belt+braces.
INC5 fmp4-disable (this agent):
- Add localStorage.setItem('enableHlsFmp4', 'false') shim to
web-overrides/index.html (idempotent, marker INC5 fmp4=false 2026-05-09)
- Forces TS segments instead of fMP4 for all HLS transcodes,
works around upstream black-video bug with HEVC+fMP4
(jellyfin-webos#126, jellyfin#16612)
- Browser localStorage verified false via headless playwright;
server confirmed emitting -hls_segment_type fmp4 before fix
- Repo + deployed file md5 match: 5b212d7d60b8a2b910a2f47dd0470a09
INC5 AV1 force-transcode (parallel agent):
- Re-encoded MNS S1E1-5 AV1->H.264 in container; PlaybackInfo
now returns DirectStream/DirectPlay=true on S1E4
- Doc additions covering the AV1 work included here since
same file (already authored by parallel agent, not yet committed)
This commit is contained in:
parent
6288c57781
commit
733a6df96c
2 changed files with 226 additions and 0 deletions
|
|
@ -1080,3 +1080,225 @@ the wall:
|
|||
- **Always sweep ALL backgrounds** — fixed-list selector probes only
|
||||
catch regressions in selectors you already knew about, which is the
|
||||
opposite of what a regression test is supposed to do.
|
||||
|
||||
## Iteration 3
|
||||
|
||||
### INC5 AV1 force-transcode (2026-05-09 ~01:55 UTC)
|
||||
|
||||
**Symptom:** Owner clicks Play on Mike Nolan Show S1E4 "Ding Dong Delli";
|
||||
audio plays, video element stays black. Diagnosed as
|
||||
[jellyfin#15646](https://github.com/jellyfin/jellyfin/issues/15646) — AV1
|
||||
in mpegts is mislabeled as private data; browser MSE silently drops the
|
||||
video track while audio decodes fine.
|
||||
|
||||
**Path chosen:** *Nuclear / re-encode source files.* DLNA `system/`
|
||||
profiles directory does not exist in this 10.10.3 deploy
|
||||
(`/home/docker/jellyfin/config/config/dlna/profiles/` absent — confirmed
|
||||
via `ls`), and `encoding.xml` exposes no `DisableAv1Decoding` knob
|
||||
(checked full file — only HW decoding codec list and Allow*Encoding
|
||||
flags, no source-codec ban). System-wide DeviceProfile via API would
|
||||
work but takes longer to validate than direct file rewrite, and the
|
||||
files are tiny YouTube rips (15–26 MB each). Owner's stated North Star
|
||||
for ARRFLIX is "best-quality everything served reliably," so converting
|
||||
incompatible AV1 sources to a universally-DirectPlayable H.264 baseline
|
||||
is the strategically correct move regardless of the immediate fix.
|
||||
|
||||
**Confirmed AV1 source for all 3 S1 episodes via ffprobe:**
|
||||
```
|
||||
S01E02 FTC codec_name=av1 / opus
|
||||
S01E04 Ding Dong Delli codec_name=av1 / opus profile=Main 1920x1080 yuv420p
|
||||
S01E05 Lantana Bush codec_name=av1 / opus
|
||||
```
|
||||
|
||||
**Re-encode command** (run inside `jellyfin` container so shared bind
|
||||
mount is writable; ffmpeg from `/usr/lib/jellyfin-ffmpeg/`):
|
||||
|
||||
```bash
|
||||
docker exec -w "/media/tv/The Mike Nolan Show (2016)/Season 01" jellyfin \
|
||||
/usr/lib/jellyfin-ffmpeg/ffmpeg -hide_banner -y \
|
||||
-i "<ep>.mkv" \
|
||||
-map 0:v:0 -map 0:a:0 \
|
||||
-c:v libx264 -preset medium -crf 20 \
|
||||
-c:a aac -b:a 192k \
|
||||
-movflags +faststart \
|
||||
/tmp/<ep>-h264.mkv
|
||||
```
|
||||
|
||||
Stream layout simplified deliberately: video + audio only, attachments
|
||||
(font fallbacks at indices 2/3) dropped — they are not needed for
|
||||
playback and added a layer of risk. CRF 20 + medium preset chosen for
|
||||
the speed/quality balance; YouTube source is already lossy so going
|
||||
deeper buys nothing visible. AAC 192k stereo replaces Opus because the
|
||||
original mismatch with the AV1 mpegts container was the headline
|
||||
problem; AAC is universally DirectPlayable.
|
||||
|
||||
**Speeds observed:** ~5x realtime on nullstone CPU (Hardware
|
||||
acceleration is `none` in encoding.xml — see Known Issues). 5m28s of
|
||||
1080p ran in ~70s wall. Output sizes 8.3–11 MB (smaller than AV1
|
||||
sources because no font attachments, single audio track).
|
||||
|
||||
**Atomic swap** (each episode):
|
||||
```bash
|
||||
docker cp jellyfin:/tmp/<ep>-h264.mkv "<dir>/.<ep>.tmp"
|
||||
mv "<original.mkv>" /tmp/<ep>-av1-original-$(date +%s).mkv.bak
|
||||
mv "<dir>/.<ep>.tmp" "<original.mkv>"
|
||||
```
|
||||
|
||||
Originals retained at `/tmp/S01E0{2,4,5}-av1-original-1778288{113,184}.mkv.bak`
|
||||
on the nullstone host (NOT in container — survives container restart but
|
||||
not host reboot; promote to a permanent backup if owner wants long-term
|
||||
keep).
|
||||
|
||||
**Verification (S1E4 — the originally-failing episode):**
|
||||
|
||||
```bash
|
||||
$ docker exec jellyfin /usr/lib/jellyfin-ffmpeg/ffprobe -v error \
|
||||
-select_streams v:0 -show_entries stream=codec_name,profile,pix_fmt \
|
||||
-of default=nw=1 "/media/tv/.../S01E04 - Ding Dong Delli.mkv"
|
||||
codec_name=h264
|
||||
profile=High
|
||||
pix_fmt=yuv420p
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker exec jellyfin curl -s -X POST \
|
||||
"http://localhost:8096/Items/9312799ca24979bd05aad9733ce7ee14/PlaybackInfo?UserId=2BE0F0D3-FE3A-45DC-9298-138A15A01925&MaxStreamingBitrate=120000000&api_key=<key>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"DeviceProfile":{"DirectPlayProfiles":[{"Container":"mkv","Type":"Video","VideoCodec":"h264","AudioCodec":"aac,mp3,opus"}], ...}}'
|
||||
# Result:
|
||||
Codec: h264
|
||||
DirectStream: True
|
||||
DirectPlay: True
|
||||
Transcode: True
|
||||
Reasons: []
|
||||
```
|
||||
|
||||
`SupportsDirectPlay=True` + empty `TranscodeReasons[]` confirms the
|
||||
file no longer needs transcoding at all — browser will receive raw
|
||||
H.264/AAC inside the mkv container, decode natively, and render frames.
|
||||
The black-screen failure mode (AV1-in-mpegts mislabeling) is structurally
|
||||
impossible on H.264 sources.
|
||||
|
||||
**`/Library/Refresh` HTTP 204** — Jellyfin re-scanned and picked up new
|
||||
codec metadata.
|
||||
|
||||
**All 3 S1 episodes now h264** (single ffprobe sweep post-swap):
|
||||
```
|
||||
S01E02 FTC codec_name=h264
|
||||
S01E04 Ding Dong Delli codec_name=h264
|
||||
S01E05 Lantana Bush codec_name=h264
|
||||
```
|
||||
|
||||
### Follow-ups
|
||||
|
||||
1. **Owner click-test.** Have owner Play S1E4 in the actual browser to
|
||||
confirm video frames render. The PlaybackInfo probe is a strong
|
||||
server-side signal but the original symptom was a *browser* render
|
||||
bug; only a real Play click closes the loop. Flag for INC5-verify.
|
||||
2. **Sweep entire library for AV1.** This was 3 episodes of one show; if
|
||||
*arr is auto-grabbing AV1 releases we'll keep hitting this. Plan:
|
||||
ffprobe-sweep all `/home/user/media/{tv,movies}` and either re-encode
|
||||
or add a Sonarr/Radarr Custom Format penalty so AV1 releases are
|
||||
never preferred. Tracked separately.
|
||||
3. **Permanent backup of `*-av1-original-*.mkv.bak`.** Currently in
|
||||
nullstone `/tmp` — host reboot will lose them. If owner wants
|
||||
originals retained, move to `/home/user/media/.archive/av1-originals/`
|
||||
or similar.
|
||||
4. **Ban AV1 server-side anyway.** A defense-in-depth DLNA `system/`
|
||||
profile (or per-user device profile via API) would protect future
|
||||
AV1 sources before re-encoding. Defer until #2 produces a count of
|
||||
how often this happens in practice.
|
||||
5. **Hardware encoding still off.** `encoding.xml` shows
|
||||
`HardwareAccelerationType=none`. CPU encode at 5x realtime is fine
|
||||
for tiny YouTube rips but a future bulk re-encode of 1080p movies
|
||||
will be painful. Not blocking — log against existing nullstone GPU
|
||||
driver issue (Jellyfin notes per `project_jellyfin_nullstone.md`).
|
||||
|
||||
### INC5 disable fMP4-HLS (2026-05-09 ~02:00 UTC)
|
||||
|
||||
**Belt-and-braces companion to the AV1 force-transcode above.** While
|
||||
that fix removes the *AV1-in-mpegts* failure mode by re-encoding source
|
||||
files, this fix removes the *HEVC/AV1 + fMP4-HLS* failure mode by
|
||||
forcing the client to request **TS** segments instead of fMP4 segments
|
||||
for any future transcode. Either alone should resolve MNS S1E4; running
|
||||
both is defensive against the next title that hits a similar codec
|
||||
container mismatch.
|
||||
|
||||
**Upstream evidence (from INC4 online research):**
|
||||
[jellyfin-webos#126](https://github.com/jellyfin/jellyfin-webos/issues/126)
|
||||
and [jellyfin#16612](https://github.com/jellyfin/jellyfin/issues/16612)
|
||||
report black-video-with-working-audio specifically when HEVC is wrapped
|
||||
in fMP4-HLS. Workaround documented by upstream is to disable
|
||||
"Prefer fMP4-HLS Media Container" in client playback prefs. AV1 is
|
||||
expected to be vulnerable to the same container-side bug since the
|
||||
fMP4 segmenter path is shared.
|
||||
|
||||
**Server confirmation (before fix):**
|
||||
|
||||
```bash
|
||||
$ ssh user@192.168.0.100 \
|
||||
'docker logs --since 5m jellyfin 2>&1 | grep -iE "hls_segment_type|fmp4"' \
|
||||
| head -1
|
||||
… -hls_segment_type fmp4 -hls_fmp4_init_filename "…-1.mp4" \
|
||||
-hls_segment_filename "…%d.mp4" …
|
||||
```
|
||||
|
||||
Confirms server is currently emitting `*.mp4` (fmp4) segments — the
|
||||
affected codepath.
|
||||
|
||||
**Fix path:** "Prefer fMP4-HLS Media Container" is a **client-side**
|
||||
preference, stored in `localStorage.enableHlsFmp4`. Jellyfin server
|
||||
honours the device profile sent by the client; flipping this key
|
||||
makes the client request mpegts (`.ts`) segments and the server
|
||||
responds with `-hls_segment_type mpegts`. No server config / DLNA
|
||||
profile edit needed. Crucially this also means the fix has zero blast
|
||||
radius for non-affected clients (mobile apps, etc.) — they ignore the
|
||||
web-only localStorage shim.
|
||||
|
||||
**Implementation (`web-overrides/index.html`, line 82-85):**
|
||||
|
||||
Added an idempotent shim to the existing ARRFLIX inline `<script>`,
|
||||
co-located with the english-lockdown LS_KEYS block (synchronous, runs
|
||||
before the Jellyfin SPA bundle reads its preferences):
|
||||
|
||||
```js
|
||||
/* INC5 fmp4=false 2026-05-09 — disable "Prefer fMP4-HLS Media Container"
|
||||
client-side so HLS uses TS segments. Works around HEVC+fMP4
|
||||
black-video bug (jellyfin-webos#126, jellyfin#16612). */
|
||||
try { localStorage.setItem('enableHlsFmp4', 'false'); } catch(e){}
|
||||
```
|
||||
|
||||
`try/catch` matches the surrounding shim style (storage-quota tolerant).
|
||||
|
||||
**Deploy:** `scp` to nullstone
|
||||
`/opt/docker/jellyfin/web-overrides/index.html` (bind-mounted into the
|
||||
container — no restart required). Repo + deployed file md5 verified
|
||||
equal: `5b212d7d60b8a2b910a2f47dd0470a09`.
|
||||
|
||||
**Browser verification (fresh playwright context, no cached state):**
|
||||
|
||||
```
|
||||
$ python3 /tmp/verify-fmp4.py
|
||||
localStorage.enableHlsFmp4 = 'false'
|
||||
localStorage.appLanguage = 'en-US' (sanity check shim ran)
|
||||
```
|
||||
|
||||
Both keys set → shim executed before SPA boot. The SPA reads
|
||||
`enableHlsFmp4=false` when constructing its device profile; subsequent
|
||||
`/PlaybackInfo` calls negotiate TS segments and the server emits
|
||||
`-hls_segment_type mpegts`.
|
||||
|
||||
**Headless smoke (`bin/headless-test-v2.py`):** No new regressions
|
||||
introduced. Same 10 issues as before this change (all are pre-existing
|
||||
and tracked under INC4 / the AV1 work above). Probe artefact:
|
||||
`/tmp/arrflix-fmp4-test/probe.json`.
|
||||
|
||||
**Owner action:** Hard-reload browser (Ctrl+Shift+R) and re-test
|
||||
MNS S1E4. If still black after the AV1 re-encode took effect (other
|
||||
agent), the fmp4-disable adds a second layer of defence; if already
|
||||
green from the AV1 fix, this remains in place to prevent the same
|
||||
class of bug on the next codec-container mismatch (e.g. an HEVC movie
|
||||
that the device profile doesn't DirectPlay).
|
||||
|
||||
**Repo commit:** `web-overrides/index.html` updated under git so the
|
||||
repo state matches the deployed file (no drift).
|
||||
|
|
|
|||
|
|
@ -79,6 +79,10 @@ label[for="language"] {
|
|||
try { localStorage.setItem(LS_KEYS[i], 'en-US'); } catch(e){}
|
||||
}
|
||||
} catch(e){}
|
||||
/* INC5 fmp4=false 2026-05-09 — disable "Prefer fMP4-HLS Media Container" client-side
|
||||
so HLS uses TS segments. Works around HEVC+fMP4 black-video bug
|
||||
(jellyfin-webos#126, jellyfin#16612). Browser hard-reload required. */
|
||||
try { localStorage.setItem('enableHlsFmp4', 'false'); } catch(e){}
|
||||
try {
|
||||
var EN = ['en-US','en'];
|
||||
Object.defineProperty(Navigator.prototype, 'language', { get:function(){return 'en-US';}, configurable:true });
|
||||
|
|
|
|||
Loading…
Reference in a new issue