Steven
Steven7 min lasīšana

Zvana atšķiršana no atvērtas lietotnes

GeekBye var pamanīt, ka esi pievienojies video sapulcei, un piedāvāt to ierakstīt. Atklāšana izrādās vieglā puse — Swift binārais fails, kas ik pēc desmit sekundēm lasa logu nosaukumus. Grūtā puse ir precizitāte: neiedarboties, kad Zoom ir tikai atvērts, neuzrunāt par sapulci, ko jau ieraksti, un neizslēgt mikrofonu zvanā, kurā tu patiešām esi. Trīs laidieni, un katrs no tiem ir aizsargs, kam bija jāiemācās nesagraut pašam sevi.

Inženierija
macOS
Desktop
GeekBye laidieni
Zvana atšķiršana no atvērtas lietotnes

GeekBye ir funkcija, kas izklausās triviāla, bet nav: tā pamana, kad esi pievienojies video sapulcei, un ar vienu klikšķi piedāvā sākt to ierakstīt. Piedāvājums ir viens teikums. Implementācija ir trīs laidieni, kuros iemācās, ka interesantā problēma nekad nebija "atpazīt sapulci" — tā bija "būt pareizam par to". Pareizam par to, vai tu patiešām esi zvanā, nevis tikai atvērta lietotne. Pareizam par to, lai neprasītu divreiz. Pareizam par to, lai neizslēgtu nepareizo mikrofonu. Katrs no trim laidieniem būtībā ir aizsargs, kam bija jāiemāca nepakļūt pašam sev.

Logu nosaukumi, nevis procesi

Pirmais un vissekojošāko izmaiņu nesošais lēmums ir ierakstīts komita ziņojumā kā dizaina apgalvojums, nevis zemsvītras piezīme:

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

Šī atšķirība ir visa funkcija. Ja tu atpazītu sapulces, skatoties, kuras lietotnes ir palaistas, tu iedarbinātos brīdī, kad Zoom tiek startēts — un Zoom ir atvērts daudzos galddatoros visu dienu bez neviena zvana. Tāpēc GeekBye nevēro procesus. macOS tas palaiž mazu native Swift bināro failu, MeetingMonitor, kas ik pēc desmit sekundēm izsauc CGWindowListCopyWindowInfo ekrānā esošajiem logiem un nolasa katra nosaukumu caur kCGWindowName. Logs ar nosaukumu "Zoom Meeting" nozīmē, ka tu esi zvanā; "Zoom Workplace" logs nozīmē, ka lietotne ir tikai atvērta. Signāls nav "vai Zoom ir dzīvs", tas ir "vai logs uz tava ekrāna izskatās pēc dzīva zvana".

Aptaujas intervāls ir stingra konstante — pollIntervalSeconds: Double = 10.0 — plus viena tūlītēja pārbaude startā, tā ka sapulce tiek pamanīta apmēram desmit sekunžu laikā pēc tās loga parādīšanās. (Tā ir koda vērtība, nevis izmērīta latence; uztver "līdz ~10 s" kā aritmētiku, nevis etalonmērījumu.)

Pārlūka sapulcēm vajag vēl vienu soli, jo Chrome logs ar nosaukumu "Meet - Weekly Sync" ir daudzsološs, bet noteiktība ir URL. Tāpēc, kad priekšplānā esošā lietotne ir zināms pārlūks — Chrome, Safari, Edge, Brave un vēl daži, kas identificēti pēc bundle id — binārais fails izvelk aktīvās cilnes URL caur AppleScript un pārbauda to pret shēmām kā meet.google.com/, zoom.us/j/ un teams.microsoft.com/. AppleScript uz citu lietotni var iestrēgt, tāpēc šis izsaukums notiek fona DispatchQueue, ko sargā DispatchSemaphore ar 3 sekunžu noildzi; iestrēdzis pārlūks izbeidzas ar noildzi, nevis iesaldē visu detektoru.

Grūtā daļa ir neiedarboties

Lūk daļa, kas mani pārsteidza, kad izlasīju shēmu sarakstu. Zvana atpazīšana ir dažas virkņu sakritības. _Ne_atpazīt to — neiedarboties, kad tu to nevēlies — ir vieta, kur aizgāja visa uzmanība. Logu nosaukumu shēmu saraksts lasās kā apzinātu izslēgšanu kopa:

  • Google Meet saskan ar "Meet - " un tā en-domuzīmes variantu "Meet – ", bet ne ar kailo virkni "Google Meet" — jo tā ir sākumlapa, ko atver pirms zvana.
  • Zoom saskan ar "Zoom Meeting" un "Zoom Webinar", bet ne ar "Zoom Workplace" vai kailo "Zoom" — lietotni, kas stāv atvērta.
  • Teams saskan ar "Meeting with", "Meeting in", " | Call", " | Meeting" — zvanā esošo nosaukumu formām, nevis dīkstāves klientu.
  • Slack huddles, Webex telpas un WhatsApp zvani katrs iegūst savus tikai-zvanā fragmentus.

Katrs no tiem ir lēmums palikt klusam parastajā gadījumā, kad lietotne ir atvērta, bet tu neesi sapulcē. Dizaina mērķis nav maksimāla atsaukšana; tas ir nebūt tam rīkam, kas izmet "gribi ierakstīt?" baneri, kamēr tu lasi Slack. Viltus pozitīvs rezultāts šeit ir dārgs veidā, kā viltus negatīvs nav: palaid garām zvanu, un lietotājs sāk ierakstīt manuāli; iedarbojies kļūdaini uz atvērtas lietotnes, un lietotājs iemācās ignorēt tavu baneri. Visa heiristika ir noskaņota tā, lai otrā kļūme būtu reta.

Ir godīgi nosaukt tik vienkāršas heiristikas robežas. Shēmas ir tikai angļu valodā — lokalizēta OS vai neangļu sapulču lietotne nesaskanēs, atzīts v1 ierobežojums. Un tā kā citu lietotņu logu nosaukumi ir redzami tikai ar piešķirtu Screen Recording atļauju, atpazīšana klusi degradējas līdz nekam bez tās. Vienkārša, salasāma un nepilnīga veidos, ko komanda pierakstīja, nevis paslēpa.

Piedāvājums un mehānika, kas neļauj tam uzmākties

Kad shēma saskan, GeekBye stūrī parāda mazu bezrāmja vienmēr-virspusē baneri — savu logu, nevis OS paziņojumu — ar pogu Sākt ierakstīšanu. Uzklikšķini uz tās, un IPC ziņojums (meeting:start-recording) paslēpj baneri, izsauc galveno logu un liek renderētājam sākt sesiju. Noraidi to, un baneris pazūd.

Bet naiva šī versija būtu prātu jaucoša. Nosaukumi zvana laikā pastāvīgi mainās — pievienojas dalībnieks, tu pārsauc sapulci, ekrāna koplietošana nomaina loga nosaukumu — un katra izmaiņa atkārtoti iedarbinātu detektoru, kas balstījās uz precīzu virkni. Tāpēc detektors failā MeetingDetector.ts nes mazu stāvokļu mašīnu, kuras viss darbs ir savaldība:

  • Stabils meetingId katrai platformai. Swift puse izdod id kā google-meet-active, apzināti ne atvasinātu no nestabilā loga nosaukuma, tā ka pievienošanās, aiziešana un pārsaukšana viena zvana ietvaros netiek lasīta kā jauna sapulce.
  • dismissedMeetings: Set<string>. Noraidi baneri, un tās sapulces id tiek ignorēts — līdz sapulce patiešām beidzas, brīdī, kad id notīrās, lai nākamā īstā sapulce varētu uzrunāt no jauna.
  • notificationActive būla vērtība. Tikai viens baneris vienlaikus, tā ka Slack huddle, kas uzlec, kamēr tu jau esi Meet, nesakrāj otru uzrunu.

Tā patiesībā ir funkcija: mazliet atpazīšanas un daudz "nebūt kaitinošam". Kas ir tieši iemesls, kāpēc pastāv nākamie divi laidieni — jo divi no šiem aizsargiem izrādījās sagraujam paši sevi.

Izslēgšana, kas izslēdza tavu īsto sapulci (v1.8.18)

Ierakstīšanai ir izslēgšanas poga, un pirmā implementācija uztvēra "izslēgt" tā burtiskākajā nozīmē. Piegādāta v1.8.15, tā izslēdza mikrofonu, iestatot operētājsistēmas ievades skaļumu uz nulli:

osascript -e "set volume input volume 0"

ar iepriekšējo skaļumu, kas saglabāts datubāzē, lai to varētu atjaunot, Windows ekvivalentu caur waveInSetVolume un before-quit drošības tīklu, lai pārliecinātos, ka tas atkal ieslēdzas. Kods to pat aprakstīja kā funkciju — izslēgšana "DIVOS līmeņos", sistēmā un lietotnē, tā ka mikrofons tika bloķēts visām lietotnēm.

Šī pēdējā frāze ir kļūda, izteikta kā lielība. Mikrofona bloķēšana visām lietotnēm nozīmē tā bloķēšanu Zoom vai Meet zvanam, kurā tu patiešām sēdi. Tu nospied izslēgšanu GeekBye, lai apturētu to no tevis dzirdēšanas, un tu esi kļuvis kluss savā īstajā sapulcē. v1.8.18 labojums ir pazemināšana: pilnībā pārstāt aizskart OS ievades skaļumu un izslēgt tikai GeekBye paša audio konveijeru. Listen režīmā RealtimeAudioCaptureService aizstāj izslēgtā mikrofona kadrus ar nulles buferi, tā ka klusums plūst uz mikseri; video režīmā ScreenRecordingService iestata Web Audio GainNode uz nulli. Sistēmas mēroga izslēgšana, saglabātā skaļuma atgūšana, viss starpplatformu aparāts — viss izdzēsts. Izslēgšanai jānozīmē "šī lietotne pārstāj klausīties", nevis "tavs mikrofons izslēdzas visiem".

Aizsargs, kas atiestatīja pats sevi (v1.8.19)

Otrs pašgraujošais aizsargs bija notificationActive. Uz papīra tas jau atbildēja "nerādi baneri, ja viens jau ir augšā" — un izskatījās, ka tam vajadzētu segt arī "nepiedāvā ierakstīt sapulci, ko es jau ierakstu". Tas to nedarīja, un komits, kas to izlabo, precīzi paskaidro kāpēc:

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

Detektors ir dzīva aptauja; garā zvanā tas var īsi nolasīt "ended" un tad atkal "detected" — nosaukuma mirgošana, logs uz mirkli ārpus ekrāna — un katrs cikls atiestatīja karodziņu. Tāpēc aizsargs turpināja klusi atkārtoti uzvilkties, kamēr tu ieraksti, un baneris atkal parādījās, piedāvājot ierakstīt zvanu, ko tu jau ieraksti. Labojums pārstāj uzticēties lokālam karodziņam un tā vietā jautā patiesības avotam: recordingActiveChecker atzvans, kas ievadīts no main.ts un savienots ar īsto isRealtimeTranscriptionActive stāvokli, pārbaudīts pirms jebkura paziņojuma parādīšanas.

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

Mācība ir maza un vispārīga: aizsargu, kura stāvokli tu pārvaldi, tava paša vadības plūsma var atiestatīt tev zem kājām. Aizsargs, kas nolasa īsto stāvokli, ko tas aizsargā, nevar. notificationActive sekoja aizstājējam jautājumam "vai mēs ierakstam"; labojums jautā, vai mēs ierakstam.

Trīs lietas, ko šie laidieni mums iemācīja

  1. Atpazīsti stāvokli, ko tu domā, nevis to, ko viegli ieraudzīt. "Lietotne ir palaista" ir triviāli pārbaudāms un nepareizs. "Pastāv logs, kas izskatās pēc dzīva zvana" ir mazliet grūtāk un pareizi. Plaisa starp šiem diviem ir visa funkcija.
  2. Proaktīvai funkcijai precizitāte ir produkts. Neviens neatceras sapulci, ko tu pareizi atpazini; viņi atceras trīs reizes, kad tu viņus pārtrauci, kad viņi nebija nevienā. Shēmu izņēmumi un pret-uzmākšanās stāvokļu mašīna nav pulējums — tie ir starpība starp noderīgu baneri un tādu, ko tu iemācies ignorēt.
  3. Aizsargs, kas var atiestatīt pats sevi, nav aizsargs. Abi turpinājuma laidieni ir vienā formā: drošības mehānisms, kas atsaucās uz aizstājēju, ko tas pārvaldīja (saglabātu skaļumu, lokālu būla vērtību), nevis uz īsto stāvokli (OS mikrofonu, transkripcijas statusu). Vērs aizsargus uz patiesību, nevis uz tās kopiju.

Iepriekšējai nodaļai v1 stāstā — backend izņemšana no augšupielādes ceļa (v1.8.11–v1.8.13); un visai lokai — programmatūras piegādes līdz pilnībai anatomiju.

Saistītie raksti

Trīs darbības vārdi, kas uztur Web Audio dzīvu
Steven
Steven8 min lasīšana

Trīs darbības vārdi, kas uztur Web Audio dzīvu

Divi GeekBye laidieni, divu mēnešu attālumā un divos dažādos failos, iemācīja mūsu audio kodam vienu un to pašu mācību no pretējiem galiem: pārstāt izturēties pret pārlūka AudioContext kā pret vienreizlietojamu. Viens laidiens iemācījās izsaukt resume() kontekstam, ko macOS bija klusi apturējis ieraksta vidū; otrs iemācījās izsaukt suspend() nevis close(), lai secīgas sesijas pārstātu triekties Chromium aptuveni sešu kontekstu griestos. Resume, suspend, close — tas ir viss sižets.

Inženierija
Audio
Desktop
Backend izņemšana no augšupielādes ceļa
Steven
Steven7 min lasīšana

Backend izņemšana no augšupielādes ceļa

GeekBye ieraksta tavu ekrānu un saglabā video tavā Google Drive. Pirmā versija piegādāja katru ierakstu caur GeekBye paša serveriem ceļā uz turieni; vienu laidienu vēlāk fails devās tieši no tavas mašīnas uz Drive, un backend tika pazemināts līdz viena rādītāja turēšanai. Interesantākā daļa ir tas, cik maz koda 'tiešā, atsākamā' versija patiesībā satur — jo atsākamība nāca no starpniekservera dzēšanas, nevis no tā rakstīšanas.

Inženierija
Arhitektūra
Desktop
Dzimis un izlabots divpadsmit minūtēs
Steven
Steven6 min lasīšana

Dzimis un izlabots divpadsmit minūtēs

GeekBye v1.8.10 izmaiņu žurnālā teikts, ka tas izlaboja avāriju, rediģējot tastatūras saīsnes. Tā arī bija — bet avārija tika ieviesta un izlabota vienā un tajā pašā pull request, divpadsmit minūšu attālumā, un nekad nesasniedza nevienu lietotāju. Īstais stāsts ir uzticamības kaskāde, kas to radīja: neliela, pareiza izmaiņa tajā, kad saīsnes tiek apturētas, un React demontāžas kļūda, kas izbira no vienas rindiņas, kurai vajadzēja padarīt redaktoru drošāku.

Inženierija
React
Desktop