Steven
Steven6 मिनट पढ़ें

बिना झिलमिलाहट के लाइव रिपोर्ट स्ट्रीम करने का तरीका

जब आपकी मीटिंग खत्म होती है, तो GeekBye की समरी अब आपको स्पिनर घूरने पर मजबूर करने के बजाय लाइव भरती जाती है। स्ट्रीमिंग UI को झटकेदार के बजाय शांत महसूस कराने के लिए हमें झिलमिलाहट दो बार सुलझानी पड़ी — एक बार स्ट्रक्चर्ड फ़ील्ड के लिए, एक बार markdown के लिए — और दूसरी बार का हल एक ऐसा रेंडरर था जो हमारे पास पहले से मौजूद था।

इंजीनियरिंग
फ्रंटएंड
स्ट्रीमिंग
GeekBye रिलीज़
बिना झिलमिलाहट के लाइव रिपोर्ट स्ट्रीम करने का तरीका

इस रिलीज़ से पहले, GeekBye मीटिंग खत्म करने का मतलब था एक स्पिनर घूरना। ऐप आपका पूरा ट्रांसक्रिप्ट विश्लेषण के लिए भेजता, पूरी समरी का इंतज़ार करता — स्कोर, मुख्य बिंदु, एक्शन आइटम, सब कुछ — और फिर एकसाथ स्क्रीन पर पटक देता। काम तो करता था, पर महसूस धीमा होता था, क्योंकि जब तक वह काम करता, आप एक खालीपन घूरते रहते।

GeekBye v1.6.13–v1.6.15 ने उसे एक ऐसी रिपोर्ट में बदल दिया जो बनते ही, फ़ील्ड-दर-फ़ील्ड, लाइव बहती हुई आती है। दिलचस्प हिस्सा स्ट्रीमिंग खुद नहीं है — बल्कि वह सब है जो हमें करना पड़ा ताकि स्ट्रीमिंग झटकेदार न दिखे। शांत स्ट्रीमिंग और लड़खड़ाती स्ट्रीमिंग एक ही फ़ीचर हैं, बस अमल में ज़मीन-आसमान का फ़र्क है।

झिलमिलाहट, भाग एक: हर chunk को React झिंझोड़ने मत दो

समरी एक ठोस गुच्छा नहीं है — यह स्ट्रक्चर्ड फ़ील्ड (एक कुल स्कोर, मुख्य बिंदु, एक्शन आइटम, वगैरह) हैं जिन्हें बैकएंड server-sent events पर एक-एक करके भेजता है। इसे रेंडर करने का भोला तरीका है हर इवेंट आने पर React state अपडेट करना।

ऐसा करो, और झिलमिलाहट मिलेगी। हर फ़ील्ड का आना अपना खुद का re-render छेड़ता है; जो कंपोनेंट "खाली" दिखा रहे थे वे mount होते हैं, फिर unmount, फिर "लोडेड" के रूप में remount; हर पैकेट पर लेआउट झटका खाता है। रिपोर्ट सबसे बुरे संभव तरीके से आपकी आँखों के सामने खुद को जोड़ती है — साफ़ दिखते हुए, बेचैनी से।

हल एक दो-हिस्सों वाला अनुशासन है। पहला, आने वाले फ़ील्ड को एक ref — एक बदल सकने वाला धारक जो रेंडर नहीं छेड़ता — में जमा करो, और हर chunk पर एक ताज़ा कॉपी के साथ React state में एक बार पब्लिश करो, ताकि ट्री हर सूक्ष्म-इवेंट पर नहीं बल्कि सोच-समझकर अपडेट हो। दूसरा, चाहे स्ट्रीमिंग चल रही हो या पूरी हो चुकी हो, हमेशा एक ही स्थिर कंपोनेंट ट्री रेंडर करो, जिन फ़ील्ड अभी नहीं आए उनके लिए इनलाइन प्लेसहोल्डर रखते हुए:

// accumulate in a ref, publish once per chunk
partialRef.current = { ...partialRef.current, [field]: value }
setPartial({ ...partialRef.current })

// one tree, never swapped; missing fields show a placeholder in place
const report = isStreaming ? partial : saved
<Score>{report.overallScore ?? '…'}</Score>

स्कोर दिखाने वाला कंपोनेंट कभी unmount नहीं होता — जब तक संख्या नहीं आती वह बस दिखाता है, फिर संख्या। कुछ भी झटका नहीं खाता। और पूरी तरह जुड़ी हुई ऑब्जेक्ट अंत में भी लोकल डेटाबेस में कैश हो जाती है, इसलिए स्ट्रीमिंग UI और टिकाऊ भंडारण आपस में लड़े बिना साथ रहते हैं।

झिलमिलाहट, भाग दो: वह रेंडरर जो हमारे पास पहले से था

दूसरी झिलमिलाहट ज़्यादा सूक्ष्म है और खुद markdown के भीतर बसती है। रिपोर्ट समृद्ध markdown रेंडर करती है — शीर्षक, बोल्ड, सूचियाँ, कोड ब्लॉक। पर स्ट्रीम के बीचोबीच आने वाली markdown, किसी भी क्षण, आधी-अधूरी होती है। एक सूची जिसमें अब तक बस एक बुलेट है। एक कोड फ़ेंस जो खुला तो है पर बंद नहीं हुआ। एक बोल्ड मार्कर जिसका अभी कोई जोड़ीदार नहीं।

एक परंपरागत markdown रेंडरर हर टोकन पर पूरी स्ट्रिंग दोबारा पार्स करता है, और वह आधी-अधूरी अवस्था हर बार किसी अलग चीज़ में पार्स होती है — इसलिए सूची झिलमिलाती है, कोड ब्लॉक खुलता-बंद होता चमकता है, टोकन पूरे होते-होते लेआउट उछलता है। यह वही बेचैन जुड़ाई है, बस एक परत नीचे।

हल लगभग शर्मनाक हद तक पास ही था: हम लाइव AI चैट के लिए एक स्ट्रीमिंग-सजग markdown रेंडरर पहले से ही इस्तेमाल कर रहे थे। एक रेंडरर जो अधूरे टोकन झेलने के लिए बना है — आधी-अधूरी markdown को स्थिरता से रेंडर करता है और उसे तभी टिकने देता है जब टोकन पूरे हों — बजाय हर बार शुरू से दोबारा पार्स करने के। रिपोर्ट को बस वही इस्तेमाल करना था। यही बिल्कुल यही समस्या हमने महीनों पहले चैट के लिए सुलझा ली थी; रिपोर्ट का "हल" था यह पहचानना कि औज़ार हमारे पास पहले से है और उसे एक दूसरी सतह की ओर मोड़ देना। पुनः उपयोग ने फिर से बनाने को हरा दिया।

बोनस: रिच कॉपी clipboard फ़ॉर्मेट की समस्या है

एक बार रिपोर्ट अच्छी दिखने लगी, तो लोग उसे पेस्ट करना चाहते थे — किसी डॉक्युमेंट में, किसी ईमेल में — और फ़ॉर्मेटिंग तथा स्क्रीनशॉट बरकरार रखते हुए। सहज प्रवृत्ति है रिपोर्ट को एक markdown स्ट्रिंग में सीरियलाइज़ करना और उम्मीद करना कि गंतव्य उसे रेंडर कर देगा। आमतौर पर वह नहीं करता।

असली जवाब यह है कि clipboard एक साथ कई प्रस्तुतियाँ रखता है। तो हम दो लिखते हैं: एक HTML वर्शन जिसमें फ़ॉर्मेटिंग बरकरार है और स्क्रीनशॉट इनलाइन इमेज के रूप में एम्बेड हैं, और उन गंतव्यों के लिए एक प्लेन-टेक्स्ट फ़ॉलबैक जो HTML नहीं लेते। किसी रिच एडिटर में पेस्ट करो और आपको तस्वीरों सहित फ़ॉर्मेट की हुई रिपोर्ट मिलती है; किसी प्लेन टेक्स्ट बॉक्स में पेस्ट करो और आपको साफ़-सुथरा टेक्स्ट मिलता है। "रिच कॉपी" कभी सीरियलाइज़ेशन की समस्या थी ही नहीं — यह सही clipboard फ़ॉर्मेट चुनने की समस्या थी।

उसी रिलीज़ ने रिपोर्ट को चैट-शैली के Me / Them लेबल भी दिए, जिसमें दोनों वक्ता आमने-सामने की तरफ़ संरेखित हैं, ताकि एक ट्रांसक्रिप्ट उसी बातचीत की तरह पढ़ा जाए जो वह असल में थी, और आपके अपने स्क्रीनशॉट को इससे मेल खाने के लिए उनकी अपनी तरफ़ हटा दिया। (इस दौर की एक रिलीज़, v1.6.14, एक शुद्ध रीबिल्ड थी — वहाँ कोई कहानी नहीं है, और यह कह देना ईमानदारी है।)

स्ट्रीमिंग UI ने हमें सिखाई तीन बातें

  1. स्ट्रीम होने वाले फ़ील्ड बफ़र करो, हर chunk पर एक बार पब्लिश करो। हर server-sent event को अपना खुद का state अपडेट चलाने देना ही झिलमिलाहट है। एक ref संचायक और हर chunk पर एक अकेला पब्लिश, एक कभी unmount न होने वाले ट्री में डालने से, बेचैन जुड़ाई शांत भराव में बदल जाती है।
  2. जो कुछ भी स्ट्रीम होता है उसे एक स्ट्रीमिंग-सजग रेंडरर चाहिए। जो रेंडरर हर टोकन पर पूरी स्ट्रिंग दोबारा पार्स करता है वह आधी-अधूरी markdown पर झिलमिलाएगा। अधूरे इनपुट के लिए बने रेंडरर का इस्तेमाल करो — और अगर तुम्हारे पास किसी दूसरी सतह के लिए पहले से एक है, तो दूसरा बनाने से पहले उसे पुनः उपयोग करो।
  3. रिच कॉपी clipboard फ़ॉर्मेट के बारे में है, सीरियलाइज़ेशन के बारे में नहीं। इमेज एम्बेड किया हुआ text/html और एक text/plain फ़ॉलबैक लिखो। clipboard दोनों ढोने के लिए ही बनाया गया था; उसका इस्तेमाल करो।

यह उस विश्वसनीयता-और-निखार की कहानी का पाँचवाँ अध्याय है जो GeekBye v2 बनती है। पिछले अध्याय के लिए, इयरबड्स वाला बग (v1.6.12) देखें; और जहाँ यही server-sent-events वाला विचार आगे चलकर एक पूरा फ़ॉलबैक ट्रांसपोर्ट बन गया, उसके लिए जब फ़ायरवॉल WebSockets रोक दे तब लाइव ट्रांसक्रिप्शन (v2.0.8) देखें; और पूरे चाप के लिए, सॉफ़्टवेयर को परिपूर्णता तक पहुँचाकर भेजने की शारीर-रचना देखें।

संबंधित लेख

ख़ामोशी भार-वहन कर रही थी
Steven
Steven9 मिनट पढ़ें

ख़ामोशी भार-वहन कर रही थी

GeekBye v1 की आख़िरी दो releases एक ही असहज सच्चाई के बारे में हैं: एक असली network पर real-time transcription लॉसलेस नहीं है, और ईमानदार चाल है यह दिखावा बंद करना कि वह है। v1.8.20 ने एक reconnect के दौरान हर audio chunk को गिराने से पहले उसकी एक प्रति disk पर रखी, और transcript के अंतरालों को ज़ोर से चिह्नित करना शुरू किया। v1.9.0 ने bandwidth बचाने के लिए silence भेजना बंद किया — और पाया कि वह silence ही वह ठीक-ठीक संकेत था जिससे transcriber जानता था कि एक वाक्य ख़त्म हो चुका है। चीज़ें फेंक देने की क़ीमत के बारे में दो releases।

इंजीनियरिंग
Audio
विश्वसनीयता
तीन क्रियाएँ जो Web Audio को ज़िंदा रखती हैं
Steven
Steven10 मिनट पढ़ें

तीन क्रियाएँ जो Web Audio को ज़िंदा रखती हैं

GeekBye की दो point releases, दो महीने के फ़ासले पर और दो अलग files में, हमारे audio code को उलटे छोरों से एक ही सबक़ सिखा गईं: browser के AudioContext को इस्तेमाल-कर-फेंको जैसा मानना बंद करो। एक release ने एक ऐसे context को resume() करना सीखा जिसे macOS ने record के बीचोंबीच चुपचाप suspend कर दिया था; दूसरी ने close() के बजाय suspend() करना सीखा ताकि पीठ-दर-पीठ चलती sessions Chromium की लगभग-छह-context वाली छत से टकराना बंद कर दें। resume, suspend, close — यही पूरी कहानी है।

इंजीनियरिंग
Audio
डेस्कटॉप
एक call को एक खुले हुए app से अलग पहचानना
Steven
Steven10 मिनट पढ़ें

एक call को एक खुले हुए app से अलग पहचानना

GeekBye यह ताड़ सकता है कि आप एक video meeting में शामिल हो गए हैं और एक क्लिक में उसे record करने की पेशकश करता है। पता चलता है कि detection ही आसान वाला आधा था — एक Swift binary जो हर दस सेकंड window titles पढ़ती है। मुश्किल आधा है सटीकता: जब Zoom बस खुला हो तब न भड़कना, एक ऐसी meeting के लिए न टोकना जिसे आप पहले से record कर रहे हैं, और उस call में microphone को mute न करना जिसमें आप असल में हैं। तीन releases, और हर एक एक ऐसा guard है जिसे ख़ुद को न हराना सीखना पड़ा।

इंजीनियरिंग
macOS
डेस्कटॉप