Regenerate the action table from lotusActions.ts: add the two actions from #50, and note which cinny PR handles each one still in flight. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
18 KiB
Lotus fork of Element Call
This fork is embedded by Lotus Chat (cinny) as the in-app call frame. Everything it adds is opt-in: without the URL flags and widget actions below it behaves like upstream Element Call.
The source of truth is src/lotus/ (the io.lotus.* actions are declared in
src/lotus/lotusActions.ts). Keep this file in step with it: when the host and
the fork drift, features fail silently (element-call#43, cinny#260).
URL flags
The host sets these on the widget URL (cinny: CallEmbed.getWidget).
| Flag | Effect |
|---|---|
lotusCallState=true |
Stream call state to the host: io.lotus.call_state (participants, speaking, mute) and io.lotus.mic_level. |
lotusTheme=true |
Apply the host theme; the frame's root color-scheme follows it. |
lotusTransparent=true |
Transparent canvas so the host's wallpaper shows through. Only meaningful with lotusTheme (the fork applies it anyway and warns). |
lotusHostControls=true |
The host draws the call bar: hide Element Call's footer and enable the host-control actions. |
lotusFrameScreenshare=true |
With lotusHostControls: show the frame's own screenshare button, for engines without Capability Delegation (Firefox, Safari) where the host can't start a share. |
lotusAudioInject=true |
Accept io.lotus.inject_audio (soundboard). |
lotusDenoiseSource=true |
Run ML noise suppression in-source as a LiveKit audio TrackProcessor. |
lotusModel=rnnoise|speex|dtln|deepfilternet |
Denoise model (default rnnoise; falls back to rnnoise once if the chosen model fails). |
lotusGate=true, lotusGateThreshold=<dB> |
Noise gate after denoise (default −45 dB). |
lotusDenoiseFloor=<0..1> |
Denoise mix floor. |
lotusDenoiseBase=<url> |
Where the denoise worklets / wasm / models are served (same-origin by default: ./denoise/). |
sendNotificationType=none |
Upstream param, extended: none sends no call-start notification at all (cinny#295; voice channels shouldn't @room-ping). |
Widget actions (io.lotus.*)
"host → call" actions must be allow-listed in LOTUS_TO_WIDGET_ACTIONS.
The table is generated from src/lotus/lotusActions.ts; the last column was
checked against cinny's lotus branch.
| Action | Direction | Payload / meaning | Handled by cinny (lotus) |
|---|---|---|---|
io.lotus.call_state |
call → host | in-call per-participant speaking / mute state. NOTE: matrix-widget-api transport.send is request/response — the host MUST register a handler that replies/acks each one (cinny's listenAction does, replying {}). If the host has no handler, ClientWidgetApi immediately error-replies "unsupported from-widget action", so the data is silently dropped and each throttled send rejects (caught) — functional miss + log churn, not a hang. |
yes |
io.lotus.focus_participant |
host → call | pin/spotlight (or clear, with userId=null) a participant. | yes |
io.lotus.inject_audio |
host → call | play an audio clip into the call as a separate published track. | yes |
io.lotus.set_quality |
host → call | set audio/screenshare encoding quality (bitrate/framerate). | yes |
io.lotus.decorations |
host → call | per-user avatar-decoration image URLs for in-call tiles. | yes |
io.lotus.set_deafen |
host → call | deafen remote audio (and optionally mute screenshare audio). | yes |
io.lotus.request_state |
call → host | sent on (re)registration of a lotus toWidget handler (e.g. decorations) so the host can re-push state that would otherwise only be sent on change (decorations roster, the current focus pin) after an EC-side reconnect/remount. cinny's resendForkState() should respond to this the same way it responds to a fresh join. |
yes |
io.lotus.denoise_state |
call → host | real state of the in-source denoise engine — { active: boolean, model: string, error?: string } — sent once the processor attaches (or after the rnnoise fallback also fails), so the host toggle can reflect reality rather than the requested state. |
yes |
io.lotus.call_summary |
call → host | one-shot end-of-call readout for the local participant — { durationMs, reconnects, poorMs, verdict } (#143) — sent on SFU disconnect or in-call teardown, whichever comes first. |
yes |
io.lotus.set_audio_output |
host → call | select the audio output device { deviceId } (#119). |
yes |
io.lotus.audio_output_state |
call → host | the currently selected output { deviceId } (#119). |
cinny #417 |
io.lotus.screenshare_notice |
call → host | local screenshare reminder { kind: "ended" | "no-frames" | "alone" } — window closed, no frames for 15 s, or 30 min of sharing with nobody else in the call (#39). Each fires at most once per share. |
yes |
io.lotus.set_layout |
host → call | switch the call layout { layout: "grid" | "spotlight" } (cinny #43 — replaces the host clicking EC's hidden layout radio). |
yes |
io.lotus.open_settings |
host → call | open EC's settings modal, or close it with { open: false }; omit open to toggle (cinny #43). |
yes |
io.lotus.toggle_reactions |
host → call | toggle the reactions / raise-hand menu (cinny #43). | yes |
io.lotus.set_screenshare |
host → call | start/stop sharing { on?: boolean } (omit to toggle; cinny #43). Starting calls getDisplayMedia, which needs the user's click: the host sends this with Capability Delegation (delegate: "display-capture"), and without it the browser rejects the share. |
yes |
io.lotus.set_hotkeys |
host → call | { codes: string[] } — KeyboardEvent.code values (PTT, deafen) to report while focus is inside this frame (cinny #43). [] stops. |
yes |
io.lotus.set_frame_screenshare |
host → call | { visible: boolean } — whether the in-frame screenshare button (lotusFrameScreenshare) may show, e.g. false while the room's call policy forbids screensharing (cinny #43). Defaults to visible. |
yes |
io.lotus.prompt_screenshare |
host → call | {} — show the "Share your screen?" prompt inside this frame (cinny #43). For hosts that can't delegate the user's click: the prompt's Share button is clicked here, so getDisplayMedia gets its activation. |
yes |
io.lotus.hotkey |
call → host | a watched key went down/up here, or this window's focus changed; see LotusHotkeyReport (cinny #43). |
yes |
io.lotus.controls_state |
call → host | { screensharing: boolean, layout: "grid" | "spotlight" | null, screenshareAction: true, hotkeys: true, frameScreenshare: false, screensharePrompt: boolean, screensharePromptOpen: boolean } whenever either changes (cinny #43), so the host stops reading EC's DOM for them. Its arrival also tells the host this fork supports the actions above. |
yes |
io.lotus.mic_level |
call → host | local mic level { bars: 0 | 1 | 2 | 3 } (cinny #146), sent only when it changes (≤ 10 Hz); 0 while muted or with no mic. |
yes |
io.lotus.call_error |
call → host | the call's error page is up ({ code, recoverable }) or cleared ({ code: null }) (cinny #318), so the host stops showing the call as joined after "Connection lost". |
cinny #418 |
io.lotus.reconnect |
host → call | press the error page's Reconnect / Try again (cinny #318). | cinny #418 |
Entries naming a cinny PR are handled once that PR merges.
Fork-only behaviour (no flag needed)
Per-person volume memory, screenshare notices (window closed / no frames / sharing alone), mono mic capture, an SFU refusal reason instead of "Something went wrong", "Try again" for failed connects, and support for running on its own origin (cinny#43).
Releasing
- Merge the change into
lotus. - Bump
versioninembedded/web/package.json(0.25.0-lotus.N), commitchore(lotus): 0.25.0-lotus.Nonlotus, and push a tagv0.25.0-lotus.N. - CI builds the embedded bundle and, for
v*tags, publishes@lotusguild/element-call-embeddedto the Gitea npm registry (https://code.lotusguild.org/api/packages/LotusGuild/npm/). - In cinny, pin the exact version in
package.json(npm install --save-exact @lotusguild/element-call-embedded@0.25.0-lotus.N) and open a PR.