From 217c982403a105e986fb7ce13b922d9f08583632 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Mon, 5 Oct 2026 09:54:59 +0000 Subject: Move skip detection to a server, and find that it detects ads The server is Peter's design and it works: listend polls the phone's feed list, mirrors the newest 8 episodes per show, detects across all of them at once, and serves the offsets *and the audio they belong to*. Deployed on tungsten behind Apache at https://listen.terst.org. FingerprintWorker and MidrollWorker are deleted -- the battery drain is gone because the work is. Serving the audio is load-bearing. Dynamic ad insertion turned out to be on every episode: Grass Apes declares 22:05 and the server was served 28:18. An offset measured on one copy is minutes wrong for another, so the phone has to play the bytes the detector examined. The offsets land in the same columns the on-device detector wrote, so playback needed no changes and there is no migration. **The headline finding is a failure, and it is proved rather than suspected.** Detection reported the intro confirmed on 8 of 8 Radiolab episodes and passed its self-check. It had locked onto an advertisement. The chosen segment was searched for in an ad-free copy of the same episode fetched hours earlier: r = 0.0621. It is in the ad break, not the programme. The confirmation threshold assumes three *independently fetched* episodes. Mirroring 8 within one minute from the same ad server makes them maximally dependent -- whatever campaign is running is in all of them, identical -- and SelfCheck cannot catch it, because the segments genuinely are the same audio. Moving to a server fixed the cost problem and broke the independence the safety mechanism rests on. The published offsets are head-window matches, so they happen to skip pre-roll advertising, which is useful by accident and will move whenever the campaign rotates. It is not intro detection and is not described as such anywhere. The fix, not made: record when each episode was fetched and require the agreeing episodes to span more than one fetch occasion. Reasoning, and an argument for detecting the ads directly instead, in docs/detection-findings.md. Also recorded there: two tooling traps that each cost real time. `android-ui tap` can land on the soft keyboard, because the dump reports layout coordinates the IME does not move -- a Save button and a text field both appeared tapped and neither fired. And the server's token is alphanumeric now because `adb shell input text` ate the leading "-" of a base64url one and produced a silent 401 that looked like broken wiring. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B --- docs/detection-findings.md | 74 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) (limited to 'docs/detection-findings.md') diff --git a/docs/detection-findings.md b/docs/detection-findings.md index 1bb7880..26b31b3 100644 --- a/docs/detection-findings.md +++ b/docs/detection-findings.md @@ -495,3 +495,77 @@ milliseconds, and `SelfCheck` tolerates half a second of position error for the one host with curl is not enough to conclude either way, and it should be measured properly before the design leans on it. - **Midroll detection is not ported.** Only intro/outro. + +--- + +## 2026-10-05, later: the server ships, and simultaneous mirroring defeats the threshold + +`server/` now runs on tungsten as `listend.service`, behind Apache at https://listen.terst.org +with a Let's Encrypt certificate. It polls the phone's subscriptions, mirrors the newest 8 +episodes per show, detects, and serves both the offsets and the audio they belong to. The phone +pushes its feed list from `SubscriptionRepository.refreshAll`, prefers the server's audio when it +has it, and writes the offsets into the same `detectedIntroMs`/`detectedOutroMs` columns the +on-device detector used -- so playback needed no changes at all. `FingerprintWorker` and +`MidrollWorker` are deleted. + +Verified live, phone to server: `POST /api/subscriptions` returned 200 from the app, the server +woke, mirrored 8 Radiolab episodes, and detected across all of them in 3m59s. + +### Dynamic ad insertion is on every episode, and it is not subtle + +Every one of the 8 came back longer than declared: + +| episode | declared | served | inserted | +| --- | --- | --- | --- | +| Grass Apes | 22:05 | 28:18 | 6:13 | +| The Sweetest Thing | 31:02 | 37:15 | 6:13 | +| 23 Weeks 6 Days | 63:27 | 72:26 | 8:59 | +| Nigel and the Real Gannet | 25:54 | 33:01 | 7:07 | +| The Shoe Thrower | 77:29 | 87:02 | 9:33 | + +This also corrects the earlier pass in this document, which found declared == served on four +fetches made with plain `curl` and doubted whether insertion was happening at all. It is, on +every episode, when fetched by the server. Why `curl` got clean files and the server does not is +unexplained and worth knowing -- the only obvious difference is the User-Agent. + +### The confirmation threshold does not survive simultaneous fetching + +Detection reported the intro confirmed on 8 of 8, self-check passed. **It had locked onto an +advertisement.** + +Proved, rather than suspected: the 8-second segment the detector chose was extracted from the +server's copy of Grass Apes and searched for in the `curl` copy of the same episode fetched +hours earlier with no inserted advertising. **r = 0.0621.** It is not in the programme. It is in +the ad break. + +The mechanism is the Darknet Diaries trap at a new scale. The threshold's whole premise is that +three *independently fetched* episodes are unlikely to share an inserted ad. Mirroring 8 +episodes within the same minute from the same ad server makes them maximally dependent: whatever +campaign is running is in all 8, identical, and the detector finds it with scores of 0.93 to +1.00. The self-check cannot catch this -- it only proves the located segments are the same +audio, and they genuinely are. + +This is *worse* than the phone's original design, where episodes arrived days apart and so were +genuinely independent. Moving to a server fixed the cost problem and broke the independence +assumption the safety mechanism rests on. + +### What the published offsets currently are + +Head-window matches only, so every offset lands inside the first four minutes, and skipping to +the end of a matched segment skips pre-roll advertising. That is useful by accident, not by +design, and it will move unpredictably whenever the ad campaign rotates. **It is not intro +detection and must not be described as such.** + +### The fix, not yet made + +Record when each episode's audio was fetched, and require the agreeing episodes to span more +than one fetch occasion before a segment is trusted as the show's own. The mirror already rolls +forward one episode at a time as feeds update, so independence arrives naturally -- it is only +the initial batch that is simultaneous. Expect no offsets at all for the first day or two after +a show is added, which is the correct answer rather than a confident wrong one. + +There is a reframing worth considering instead, which the data argues for: what this reliably +detects, with very high confidence, is **the inserted advertising itself** -- because +simultaneous fetches share it exactly. Skipping ads is the actual goal, and the declared-versus- +served gap is a free, independent check on whether a scan has accounted for all of it. Detecting +ads directly may be a better target than detecting intros and hoping ads do not get in the way. -- cgit v1.2.3