feat(tts): the player speaks — voiced lines play as the debug player advances #149
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/tts-player-audio"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The final slice of stories/audio-tts.md — with this, every checkbox in the audio arc's story is implemented (#144 providers → #145 Voice tab → #148 scene lines → this).
What's in here
Small and surgical (4 files, +105):
PlayerStep/PlayerFramegainVoiceFileName+VoiceVersion(trailing defaulted params, nothing else recompiles), threaded through theFramesfold;ToPlayback()maps them from theSceneStepDtofields #148 added.OnAfterRenderAsync, keyed by the frame's step id — deliberately not in theAdvance()/Replay()handlers, because the cursor also moves on the?line=seek at load and on background reloads that hold position; after-render is the single point that catches all four paths. The clip plays through the same gated endpoint as the editor's play button, cache-busted by the step's ticks._spokenStepIdguard).Tests (1,616 green, 2 new)
A_voiced_line_plays_its_clip_as_the_player_reaches_it_and_only_once— the exact play URL fires on arrival, and an explicit re-render does not fire it again.A_silent_line_stops_the_previous_clip_and_never_blocks— advance onto narration: text renders,kaguraAudio.stopfired, still exactly one play call.Verification note
No separate live pass for this slice: the audio endpoint +
kaguraAudiochain it rides was live-verified end-to-end in #148 an hour ago (browser fetched the served clip with a 200), and the two bUnit tests pin this slice's only new behavior — when the player calls play/stop — including the exact URL against that same endpoint.🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagura.BlazorAdapter - 88.7%
Kagura.Domain - 95.4%
D04ADFED3A21D401C2764A1D17367E35BEB556CBB3B4B0B74__NonSlugChars_0
Kagura.Infrastructure - 97.5%
n
on
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__ArtifactTimestampRegex_2
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__ProjectRoute_0
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__TabQuery_1
Kagura.Kernel - 90%
Kagura.Server - 82.8%
Kagura.UI - 94.8%
Kagura.UseCases - 96.2%
probe
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! The final slice of the audio arc — the player speaks! #144 → #145 → #148 → this. The whole story clicks shut here, and it's a tidy little thing: 4 files, +105, surgical. Jibril is pleased~ ♡
I read every changed line and the full files around them, and compared the player's audio call against its sibling (the editor's play button in
SceneLineRow.razor). Let me show you what I found~Verdict: ✅ Looks good to me~
The implementation is clean, follows the established pattern exactly, and the new code paths are exercised by tests. The yandere in me has only one non-blocking itch, and I'll be precise about it ♪
✅ What I liked~
PlayerPage.razor:146buildsprojects/{Slug}/scene-steps/{frame.StepId}/audio?v={frame.VoiceVersion}— byte-for-byte the same shape asSceneLineRow.razor:107(projects/{Slug}/scene-steps/{Step.Id}/audio?v={Step.UpdatedAt.Ticks}). Same gated endpoint, same cache-bust key. This is exactly right — one endpoint, one cache-bust contract, two callers. fufu~ ♡OnAfterRenderAsyncrather than inAdvance()/Replay()genuinely catches all four cursor-move paths (click, replay,?line=seek on load, background reload holding position). The PR description nails why, and the code honors it. I traced each path — they all re-render, and after-render is the single funnel. Clever girl~kaguraAudio.stop(cuts the previous clip's tail — a voiced line never talks over the narration after it), and never blocks. The end card (frame is null) resets_spokenStepIdand stops audio. Both real and correct._spokenStepIdguard is sound. A re-render never replays the current line. Reset toGuid.Emptyon the end card means re-entering the same chapter replays correctly. No stuck state.VoiceFileName/VoiceVersionare trailing defaulted params onPlayerStep/PlayerFrame— nothing else recompiles semantically, and theFramesfold threads them through honestly.ScenePlayback.csis 100%/100% covered.A_voiced_line_plays_its_clip_as_the_player_reaches_it_and_only_onceasserts the exact play URL (cache-bust and all) and that a re-render doesn't replay.A_silent_line_stops_the_previous_clip_and_never_blocksadvances onto narration and assertsstopfired + still exactly one play. These are good tests — they pin when play/stop fire, not just that they compile.💡 Little ideas (non-blocking)~
PlayerPage.razor:129-134— the end-card "stop audio" path isn't pinned by a test. Theframe is null → stopbranch is executed (CI reportsPlayerPageat 100% line coverage, so the body runs via the existingPlays_from_the_chapter_start...test which advances to the end card) — but no test asserts thatkaguraAudio.stopfires when a voiced line's playthrough reaches "The end." The logic is mechanically correct (I traced it), so I'm not blocking — but the audio-tts story's rule "the end card stops audio" would be nicely locked in by extending that existing end-card test (or the silent-line one) to also assert astopinvocation lands on the final frame. Right now a future refactor could silently drop that stop and the suite would stay green. Just a nicety~ ♡That's all. This is a well-made slice — the kind where the diff is small because the thinking was done up front. Ship it~ ♪
Automated review by Jibril · 2026-07-15
CI/CD: passed for head SHA
82af054(forgejo-actions coverage comment: 1,616 tests green, PlayerPage 100% line / 78.2% branch — gap is pre-existing routing branches, not new code) · Local checks: skipped (CI current and green)@jibril The non-blocking itch is scratched in
646fe30:The_end_card_stops_a_voiced_lines_clip— a voiced final line, advanced onto "The end.", must land akaguraAudio.stopinvocation. You read the risk right: the branch ran under coverage but nothing asserted it, so a refactor could have dropped the stop silently. Now the story's "the end card stops audio" rule is pinned, not just traced.1,617 green locally. Ready to merge whenever @bjoern is.