From 62d49ba23bc97c725203ca736926326f670d0320 Mon Sep 17 00:00:00 2001 From: Patrick Stel Date: Sun, 4 Oct 2026 17:41:07 +0200 Subject: [PATCH] Carry this run's trail in the NUI report, read back from disk (issue 100) The page FUSIONOF sent on 2026-10-04, fetched while TikTok was on screen refusing every video, carried the header's one-line snapshot of each probe (media: 0x0 rs0 starved stuck, mse format: one avc1 init segment) and not one line of the sequence behind it. The NUI report had the previous run's trail and this run's DiagLog, and every media:, mse:, hold: and page error: line is trail-only by design, so the evidence the reporter was asked for twice was on disk and on no page. Breadcrumbs.Current reads this run's file back on the diagnostics server's thread, shared for writing, the last megabyte past the cap, and the NUI report carries it as "this run (the trail so far)", with trail write: in its header as the ElmSharp report already has. The trail harness holds the read-back. INTERNALS reads the page: the proxy that refused every load the day before now starves video, no element error, nothing of ours refused, and what decides it needs no build. --- CLAUDE.md | 5 +- docs/INTERNALS.md | 121 +++++++++++++++++++++++++++++++++++++- src/common/Breadcrumbs.cs | 64 ++++++++++++++++++++ src/nui/NuiBrowserApp.cs | 12 ++++ tools/trail/Program.cs | 27 +++++++++ 5 files changed, 225 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index de9c89a..b3fc64f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -429,7 +429,10 @@ around spending it well. - **`http://:8081`** serves the diagnostics report (`DiagServer`), and key `3` shows a shorter version on the TV. Ask for the *whole* page: the `previous run` block is where a launch actually ended, and the `this run` block - only shows how far the current start-up had got when the page was loaded. + only shows how far the current launch had got when the page was loaded. On the + NUI build that block is the trail file read back, so it has the probe lines + (`media:`, `mse:`, `hold:`, `page error:`); on the ewk builds it is the + sixty-line on-screen log, which never has them. - **Whatever is most likely not to survive the firmware goes last.** A probe in front of the thing it is explaining has cost this project a build three times now — issues #13, #17 and #17 again. If a diagnostic can kill the process, it diff --git a/docs/INTERNALS.md b/docs/INTERNALS.md index c29f57f..2b18ae9 100644 --- a/docs/INTERNALS.md +++ b/docs/INTERNALS.md @@ -2863,6 +2863,99 @@ reading — "pause and resume brings it back, which restarts the pipeline" — i evidence it would work. It is not in `build-ab20b83`: it changes playback, and the question of what opened the pipeline is worth one page first. +### Every video refused, and the page without its trail + +The page FUSIONOF posted on 2026-10-04 (10:12 UTC, a gist, 38 KB) is from +`build-ab20b83` and it is a different complaint: "We're having trouble playing +this video. Please refresh and try again." on **every** TikTok video, and the same +proxy "works fine on laptop". Not a freeze. Nothing plays at all. + +What the page says, and it is the header saying it, not the trail: + +``` +forced UA : Mobile Safari (the fifth preset he tried; TV default, Chrome 130, Chrome 125, Safari 17 before it) +media : playing=1 of 1 — 0x0 rs0 starved stuck +mse format: ms1 video init #1 avc1 576x1024 +mse stall : (no stall seen yet) +one video : off (menu) — videos preload freely +ad block : ... 1775 requests, 0 refused, 0 silenced +proxy : http://191.96.254.138:6185 (login set) · this page through it +requests : v16-webapp-prime.us.tiktok.com/video — 7 GET, range no +``` + +Read in order: + +- **The element is playing nothing and the engine has refused nothing.** `rs0` + with `0x0` is an element that never reached metadata; `starved` is nothing + buffered past the playhead; no `ERROR n` on the line is `v.error` null, and an + init segment the engine could not parse ends in the append-error algorithm and + `MEDIA_ERR_SRC_NOT_SUPPORTED` on the element, which the census would carry. So + TikTok built one MediaSource, appended one video init segment (`avc1`, + 576x1024, a portrait reel; the day before, `avc1` 1024x576 went from `init #1` + to `loadedmetadata` within the same second), and then nothing else arrived + for it to append. The error on screen is TikTok's own toast for a fetch that + failed or never finished, not the engine's. +- **The requests went out and the census cannot say what came back.** Seven + `GET`s to the video CDN over five page loads, none refused by us (`0 refused`), + no `Range` header on any of them. The `answered` column is ours only — what + the interceptor itself answered — and the hook never sees a response, so the + page cannot tell a 403 from a byte stream that stopped. That is the one + thing it would have taken to call this closed from here. +- **It is the proxy that refused every load the day before.** `191.96.254.138: + 6185` is the one *The hold held, and the reel froze anyway* has refusing + TikTok, `tiktok.com` and DuckDuckGo for eight minutes on 2026-10-03 (`Unknown, + Unknown (via proxy)`), after which he changed to `198.23.243.226:6361` and + everything through that one played, freeze included. On 2026-10-04 he is back + on the first proxy; it answers pages now and starves video. That is the shape + *A failed load is an answer, not silence* already named — `rs1`/`rs2` with the + `GstTZAppSrc` underflow, "a shared proxy that cannot keep up with video" — + one step further down, at `rs0`. "The same proxy works on the laptop" is the + one thing against it, and it is weaker than it sounds: a shared proxy hands + out exits per connection, a CDN that refuses an exit refuses it for the one + client on it, and nothing on this page says the laptop's video went through + the same exit, or through the proxy at all for the CDN host. +- **The hold is off**, by the menu row, which he will have done looking for the + cause. It has nothing to do with this (`playing=1 of 1`: no second element for + it to hold) and the freeze comes back with it off, so it goes back on. +- **The UA presets are not it.** Five of them, the error on all five. + +**And the page carried none of the sequence, which is the finding about us.** The +report asked him for, twice now in this issue, is "the `:8081` page while the app +is still open". He sent exactly that. The NUI report had the previous run's +trail, this run's `log` — `DiagLog`'s sixty on-screen lines — and this run's +*header*, one line per probe with its last reading. Every line that would have +shown the sequence (`mse: ms1 add video …`, the audio buffer it did or did not +add, `page error:` with TikTok's own words for the fetch that failed, the +`media:` census going from `rs0` to `rs0`) goes to the trail alone through +`DropToTrail`, by design (`DiagLog` is sixty lines and a heartbeat would evict +the start-up), and the trail is on disk and was on no page. The passage in *What +is left on the 2025 sets* saying "the `this run` block carries it" was written +from the ElmSharp report, whose `this run` is also `DiagLog`; it was never true of +either build. So `Breadcrumbs.Current` reads this run's file back — on the +diagnostics server's thread, shared for writing, the last megabyte if it has +grown past that — and the NUI report carries it as `this run (the trail so +far)`, with `trail write:` in the header above it the way the ElmSharp one has. +`tools/trail/run.sh` holds the read-back to carrying trail-only lines, picking +up a later one, and saying what it cut past the cap. Nothing in the app's +behaviour changes; the ewk packages compile the accessor and never call it. + +What decides it, and none of it needs a build: + +- **The other proxy.** TikTok through `198.23.243.226:6361` played reels on + 2026-10-03 through the same probe and hold. Videos playing through it today + and not through `191.96.254.138:6185` is the proxy, and nothing on this side. +- **The page from the build with the read-back, while TikTok is on screen + refusing.** `page error:` lines are TikTok's own console errors and will name + the fetch (a status, `Failed to fetch`, a `net::ERR_` code); the `mse:` lines + say whether an audio buffer was ever added and what it got; the `media:` + census at `rs0 starved` for the whole session is the data never coming. And + the native output: not one `omxtzuhdvideodec` for the whole session is an + engine that was never handed a frame to decode. +- **The one answer that would be ours:** an `init #1` followed by `ERROR 4` or + `ERROR 3` on the census, or a `page error:` naming a `SourceBuffer` append. + That is the engine refusing the stream, and the format on the `init` line is + what to go to. + ## What the NUI build never asked the engine for The two builds share `src/common` and nothing else, and everything the ElmSharp @@ -3805,9 +3898,10 @@ the one its report has to come from. The state is: **Shipped in `build-91f53ce`: the round of favourites** (see *Switching sites* above; `tools/siterules` holds it). Reply posted 2026-10-03 quoting that tag. **Waiting on:** the freeze itself, which needs the page from a launch that *had* it: `:8081` while the app is still open after - the freeze (the `this run` block carries it), or at the very next launch (the - `previous run` block does), and not a launch later, because that is the one the - report forgets. Read it by the markers above: `dur inf` and `live page` name the + the freeze (the `this run` block carries it — **it did not, until the build + after `build-ab20b83`**; see *Every video refused, and the page without its + trail*), or at the very next launch (the `previous run` block does), and not a + launch later, because that is the one the report forgets. Read it by the markers above: `dur inf` and `live page` name the stream, a `hold:` line in front of the stall says the hold was in, no `hold:` line on a page that was playing says it was not. **That page came the same morning (2026-10-03, 08:33 UTC), from `build-e1a648d` @@ -3829,6 +3923,27 @@ the one its report has to come from. The state is: the same fix — the pause/play recovery set aside in *Two videos, one decoder*, a different trade now that the freeze is confined to live streams — and the first one has the hold. + **Then a different page (2026-10-04, 10:12 UTC, from `build-ab20b83`): every + TikTok video refused with TikTok's own "trouble playing this video" toast, and + the same proxy "works fine on laptop".** See *Every video refused, and the page + without its trail*. The header has the one element at `0x0 rs0 starved stuck` + with no element error after one `avc1` init segment, seven requests to the video + CDN that nothing of ours refused, and the proxy is `191.96.254.138:6185` — the + one that refused every load for eight minutes the day before, after which the + reels that played went through `198.23.243.226:6361`. The reading is the proxy + starving video, as *A failed load is an answer, not silence* read `rs1`/`rs2`, + and the page cannot prove it, because the page fetched while the app is open + carried no trail: the NUI report had never had a `this run` block for the trail + file, only `DiagLog`'s sixty lines, and every `mse:`/`media:`/`page error:` line + is trail-only. Fixed: `Breadcrumbs.Current` and the `this run (the trail so + far)` block, `trail write:` in the header. His hold is **off** by the menu row + and should go back on. **Waiting on:** TikTok through the other proxy (plays: + it is this proxy, nothing on our side), and the page from the new build while + TikTok is refusing, read by `page error:` (TikTok's words for the failed fetch), + the `mse:` lines (an audio buffer added or not), and the native output (no + `omxtzuhdvideodec` at all is an engine never handed a frame). The answer that + would be ours is `ERROR 3`/`ERROR 4` on the census after an `init #1`, and the + freeze question above is unchanged behind this one. Five things about that set are settled and should not be re-derived: **key `5` is his, not ours** — the engine's overlay path is the only one that gives him a diff --git a/src/common/Breadcrumbs.cs b/src/common/Breadcrumbs.cs index d093160..49e7a94 100644 --- a/src/common/Breadcrumbs.cs +++ b/src/common/Breadcrumbs.cs @@ -68,6 +68,70 @@ public static string Location get { return _path ?? "(nowhere writable)"; } } + /// + /// How much of this run's trail the report carries when the file has grown + /// past it: the end, because the end is where the thing being asked about + /// is. A session's own lines are a few dozen kilobytes an hour; this is only + /// reached by a page that writes errors without end. + /// + private const int CurrentCap = 1024 * 1024; + + /// + /// This run's trail so far, read back from disk for the report. + /// + /// The report used to carry the previous run's trail and this run's + /// , and nothing else — because the app that dies has no + /// afterwards in which to read anything back, the launch that died is the one + /// with the answer in it, and sixty on-screen lines are what an app that is + /// still up needs. But the lines that go to the trail alone + /// (: the memory readings, the media census, the + /// MSE probe, the hold) are exactly the evidence a reporter is asked for + /// *while the app is still open*, and until this existed the page fetched + /// then did not have them: issue #100's 2026-10-04 page was fetched with + /// TikTok on screen refusing every video, and carried a header snapshot + /// (media : ... rs0 starved stuck) with not one mse: line + /// behind it, the sequence being on disk and nowhere on the page. + /// + /// Read on the diagnostics server's thread, never the writer's, and with the + /// file shared for writing so the writer is never waited on. A trail still + /// in the writer's queue (see ) is not on disk yet and so + /// not here; the header's trail write: line says when that is the case. + /// + public static string Current + { + get + { + string path = _path; + if (path == null) + { + return "(no trail: nowhere was writable)"; + } + + try + { + using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + long length = stream.Length; + string note = string.Empty; + if (length > CurrentCap) + { + stream.Seek(length - CurrentCap, SeekOrigin.Begin); + note = "(first " + (length - CurrentCap) + " bytes of this run's trail left out; the end is below)\n"; + } + + using (var reader = new StreamReader(stream)) + { + return note + reader.ReadToEnd(); + } + } + } + catch (Exception ex) + { + return "(this run's trail could not be read back: " + ex.GetType().Name + ": " + ex.Message + ")"; + } + } + } + /// /// The directory the trail landed in, or null when none was writable. /// needs a scratch file and this is the one diff --git a/src/nui/NuiBrowserApp.cs b/src/nui/NuiBrowserApp.cs index 0df6d42..6e7d929 100644 --- a/src/nui/NuiBrowserApp.cs +++ b/src/nui/NuiBrowserApp.cs @@ -3071,10 +3071,20 @@ private string Report() // The previous run's trail, exactly as the ElmSharp report carries it. // A page that closes the app leaves nothing to read in this run: the // launch that died is the one with the answer in it. + // + // And this run's, read back from disk (issue #100, 2026-10-04). The + // `log` block at the end is DiagLog's sixty on-screen lines, which the + // trail-only lines — `media:`, `mse:`, `hold:`, `page error:`, the + // memory readings — never enter. A reporter asked for the page "while + // the app is still open" sent exactly that page, with TikTok refusing + // every video on screen, and it carried the header's one-line snapshot + // of each probe and not one line of the sequence behind it. The trail + // was on disk the whole time. Breadcrumbs.Current explains the read. return "Overscan diagnostics (NUI build)\n\n" + "build : " + BuildInfo.Tag + "\n" + "platform : NUI WebView, api-version 9.0+\n" + "trail file : " + Breadcrumbs.Location + "\n" + + "trail write: " + Breadcrumbs.Status + "\n" + engine + "\n\n" + // Issue #37. dest is the engine's own Sec-Fetch-Dest for the // request, and build-f295172's report answered what it was put @@ -3086,6 +3096,8 @@ private string Report() "sites remembered (issue 74)\n" + SiteRules.Dump() + "\n" + "requests this run (one line per host and first path segment, most first)\n" + RequestTrail.Dump() + "\n\n" + + "this run (the trail so far — the media, mse, hold and page error lines are here and not in the log)\n" + + Breadcrumbs.Current + "\n\n" + "previous run (last line is where it died)\n" + Breadcrumbs.Previous + "\n\n" + "previous run's native output\n" + Breadcrumbs.PreviousStdErr + "\n\n" + "log\n" + DiagLog.Dump(); diff --git a/tools/trail/Program.cs b/tools/trail/Program.cs index ca21c52..20153e4 100644 --- a/tools/trail/Program.cs +++ b/tools/trail/Program.cs @@ -77,6 +77,33 @@ private static void Plain(string dir) Expect(Tizen.Log.Calls == 1, "dlog saw the one line meant for it (" + Tizen.Log.Calls + ")"); Expect(DiagLog.Dump().Contains("first") && !DiagLog.Dump().Contains("second"), "and the on-screen log kept the one meant for it"); + + // Issue #100, 2026-10-04: the report carries this run's trail read back + // from disk, so a page fetched while the app is open has the trail-only + // lines (media, mse, hold) and not just the on-screen log. + string current = Breadcrumbs.Current; + Expect(current.Contains("--- run started ---") && current.Contains(" first\n") && current.Contains(" second\n"), + "the report's read-back has this run's trail, trail-only lines included"); + Breadcrumbs.DropToTrail("third"); + Expect(Breadcrumbs.Current.Contains(" third\n"), "and a later line the next time it is asked"); + + // A trail past the cap comes back from its end, with the cut said out loud. + using (var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite)) + { + byte[] filler = System.Text.Encoding.ASCII.GetBytes(new string('x', 4095) + "\n"); + for (int i = 0; i < 300; i++) + { + stream.Write(filler, 0, filler.Length); + } + } + + Breadcrumbs.DropToTrail("last"); + string capped = Breadcrumbs.Current; + Expect(capped.StartsWith("(first ") && capped.Contains(" bytes of this run's trail left out"), + "a trail past a megabyte says what it left out"); + Expect(!capped.Contains("--- run started ---") && capped.EndsWith(" last\n"), + "and keeps the end, where the last line is"); + Expect(capped.Length <= 1024 * 1024 + 200, "at about the cap (" + capped.Length + " chars)"); } ///