
A backend kivétele a feltöltési útvonalból
A GeekBye rögzíti a képernyődet, és elmenti a videót a Google Drive-odba. Az első verzió minden felvételt a GeekBye saját szerverein keresztül továbbított odafelé; egy kiadással később a fájl egyenesen a gépedről ment a Drive-ba, a backendet pedig lefokozták egyetlen mutató tartására. Az érdekes rész az, hogy milyen kevés kódot tartalmaz valójában a „közvetlen, folytatható" verzió — mert a folytathatóság egy proxy törléséből jött, nem a megírásából.
A GeekBye rögzíteni tudja a képernyődet — videó, rendszerhang és mikrofon — és a kész felvételt automatikusan beejti a Google Drive-odba. A v1.8.11 és a v1.8.13 között a funkció felhasználó felé mutatott leírása alig változott, de az útvonal, amelyet a videó bejár, hogy eljusson a Drive-ba, teljesen átirányítódott. Az első verzióban a felvételed a GeekBye szerverein utazott át. A másodikban egyenesen a gépedről ment a Drive-odba, és a GeekBye backendje soha nem látott belőle egyetlen bájtot sem. Ez az átirányítás az egész történet, és a kielégítő rész az, hogy a „jobb" verzió szembeötlően kevesebb kódot tartalmaz, mint az, amelyet felváltott.
A pipeline, elejétől a végéig
A rögzítés a rendererben történik. A ScreenRecordingService.ts létrehoz egy MediaRecorder-t a capture stream fölött { mimeType: 'video/webm;codecs=vp9,opus', videoBitsPerSecond: 1_000_000 }-mal, és egy egymásodperces timeslice-szal indítja (CHUNK_INTERVAL_MS = 1000). Minden ondataavailable blob IPC-n keresztül kerül a fő folyamathoz, ahol a recording:save-chunk handler hozzáfűzi egy lemezen lévő fájlhoz a fs.promises.appendFile-lal. Érdemes korán jelezni, mert csábító félreérteni: ez az egymásodperces ütem a lemezre-írás ritmusa, nem egy feltöltési chunkméret. Mire a feltöltés elindul, egyetlen kész WebM fájl ül a lemezen.
Ami különbözik a két kiadás között, az csak az, hogy mi történik azzal a kész fájllal.
Az előtte: minden a backenden át (v1.8.11)
A v1.8.11 feltöltési útvonala, a CloudUploader.processRecording()-ban, a kézenfekvő terv és egy teljesen ésszerű első verzió:
POST /api/geekbye/recordingsegy backend rekord létrehozásához a felvétel metaadataival.- Olvasd be az egész videót a memóriába, és POST-old a backendre multipartként —
const fileBuffer = fs.readFileSync(filePath)— aPOST /api/geekbye/recordings/${id}/upload-ot elérve. A backend aztán feltölti azt a fájlt a Drive-ba, és visszaad egydriveUrl-t. POST …/processa transzkript AI elemzésre küldéséhez.
A teherhordó részlet a 2. lépés: a kliens az egész felvételt a memóriába tölti, és annak minden bájtja a GeekBye saját szerverein halad át a Drive felé vezető úton. Egy rövid klipnél ez rendben van. Egy hosszú képernyőfelvételnél ez három probléma egyszerre — memórianyomás a kliensen a readFileSync-től, sávszélesség- és továbbítási költség a backenden egy fájlért, amelyet meg sem tart, és egyetlen monolitikus POST, amelynek nulláról kell újraindulnia, ha a hálózat félúton csuklik egyet.
Azt is érdemes őszintén elmondani, hogy ez az „előtte" nem valami makulátlan eredeti terv volt. A v1.8.11 funkció egy héten belüli pivotot gyömöszölt össze: egy korai változat Cloudflare R2-be töltött fel előre aláírt URL-eken keresztül, és WebM-et konvertált MP4-re egy csomagolt ffmpeg-static-kel; azt felváltotta a backend-közvetített Drive flow; majd az ffmpeg konverziót teljesen kitépték a WebM változatlan feltöltése javára (a commit megjegyzi, hogy leszállította a bitrátát, és nagyjából négyszer kisebb fájlokat produkált — egy inline becslés, nem benchmark). Így már az „előtte" is megtanulta egyszer törölni egy natív függőséget. A következő kiadás a proxyt is törölte volna.
Az utána: egyenesen a Drive-ba (v1.8.13)
A v1.8.13 újraírja a CloudUploader.processRecording()-ot egyetlen mondat köré abból a commitból, amely kiszállította: tölts fel közvetlenül a Drive-ba, „keeping backend for metadata record + AI transcript analysis only." Az új útvonal:
POST /api/geekbye/recordings— csak metaadat (cím, időtartam, fájlméret,format: 'webm'). A kommentár a kódban nyers:// Create backend record (metadata only — no file upload).- Töltsd fel a videót közvetlenül a Google Drive-ba, teljesen kihagyva a backend közvetítőt.
PATCH /api/geekbye/recordings/${backendRecordingId}a keletkező{ driveUrl, driveFolderId }-vel — a backend rekordnak megmondják, hova került a fájl.- A transzkript továbbra is a
…/process-hez megy elemzésre.
A backend abból, hogy benne volt a fájl útvonalában, oda jutott, hogy tájékoztatják a fájlról utólag. Tart egy sort és egy mutatót; a bájtok csak a felhasználó lemezén és a felhasználó Drive-jában élnek.
Valódi Drive kliensé válni
Ahhoz, hogy a kliens közvetlenül beszéljen a Drive-val, egy tényleges Google Drive API kliensnek kell lennie, és a v1.8.13 hozzáadja ehhez a gépezetet: egy új DriveService (a Drive kliens), egy új DriveAuthRepository, amelyet egy új drive_auth SQLite tábla támogat, és a googleapis SDK mint függőség.
Az engedélyezés egy tiszta képesség-átadás. A backend app-config végpontja lehozza a Google OAuth kliens-hitelesítő adatait — egy clientId-t és egy clientSecret-et — az alkalmazáshoz, amelyet a CloudUploader beolvas és átad a driveService.initialize(...)-nek. Onnan a desktop alkalmazás maga futtatja az egész OAuth flow-t: felállít egy ideiglenes http szervert egy véletlenszerű 127.0.0.1 porton, megnyitja a hozzájárulási képernyőt a rendszer böngészőjében a shell.openExternal-lal, elkapja az átirányítást egy /callback-en, és a google-auth-library-vel tokenekre cseréli a kódot. Azokat a tokeneket helyben menti — saveDriveAuth(access_token, refresh_token, expiry_date) a drive_auth-ba — a drive.file scope-pal, amely engedi az alkalmazásnak, hogy csak az általa létrehozott fájlokat kezelje. A frissítés is helyi: egy oauth2Client.on('tokens', …) listener a frissített tokeneket egyenesen visszaírja a táblába. A backend egyszer átadja a képességet, majd kimarad a hurokból.
A „folytatható" rész, őszintén
Íme a részlet, amivel a legjobban egyenes akarok lenni, mert pontosan az a fajta dolog, amit egy changelog lekerekít. A kiadási jegyzet azt mondja: „resumable uploads", és ez igaz onnan, ahol a felhasználó áll. De a GeekBye nem implementálta a folytatható protokollt. Nincs chunkméret-konstans, nincs bájt-offset-követő, és nincs Content-Range / 308 Resume Incomplete kezelés sehol a GeekBye forráskódjában. Keress rájuk grep-pel, és semmit sem kapsz — a googleapis SDK-ban élnek.
Amit a GeekBye saját kódja csinál, az ez:
const fileStats = statSync(videoFilePath)
const videoStream = createReadStream(videoFilePath)
const videoRes = await this.drive.files.create(
{
requestBody: { name: `${title}.webm`, parents: [folderId] },
media: { mimeType: 'video/webm', body: videoStream },
fields: 'id',
},
{
onUploadProgress: (evt) => {
const percent = Math.round((evt.bytesRead / fileStats.size) * 100)
onProgress(percent)
},
},
)
Két döntés végzi az egész munkát. Először, a media.body egy stream (createReadStream), nem egy buffer — és egy stream átadása pontosan az, ami ráveszi a googleapis-t, hogy a nevedben folytatható munkamenetet tárgyaljon le ahelyett, hogy egyetlen lövéssel csinálná. Másodszor, a haladás az SDK onUploadProgress callbackjéből olvasódik ki a fileStats.size-hoz mérve. Ez az egész „resumable upload" implementáció alkalmazásszinten: stream be, haladás ki. A nehéz protokoll — nyiss egy munkamenetet, tölts fel chunkokban, folytasd egy offsettől egy megszakadás után — az SDK dolga, és a helyes mérnöki döntés az volt, hogy hagyjuk elvégezni azt a munkát, ahelyett, hogy újra feltalálnánk.
Ami az egész kiadást újrakeretezi. A megbízhatósági javulás nem egy feltöltési motor megírásából jött. Abból jött, hogy töröltük a proxyt, amely egy ilyen előtt ült — és, korábban ugyanazon a héten, hogy töröltük az ffmpeg lépést az előtt. Kevesebb kód, több ellenállóképesség, mert az ellenálló dolog egy jól bejáratott könyvtár volt abban a pillanatban, amikor abbahagytad, hogy kézzel etesd egy bufferrel.
A darabok, amelyek valóban a GeekBye-é
Az átvitel átadása az SDK-nak nem jelenti azt, hogy nem maradt semmi megépíteni való. Két darab valódi alkalmazáslogika veszi körül.
Egy árva-mappa visszagörgetés. Minden felvétel megkapja a saját Drive almappáját (amelyet mimeType: 'application/vnd.google-apps.folder'-rel hoznak létre), és a transzkript oda érkezik második fájlként a videó mellé. Ha a feltöltés kivételt dob, a catch törli azt a mappát — this.drive.files.delete({ fileId: folderId }), Rolled back orphaned Drive folder néven naplózva — hogy egy sikertelen feltöltés ne hagyjon üres mappák nyomát a Drive-odban. Egy konténer létrehozása azelőtt, hogy tudnád, a művelet sikerülni fog, egy kis kötelezettség; ha a hibaútvonalat rábírod, hogy takarítsa el, az a javítás.
Életciklus egy singleton körül. A DriveService egy singleton, amely egy memóriabeli OAuth klienst tart, és ez a memóriabeli kliens nem éli túl az alkalmazás újraindítását, még ha a tokenek túl is élik (azok az adatbázisban vannak). Így a CloudUploader újrainicializálja a DriveService-t a tárolt tokenekből minden feltöltés előtt, ami azt jelenti, hogy egy közvetlenül újraindítás után készített felvétel még mindig feltöltődik anélkül, hogy megkérne, hogy csatlakozz újra. És a review menet a drive-connect handlert fire-and-forget-té alakította, mert egy blokkoló connect hívás megakasztotta a renderer állapot-pollingját — a UI nem tudta megjeleníteni, hogy „connecting…", ha a connect hívás fogta a szálat. Ez a kettő — újrainicializálás-tárolóból és nem-blokkoló connect — a közvetlen útvonal valódi peremesetei, és figyelemre méltóan egyik sem a feltöltési chunkolásról szól. Amikor delegálod a nehéz részt, a bugok, amelyek megmaradnak neked, életciklus-bugok.
Három dolog, amit ez a kiadás megtanított nekünk
- Kérdezd meg, merre folynak a bájtok, ne csak azt, hogy működik-e. A v1.8.11 proxy működött. De ha minden felvételt a saját szervereiden vezetsz át, az sávszélességbe, memóriába és újraindíthatóságba kerül egy fájlért, amelyet nem tartasz meg. Az adatútvonal újrarajzolása úgy, hogy a kliens közvetlenül a célponttal beszéljen, gyakran maga az egész optimalizálás.
- A legjobb folytatható feltöltés az, amelyet nem írtál meg. Egy stream átadása egy érett SDK-nak egy teljes folytatható munkamenetet vett meg két sor kódért. Az ösztön, hogy kézzel írd meg a chunkolást és az offset-matekot, több kódot és kevesebb megbízhatóságot eredményezett volna.
- Amikor delegálod a magot, életciklust örökölsz, nem algoritmusokat. A közvetlen kliens valódi bugjai „inicializáld újra a singletont újraindítás után" és „ne blokkold a connect hívást" voltak, nem bájt-offsetek. Ez jó csere — az életciklus-bugok láthatók és lokálisak; a protokoll-bugok egyik sem.
A v1-történet előző fejezetéért lásd megszületett és megjavult tizenkét perc alatt (v1.8.10); a teljes ívért pedig a szoftver tökéletesre szállításának anatómiája.