
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.
GeekBye punya sebuah fitur yang terdengar sepele padahal tidak: ia memperhatikan saat kamu telah bergabung ke sebuah rapat video dan menawarkan, dengan satu klik, untuk mulai merekamnya. Pitch-nya satu kalimat. Implementasinya adalah tiga rilis dari belajar bahwa masalah yang menarik tak pernah "mendeteksi sebuah rapat" — melainkan "benar tentangnya." Benar tentang apakah kamu sungguh di sebuah panggilan versus sekadar punya aplikasi yang terbuka. Benar tentang tidak bertanya dua kali. Benar tentang tidak membisukan mikrofon yang salah. Masing-masing dari ketiga rilis itu, pada intinya, adalah sebuah penjaga yang harus diajari untuk tidak tersandung dirinya sendiri.
Judul jendela, bukan proses
Keputusan pertama dan paling berkonsekuensi ditulis ke dalam sebuah pesan commit sebagai sebuah pernyataan desain, bukan sebuah catatan kaki:
Detection uses window title matching (not process detection) to distinguish "app is open" from "in an active call."
Distingsi itu adalah keseluruhan fiturnya. Jika kamu mendeteksi rapat dengan melihat aplikasi mana yang berjalan, kamu akan memicu seketika Zoom diluncurkan — dan Zoom terbuka di banyak desktop sepanjang hari tanpa satu pun panggilan. Jadi GeekBye tak mengawasi proses. Di macOS ia menjalankan sebuah binary Swift native kecil, MeetingMonitor, yang setiap sepuluh detik memanggil CGWindowListCopyWindowInfo untuk jendela-jendela di layar dan membaca judul masing-masing via kCGWindowName. Sebuah jendela berjudul "Zoom Meeting" berarti kamu di sebuah panggilan; jendela "Zoom Workplace" berarti aplikasi sekadar terbuka. Sinyalnya bukan "apakah Zoom hidup," melainkan "apakah sebuah jendela di layarmu terlihat seperti sebuah panggilan langsung."
Interval poll adalah sebuah konstanta keras — pollIntervalSeconds: Double = 10.0 — plus satu pemeriksaan seketika saat startup, sehingga sebuah rapat diperhatikan dalam sekitar sepuluh detik sejak jendelanya muncul. (Itu sebuah nilai kode, bukan latensi terukur; perlakukan "hingga ~10 s" sebagai aritmetika, bukan sebuah benchmark.)
Rapat di browser butuh satu langkah lagi, karena sebuah jendela Chrome berjudul "Meet - Weekly Sync" menjanjikan tapi kepastiannya ada di URL. Jadi saat aplikasi terdepan adalah sebuah browser yang dikenal — Chrome, Safari, Edge, Brave, dan segelintir lagi yang diidentifikasi oleh bundle id — binary itu mengekstrak URL tab aktif melalui AppleScript dan memeriksanya terhadap pola seperti meet.google.com/, zoom.us/j/, dan teams.microsoft.com/. AppleScript ke aplikasi lain bisa menggantung, jadi panggilan itu berjalan di sebuah DispatchQueue latar yang dijaga oleh sebuah DispatchSemaphore dengan timeout 3 detik; sebuah browser yang macet kehabisan waktu alih-alih membekukan seluruh detektor.
Bagian sulitnya adalah tidak memicu
Inilah bagian yang mengejutkanku saat aku membaca daftar pola. Mendeteksi sebuah panggilan adalah beberapa pencocokan string. Tidak mendeteksi satu pun — tidak memicu saat kamu tak menginginkannya — adalah ke mana semua kehati-hatian pergi. Daftar pola judul-jendela terbaca seperti sekumpulan pengecualian yang disengaja:
- Google Meet cocok dengan
"Meet - "dan variannya bertanda-hubung-en"Meet – ", tapi bukan string telanjang"Google Meet"— karena itu halaman pendaratan yang kamu buka sebelum sebuah panggilan. - Zoom cocok dengan
"Zoom Meeting"dan"Zoom Webinar", tapi bukan"Zoom Workplace"atau sebuah"Zoom"telanjang — aplikasi yang duduk terbuka. - Teams cocok dengan
"Meeting with","Meeting in"," | Call"," | Meeting"— bentuk-bentuk judul saat-di-panggilan, bukan klien yang menganggur. - Huddle Slack, ruang Webex, dan panggilan WhatsApp masing-masing mendapat fragmen hanya-saat-di-panggilan-nya sendiri.
Setiap satu dari itu adalah sebuah keputusan untuk tetap diam dalam kasus umum di mana sebuah aplikasi terbuka tapi kamu tak di sebuah rapat. Tujuan desainnya bukan recall maksimal; ia bukan menjadi alat yang memunculkan sebuah banner "mau merekam?" saat kamu sedang membaca Slack. Sebuah false positive di sini mahal dengan cara yang tak dimiliki sebuah false negative: lewatkan sebuah panggilan dan pengguna mulai merekam manual; salah memicu di sebuah aplikasi terbuka dan pengguna belajar mengabaikan banner-mu. Seluruh heuristik disetel ke arah kegagalan kedua yang jarang terjadi.
Jujur untuk menyebut batas-batas sebuah heuristik sesederhana ini. Pola-polanya hanya-Inggris — sebuah OS terlokalisasi atau sebuah aplikasi rapat non-Inggris tak akan cocok, sebuah keterbatasan v1 yang diakui. Dan karena judul jendela untuk aplikasi lain hanya terlihat dengan izin Perekaman Layar diberikan, deteksi diam-diam merosot menjadi tak ada tanpanya. Sederhana, terbaca, dan tak sempurna dengan cara-cara yang ditulis tim alih-alih disembunyikan.
Tawaran itu, dan mesin yang menjaganya dari merengek
Saat sebuah pola cocok, GeekBye menampilkan sebuah banner kecil tanpa-bingkai selalu-di-atas di sudut — jendelanya sendiri, bukan sebuah notifikasi OS — dengan sebuah tombol Start Recording. Klik dan sebuah pesan IPC (meeting:start-recording) menyembunyikan banner, memunculkan jendela utama, dan menyuruh renderer memulai sebuah sesi. Tolak dan banner-nya pergi.
Tapi sebuah versi naif dari ini akan menjengkelkan. Judul berubah terus-menerus selama sebuah panggilan — seorang peserta bergabung, kamu mengganti nama rapat, sebuah berbagi-layar menukar nama jendela — dan setiap perubahan akan memicu ulang sebuah detektor yang berpatokan pada string persis. Jadi detektor, di MeetingDetector.ts, membawa sebuah state machine kecil yang seluruh tugasnya adalah menahan diri:
- Sebuah
meetingIdstabil per platform. Sisi Swift memancarkan sebuah id sepertigoogle-meet-active, sengaja tidak diturunkan dari judul jendela yang mudah berubah, sehingga bergabung, keluar, dan mengganti nama dalam satu panggilan tak terbaca sebagai sebuah rapat baru. - Sebuah
dismissedMeetings: Set<string>. Tolak banner dan id rapat itu diabaikan — sampai rapat benar-benar berakhir, di titik mana id-nya dibersihkan agar rapat nyata berikutnya bisa menawari dari awal. - Sebuah boolean
notificationActive. Hanya satu banner sekaligus, sehingga sebuah huddle Slack yang muncul saat kamu sudah di sebuah Meet tak menumpuk sebuah prompt kedua.
Itulah fiturnya, sebenarnya: sedikit deteksi dan banyak "jangan menyebalkan." Yang persis mengapa dua rilis berikutnya ada — karena dua dari penjaga itu ternyata mengalahkan diri mereka sendiri.
Pembisuan yang membisukan rapat sungguhanmu (v1.8.18)
Perekaman punya sebuah tombol bisu, dan implementasi pertama mengartikan "bisu" secara paling harfiah. Dikirim di v1.8.15, ia membisukan mikrofon dengan menyetel volume input sistem operasi ke nol:
osascript -e "set volume input volume 0"
dengan volume sebelumnya disimpan ke database agar bisa dipulihkan, sebuah setara Windows melalui waveInSetVolume, dan sebuah jaring pengaman before-quit untuk memastikan ia membunyikan kembali. Kode itu bahkan menggambarkannya sebagai sebuah fitur — membisukan "di DUA level," sistem dan aplikasi, sehingga mik diblokir untuk semua aplikasi.
Frasa terakhir itu adalah bug yang dinyatakan sebagai sebuah kesombongan. Memblokir mik untuk semua aplikasi berarti memblokirnya untuk panggilan Zoom atau Meet yang sebenarnya kamu duduki. Kamu menekan bisu di GeekBye untuk menghentikan ia mendengarmu, dan kamu telah bisu di rapat sungguhanmu. Perbaikan v1.8.18 adalah sebuah penurunan: berhenti menyentuh volume input OS sepenuhnya, dan hanya membisukan pipeline audio milik GeekBye sendiri. Di mode Listen, RealtimeAudioCaptureService mengganti frame mik yang dibisukan dengan sebuah buffer nol sehingga keheningan mengalir ke mixer; di mode video, ScreenRecordingService menyetel sebuah GainNode Web Audio ke nol. Pembisuan seluruh-sistem, pemulihan volume-tersimpan, seluruh aparatus lintas-platform — semua dihapus. Bisu harusnya berarti "aplikasi ini berhenti mendengarkan," tak pernah "mikrofonmu mati untuk semua orang."
Penjaga yang mereset dirinya sendiri (v1.8.19)
Penjaga lain yang mengalahkan diri sendiri adalah notificationActive. Di atas kertas ia sudah menjawab "jangan tampilkan sebuah banner jika ada satu" — dan tampak seolah ia juga harus mencakup "jangan tawari merekam sebuah rapat yang sudah kurekam." Ia tak melakukannya, dan commit yang memperbaikinya menjelaskan alasannya dengan tepat:
The notificationActive flag resets when the meeting detector cycles through ended/detected states, causing notifications to reappear mid-recording.
Detektor adalah sebuah poll langsung; sepanjang sebuah panggilan panjang ia bisa sejenak membaca "ended" lalu "detected" lagi — sebuah kedipan judul, sebuah jendela sesaat di luar layar — dan setiap siklus mereset flag. Jadi penjaga terus diam-diam mempersenjatai diri lagi saat kamu merekam, dan banner akan muncul lagi menawari merekam panggilan yang sudah kamu rekam. Perbaikannya berhenti memercayai sebuah flag lokal dan malah bertanya ke sumber kebenaran: sebuah callback recordingActiveChecker, disuntikkan dari main.ts dan dikawatkan ke state isRealtimeTranscriptionActive yang sebenarnya, diperiksa sebelum notifikasi apa pun muncul.
// Don't show notification if recording/transcription is already active
if (this.recordingActiveChecker?.()) {
return
}
Pelajarannya kecil dan umum: sebuah penjaga yang state-nya kamu miliki bisa direset dari bawahmu oleh alur kontrolmu sendiri. Sebuah penjaga yang membaca state sebenarnya yang ia lindungi tak bisa. notificationActive melacak sebuah proxy untuk "apakah kita merekam"; perbaikannya bertanya apakah kita merekam.
Tiga hal yang diajarkan rilis-rilis ini kepada kami
- Deteksi state yang kamu maksud, bukan yang mudah dilihat. "Aplikasi sedang berjalan" sepele diperiksa dan salah. "Ada sebuah jendela yang terlihat seperti panggilan langsung" sedikit lebih sulit dan benar. Jurang antara keduanya adalah keseluruhan fiturnya.
- Untuk sebuah fitur proaktif, presisi adalah produknya. Tak ada yang ingat rapat yang kamu deteksi dengan benar; mereka ingat tiga kali kamu menyela mereka saat mereka tak di satu pun. Pengecualian pola dan state machine anti-cerewet bukanlah pemolesan — keduanya adalah beda antara sebuah banner yang membantu dan satu yang kamu latih dirimu untuk mengabaikan.
- Sebuah penjaga yang bisa mereset dirinya sendiri bukanlah sebuah penjaga. Kedua rilis lanjutan berbentuk sama: sebuah mekanisme keamanan yang merujuk sebuah proxy yang ia kendalikan (sebuah volume tersimpan, sebuah boolean lokal) alih-alih state yang sebenarnya (mik OS, status transkripsi). Arahkan penjaga ke kebenaran, bukan ke sebuah salinannya.
Untuk bab sebelumnya dalam kisah v1, mengeluarkan backend dari jalur unggah (v1.8.11–v1.8.13); dan untuk keseluruhan alurnya, anatomi merilis software sampai sempurna.