Steven
Steven7 menit baca

Kesunyian Itu Menopang Beban

Dua rilis terakhir GeekBye v1 membahas kebenaran tak nyaman yang sama: transkripsi real-time di atas jaringan nyata tidaklah nirsusut, dan langkah jujurnya adalah berhenti berpura-pura seolah begitu. v1.8.20 menyimpan salinan setiap chunk audio ke disk sebelum membuangnya saat sebuah reconnect, dan mulai menandai celah-celah di transkrip secara terbuka. v1.9.0 berhenti mengirim keheningan demi menghemat bandwidth — dan menemukan bahwa keheningan itu adalah sinyal persis yang dipakai transkriber untuk tahu bahwa sebuah kalimat telah berakhir. Dua rilis tentang biaya membuang sesuatu.

Engineering
Audio
Keandalan
GeekBye Releases
Kesunyian Itu Menopang Beban

Dua rilis terakhir GeekBye v1 — v1.8.20 dan v1.9.0, yang tepat sebelum tonggak versi 2 — berbagi sebuah tema yang baru menjadi jelas saat kamu membacanya bersamaan. Keduanya tentang hal-hal yang diam-diam dibuang aplikasi, dan keduanya sampai pada kesimpulan yang sama: hal yang kamu buang demi efisiensi ternyata mengerjakan lebih banyak pekerjaan daripada yang kamu kira. Satu rilis membuang audio selama reconnect jaringan. Yang lain membuang keheningan demi menghemat bandwidth. Dalam kedua kasus, hal yang dibuang ternyata menopang beban.

Transkripsi real-time tidaklah nirsusut, dan v1.8.20 mengakuinya

Inilah sebuah fakta tentang speech-to-text streaming di atas sebuah WebSocket yang mudah dipalingkan: saat koneksi putus dan tersambung ulang — atau saat aplikasi gagal-alih dari satu penyedia transkripsi ke penyedia lain, Deepgram ke ElevenLabs — klien sengaja membuang chunk audio yang terekam selama celah itu. Ia harus. Sebuah penyedia yang baru tersambung yang tiba-tiba menerima tumpukan audio basi akan tersedak, atau lebih buruk, mengunci detektor aktivitas-suaranya pada hal yang salah. Jadi klien membuang chunk-chunk itu.

Masalahnya bukan pembuangannya. Masalahnya adalah apa yang dulu dimaksud oleh pembuangan: kata-kata itu sama sekali tak pernah muncul di transkrip, dan tak ada yang memberitahumu bahwa mereka hilang. Rekamannya tampak lengkap. Tidak lengkap.

v1.8.20 menyerang itu di dua front, dan tak satu pun dari keduanya adalah "buat reconnect tak pernah kehilangan audio" — karena kamu tak bisa, tidak secara real-time. Alih-alih, ia membuat kehilangan itu dapat dipulihkan dan jujur.

Paruh yang dapat dipulihkan adalah sebuah tee PCM yang tahan lama. Di handler penangkapan proses-utama, setiap chunk terekam kini ditulis ke sebuah berkas di disk sebelum ia mencapai gerbang pembuangan reconnect dan masa-tenggang:

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

Mekanismenya menyenangkan karena rendah-teknologi: teeChunkToDisk membuka sebuah WriteStream, memesan di depan sebuah header WAV 44-byte, menambahkan sampel mono 16-bit mentah begitu tiba, dan menambal header dengan ukuran final saat stop (fs.openSync(..., 'r+'), tulis 44 byte pada offset 0). Hasilnya adalah sebuah WAV yang bisa diputar dari audio lengkap — termasuk bagian-bagian yang tak pernah dilihat transkrip real-time. Saat perekaman berakhir ia memancarkan sebuah event session-audio-saved dengan path berkas dan jumlah byte.

Dua catatan kejujuran penting di sini, karena mudah untuk melebih-lebihkan ini. Pertama: tee tidak mengisi celahnya di rilis-rilis ini. Ia menjaga audio mentah untuk sebuah lintasan rekonsiliasi kemudian — sebuah transkripsi-ulang batch yang merekonsiliasi berkas dengan transkrip langsung — yang merupakan sebuah kait yang dipasang rilis-rilis ini, bukan sebuah fitur yang mereka rilis. Commit lanjutannya bahkan menganotasi session-audio-saved dengan sebuah TODO yang menunjuk ke isu-isu rekonsiliasi agar tak ada yang menyalahartikannya sebagai kode mati. Kedua: ini memakan disk. Tim memperkirakan kira-kira 86 MB per rekaman 30 menit, jadi rilis yang sama menambahkan sebuah StartupCleanupService yang memangkas sesi tersimpan yang lebih tua dari 7 hari. Sebuah jaring pengaman yang tumbuh tanpa batas adalah sebuah kebocoran; pembersihan itulah yang membuat tee bisa dirilis.

Paruh yang jujur bahkan lebih sederhana. Pada upaya reconnect pertama, klien menulis sebuah entri nyata dan terlihat ke dalam transkrip:

[reconnecting — audio may be missing]

Satu baris itu adalah keseluruhan filosofi rilisnya dalam bentuk miniatur. Aplikasi tak bisa menjamin bahwa ia mendengar segalanya, jadi ia berhenti menyiratkan bahwa ia begitu. Sebuah celah yang bisa kamu lihat adalah sebuah celah yang bisa kamu siasati; sebuah celah yang tak bisa kamu lihat adalah sebuah laporan bug yang menunggu terjadi.

v1.9.0: keheningan yang kamu optimalkan-buang itu adalah sinyalnya

v1.9.0 adalah rilis v1 terakhir, dan di sanalah backend menyerahkan seperangkat tanggung jawab transkrip-langsung ke klien — membuang keheningan, mengatribusikan setiap baris transkrip ke audio mik atau sistem dari sebuah linimasa aktivitas-suara lokal, memiliki loop reconnect, menegakkan batas perekaman. Sebagian besar dari itu adalah pekerjaan dasar untuk arsitektur versi-2 dan telah diceritakan di tempat lain. Tapi satu utas di dalamnya adalah kisah kecil terbaik di antara kedua rilis, karena ia adalah sebuah perbaikan yang harus membatalkan kerusakan dari sebuah optimasi yang sepenuhnya benar secara terpisah.

Ia dimulai dengan sebuah perubahan yang jelas-jelas baik: berhenti mengirim keheningan. Klien mulai menyaring keluar chunk yang sumber dominannya adalah keheningan sebelum mengirimnya lewat socket — lebih sedikit bandwidth, lebih sedikit audio tak berguna bagi backend untuk diproses, dan ia menghormati sebuah kontrak-klien backend eksplisit yang berkata "jangan kirim keheningan." Setiap alasan untuk melakukannya masuk akal.

Dan ia merusak finalisasi utterance sepenuhnya. Commit yang memperbaikinya memaparkan rantai sebab-akibat dengan keterusterangan yang tak biasa:

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.

Ini adalah bentuk platonis dari sebuah abstraksi bocor yang balik menggigit. Detektor aktivitas-suara si transkriber tak punya sebuah input "pembicara selesai" yang terpisah — ia menyimpulkan akhir sebuah kalimat dari mendengar sekitar satu detik keheningan di audio. Keheningan bukanlah derau di kabel; ia adalah sebuah pesan. Dengan menyaringnya keluar demi menghemat bandwidth, klien tanpa sengaja berhenti mengirim satu-satunya sinyal yang memberitahu transkriber bahwa sebuah kalimat telah berakhir. Jadi setiap transkrip parsial terus saja tumbuh, ekornya melamur menjadi sebuah ... yang tak pernah terselesaikan.

Kamu bisa memperbaiki ini dengan menaruh kembali keheningannya — tapi lalu kamu telah membatalkan optimasinya. Perbaikan sesungguhnya lebih baik: kirim makna keheningan tanpa mengirim keheningannya. Klien kini melacak berapa lama audio telah hening secara lokal, dan saat itu melewati SILENCE_COMMIT_THRESHOLD_MS = 1000 — sengaja disamakan dengan ambang keheningan VAD milik backend sendiri — ia menembakkan sebuah sinyal commit eksplisit di luar-jalur: sendMixedChunk menyadari keheningan berkelanjutan itu, mengirim sebuah realtime-audio:commit lewat IPC, proses utama meneruskan sebuah { type: 'commit' } di socket, dan backend menyuruh ElevenLabs memfinalisasi parsialnya. Dua potong kecil state, silenceStartedAt dan silenceCommitFired, mencegahnya menembak berulang-ulang. Kemenangan bandwidth bertahan; finalisasinya berfungsi lagi; ... ekor terselesaikan.

Ada juga sebuah catatan kaki kecil yang jujur untuk rilis ini: logika reconnect tanpa batas yang diperkenalkan v1.9.0 mula-mula dibangun di renderer sebagai sebuah WsReconnectController, lalu dihapus dalam rilis yang sama begitu implementasi sesungguhnya mendarat di handler proses-utama tempat ia seharusnya berada. Kisah reconnect itu sendiri — backoff tanpa batas, penyedia bergantian setiap lima kegagalan — diceritakan di babnya sendiri; yang ditambahkan v1.9.0 adalah bahwa di sanalah arsitektur itu lahir, dan tempat klien pertama kali mengambil kepemilikannya.

Tiga hal yang diajarkan rilis-rilis ini

  1. Saat kamu tak bisa mencegah kehilangan, buatlah ia dapat dipulihkan dan buatlah ia terlihat. v1.8.20 tak menghentikan reconnect dari membuang audio — itu tak mungkin secara fisik di real-time. Ia menyimpan salinan audio mentah untuk nanti, dan ia melabeli celahnya di transkrip. Tak satu pun glamor; bersama-sama mereka mengubah kehilangan data yang sunyi menjadi sebuah kondisi yang diketahui, terbatas, dan dapat dipulihkan.
  2. Sebuah jaring pengaman butuh batasnya sendiri. Tee tahan lama itu akan diam-diam memenuhi disk jika dibiarkan — sebuah perbaikan yang menjadi bug gerak-lambat. Pembersihan 7-hari yang dirilis dalam rilis yang sama bukanlah renungan belakangan; ia adalah hal yang membuat tee aman untuk dirilis sama sekali. Jika kamu menambahkan sebuah mekanisme yang menumpuk, tambahkan mekanisme yang memangkasnya dalam napas yang sama.
  3. Hal yang kamu buang demi efisiensi bisa jadi sebuah sinyal. Pelajaran paling tajam di antara kedua rilis: menyaring keluar keheningan itu benar di setiap sumbu yang biasanya kamu periksa — bandwidth, beban, kepatuhan kontrak — dan ia tetap merusak produk, karena sebuah sistem hilir sedang membaca makna ke dalam keheningan. Saat kamu menghapus data demi efisiensi, tanyakan apa yang sedang menyimpulkan sesuatu dari data itu. Kadang kamu harus mengirim maknanya secara eksplisit justru karena kamu berhenti mengirim hal yang dulu darinya makna itu disimpulkan.

Dan itulah akhir dari kisah versi-1 — pengerasan terakhir sebelum penulisan ulang. Untuk bab sebelumnya, tiga kata kerja yang menjaga web audio tetap hidup (v1.8.16 + v2.0.2); untuk ke mana semua ini menuju, apa yang sebenarnya dibutuhkan sebuah versi 2: 206 commit (v2.0.0); dan untuk keseluruhan alurnya, anatomi merilis software sampai sempurna.

Artikel Terkait

Tiga Kata Kerja yang Menjaga Web Audio Tetap Hidup
Steven
Steven8 menit baca

Tiga Kata Kerja yang Menjaga Web Audio Tetap Hidup

Dua point release GeekBye, terpaut dua bulan dan di dua berkas berbeda, mengajari kode audio kami pelajaran yang sama dari ujung yang berlawanan: berhenti memperlakukan AudioContext milik browser sebagai barang sekali pakai. Satu rilis belajar untuk resume() sebuah context yang diam-diam disuspend macOS di tengah perekaman; yang lain belajar untuk suspend() alih-alih close() agar sesi beruntun berhenti menabrak plafon kira-kira enam context milik Chromium. Resume, suspend, close — itulah keseluruhan plotnya.

Engineering
Audio
Desktop
Membedakan Panggilan dari Aplikasi yang Terbuka
Steven
Steven8 menit baca

Membedakan Panggilan dari Aplikasi yang Terbuka

GeekBye bisa memperhatikan bahwa kamu telah bergabung ke sebuah rapat video dan menawarkan untuk merekamnya. Deteksinya ternyata paruh yang mudah — sebuah binary Swift yang membaca judul jendela setiap sepuluh detik. Paruh yang sulit adalah presisi: tidak memicu saat Zoom sekadar terbuka, tidak menawari sebuah rapat yang sudah kamu rekam, dan tidak membisukan mikrofon di panggilan yang sebenarnya kamu ikuti. Tiga rilis, dan masing-masing adalah sebuah penjaga yang harus belajar untuk tidak mengalahkan dirinya sendiri.

Engineering
macOS
Desktop
Mengeluarkan Backend dari Jalur Unggah
Steven
Steven8 menit baca

Mengeluarkan Backend dari Jalur Unggah

GeekBye merekam layarmu dan menyimpan videonya ke Google Drive-mu. Versi pertama mengirim setiap rekaman melalui server milik GeekBye sendiri dalam perjalanannya; satu rilis kemudian, file itu pergi langsung dari mesinmu ke Drive, dan backend diturunkan menjadi hanya memegang satu pointer. Bagian menariknya adalah betapa sedikit kode yang sebenarnya dimuat versi 'langsung, resumable' itu — karena kemampuan-melanjutkan datang dari menghapus sebuah proxy, bukan dari menulis satu.

Engineering
Architecture
Desktop