Steven
Steven7 min lesing

Stillheten var bærende

GeekBye v1s to siste releaser handler om den samme ubehagelige sannheten: sanntidstranskripsjon over et ekte nettverk er ikke tapsfri, og det ærlige trekket er å slutte å late som om den er det. v1.8.20 beholdt en kopi av hver lydbit på disk før den ble kastet under en gjenoppkobling, og begynte å markere transkripsjonens hull høyt. v1.9.0 sluttet å sende stillhet for å spare båndbredde — og oppdaget at stillheten var nøyaktig det signalet transkribereren brukte for å vite at en setning var slutt. To releaser om prisen for å kaste ting.

Utvikling
Audio
Pålitelighet
GeekBye-releaser
Stillheten var bærende

GeekBye v1s to siste releaser — v1.8.20 og v1.9.0, de like før versjon 2-milepælen — deler et tema som først blir tydelig når du leser dem sammen. Begge handler om ting appen stille kastet bort, og begge lander på den samme konklusjonen: det du kaster for å være effektiv gjorde mer arbeid enn du trodde. Den ene releasen kastet lyd under nettverksgjenoppkoblinger. Den andre kastet stillhet for å spare båndbredde. I begge tilfeller viste det kastede seg å være bærende.

Sanntidstranskripsjon er ikke tapsfri, og v1.8.20 innrømmer det

Her er et faktum om å strømme tale-til-tekst over en WebSocket som er lett å se bort fra: når forbindelsen faller og kobler seg til igjen — eller når appen bytter fra én transkripsjonsleverandør til en annen, Deepgram til ElevenLabs — kaster klienten bevisst lydbitene som ble fanget under det gapet. Den må. En nyoppkoblet leverandør som plutselig mottar en kø av gammel lyd, setter den i halsen, eller enda verre, låser stemmeaktivitetsdetektoren sin på feil ting. Så klienten kaster de bitene.

Problemet er ikke kastingen. Problemet er hva kastingen før betydde: de ordene dukket rett og slett aldri opp i transkripsjonen, og ingenting fortalte deg at de manglet. Opptaket så komplett ut. Det var det ikke.

v1.8.20 angriper det på to fronter, og ingen av dem er "få gjenoppkoblinger til aldri å miste lyd" — for det kan du ikke, ikke i sanntid. I stedet gjør den tapet gjenopprettelig og ærlig.

Den gjenopprettelige halvdelen er et holdbart PCM-tee. I hovedprosessens fangsthåndterer skrives hver fanget bit nå til en fil på disk før den når kasteportene for gjenoppkobling og nådeperiode:

every captured chunk is written to <userData>/sessions/session-<ts>.wav before realtime drop gates so reconnect-lost audio is preserved for a future batch-STT reconciliation pass

Mekanismen er behagelig lavteknologisk: teeChunkToDisk åpner en WriteStream, reserverer en 44-byte WAV-header på forhånd, legger til rå 16-bits mono-samples etter hvert som de kommer, og lapper headeren med de endelige størrelsene ved stopp (fs.openSync(..., 'r+'), skriv 44 byte ved offset 0). Resultatet er en spillbar WAV av den komplette lyden — inkludert delene sanntidstranskriptet aldri så. Når opptaket slutter, sender den ut en session-audio-saved-hendelse med filsti og byteantall.

To ærlighetsmerknader er viktige her, for det er lett å overdrive dette. For det første: teet fyller ikke hullet i disse releasene. Det bevarer rålyden til en senere avstemmingskjøring — en batch-retranskripsjon som avstemmer filen mot livetranskriptet — som er en krok disse releasene legger, ikke en funksjon de leverer. Oppfølgingscommitten annoterer til og med session-audio-saved med en TODO som peker på avstemmingssakene, så ingen forveksler den med død kode. For det andre: dette koster disk. Teamet anslo omtrent 86 MB per 30-minutters opptak, så den samme releasen legger til en StartupCleanupService som rydder lagrede økter eldre enn 7 dager. Et sikkerhetsnett som vokser uten grense er en lekkasje; oppryddingen er det som gjør teet leveringsklart.

Den ærlige halvdelen er enda enklere. Ved det første gjenoppkoblingsforsøket skriver klienten en ekte, synlig oppføring i transkripsjonen:

[reconnecting — audio may be missing]

Den ene linjen er hele releasens filosofi i miniatyr. Appen kan ikke garantere at den hørte alt, så den slutter å antyde at den gjorde det. Et hull du kan se er et hull du kan jobbe rundt; et hull du ikke kan se er en bugrapport som venter på å skje.

v1.9.0: stillheten du optimerte bort var signalet

v1.9.0 er den siste v1-releasen, og det er der backenden overleverte et sett med livetranskript-ansvar til klienten — å kaste stillhet, å tilskrive hver transkriptlinje til mikrofon eller systemlyd ut fra en lokal stemmeaktivitetstidslinje, å eie gjenoppkoblingsløkken, å håndheve opptakstak. Det meste av det er grunnarbeid for versjon 2-arkitekturen og er fortalt annetsteds. Men én tråd i det er den beste lille historien i noen av releasene, fordi det er en fiks som måtte gjøre ugjort skaden fra en optimalisering som var fullstendig korrekt isolert sett.

Det starter med en åpenbart god endring: slutt å sende stillhet. Klienten begynte å filtrere bort biter hvis dominerende kilde var stillhet før de ble sendt over socketen — mindre båndbredde, mindre unyttig lyd for backenden å behandle, og det overholdt en eksplisitt backend-klientkontrakt som sa "ikke send stillhet". Enhver grunn til å gjøre det var god.

Og det brøt fullføringen av ytringer fullstendig. Committen som fikser det legger ut den kausale kjeden med uvanlig åpenhet:

After the client began filtering silence chunks … ElevenLabs Scribe's VAD-based commit strategy no longer fires — EL needs ~1s of silence in the audio stream to commit a partial, and that silence never reaches it. Result: partials accumulate forever, UI shows trailing "...", utterances never finalize.

Dette er den platonske formen av en lekk abstraksjon som biter tilbake. Transkribererens stemmeaktivitetsdetektor har ikke et separat "taleren er ferdig"-inndata — den slutter seg til slutten på en setning ved å høre omtrent ett sekunds stillhet i lyden. Stillhet var ikke støy på ledningen; det var en beskjed. Ved å filtrere den bort for å spare båndbredde sluttet klienten ved et uhell å sende det ene signalet som fortalte transkribereren at en setning var slutt. Så hvert delvis transkript bare fortsatte å vokse, halen tonende ut i en ... som aldri ble løst opp.

Du kunne fikse dette ved å legge stillheten tilbake — men da har du gjort optimaliseringen ugjort. Den faktiske fiksen er bedre: send stillhetens betydning uten å sende stillheten. Klienten sporer nå lokalt hvor lenge lyden har vært stille, og når det krysser SILENCE_COMMIT_THRESHOLD_MS = 1000 — bevisst matchet mot backendens egen VAD-stillhetsterskel — avfyrer den et eksplisitt commit-signal utenfor båndet: sendMixedChunk merker den vedvarende stillheten, sender et realtime-audio:commit over IPC, hovedprosessen videresender et { type: 'commit' } på socketen, og backenden ber ElevenLabs om å fullføre det delvise resultatet. To små biter tilstand, silenceStartedAt og silenceCommitFired, hindrer den i å avfyre gjentatte ganger. Båndbreddegevinsten overlever; fullføringen fungerer igjen; det etterslepende ... løses opp.

Det er også en liten, ærlig fotnote til denne releasen: den ubegrensede gjenoppkoblingslogikken som v1.9.0 introduserte, ble først bygget i rendereren som en WsReconnectController, deretter slettet innenfor den samme releasen da den ekte implementasjonen landet i hovedprosesshåndtereren der den hørte hjemme. Selve gjenoppkoblingshistorien — ubegrenset backoff, vekslende leverandører hver femte feil — fortelles i sitt eget kapittel; det v1.9.0 tilfører, er at det er her den arkitekturen ble født, og der klienten først tok eierskap over den.

Tre ting disse releasene lærte oss

  1. Når du ikke kan forhindre tap, gjør det gjenopprettelig og gjør det synlig. v1.8.20 hindrer ikke gjenoppkoblinger i å kaste lyd — det er ikke fysisk mulig i sanntid. Den beholder en kopi av rålyden til senere, og den merker hullet i transkripsjonen. Ingen av delene er glamorøse; sammen forvandler de stille datatap til en kjent, avgrenset, gjenopprettelig tilstand.
  2. Et sikkerhetsnett trenger sine egne grenser. Det holdbare teet ville stille fylle disken hvis det ble latt i fred — en fiks som blir en bug i sakte film. Den 7-dagers oppryddingen som ble levert i den samme releasen er ikke en ettertanke; det er det som gjør teet trygt å levere i det hele tatt. Hvis du legger til en mekanisme som hoper seg opp, legg til mekanismen som rydder den i samme åndedrag.
  3. Det du kaster for å være effektiv kan være et signal. Den skarpeste lærdommen i noen av releasene: å filtrere bort stillhet var korrekt på hver akse du normalt ville sjekke — båndbredde, last, kontraktsoverholdelse — og det brøt likevel produktet, fordi et nedstrøms system leste betydning inn i stillheten. Når du fjerner data av effektivitetshensyn, spør hva som sluttet seg til noe fra de dataene. Noen ganger må du sende betydningen eksplisitt nettopp fordi du sluttet å sende det den før ble sluttet fra.

Og det er slutten på versjon 1-historien — den siste herdingen før omskrivingen. For det forrige kapittelet, de tre verbene som holder Web Audio i live (v1.8.16 + v2.0.2); for hvor alt dette var på vei, hva en versjon 2 faktisk krever: 206 commits (v2.0.0); og for hele buen, anatomien i å shippe programvare til perfeksjon.

Relaterte artikler

De tre verbene som holder Web Audio i live
Steven
Steven8 min lesing

De tre verbene som holder Web Audio i live

To GeekBye-punktreleaser, to måneder fra hverandre og i to ulike filer, lærte lydkoden vår den samme lærdommen fra motsatte ender: slutt å behandle nettleserens AudioContext som engangsbruk. Den ene releasen lærte å kalle resume() på en kontekst macOS stille hadde suspendert midt i opptaket; den andre lærte å kalle suspend() i stedet for close(), så opptak på rad slutter å dundre inn i Chromiums tak på omtrent seks kontekster. Resume, suspend, close — det er hele handlingen.

Utvikling
Audio
Desktop
En mistet tilkobling burde ikke krasje hele appen din — men vår gjorde det
Steven
Steven6 min lesing

En mistet tilkobling burde ikke krasje hele appen din — men vår gjorde det

Da backend-en vår gikk offline midt i et møte, satte den ikke bare transkripsjonen på pause — den krasjet hele appen. Årsaken var én enkelt uhåndtert hendelse, og fiksen var ti linjer. Dette er release-klyngen som fikk GeekBye til å forbli innlogget og forbli tilkoblet gjennom tingene som før tok livet av den.

Utvikling
Pålitelighet
Electron
Å skille en samtale fra en åpen app
Steven
Steven8 min lesing

Å skille en samtale fra en åpen app

GeekBye kan merke at du har blitt med i et videomøte og tilby å ta det opp. Deteksjonen viser seg å være den lette halvdelen — en Swift-binær som leser vindustitler hvert tiende sekund. Den vanskelige halvdelen er presisjon: å ikke utløse når Zoom bare er åpent, å ikke spørre om et møte du allerede tar opp, og å ikke dempe mikrofonen i samtalen du faktisk sitter i. Tre releaser, og hver enkelt er et vern som måtte lære å ikke beseire seg selv.

Utvikling
macOS
Desktop