Steven
Steven8 minit bacaan

Membezakan Panggilan daripada Aplikasi yang Terbuka

GeekBye boleh perasan bahawa anda telah menyertai satu mesyuarat video dan menawarkan untuk merakamnya. Pengesanan ternyata separuh yang mudah — satu binari Swift yang membaca tajuk tetingkap setiap sepuluh saat. Separuh yang sukar ialah kepersisan: tidak mencetus apabila Zoom hanya terbuka, tidak menawarkan satu mesyuarat yang sudah anda rakam, dan tidak membisukan mikrofon dalam panggilan yang sebenarnya anda sertai. Tiga keluaran, dan setiap satu ialah satu pengawal yang terpaksa belajar untuk tidak mengalahkan dirinya sendiri.

Kejuruteraan
macOS
Desktop
GeekBye Releases
Membezakan Panggilan daripada Aplikasi yang Terbuka

GeekBye ada satu ciri yang kedengaran remeh tetapi bukan: ia perasan apabila anda telah menyertai satu mesyuarat video dan menawarkan, dengan satu klik, untuk mula merakamnya. Tawarannya satu ayat sahaja. Pelaksanaannya ialah tiga keluaran daripada belajar bahawa masalah yang menarik tidak pernah "mengesan satu mesyuarat" — ia ialah "betul tentangnya." Betul tentang sama ada anda sebenarnya dalam satu panggilan berbanding sekadar mempunyai aplikasi terbuka. Betul tentang tidak bertanya dua kali. Betul tentang tidak membisukan mikrofon yang salah. Setiap satu daripada tiga keluaran itu, pada terasnya, ialah satu pengawal yang terpaksa diajar untuk tidak tersandung dirinya sendiri.

Tajuk tetingkap, bukan proses

Keputusan pertama dan paling berpengaruh ditulis ke dalam satu mesej commit sebagai satu pernyataan reka bentuk, bukan satu nota kaki:

Detection uses window title matching (not process detection) to distinguish "app is open" from "in an active call."

Perbezaan itu ialah keseluruhan ciri. Jika anda mengesan mesyuarat dengan melihat aplikasi mana yang berjalan, anda akan mencetus seketika Zoom dilancarkan — dan Zoom terbuka pada banyak desktop sepanjang hari tanpa satu panggilan pun. Jadi GeekBye tidak memerhatikan proses. Pada macOS ia menjalankan satu binari Swift natif kecil, MeetingMonitor, yang setiap sepuluh saat memanggil CGWindowListCopyWindowInfo bagi tetingkap pada skrin dan membaca tajuk setiap satu melalui kCGWindowName. Satu tetingkap bertajuk "Zoom Meeting" bermakna anda dalam satu panggilan; tetingkap "Zoom Workplace" bermakna aplikasi hanya terbuka. Isyaratnya bukan "adakah Zoom hidup," ia ialah "adakah satu tetingkap pada skrin anda kelihatan seperti satu panggilan langsung."

Selang poll ialah satu pemalar tegar — pollIntervalSeconds: Double = 10.0 — tambah satu pemeriksaan serta-merta pada permulaan, supaya satu mesyuarat diperasan dalam lebih kurang sepuluh saat selepas tetingkapnya muncul. (Itu satu nilai kod, bukan satu kependaman yang diukur; anggap "sehingga ~10 s" sebagai aritmetik, bukan satu benchmark.)

Mesyuarat dalam pelayar memerlukan satu langkah lagi, kerana satu tetingkap Chrome bertajuk "Meet - Weekly Sync" menjanjikan tetapi kepastian ada pada URL. Jadi apabila aplikasi paling hadapan ialah satu pelayar yang dikenali — Chrome, Safari, Edge, Brave, dan segelintir lagi yang dikenal pasti melalui bundle id — binari itu mengekstrak URL tab aktif melalui AppleScript dan memeriksanya terhadap corak seperti meet.google.com/, zoom.us/j/, dan teams.microsoft.com/. AppleScript ke aplikasi lain boleh tergantung, jadi panggilan itu berjalan pada satu DispatchQueue latar yang dikawal oleh satu DispatchSemaphore dengan tamat masa 3 saat; satu pelayar yang tersekat tamat masa dan bukan membekukan seluruh pengesan.

Bahagian sukar ialah tidak mencetus

Inilah bahagian yang mengejutkan saya apabila saya membaca senarai corak. Mengesan satu panggilan ialah beberapa padanan rentetan. Tidak mengesan satu — tidak mencetus apabila anda tidak mahu — ialah ke mana semua penjagaan pergi. Senarai corak tajuk-tetingkap terbaca seperti satu set pengecualian yang disengajakan:

  • Google Meet padan dengan "Meet - " dan variannya bertanda-sempang-en "Meet – ", tetapi bukan rentetan telanjang "Google Meet" — kerana itu halaman pendaratan yang anda buka sebelum satu panggilan.
  • Zoom padan dengan "Zoom Meeting" dan "Zoom Webinar", tetapi bukan "Zoom Workplace" atau satu "Zoom" telanjang — aplikasi yang duduk terbuka.
  • Teams padan dengan "Meeting with", "Meeting in", " | Call", " | Meeting" — bentuk tajuk semasa-dalam-panggilan, bukan klien yang melahu.
  • Huddle Slack, bilik Webex, dan panggilan WhatsApp masing-masing mendapat serpihan hanya-dalam-panggilan tersendiri.

Setiap satu daripadanya ialah satu keputusan untuk kekal senyap dalam kes biasa di mana satu aplikasi terbuka tetapi anda tidak dalam satu mesyuarat. Matlamat reka bentuk bukan penarikan balik maksimum; ia ialah tidak menjadi alat yang menyembulkan satu sepanduk "mahu rakam?" semasa anda sedang membaca Slack. Satu positif palsu di sini mahal dengan cara yang satu negatif palsu tidak: terlepas satu panggilan dan pengguna mula merakam secara manual; tersilap cetus pada satu aplikasi terbuka dan pengguna belajar mengabaikan sepanduk anda. Seluruh heuristik ditala ke arah kegagalan kedua menjadi jarang.

Adalah jujur untuk menamakan had satu heuristik yang semudah ini. Coraknya hanya-Inggeris — satu OS yang disetempatkan atau satu aplikasi mesyuarat bukan-Inggeris tidak akan padan, satu batasan v1 yang diakui. Dan kerana tajuk tetingkap bagi aplikasi lain hanya kelihatan dengan kebenaran Rakaman Skrin diberikan, pengesanan secara senyap merosot menjadi tiada tanpanya. Ringkas, boleh dibaca, dan tidak sempurna dengan cara-cara yang ditulis pasukan dan bukan disembunyikan.

Tawaran itu, dan jentera yang menghalangnya daripada meleteh

Apabila satu corak padan, GeekBye menunjukkan satu sepanduk kecil tanpa-bingkai sentiasa-di-atas di sudut — tetingkapnya sendiri, bukan satu pemberitahuan OS — dengan satu butang Start Recording. Klik padanya dan satu mesej IPC (meeting:start-recording) menyembunyikan sepanduk, memunculkan tetingkap utama, dan memberitahu renderer untuk memulakan satu sesi. Ketepikan ia dan sepanduk itu hilang.

Tetapi satu versi naif bagi ini akan menyakitkan hati. Tajuk berubah sepanjang masa semasa satu panggilan — seorang peserta menyertai, anda menamakan semula mesyuarat, satu perkongsian-skrin menukar nama tetingkap — dan setiap perubahan akan mencetus semula satu pengesan yang berpaut pada rentetan yang tepat. Jadi pengesan, dalam MeetingDetector.ts, membawa satu mesin keadaan kecil yang seluruh tugasnya ialah menahan diri:

  • Satu meetingId stabil setiap platform. Pihak Swift memancarkan satu id seperti google-meet-active, sengaja tidak diterbitkan daripada tajuk tetingkap yang tidak menentu, supaya menyertai, keluar, dan menamakan semula dalam satu panggilan tidak dibaca sebagai satu mesyuarat baharu.
  • Satu dismissedMeetings: Set<string>. Ketepikan sepanduk dan id mesyuarat itu diabaikan — sehingga mesyuarat benar-benar tamat, pada ketika itu id dibersihkan supaya mesyuarat sebenar seterusnya boleh bertanya semula.
  • Satu boolean notificationActive. Hanya satu sepanduk pada satu masa, supaya satu huddle Slack yang menyembul semasa anda sudah dalam satu Meet tidak menimbun satu gesaan kedua.

Itulah cirinya, sebenarnya: sedikit pengesanan dan banyak "jangan menjengkelkan." Yang mana tepat sebab dua keluaran seterusnya wujud — kerana dua daripada pengawal itu ternyata mengalahkan diri sendiri.

Pembisuan yang membisukan mesyuarat sebenar anda (v1.8.18)

Rakaman ada satu butang bisu, dan pelaksanaan pertama mengambil "bisu" dalam ertinya yang paling harfiah. Dihantar dalam v1.8.15, ia membisukan mikrofon dengan menetapkan volum input sistem pengendalian kepada sifar:

osascript -e "set volume input volume 0"

dengan volum sebelumnya disimpan ke pangkalan data supaya boleh dipulihkan, satu setara Windows melalui waveInSetVolume, dan satu jaring keselamatan before-quit untuk memastikan ia menyahbisu semula. Kod itu malah menggambarkannya sebagai satu ciri — membisukan "pada DUA peringkat," sistem dan aplikasi, supaya mik disekat untuk semua aplikasi.

Frasa terakhir itu ialah pepijat yang dinyatakan sebagai satu megahan. Menyekat mik untuk semua aplikasi bermakna menyekatnya untuk panggilan Zoom atau Meet yang sebenarnya anda duduki. Anda menekan bisu dalam GeekBye untuk menghentikan ia daripada mendengar anda, dan anda telah menjadi senyap dalam mesyuarat sebenar anda. Pembetulan v1.8.18 ialah satu penurunan pangkat: berhenti menyentuh volum input OS sepenuhnya, dan bisukan hanya saluran paip audio milik GeekBye sendiri. Dalam mod Listen, RealtimeAudioCaptureService menggantikan bingkai mik yang dibisukan dengan satu penimbal sifar supaya kesenyapan mengalir ke pencampur; dalam mod video, ScreenRecordingService menetapkan satu GainNode Web Audio kepada sifar. Pembisuan seluruh-sistem, pemulihan volum-tersimpan, seluruh radas rentas-platform — semua dipadam. Bisu sepatutnya bermaksud "aplikasi ini berhenti mendengar," tidak pernah "mikrofon anda dimatikan untuk semua orang."

Pengawal yang menetap semula sendiri (v1.8.19)

Pengawal lain yang mengalahkan diri sendiri ialah notificationActive. Di atas kertas ia sudah menjawab "jangan tunjuk satu sepanduk jika ada satu" — dan ia kelihatan seolah-olah patut juga meliputi "jangan tawarkan untuk merakam satu mesyuarat yang sudah saya rakam." Ia tidak, dan commit yang membetulkannya menerangkan sebabnya dengan tepat:

The notificationActive flag resets when the meeting detector cycles through ended/detected states, causing notifications to reappear mid-recording.

Pengesan ialah satu tinjauan langsung; sepanjang satu panggilan panjang ia boleh sebentar membaca "ended" dan kemudian "detected" semula — satu kelipan tajuk, satu tetingkap sekejap di luar skrin — dan setiap kitaran menetapkan semula penanda. Jadi pengawal terus senyap-senyap mempersenjatai diri semula semasa anda merakam, dan sepanduk akan muncul semula menawarkan untuk merakam panggilan yang sudah anda rakam. Pembetulannya berhenti mempercayai satu penanda setempat dan sebaliknya bertanya kepada sumber kebenaran: satu panggilan balik recordingActiveChecker, disuntik daripada main.ts dan disambung ke keadaan isRealtimeTranscriptionActive yang sebenar, diperiksa sebelum sebarang pemberitahuan muncul.

// Don't show notification if recording/transcription is already active
if (this.recordingActiveChecker?.()) {
  return
}

Pengajarannya kecil dan umum: satu pengawal yang keadaannya anda miliki boleh ditetapkan semula dari bawah anda oleh aliran kawalan anda sendiri. Satu pengawal yang membaca keadaan sebenar yang ia lindungi tidak boleh. notificationActive menjejaki satu proxy bagi "adakah kita merakam"; pembetulannya bertanya sama ada kita sedang merakam.

Tiga perkara yang keluaran ini ajarkan kepada kami

  1. Kesan keadaan yang anda maksudkan, bukan yang mudah dilihat. "Aplikasi sedang berjalan" remeh untuk diperiksa dan salah. "Wujud satu tetingkap yang kelihatan seperti satu panggilan langsung" sedikit lebih sukar dan betul. Jurang antara kedua-dua itu ialah keseluruhan ciri.
  2. Untuk satu ciri proaktif, kepersisan ialah produknya. Tiada siapa ingat mesyuarat yang anda kesan dengan betul; mereka ingat tiga kali anda mengganggu mereka ketika mereka tidak dalam satu pun. Pengecualian corak dan mesin keadaan anti-meleteh bukan penggilapan — ia ialah beza antara satu sepanduk yang membantu dan satu yang anda latih diri untuk mengabaikan.
  3. Satu pengawal yang boleh menetap semula sendiri bukanlah satu pengawal. Kedua-dua keluaran susulan berbentuk sama: satu mekanisme keselamatan yang merujuk satu proxy yang ia kawal (satu volum tersimpan, satu boolean setempat) dan bukan keadaan sebenar (mik OS, status transkripsi). Halakan pengawal ke arah kebenaran, bukan ke arah satu salinannya.

Untuk bab sebelumnya dalam kisah v1, mengeluarkan backend daripada laluan muat naik (v1.8.11–v1.8.13); dan untuk keseluruhan lengkungnya, anatomi mengeluarkan perisian hingga ke kesempurnaan.

Artikel Berkaitan

Tiga Kata Kerja yang Memastikan Web Audio Terus Hidup
Steven
Steven8 minit bacaan

Tiga Kata Kerja yang Memastikan Web Audio Terus Hidup

Dua keluaran titik GeekBye, berselang dua bulan dan dalam dua fail berbeza, mengajar kod audio kami pelajaran yang sama daripada hujung yang bertentangan: berhenti memperlakukan AudioContext pelayar sebagai barangan sekali guna. Satu keluaran belajar untuk resume() satu context yang telah macOS gantung secara senyap di tengah rakaman; satu lagi belajar untuk suspend() dan bukan close() supaya sesi berturut-turut berhenti melanggar siling kira-kira enam context milik Chromium. Resume, suspend, close — itulah keseluruhan jalan ceritanya.

Kejuruteraan
Audio
Desktop
Mengeluarkan Backend daripada Laluan Muat Naik
Steven
Steven8 minit bacaan

Mengeluarkan Backend daripada Laluan Muat Naik

GeekBye merakam skrin anda dan menyimpan videonya ke Google Drive anda. Versi pertama menghantar setiap rakaman melalui pelayan milik GeekBye sendiri dalam perjalanannya; satu keluaran kemudian, fail itu pergi terus daripada mesin anda ke Drive, dan backend diturunkan taraf kepada hanya memegang satu penunjuk. Bahagian yang menarik ialah betapa sedikit kod yang sebenarnya dikandung versi 'terus, boleh sambung semula' itu — kerana keupayaan-menyambung datang daripada memadam satu proxy, bukan daripada menulis satu.

Kejuruteraan
Seni Bina
Desktop
Lahir dan Dibetulkan dalam Dua Belas Minit
Steven
Steven7 minit bacaan

Lahir dan Dibetulkan dalam Dua Belas Minit

Changelog GeekBye v1.8.10 berkata ia membetulkan satu crash semasa menyunting pintasan papan kekunci. Memang begitu — tetapi crash itu diperkenalkan dan dibetulkan dalam pull request yang sama, berselang dua belas minit, dan tidak pernah sampai kepada seorang pun pengguna. Kisah sebenarnya ialah lata kebolehpercayaan yang menghasilkannya: satu perubahan kecil dan betul tentang bila pintasan digantung, dan bug teardown React yang jatuh daripada satu baris yang bertujuan menjadikan editor lebih selamat.

Kejuruteraan
React
Desktop