Skip to main content
Tuturuuu Meet calls are Google-Meet-style conferences carried by the Cloudflare Realtime SFU. This is a different product from Cloudflare Stream Live, which powers one-to-many broadcast. Meet opens Personal from the app launcher; use the workspace picker or direct workspace links for team meetings. Every participant holds exactly two peer connections to the SFU — one publishing, one subscribing — regardless of room size. Connection count stays fixed; bandwidth and decoding work still grow with subscribed media tracks.

Preview resource budgets

Hosting is available to all verified Plus, Pro, and Enterprise accounts, as well as verified @tuturuuu.com accounts. Eligibility uses the current confirmed authentication email and the host’s personal subscription; session claims cannot override it. Other accounts can join by invitation. Workspace membership and Calendar permissions still apply when creating or scheduling a meeting. A room lasts up to two hours for eligible Free hosts, ten hours for Plus, and twenty-four hours for Pro/Enterprise. The server resolves the host’s personal subscription in both Meet and the workspace token endpoint. The deadline starts with the first admitted participant; reconnects keep it. Signed room limits default to eight publishers and 96 viewers and may be lower. Server hard ceilings still apply to every role and tier. Each publisher can register at most six media tracks. The server accrues participant-milliseconds through the room deadline. Expiration ends the room, disconnects clients and persists outstanding SFU closures before provider cleanup. Failed cleanup retries with exponential backoff from ten seconds to one hour. Progress is persisted after each completed provider session so a later failure does not repeat already-confirmed work. Unconfirmed tracks remain recorded for retry/operator reconciliation. The UI shows the room deadline and explains resource-limit failures. Browser bandwidth counters are diagnostic, not billing evidence. Monthly workspace media aggregation and provider invoice reconciliation remain prerequisites for selling meeting overages. Internal or sponsored workspaces do not bypass these ceilings.

Pieces

room.ts is a pure reducer: it returns the next room snapshot plus the messages to fan out and any Cloudflare work to perform. Both transports run the same reducer, so the Bun server and the Durable Object cannot drift. The app secret never reaches a browser. The client receives only a short-lived HMAC join token; the room server performs every SFU call on its behalf.

Running a call locally

Create an SFU app under Cloudflare dashboard → Realtime → SFU and put the values in apps/meet-realtime/.dev.vars (gitignored). See the apps/meet-realtime README for variable names.
Start the room server and the app, then open a meeting and press Join call:
Point the app at it with NEXT_PUBLIC_MEET_REALTIME_URL, which defaults to ws://127.0.0.1:8786/realtime.

Verifying without a full environment

Two checks run against real Cloudflare without needing Supabase, auth or seed data. Signaling and host controls — boots the room server, connects a host and a guest, and exercises the lobby, chat, hand raise, force mute and removal:
Media — bundles the real client modules, opens two browser peers and proves audio and video traverse the SFU. Tracks are synthesised from a canvas and an oscillator, so no camera or microphone permission is needed:
Then open http://127.0.0.1:7898/?peer=a and ?peer=b. Each tab reports bytesReceived and framesDecoded, and renders the other peer’s video.

Two contract details worth remembering

Both of these were found only by running against the live API, and both produce confusing errors if reintroduced:
  • Read transceiver.mid only after setLocalDescription(). It is null before that, and publishing without it fails with 406 tracks[0]: Missing mid in track.
  • Do not send a sessionDescription when pulling remote tracks. Cloudflare returns the offer, which the client answers via sfu.renegotiate. Sending an empty SDP is rejected as a malformed event.
Creating a session with no local offer must also send no request body at all; {} and {autoDiscover: true} are both rejected.

Attributing remote tracks

An inbound track arrives with an opaque id, so nothing on the RTCPeerConnection says who published it. Attribution comes from the track name, which is deliberately <deviceIdentity>-<kind>: the subscribe response returns a mid per track, the client maps mid → userId (the device identity, not the account ID), and the track event is matched against that map. This is load-bearing. Keying inbound streams by the browser’s track id instead means the UI cannot tell participants apart, and every tile ends up rendering whichever stream arrived first. One MediaStream is accumulated per participant, since their audio and video arrive as separate tracks but belong to one tile. The map and the streams are both cleared on reconnect, because mids are only meaningful within a session.

Reconnection

Signaling drops are expected: servers restart, networks blip, laptops sleep. The client reconnects with exponential backoff and full jitter, so a room server restart does not produce a synchronised retry stampede from every participant. Two details make it actually work:
  • Each attempt fetches a fresh join token from Meet’s Cloudflare-local POST /api/meet-call/<meetingId>/token, preserving the current device identity. Join tokens are short-lived and calls are not, so reusing the original token would let a client reconnect for ten minutes and then fail forever on “Reconnecting…”. Membership is re-checked on every refresh.
  • A reconnect rebuilds room state. The client re-announces itself with presence.join and clears its subscription ledger and peer connections, so every remote track is pulled again. Without that it would rejoin a room it could not see.
A clean close (1000) and a host removal (4403) are decisions, not blips, and are never retried. Media survives a signaling drop: the peer connections to the SFU are independent, so audio and video keep flowing while signaling is down.
That check stops the room server mid-session and asserts the client recovers, mints a new token for the retry, and stays closed when the close was intentional.

Meeting codes

Every meeting has a shareable code and a /r/<code> link, surfaced by the Copy invite button in the call header. The code is a reversible Crockford base32 encoding of the meeting id rather than a stored random string, so there is no schema change, no collision risk and no lookup table — an invalid code is rejected before any query runs. Decoding tolerates case, spaces, dashes, and the I/L/O/U characters Crockford omits, folding them onto the digits the sender meant. Only signed-in accounts with a verified @tuturuuu.com address can create online meetings. Other signed-in users can follow an invitation and ask the host to admit them. Sign-in preserves the room link, and an existing profile display name is reused. A missing name is saved to the profile before joining.

Call controls and admission

The sending and receiving arrow badges open connection details. The displayed latency is the selected SFU connection’s round-trip time, not a measurement to another participant. Unavailable measurements show a dash. A device stays busy until its SFU publication and peer connection are ready; a local preview alone does not prove another participant receives media. Choose automatic, grid, spotlight or sidebar layouts. Each camera and shared screen can be focused independently or opened fullscreen. Videos fit their frames without cropping. Fullscreen uses a viewport-sized fallback when the browser denies native fullscreen. Camera color filters and soft portrait processing run locally and affect the transmitted video; no image is sent to an AI provider for these effects. Soft portrait is subtle whole-frame smoothing and lighting, not face-aware cosmetic placement. Reactions are temporary and rate limited. The host’s People panel contains persistent approved participants. Admission adds a signed-in identity to this room’s approved list, so reconnects and later visits bypass the lobby. Forgetting approval requires admission on the next visit; removing a current participant also removes their saved approval. Transcript and notes sharing is private by default. The host can enable it for admitted and approved participants, including guests outside the workspace. Every non-host notes read checks the signed room policy on the Durable Object; removing permission blocks subsequent reads. Existing downloaded information cannot be recalled. Transcription mutations remain host-only. Leaving stops local capture and signaling immediately while recording/notes finalization continues on the post-call screen. The host can leave alone or end the room for everyone. Ending is durable, disconnects participants and prevents rejoining that room. Approved participants can still read shared post-call notes while the host’s sharing setting permits it. Room policy and approval state live in Cloudflare Durable Object storage; no self-hosted server or new database migration is required.

Missing microphone or camera

If enabling a device reports that none was found, the browser returned NotFoundError before media reached Cloudflare. Connect an input device or use a browser that can see it. Permission denial and a device that cannot start are reported separately; check browser/system privacy settings for denied access, and other apps using the device for startup failures. Repeated activation uses one notification per device rather than stacking identical warnings.

Recording

A host can record from inside the call. The control creates a row in recording_sessions through the existing /record endpoint, captures local audio with MediaRecorder, uploads through the existing /upload endpoint, and broadcasts recording.state so every participant sees the indicator. Because it reuses those endpoints, a call recording is indistinguishable from one started on the meeting page and feeds the same transcription pipeline.

Cloudflare hosting

Meet’s frontend is packaged with OpenNext and runs on the tuturuuu-meet Worker. Realtime runs on tuturuuu-meet-realtime, with one Durable Object per meeting. Browsers send audio, camera and screen media directly to Cloudflare Realtime SFU. Neither Worker requires an internal host, Docker or a Tunnel. Shared platform authentication and APIs remain at tuturuuu.com, with the existing managed Supabase database; this does not move platform API ownership. apps/meet/wrangler.jsonc configures frontend assets, image optimization, self-reference and the tuturuuu-meet-cache R2 bucket. apps/meet/open-next.config.ts configures the incremental cache. Build with bun run build:cloudflare from apps/meet; use NEXT_WEBPACK_BUILD=1 for the existing local Webpack fallback. The normal CI build uses Turbopack. bun run preview:cloudflare runs the built frontend in workerd for runtime verification. Run bun run cf:typegen after binding changes and bun run cf:typecheck to validate the generated environment with Worker types separately from browser types. CI runs this check after building. The frontend requires NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY, SUPABASE_SECRET_KEY and MEET_REALTIME_TOKEN_SECRET. Configure a separate app-coordination signing secret only if the platform uses one. Public Supabase values are also build inputs; private secrets belong only in Worker runtime configuration. Realtime requires its dedicated token secret and the two Cloudflare SFU credentials from its README. Direct Worker verification requires a wss:// endpoint; ws:// is accepted only for loopback development (localhost, 127.0.0.1, or [::1]). Endpoints must not include credentials, a query, or a fragment. Connected participants get a ten-minute heartbeat grace period for throttled tabs; the Worker closes and removes silent connections after that bound so ghost participants cannot persist. All call token issuers and verifiers must share MEET_REALTIME_TOKEN_SECRET; Supabase service keys are not used for call tokens. .github/workflows/meet-cloudflare.yaml validates both Workers and deploys realtime before the frontend on production. It uses MEET_CLOUDFLARE_API_TOKEN, or the existing account deployment credential COLAB_CLOUDFLARE_API_TOKEN while bootstrapping. The deployment token needs Workers Scripts:Edit and Workers R2 Storage:Edit on the Tuturuuu account, plus Workers Routes:Edit limited to tuturuuu.com. Store the dedicated token in the meet-production GitHub environment, restricted to the production branch. A Worker-only token can upload signaling but fails when OpenNext populates the R2 cache, so verify the complete frontend deployment and authenticated smoke check. Automatic Vercel Meet production deployment is disabled in the CI switchboard. Preserve the Vercel deployment until the canonical Cloudflare sign-in and call checks pass so rollback remains available. Before cutover, deploy the frontend with an explicit verification configuration that has no custom-domain routes. Verify public rendering and API guards there, then deploy the checked canonical configuration. Sign-in returns to the registered canonical hostname, so complete signed-in navigation, API proxying and an authenticated two-participant call after cutover. Do not change DNS until both Worker versions and runtime secrets are verified, and confirm the active versions after deployment. For a direct Worker protocol check, set MEET_CHECK_REALTIME_URL to its WebSocket endpoint when running integration-check.ts. The same variable works with media-check.ts --call-controller; localhost then hosts only the test page, not signaling. The check uses a fresh synthetic room and performs no database meeting writes. A health response alone cannot detect broken SFU calls. Native fetch must retain its global receiver in Workers. The SFU client binds globalThis.fetch to globalThis; calling an unbound native fetch as an object method throws Illegal invocation, even though Bun-based checks pass.

URL shapes

Meet matches the other satellites: workspace routes sit directly under the workspace segment, with no /workspace prefix. plans and r are static segments, which is what frees the bare /<wsId> position — public plan pages previously occupied it, so /<wsId> and /<planId> could not coexist. Every retired shape is permanently redirected in proxy.ts: /workspace/*/*, /call/<wsId>/<meetingId> and /join/<code>/r/<code>, and a bare 32-hex segment → /plans/<planId>, so shared plan links keep working.

Hosting eligibility and call verification

The meetings POST handler checks the creator’s current confirmed authentication email and personal subscription. Verified Plus, Pro, and Enterprise accounts can host regardless of email domain; verified @tuturuuu.com accounts remain eligible on Free. Workspace membership is required, and Calendar scheduling additionally requires Calendar permission. Both instant creation and scheduled creation use a trusted server writer only after these checks; the actor is always server supplied. The older restrictive direct-client INSERT policy remains in place. No production migration is needed to unlock paid hosting through the application API. Existing workspace members may still join calls regardless of their email domain. Signed-in invitees outside the workspace can join a meeting whose creator is an eligible verified host. They wait in the lobby until the host admits them; the invite grants no workspace membership, recordings, or archived transcript access. Guests see a transcription capability notice before joining and in the call. Meeting-only access is rechecked by the Cloudflare-local POST /api/meet-call/:meetingId/token route whenever the call reconnects. The creator must still belong to the workspace to receive host privileges. Anonymous invite navigation preserves /r/<code> (including locale) through the central login and token verification handoff. The call page resolves invite access directly rather than redirecting non-members through a workspace dashboard. If a login seems to lose the invite, inspect the nested returnUrl/nextUrl chain and the call authorization separately. Signed-in root navigation opens online meetings. Hosts can start an instant meeting, and members can enter a room code or paste a same-origin room link. Legacy UUID room links remain accepted. The client keeps an audio playback element mounted when cameras are off, selects the screen track while someone presents, and restores the camera after a share ends. Publishing uses stable senders with replaceTrack for device toggles and share restarts. Subscribe negotiations are serialized and retried because track announcements can arrive before the SFU has finished publishing. A ten-second heartbeat keeps presence alive. Reconnect refreshes the token, preserves the member’s speaker role for calls, and republishes enabled media. To test the actual React call controller and participant tile against the live Cloudflare SFU, run the local-only harness:
Open http://127.0.0.1:7898/?peer=a and http://127.0.0.1:7898/?peer=b in separate tabs. Peer A is a host and B is a speaker. The harness supplies synthetic audio, camera, and screen sources; it does not capture the desktop or microphone. Verify nonzero received audio energy, zero energy while muted, decoded video frames, screen-to-camera transitions, two-way chat, host mute, and reconnect. Stop the harness after testing. It uses scratch room identities and does not create application database records. This proves the media path through the real SFU, but does not certify production authentication, OS device permissions, physical devices, or restrictive corporate networks. Those require additional checks on the canonical deployed app. The restrictive insert policy protects authenticated direct database writes. Satellite app-session requests use a service-role client, so their creation gate is the API check against the current confirmed email fetched from Supabase Auth; service-role requests bypass RLS. Neither path trusts handoff or user-editable profile metadata for the creation domain. The policy is not an additional RLS layer on service-role inserts. If a browser blocks remote autoplay, use Play meeting audio. Any keyboard or pointer interaction also retries playback. The controller harness deliberately rebuilds its client bundle on each reload so ongoing controller edits are exercised without restarting its signaling room. Presence stays active while its Durable Object WebSocket is open, even when a background browser delays heartbeats beyond the normal expiry window. Close/error handlers remove disconnected participants. The direct Worker integration check includes a 40-second idle interval to verify presence and host controls survive browser timer throttling. The meet-production GitHub environment must allow only the production branch and contain MEET_REALTIME_TOKEN_SECRET matching the Workers and platform API. Deployment checks required Worker secret names before rollout, then runs the signed-token realtime/SFU integration check against the canonical endpoint.

Gemini transcript and post-call notes

Meet’s own Cloudflare frontend Worker serves /api/meet-ai/:wsId/:meetingId and its /chunks endpoint. These routes do not depend on a self-hosted recording server. Configure GOOGLE_GENERATIVE_AI_API_KEY as a secret on tuturuuu-meet; without it, the panel reports that Gemini is not configured. Do not put the key in public environment variables or the browser. Existing deployment credentials do not grant permission to transfer a Gemini key to a new destination. Apply 20260907040000_meet_ai_sessions.sql through the normal database release process before enabling the feature. A missing schema produces a visible unavailable state without breaking the call. Browser reads require workspace membership; only the meeting creator can start transcription, submit audio, or finalize notes. The server independently verifies the signed Meet app session, workspace membership, meeting ownership, and same-origin mutation requests. The host opens Transcript & notes, tells participants that audio will be sent to Gemini, then starts live transcription. The panel and call header show an active-session indicator to members. The browser mixes its current microphone track with received participant audio; it does not request another microphone or capture unrelated desktop audio. Muted tracks contribute silence. AudioWorklet emits independent mono 16 kHz WAV chunks about every ten seconds, skipping silence. Transcripts arrive after Gemini processes each chunk. This is chunked live transcription, not word-by-word captions, and overlapping speech is not assigned to guessed speaker identities. Stop & generate notes flushes pending audio and generates a summary, decisions, action items, and open questions. Leaving through the call’s leave control does this automatically for the capturing host. Keep the page open while it finishes. If finalization fails, leaving still works and notes can be recovered from meeting details. Abrupt tab closure or a browser crash can lose the current unsubmitted chunk; return to meeting details and finish the active session to generate notes from saved text. Interrupted or failed captures are identified as incomplete. A stuck notes attempt becomes eligible for manual recovery after two minutes; this can incur another provider charge and the earlier unknown usage remains visible in statistics. Notes are AI generated and should be reviewed. Only text, notes, and provider usage are retained in Supabase, not audio files. The capture queue holds up to 60 chunks in browser memory and retries each stable chunk ID for up to five minutes. A recovery status shows pending chunks. Tab closure still loses buffered audio; keep the page open until uploads finish. Overload and unrecoverable chunks remain explicit gaps. Sessions retain the three-hour/1,080-chunk limit. Apply 20260909153000_meet_transcription_recovery.sql before enabling server retries: row locks and attempt leases prevent overlapping provider calls and stale writes. Up to five provider attempts are allowed; uncertain prior costs remain unpriced. The default model is gemini-3.1-flash-lite. Costs are informational estimates in USD, computed on the server from Gemini usage metadata and a dated price snapshot: text input 0.25,audioinput0.25, audio input 0.50, and output including thinking $1.50 per million tokens (standard API pricing checked September 7, 2026). The panel separates transcription and notes costs, submitted audio minutes, and input/output tokens. Missing provider usage and interrupted attempts are shown explicitly; totals are not invoice totals and do not debit workspace AI credits. Review Google’s pricing when changing the model or rates. Never log raw audio, transcript text, or credentials. Validation: use the Meet AI API/audio tests, AI package usage tests, a real browser AudioWorklet check, and the disposable database runner with --test supabase/tests/meet-ai.sql --typegen packages/types/src/supabase.ts. The disposable runner copies Git-tracked files; new migrations and tests must be tracked before running it. Run the Meet Cloudflare build before deployment and verify a signed-in live transcript plus persisted notes against Gemini afterward.

Production upload and provider diagnostics

If an audio chunk is rejected before reaching the Worker, inspect the request’s Ray ID in Cloudflare Security Analytics. Binary WAV uploads can trigger OWASP 949110: Inbound Anomaly Score Exceeded. Meet has a logged managed-rule exception (6bcb3c2398ca4f2896222b76bc1ae9b2) before the managed rulesets, skipping only rule 6179ae15870a4bb7b2d480d4843b323c in the OWASP ruleset for this expression:
Keep the other managed rules active. The Worker still checks the signed actor, meeting ownership, same-origin request, byte limit, canonical WAV format, and atomic usage reservation. Do not replace this with a domain-wide WAF bypass. Provider failures return a safe 502 response. Worker logs contain only a fixed failure category and provider HTTP status, never provider messages, request URLs, credentials, audio, or transcripts. Use those categories to distinguish invalid keys, disabled APIs, quota, access, location, timeout, and runtime failures. An accepted chunk is not automatically resubmitted to the provider; preserve its unknown-cost record when investigating a failed attempt. Google’s request logs identified the production synthetic failure as 412 FAILED_PRECONDITION: User location is not supported for the API use. The Meet frontend Worker therefore uses placement.region: aws:ap-southeast-1 to run near Singapore, which Google lists as a supported Gemini API region. Signaling and media remain on the separate globally distributed realtime Worker and Cloudflare SFU. This is a Cloudflare placement hint, not an AWS deployment or a strict geographic guarantee; verify the provider path after each release. See Cloudflare placement and Gemini supported regions. Missing keys are classified as missing_configuration and return a safe 500; provider failures return a safe 502 with fixed-category diagnostics.

Media connection readiness

SFU_HTTP_425 means the SFU rejected an operation before the session was ready. After the first offer/answer exchange, subsequent publish and subscribe operations wait for the browser peer connection to reach connected (up to 12 seconds). SFU_CONNECTION_TIMEOUT and SFU_CONNECTION_FAILED distinguish a stalled or failed transport from device permissions. Rejoining creates fresh peer connections. Receiving a successful SDP response alone does not prove playback: the client reconciles negotiated receiver tracks with participant ownership and only marks live, attached tracks subscribed. This also handles receiver reuse without another browser track event. Production verification should include late guest admission, rapid microphone/camera activation, and media flowing in both directions.

Muted tracks and media reconnection

Cloudflare expires tracks after 30 seconds without media packets. Keep muted microphone/camera tracks attached with enabled = false, which sends silence or black frames, so unmuting resumes the existing subscription. Detaching a sender with replaceTrack(null) silently invalidates that track after the timeout. Stopped screen shares are explicitly closed at the SFU with force: true, removed from the publication ledger, and published afresh when sharing resumes. Media connectivity is independent of the signaling WebSocket. Meet rebuilds a failed peer after one second, a disconnected peer after ten seconds, or an unfinished connection handshake after twenty seconds. Brief disconnections that recover within the grace period retain the existing connection. Publisher recovery republishes enabled sources; subscriber recovery clears its ledger and pulls the current room tracks again.

Diagnosing media on another device

Open Connection in the call header to inspect outgoing and incoming media transport states and packet counts. Reconnect media rebuilds both media connections without leaving the room. Copy diagnostics includes only transport states, aggregate media counters, and receiver track states; it omits SDP, IP addresses, track identifiers, participant names, and authentication data. A working local camera preview proves capture succeeded, but does not prove other participants can receive it. If enabling a device shows a transmission warning, record its diagnostic code and the action that triggered it. SFU_HTTP_* identifies an upstream SFU rejection; SIGNALING_* identifies the signaling connection or request timeout; RTC_* identifies a browser peer-connection failure. The code contains no raw provider message, SDP, credential, or meeting content. A successful retry clears the previous warning for that device. Reproduce on the affected device before changing transport settings; synthetic sources in another browser do not establish physical-device compatibility. Browser audio probes should attach the received stream to a playing media element before measuring decoded audio energy; packet counts or an unattached analyser alone do not establish working playback.

Participant names and admission

Meet reuses the participant’s saved profile display name. If it is missing, the invite page suggests an available account name and asks the participant to confirm or edit it before opening the realtime session. It saves the name through the current-user profile API, and refreshes the same invite. Later joins reuse the saved display name. A failed save leaves the form open for retry; a temporary profile lookup failure does not invite the user to overwrite an existing name. The lobby separates connection progress from waiting for host admission. Guests can leave while waiting, and their preview is not published until they join. Host admission actions are labeled, and a muted participant has one mute status indicator rather than an additional redundant mute action.

Cloudflare browser integrations

Meet must not mount Vercel Analytics or Speed Insights: their /_vercel/* script endpoints are not hosted by the Cloudflare Worker. A 404 for those scripts is separate from SFU media delivery. The platform user-config API uses the shared satellite app-session audience list in apps/web/src/lib/api-auth-audiences.ts. Keep Meet in that list and in the Rust current-user session targets. A successful local Meet call-token refresh does not verify authorization for proxied platform APIs such as SHOW_VERSION_BADGE. A recovered publisher announces a new SFU session for its stable microphone, camera, or screen track name. The room retires older registrations for that participant and track and broadcasts track.closed before the replacement track.published, so listeners discard obsolete receivers. Snapshot consumers also deduplicate old session registrations left by earlier Worker versions.

SFU commit boundary

Both realtime transports use MeetCommandExecutor. SFU operations are serialized per participant, while other participants and leave/end controls stay responsive. A track registration or closure is persisted and broadcast only after Cloudflare returns success without per-track errors. The completed operation is applied to the latest room snapshot so concurrent host settings are preserved; a participant who left during the request cannot be resurrected. Closing a track requires the registered owner to match the signed caller. Recording finalization targets the specific session with an idempotent status update, never another toggle. SPA unmount and leave share the same finalizer. The post-call navigation buttons wait for finalization while capture and signaling are already stopped. A hard browser/process termination can still interrupt an upload; do not claim a saved recording without its storage receipt.

Screen audio and in-call controls

Screen sharing requests audio separately from the microphone, including browser hints for window and system audio. The browser picker remains authoritative: capabilities depend on browser, operating system, and the chosen source. Select Share audio when offered. The in-call badge reports whether captured audio is available; a video-only capture still works. Remote screen tiles play shared audio, and muting the microphone does not mute shared audio. Stopping screen sharing stops both its audio and video tracks. The host’s shared audio is included in the existing consent-controlled transcription mixer. The host can click the meeting title to rename it in a dialog, without changing the meeting time or calendar linkage, and hover or keyboard-focus a participant’s name to mute their microphone. Touch layouts keep the action visible. Participants cannot remotely unmute someone. Raised hands use a prominent orange tile badge and outline; reactions use larger, labeled overlays with reduced-motion support. Transcript and Notes have separate tabs, with expandable usage and session notes. Receiving badges distinguish an enabled remote device from an arriving track. A connected SFU receiver with a live track is not proof of packet delivery. Meet checks receiving counters with a 20-second grace period and rebuilds only the subscriber when an expected stream never delivers packets or remains muted and stalled. Automatic rebuilds are limited to one per minute. Missing statistics, normal silence/DTX, and a static screen with an unmuted track do not trigger this recovery. This is a recovery mechanism, not evidence that an affected physical network or device is fixed; verify each audio/video direction on those devices. Packet counters that are unavailable or invalid remain unknown rather than being counted as zero. The tile indicator distinguishes unknown packet statistics from confirmed receiving and waiting states. Ended tracks are excluded from stall recovery. Persisted title announcements are acknowledged by the room and retained across signaling reconnects until delivery; intentional leave clears that outbox. A browser can keep its signaling socket in CLOSING while waiting for a remote close handshake. Removal, admission denial, and meeting-end messages stop retries immediately, before the delayed policy close event arrives. Meet watches that state and retries after a five-second grace period instead of waiting for the browser’s longer timeout. Events from the old socket cannot change the replacement connection or settle its pending requests. Intentional leave and promptly received policy-close codes still stop retries.

Media timeout and managed TURN fallback

SFU_CONNECTION_TIMEOUT means ICE/DTLS did not connect to the media service. A working local microphone preview does not prove that audio can reach the SFU. Meet now obtains expiring Cloudflare TURN credentials with each admitted SFU session and installs them on both publishing and receiving peer connections before negotiation. UDP, TCP, and TLS on port 443 provide alternative network paths; no self-hosted relay is required. Port 53 is filtered because browsers restrict it. Direct connections remain eligible, avoiding unnecessary relaying. Create a dedicated Meet TURN app in Cloudflare and install only CLOUDFLARE_TURN_KEY_ID and CLOUDFLARE_TURN_API_TOKEN in the tuturuuu-meet-realtime Worker before deploying this change. The permanent key never reaches the browser or logs. Only authenticated, admitted participants can create sessions and receive the 24-hour credentials. Each replacement session receives fresh credentials. Missing configuration or generation failures log a fixed warning and preserve direct ICE connectivity. Production checks still fail if relay credentials are unavailable, so degraded coverage stays visible. To verify restrictive-network behavior, run the isolated call-controller harness against the production Worker and open peers with &relay=1. Confirm a selected relay candidate, incoming audio energy and decoded video in both directions; repeat with &relay=tls to allow only TLS/443 and rule out direct UDP success. Synthetic media avoids recording real microphones or cameras. This does not substitute for checking previously affected physical devices.

Audio works but a remote camera stays blank

The receiving watchdog also handles video tracks with no initial RTP report when another incoming track proves that the browser exposes inbound statistics. WebRTC may create an inbound report only after the first packet, so an absent video report is not always an unsupported-statistics case. A live, muted video track with enabled remote camera and working sibling inbound stats gets a 20-second grace period before subscriber recovery. With no usable inbound statistics at all, the watchdog continues to avoid guessing. Video that receives packets but reports zero decoded frames also gets a separate 20-second first-frame deadline. Incoming byte growth does not reset this deadline. A successfully decoded static camera or screen does not trigger this condition. Disabling media, replacing a publication, or ending its track clears the deadline. Recovery rebuilds the subscriber connection while preserving local publishing and room membership, using the existing one-minute recovery throttle. This corrects missed recovery cases; it does not identify a codec or network fault on a device that has not been inspected.

Decoded video with an avatar-only tile

If incoming diagnostics show decoded video frames and a live, unmuted receiver, inspect presentation before resetting the SFU. Tile readiness uses a React external store so an unmute event between rendering and listener installation cannot leave an old avatar visible. Audio and muted video have separate media elements; camera replacement preserves the audio element’s stream. Playback retries on metadata and can-play events after a replaced stream interrupts play(). The incoming-video retry action preserves local publishing. Rejected subscription responses and stale publisher sessions cannot take ownership of a replacement receiver. Unexpected socket closure retains presence and SFU tracks for a 30-second grace period. A resumed device keeps its media connections; intentional leave still releases them immediately.

Call alerts and post-meeting privacy

Join, admission-request, chat and raised-hand changes display actionable in-call notifications. Short sound cues are unlocked by browser interaction, rate-limited, and can be muted from the header. Initial snapshots, reconnect snapshots and the participant’s own actions do not produce notification bursts. Live notes sharing and post-meeting sharing are separate room settings. Both are disabled by default. The host may enable post-meeting access for approved people from the transcript panel or centered ended-meeting screen. Ended invite/detail pages perform a signed read-only room-state check and never open a call socket. Notes open on demand with transcript and notes tabs; there is no join button for an ended room. The authenticated host-only sharing mutation reaches the room Worker over signed HTTP and also works after its WebSocket has closed. AI read responses use explicit field projections. Non-admin readers receive no raw provider usage and null cost/token fields; cost totals and breakdowns render only for the room admin. Sharing permission is rechecked on every notes request.

Room settings, recordings, and devices

The in-call settings dialog contains audio/video input, supported audio output, connection diagnostics, notification sounds, and admin-only cost breakdowns. Changing an input replaces its track on the existing call. Output selection uses HTMLMediaElement.setSinkId where supported; unsupported browsers explain the limitation instead of presenting a non-working selector. Each browser call generates a device ID. The server derives a signed device identity from the account and device IDs; media and SFU sessions use that identity, while admission approvals use the account ID. Joining another device offers a switch or an additional connection. Switching only removes that account’s other connections. Keep microphones or speakers muted on nearby devices to avoid echo. Recording permissions start disabled for participants. Admins can independently share saved recordings and allow participants to start/stop recording. The Durable Object holds one recording lease for the room; a remote stop cannot be overwritten by a delayed start. The recording device composites its available video and mixes audio locally, and must remain available until saving completes. Recordings stop at 55 MiB and upload directly to the creator’s personal Drive using a scoped signed URL, followed by server-side metadata verification. Failed saves offer a local copy. This is browser capture, not a Cloudflare server-side recording service.

Chat and Mira

Room chat supports Markdown, avatars, and up to five 25 MiB attachments per message. Chat saving defaults on; admins can stop retaining new messages in Settings without deleting earlier messages. The recent 500-message snapshot survives Worker eviction; chat retries reuse a stable message ID to avoid duplicates after lost acknowledgements. Files use the creator’s personal Drive provider and storage capacity checks. Room admission controls attachment access; a signed URL is short-lived. Mention @Tuturuuu or @ttr (case-insensitive) to ask Mira using the recent 40 room messages. Uploaded file contents are not automatically read by Mira. The mention author’s personal AI allowance is checked and reported tokens are deducted from that author’s quota. Mira uses the plan-default Google language model for checks, generation, and deduction. It has current-time, admitted-participant, and Google Search tools; web replies include source links. The browser supplies the requester timezone, and participant counts distinguish people from multiple devices. All generation steps and reported search queries count toward the requester quota. Search cost estimates use paid list rates before project-wide free allowances; missing provider usage or grounding coverage stays incomplete. Cost estimates use its catalog prices and reported cached input; missing cache prices or tiered pricing remain incomplete. Requests are deduplicated by chat message ID. The internal server-service token scope is never included in browser join tokens.

Bandwidth and cost attribution

Balanced mode caps camera sending at 900 kbps / 24 fps and screen sharing at 1.8 Mbps / 15 fps. Data saver lowers camera resolution and frame rate, while screen sharing retains text resolution at a lower frame rate. Higher-quality camera mode allows 1.6 Mbps. These are ceilings, not promised usage or minimum bandwidth. Speech has a separate 48 kbps ceiling; screen audio uses 96 kbps. Video shares the available outgoing bandwidth estimate with a reserve for audio. Congestion reduces quality promptly, while recovery requires four measured healthy samples to avoid oscillation. Missing statistics never trigger an upgrade. Video pauses when its share of estimated capacity is too small after reserving speech bandwidth. Sending pauses when the participant is alone and resumes when an audience joins. Sender parameter failures preserve the working transport rather than forcing a reconnect. Initial encodings are capped before negotiation. Admins see client-reported received RTP bytes and server request counters in settings. Estimates use SFU pricing and Durable Object request pricing, checked September 8, 2026, before account-wide allowances or discounts. TURN-to-SFU traffic is not charged twice. Reports are cumulative and deduplicated across samples. Per-device accounting retains at most 4,096 identities per room; reports beyond that limit are marked as incomplete rather than growing storage without bound. These are partial usage estimates, not invoices. RTP payload counters omit transport overhead and reports can miss disconnected devices. Worker CPU, Durable Object duration/storage, R2 operations/storage, and shared plan charges are not yet attributed per room and are shown as unallocated. Reconcile against Cloudflare account billing before using these estimates as a complete budget. Unknown AI costs are shown as missing coverage rather than zero-cost requests. Merely reading a cost panel does not force a Durable Object snapshot write.

Diagnosing call preparation

The centered Preparing your call screen checks the account’s device session before opening the lobby. Its request is POST /api/meet-call/<meetingId>/token; a failure here is distinct from a media or WebSocket connection failure inside the call. The browser bounds each preparation request to 12 seconds and retries transient failures twice. Switching an existing device is not automatically retried, because it can disconnect that device. The token handler checks the realtime Worker’s /room-device endpoint with a five-second timeout. An unavailable upstream returns HTTP 503 with MEET_REALTIME_UNAVAILABLE and Retry-After: 2, without bypassing device policy. Use the request status and Cloudflare Ray ID to distinguish an edge block from an unavailable Worker; a successful /health check alone does not prove the authenticated device endpoint works. Never collect cookies or bearer tokens. Cloudflare’s Meet-only managed-rule exception skips cookie-character checks 942420 and 942421 for GET and POST requests to meet.tuturuuu.com. Other rules and request-body inspection remain active, and exception matches are logged. Inspect the contributing rules before changing this scope; do not disable an entire ruleset to resolve a preparation failure. After the lobby opens, a WebSocket that remains in its initial connecting state for ten seconds is replaced through the normal bounded reconnect policy. Late events from the abandoned socket cannot replace the current connection.

Room policy verification

bun apps/meet-realtime/src/room-controls-check.ts verifies lobby chat privacy, one room-wide recording, private recording/cost access, and same-account device switching against the Worker. Set MEET_CHECK_REALTIME_URL and MEET_REALTIME_TOKEN_SECRET; the default endpoint is local port 8799. The check uses a fresh synthetic room, ends it after success, and never uploads files or calls Gemini. The production deployment workflow runs this after the existing signaling/SFU check. Abandoned Mira requests release their per-account pending limit after two minutes; late provider costs can still be recorded.

Assistant identity and panel interactions

Mira replies carry a server-only assistant marker and reserved sender identity. The Nova avatar and official badge require both; choosing the display name Mira does not grant either. Standalone @Tuturuuu and @ttr mentions (case-insensitive) open the assistant mini profile on hover, keyboard focus, or tap. Code and existing Markdown links are not rewritten into mentions. Paragraphs containing raw HTML are also inert for assistant requests and highlighting. Chat message toasts are suppressed while chat is open; admission and other call alerts remain active. Desktop chat, participants, and transcript panels have a draggable separator. Focus the separator and use left/right arrows to adjust it with a keyboard. The meeting settings button is last in the call header. Returning from the post-call screen invalidates cached meeting lists and room state before navigation, including personal-workspace aliases, so newly created meetings are fetched.

Private workspace tool reviews in Meet

When composing an assistant mention, choose the workspace for task, calendar, finance, and time-tracking tools. Personal workspace is the default; billing always uses the requester personal AI allowance. Meet reuses the platform tool definitions and executors, checks membership and permissions on every generation, and only sends detailed schemas for selected tools. Workspace tool calls require the requester to review and approve the exact inputs. Their results remain in a private Mira review; Share with meeting is a separate explicit action. Other participants cannot read, approve, or share those drafts. Draft continuations remain server-side in the room Durable Object and are omitted from room broadcasts and browser review responses. Revisions and an execution claim prevent duplicate approvals from repeating a mutation. If a request remains in an uncertain executing state, do not repeat it; inspect the platform result first. Failed or timed-out reviews can be discarded without making their actions executable again, including after the room ends. Web search runs in an isolated request containing only the explicit question; model-supplied query rewrites and recent room history are not forwarded. Questions containing known participant names or room-title terms are blocked at that boundary. Missing grounded sources produce an unavailable-search result. Web search is disabled while continuing private workspace results. Unsupported platform-only tools such as E2EE key setup, file conversion, and client UI controls are not exposed in the Meet tool set. Verify private reviews with the production React compiler enabled: let a review arrive inline while its detail query is still loading, then deny the action. The list can load before the detail query, so callback inputs such as the revision must remain guarded during that interval. An uncompiled development fixture alone does not cover compiler-generated callback dependency reads. Private reviews appear inline beneath the request that created them. Approval, denial, and the separate sharing decision stay requester-only; orphaned reviews whose request has left the retained chat history appear at the end of the thread. Provider citation IDs are resolved only against their exact returned sources. Older unmapped references show an unavailable-reference badge rather than a guessed link, and the source footer is displayed as an expandable list of source cards. Source metadata is limited to eight URLs and 4,000 characters within the 16,000-character message limit; oversized prose is safely truncated. Empty final synthesis reuses only an available grounded public-search answer, without another paid call. Otherwise generation fails visibly after incurred usage is accounted for; pending approvals stay private. Legacy sources-only replies display an incomplete-answer notice. Sources alone never count as a completed answer.

Fast return to ended meetings

Meeting cards remember an observed ended state on the current browser, scoped to its signed-in account. Only meeting IDs and observation timestamps are retained, for up to 30 days and 256 rooms per account. Returning from an ended call updates this hint immediately, so known ended cards do not queue another room-state request or poll the realtime Worker. Active and unknown rooms still refresh. The cache is only a display hint: opening notes or transcripts always performs server permission checks. No room names, chat, transcripts, or access grants are stored in this cache, and unavailable browser storage does not block navigation.

Echo and microphone processing

Settings → Devices exposes native echo cancellation, background noise reduction, and automatic voice leveling. All three default on. Changes apply to the current microphone and subsequent device switches in that call, without unmuting it. The status under each control comes from the captured track’s getSettings(); a requested setting is not proof that a browser or device applied it. Screen capture excludes whole-system audio and requests restrictOwnAudio to avoid returning meeting playback to peers. These are browser hints, so browser compatibility still matters. Share a browser tab with audio when presenting media. Each remote camera or screen feed also has a local mute control: it silences only that feed on the listener’s device, without changing room participant permissions. For persistent echo, identify the participant or shared-audio feed returning the sound. Enable echo cancellation on the source device, use headphones, or mute additional microphones and speakers in the same physical room. Native processing cannot guarantee cancellation across multiple nearby devices. Verify actual speaker/microphone calls separately from synthetic audio and unit tests.

Nearby participants and same-room audio

Meet automatically pauses the later device’s microphone when the same verified account already has an active microphone in the call. For different accounts, a local audio monitor looks for repeated matching speech dynamics and changing frequency patterns between existing microphone tracks. Only the later device backs off, using join time and device ID as a stable tie-breaker. This avoids two devices muting each other. The microphone is disabled before a compact audio-control popover suggests Share nearby audio. Closing the popover keeps the microphone paused. Confirming sharing also silences this device’s meeting playback, voice assistant and notifications; video, chat and notes remain available. Use my microphone, or explicitly pressing the microphone control, restores this device’s own audio. Device replacement and pending permission requests cannot reopen a paused mic. If the identified primary device leaves, local playback resumes and the microphone stays paused until the user chooses to speak. Analysis runs only on already-enabled microphone tracks, never screen-share audio. It creates no extra capture, cloned tracks, recordings or network payloads, and compares at most eight incoming microphones. Several consecutive matching windows are required; silence and steady tones are insufficient. An explicit override suppresses the current device pair. Unsupported Web Audio falls back to native browser echo cancellation and the manual sharing control. This is a conservative audio-overlap heuristic, not proof of physical proximity or speaker identity. It does not use IP addresses or merge independent recordings on the server. Headphones and one primary microphone/speaker pair remain useful for rooms where acoustic conditions defeat detection. The meeting deadline appears as a compact clock button with a circular countdown ring. Hover or focus shows the remaining time and localized deadline; tap opens the same details. The ring represents the time remaining when this device joined, while the deadline always follows the server’s room expiry. Resource-limit errors still appear as actionable notices.