| Age | Commit message (Collapse) | Author |
|
Verified on the live server: a mark vouching for a real detected break
reaches the signature that break produced and sets confirmed on it.
The probe and the flag it set were removed afterwards; that corpus
holds the listener's statements, not test data.
Also records the property that cost two runs to notice -- a label is
only actionable while its episode is still mirrored. They outlive the
audio deliberately, so the server holds plenty naming episodes long
rotated out, and those are simply not applied.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|
|
The labels have been arriving and sitting there. Now the shadow run
acts on all three kinds.
A report is advertising pointed at where detection found nothing, so
it becomes a signature in its own right -- that being the whole gap
the library exists to close, since the scan accounts for about 38% of
what the feeds imply is there.
A mark saying a break is right adds no audio, because the break is
harvested anyway. What it adds is that a person vouched for it.
A mark saying a break is wrong does two things. It stops that audio
being harvested, and it takes back any signature already held that
matches it. That second half is the one that matters: a signature
built from programme would cut the programme in every episode carrying
it, until somebody noticed. So his "no" removes it outright rather
than lowering a score.
Being pointed at outranks partners and occasions both. Those are ways
of inferring what a stretch of audio is; he was there. A confirmed
signature is trusted at once and stays confirmed -- he does not
un-hear it next week.
A report whose end was never corrected is trimmed to its first fifteen
seconds. An uncorrected end is wherever the 25-second jump landed
rather than where the advertising stopped, so its tail may be
programme -- and one ad unit is already far more audio than a landmark
fingerprint needs to be distinctive.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|
|
The dependencies for this were already in the build -- ui-test-junit4
on androidTest, ui-test-manifest on debug -- and had never been used.
So the spike was one test, and it passed first run. Eighteen now.
What they cover is the data-driven composables, chosen because a
regression in them looks completely normal: a readout that quietly
stops printing one of its three numbers, a control that stops
reporting whether it will play or pause, a bar that stops naming what
is playing while a screen shows something else.
The episode transport: the ad button is offered only while something
is playing, because with nothing playing there is no advertising to be
in the middle of and an enabled button would record a claim about a
position nobody is at. The primary control says what pressing it will
do. The skip buttons move by the amount they advertise.
The skip readout: all three numbers on one line, the status when there
is nothing to cut -- "nothing found" and "not checked yet" being
different answers is the mistake the old readout made -- and a legend
that names only what is actually on the track.
The now-playing bar: names the episode and the show, opens what is
playing when tapped, and reports play against pause.
Screens that need view models are still verified by eye. This is the
half that did not need them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|
|
401 and 403 are permanent by decision -- a refused token cannot fix
itself -- and the notice saying so was held in memory. So it vanished
with the process and only came back once the next refresh had gone and
been refused all over again, which for 61 feeds is hours of the app
quietly downloading from publishers with nothing on screen to say why.
It now lives in the preferences file and is there at launch.
Saving different credentials clears it at once rather than waiting for
a request to prove it, because that is the one act that answers the
complaint. Saving the same ones again proves nothing and leaves the
notice standing.
Behind a small interface so SkipServer does not have to know whether
it is writing to a preferences file or to nothing: every test wants it
in memory, without a Context to hang a DataStore off.
The stored one takes the reading and the writing as two narrow seams
rather than a Preferences. Passing Preferences dragged a Context in
and made it the one piece of the server client that could only be
exercised on a device -- the coverage gate caught that, which is what
it is for. What it actually decides, when *not* to write, is now plain
Kotlin with five tests on it: clearing runs after every successful
reply, so two hundred successes in a sweep must write nothing.
Verified on a device: a refusal written by one Preferences instance is
read back by another, and four cases around saving credentials behave
as described.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|
|
Records what moved and why, including the two controls that left
Settings -- speed because the episode screen already had it, the sleep
timer because it is an action rather than a setting.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|
|
Four and five are done and six is deliberately not. Everything six
needs exists; what is missing is weeks of real batches, and building
it now would throw away the only reason the shadow run exists.
Records what to watch for in the log, and the one decision the tests
forced: a copy that arrives with partners is trusted at once, because
holding it back would mean only ever proposing advertising detection
had already found twice. A cue gets no shortcut, which is the
independence intro and outro are waiting for.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|
|
The phone has had two kinds of label and nowhere to put them: a mark,
which is a judgement on a break detection found, and a report, which
is the notification button pointing at advertising it missed. They
catch opposite errors, and the signature library needs both.
POST /api/labels now takes them and state.json keeps them. Episodes
are named by feed and guid rather than by either side's own id -- the
phone never sees the server's, and the server's belongs to a mirror
that forgets an episode after eight. The labels sit beside the shows
rather than on them for the same reason: the corpus has to outlive the
audio it was given on.
A report's end is sent as guessed or measured. It is only measured
once the listener came back and trimmed it; otherwise it is wherever
the jump landed, and a signature cut to that would carry programme.
The phone re-sends everything each sweep and keeps no record of what
arrived. The server replaces rather than accumulating, so a re-send is
free, there is no sync state to get wrong, and a label lost to a crash
comes back on the next refresh. Gated on audioFromServer, like every
other server re-query: a label names a position in a particular file,
and dynamic ad insertion means the publisher's copy is a different one.
Accepting a label writes the state file immediately. Everything else
is written at the end of a poll cycle, which for 61 feeds is hours,
and a tap made once should not wait that long to become durable.
Deployed and checked against the live server: a label is accepted, a
re-send leaves one, an unauthenticated POST gets 401, and the label is
on disk within seconds. The probe label used for that check was
removed afterwards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|
|
The plan made this the gate: a linear scan of N stored signatures
against each episode does not survive, so the library is only possible
if an index can shortlist candidates cheaply. It can.
Built as a throwaway harness against the live corpus. The episodes the
plan named had rotated out of the server's 8-episode window, so the
test set was rebuilt from current audio and came out stronger than the
one it replaces -- 6 true repeats and 16 non-repeats, against 2 and 3.
Six repeats scored 813-1564 votes; sixteen non-repeats scored 27-94.
The narrowest separation is 8.6x, where the correlator it replaces
manages 4.9x on the same audio. Nothing sits in the gap.
Offsets land within 1.3s and every proposal is cheap to confirm by
correlating against a short window at the claimed position, so the
shape is: index proposes, correlator disposes. That is not a
formality. In one episode the advertisement occurs twice and the index
picked the weaker instance, which only a confirming pass would catch.
Measured: 9.8ms to hash a signature when it is stored, 196ms to look
one up against 23 episodes, ~8s per episode to index, 353MB for 23.
Five hundred signatures cost 98 seconds where linear correlation costs
about 63 minutes.
The harness is not committed -- it read absolute server paths and had
no tests, and its output was an answer rather than code to keep. The
parameters it found are written into the plan so it can be rebuilt
test-first, along with the one weakness worth knowing: the hashes
collide heavily, and that density is what makes a lookup cost
milliseconds rather than microseconds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|
|
The "this is an ad" press jumped 45 seconds, a number argued from
ad-unit arithmetic rather than from the controls that already exist.
Peter: "the jump should stay 25 seconds. Don't go inventing new
timing."
Skipping forward is already what you do to an advertisement, so the
button goes the same distance and additionally says why. One number to
know, and a second press goes the same distance again.
The amount had three definitions already -- the player's increments,
the episode screen's transport buttons, and the notification caption's
hardcoded "Forward 25 seconds" -- and this nearly became a fourth with
a different value. They now all read one, and the captions are derived
from it so a caption cannot name a number its button does not do.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|
|
The notification carried four buttons doing two jobs: Media3's default
previous/next (which PodcastPlayer redefines as seek) plus our own
back-15/forward-25 pair. Media button preferences with explicit slots
let the custom pair claim the two seek slots, so the duplicates go and
the freed space carries "this is an ad". No player command is removed
to achieve it, which is the route that once silently killed the
headphone skip button.
One press means four things, told apart by where the playhead lands
rather than by guessing: press claims advertising from here and jumps
past it, pressing again extends the same claim, coming back inside the
jump moves its end to where you landed, and coming back past the press
withdraws it. Outside a 20-second window a seek is ordinary navigation
and says nothing. The window is stored with the label so changing it
later cannot reinterpret old data.
Every backwards seek feeds the gesture, whatever caused it --
notification, headphone remote, in-app bar, dragged slider -- because
you correct with whatever is to hand.
This is the opposite evidence to the tap-a-break popup. That one can
only correct breaks detection already found, and detection accounts for
about 38% of the advertising the feeds imply.
Verified on the emulator: the notification now shows exactly back-15,
pause, forward-25 and the new button; the lock screen shows it too and
pressing it there records a claim without unlocking; all four gesture
arms behave as specified; and the media transport keys still seek by
25s and 15s. Android Auto still needs the car.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|
|
Keeps partners on episode_midrolls (schema 15), stores verdicts in their own
break_verdicts table so a server refresh cannot wipe them, and drops invalidated
breaks from skipping on the next tick. Verdicts are local only.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|
|
Eight tasks covering the spec decisions end to end: the server decides a
kind for every break it publishes, the phone stores it, advertising is
always skipped and a show own intro, cue and credits follow one
preference, and the readout names which is which.
The cue prior and the furniture library are deliberately not planned
here. Each is independently shippable, and the cue signature thresholds
should be chosen against kinds visible in real data rather than guessed
at before any exist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|
|
Approach 2 chosen, built in the phase order already proposed -- it is a
superset of approach 1, so the ad-detection work still lands first and the
library last.
Advertising is always skipped; the show own intro, cue and credits obey a
single preference defaulting to on, because that is what happens today.
The toggle settles a bootstrapping question the design had left open. A
furniture clip cannot be promoted until a second fetch occasion has
passed, so until then there is nothing to label with. The zero-gap
classification fills that gap: served length equal to declared means
nothing was inserted, so every break is furniture and position decides
which kind; otherwise a break is advertising unless a promoted clip says
otherwise. Day one is correct on the 138 zero-gap episodes and identical
to current behaviour everywhere else.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|
|
Three defects turn out to be one missing distinction. Ad detection finds
a median 67% of the advertising the length gap implies, because it can
only see what two simultaneously-mirrored episodes share. It reports a
show's own intro, break cue and credits as advertisements on 111 of the
138 episodes that had nothing inserted into them. And intro/outro
offsets stay withheld for want of independent evidence.
Peter's observation about theme music around ad breaks is the clue, and
probing it showed both what it gives and what it does not: the cue is
real, already detected, and marks a break's entry but not its extent --
scanning to r=0.6 finds one isolated hit per slot in ad-laden and ad-free
copies alike, so there is no closing cue to bracket against.
Writing this up changed the recommendation. The furniture library is only
needed for the intro/outro problem; the mislabelling falls out exactly
and for free from served length versus declared, and cues are
identifiable from their signature within a single batch. So the proposal
is now the cheap approach first, with the library kept for later and only
if the withheld offsets are wanted.
Nothing here is built. Review document, not an implementation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|