<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[MeetVoice]]></title><description><![CDATA[MeetVoice]]></description><link>https://meetvoice.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6ad4bbe5b28b67ea19d4ddd6/b94a8dc9-b981-40a1-827c-98a8c2661c78.png</url><title>MeetVoice</title><link>https://meetvoice.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 24 Oct 2026 18:25:20 GMT</lastBuildDate><atom:link href="https://meetvoice.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[What we learned building two-way voice translation for Google Meet]]></title><description><![CDATA[Engineering lessons from two-way meeting translation: audio routing, extension and desktop ownership, playback queues, and session cleanup.]]></description><link>https://meetvoice.hashnode.dev/what-we-learned-building-two-way-voice-translation-for-google-meet</link><guid isPermaLink="true">https://meetvoice.hashnode.dev/what-we-learned-building-two-way-voice-translation-for-google-meet</guid><category><![CDATA[Browser Extension]]></category><category><![CDATA[Audio Processing]]></category><category><![CDATA[software architecture]]></category><dc:creator><![CDATA[Oleksii Herasymchuk]]></dc:creator><pubDate>Sun, 18 Oct 2026 12:38:50 GMT</pubDate><content:encoded><![CDATA[<p>Adding translated captions to a meeting and translating both sides of a spoken conversation create different engineering problems. Captions appear on one person's screen. A spoken reply has to reach the other participants through the meeting's audio path, while the original conversation keeps moving.</p>
<p>We work on <a href="https://meetvoice.app/">MeetVoice</a>, a Chrome extension and desktop app for Google Meet. This article explains several design choices in its two-way language-pair mode: separating audio directions, choosing a runtime boundary, controlling playback, and stopping asynchronous work cleanly.</p>
<h2>Model the two directions separately</h2>
<p>For an English-speaking participant talking to a Ukrainian-speaking participant, the local user's two paths look like this:</p>
<pre><code class="language-text">Incoming
Meeting audio → recognition → translation → local captions and voice playback

Outgoing
Local microphone → recognition → translation → voice injected into the meeting
</code></pre>
<p>The destination changes the behavior. Incoming translation helps the local user understand the meeting. Outgoing translation changes what everyone else hears.</p>
<p>In MeetVoice, the two pipelines share a WebSocket connection, but carry direction information. Each direction has its own source and target language, synthesis work, and playback destination. That distinction also needs to survive errors and cancellation: stopping an incoming phrase should not accidentally target an outgoing reply.</p>
<p>There is a product consequence too. Translating the user's own speech is off by default and must be enabled separately. A successful incoming subtitle is therefore insufficient evidence that a two-way call is ready. Test from the remote participant's side as well: can they actually hear the translated reply?</p>
<h2>Give each runtime a clear responsibility</h2>
<p>MeetVoice uses both a Chrome extension and an Electron desktop app. The extension handles the meeting-facing work: tab audio, the microphone bridge, captions, and playback integration. The desktop app starts and manages a local server process that coordinates recognition, translation, and speech synthesis.</p>
<p>Within the extension, a dedicated offscreen document owns tab capture and the WebSocket connection. The popup provides controls without owning the long-running capture session. Closing that small interface should not become the lifecycle boundary of the audio pipeline.</p>
<p>These boundaries make ownership explicit, but they also add failure cases. The extension may be installed while the desktop app is stopped. A meeting tab may disappear while background work is still running. Connection state and cleanup belong in the design from the beginning.</p>
<p>A local process also says little about where data is processed. MeetVoice uses cloud services for speech and translation. We need to describe that clearly rather than let users infer offline operation from the presence of a desktop app.</p>
<h2>Design the playback graph alongside recognition</h2>
<p>Producing translated audio is only part of the job. The system has to play it at the right destination without feeding its own synthetic speech back into recognition.</p>
<p>For incoming translation, the original meeting audio and local translated playback have separate roles. We lower the original audio while the translated voice is audible, then restore it. This is commonly called ducking.</p>
<p>The restoration path deserves as much attention as playback start. A synthesis failure, an interrupted clip, or a stopped session must release the volume adjustment. Otherwise a translation error can leave the rest of the meeting unexpectedly quiet.</p>
<p>Outgoing audio has a different destination: it is injected into the audio path used by the meeting. A local playback test cannot establish that this path works. Use an actual second participant or a separate test device with headphones, and check both what is heard and what happens when the microphone is muted.</p>
<h2>Separate readable text from speakable text</h2>
<p>Recognition can produce partial hypotheses before a phrase is complete. Text on screen can be revised as more context arrives. Spoken audio cannot be taken back once another participant has heard it.</p>
<p>That gives captions and voice output different timing requirements. A streaming interface can display progress while translation and synthesis still need useful phrase boundaries. Trying to speak every unstable fragment risks producing broken or repeatedly corrected sentences.</p>
<p>When evaluating a pipeline, follow the whole path to audible playback. Recognition time, translation, synthesis, queueing, and playback startup all contribute to the experience. A fast response from one service does not establish the delay the participant actually hears.</p>
<h2>Bound the age of queued speech</h2>
<p>An audio queue can preserve order while falling further behind the live conversation. Once participants have moved to a new topic, an old translated sentence can arrive at a confusing moment.</p>
<p>MeetVoice has playback lag budgets and cancellation paths for stale speech. The general design question is what to do when a phrase is no longer timely. Queue management needs a policy for dropping work, as well as one for delivering it.</p>
<p>Cancellation has to reach work already in progress. Removing an item from a pending list does not stop an active synthesis request or prevent a late result from arriving. Playback cleanup must still run when that work is aborted.</p>
<h2>Treat stopping as a state transition</h2>
<p>A live audio feature has several asynchronous owners: capture, recognition, translation, synthesis, transport, and playback. Any one of them can finish after the user stops the session.</p>
<p>MeetVoice uses generation checks to reject work belonging to an earlier session or capture. Stopping also releases capture resources, aborts playback work, and restores audio levels. The checks remain relevant after asynchronous waits, because the session may have changed while a request was in flight.</p>
<p>Useful test scenarios include stopping during synthesis, restarting before an old result returns, closing the meeting tab during playback, and interrupting one direction while the other is active. Each scenario should have an observable outcome: no stale speech in the new session, no abandoned capture, and restored volume.</p>
<p>The central lesson for us is that two-way translation is an audio lifecycle feature as much as a language feature. Define which side receives each output, which component owns it, and how it ends. Those decisions shape whether a translated conversation remains understandable when the network slows down or the user changes their mind mid-sentence.</p>
<p>Writing disclosure: this article was drafted with AI assistance. Its technical descriptions were checked against the current MeetVoice implementation.</p>
]]></content:encoded></item></channel></rss>