* Consider that HTTP 400 errors are due to corrupted
audio files by default.
* Properly reraise the http error otherwise so that
the retry mechanism actually works.
The existing manifest was not exposing the add-in tools when
creating a meeting in a shared calendar. Amend the manifest with the
missing instructions so the add-in shows up in that context as well.
Manifest changes generated with Claude's assistance.
Copy the MediaPipe WASM modules into the frontend build output and
serve them locally, instead of loading them from the Google CDN at
runtime.
Mediapipe was a transitive dependency, add it explicitly.
Requested by some members of the community.
It closes#1168
Add vite-plugin-static-copy as a dependency. It will be used to copy
and serve the WASM modules shipped by the MediaPipe JS dependency
from the frontend build output.
Inline the model weights instead of fetching them from a Google
remote location at runtime. The weights do not update frequently, so
inlining removes an external dependency.
Update the calls to the background processors imported from
livekit-track-processor so they use the new instantiation method
instead of the previous, now-deprecated one.
The info panel was crashing when opening a room that was not
registered in the database (with ALLOW_UNREGISTERED_ROOMS=true),
because the API response did not include the room slug.
Instead of adding frontend fallbacks, update the unregistered-room
response to include the slug, keeping the API contract consistent
with registered rooms.
Note: this still relies on the unregistered-room response staying
aligned with the registered-room schema. Any future divergence
between the two responses could introduce similar issues.
A refactoring on the backend side is needed.
It closes#1441.
Add a new setting that controls whether an authenticated user can
rename or modify their display name, or if they must use the one
returned by the SSO.
When the setting is disabled, only anonymous users can set a display
name freely; authenticated users always use their SSO display name.
Requested by many self-hosters.
Create a new dedicated store for user choices, starting with the
username, to decouple this part from any LiveKit elements. This is
better for tree-shaking, and depending on LiveKit for storing the
username brings no real value.
The refactoring will be continued later for the other user choices
that can be persisted.
In the external API, applications authenticate with a
client_id/secret and can provision users with only an email. In that
flow, users are not identified to PostHog, so the user DB id alone
is not enough to identify them in analytics afterwards.
The issue does not exist in the public API, where users are
authenticated and therefore already identified.
Inspired by @flochehab's approach in summary.
Add the owner's email to the searchable fields of the recording
admin table. This makes it much more convenient for the support team
to look up recordings by user.
Avoids mounting the button's hooks and rendering its logic for users who
are not admin or owner, which previously happened on every re-render
of the parent.
Add Sentry observability to the `agents`. Introduce a dedicated
`observability.py` module. Refactor both agents to use the shared
observability layer and extract task helpers into a new `tasks.py` module,
making task execution easier to instrument and maintain.
Implement feature flags related functions in
Posthog analytics backend.
We cache the results in django cache to avoid
too frequent calls to Posthog.
Especially for when we are checking multiple features in
the same user request.
Extend the AnalyticsBackend class to support
user feature flags, without a breaking change to the
current implementation.
A flag value is considered to be a bool or a string.
Switch the analytics backend interface from a typing Protocol to an
abstract base class, so the contract that backends must implement
is explicit and enforced at instantiation time.
Created a shared class for handling Header based authentification.
This avoid duplicating logic and helps with a maintaining a signle
security related peace of code.
This commit introduces the compatibility with summary v2.
It tecnically doesn't break the compatibility with v1 as
v1 params are still sent. But we advise people using the
transcribe feature in their own deployments to adapt to the
new v2 API, as this compatibility will be removed in a
future major version.
* RecordingStatusChoices now has
EXTERNAL_PROCESS_SUCCESSFUL
& EXTERNAL_PROCESS_FAILED
Values, which are changed by a new webhook that
can be called by the transcribe service.
This webhook is protected by its own bearer token.
* Title for the document is computed in visio,
* Tests are added / updated accordingly
* Update the v2 create transcribe payload to support
publishing directly to docs and performing automatically
a summary if requested
* Note that title computation is to be handled by the caller
now as it makes more sense and avoids throwing a bunch
of parameters to the endpoint.
* All files are send as signed URL now, we don't read
directly from s3 anymore,
* Docs integration is now explicit in summary settings
* To make analytics work properly accross projects,
we use the user sub as distinct id,
this will require a new posthog project to be deployed
to work properly.
We create a post hog event at the creation request processing,
to make sure feature flags work properly after that.
* User email should now be provided to the API, it's not
mandatory to avoid a breaking change.
* Use a specific user agent for better tracking
Finally note that existing helpers don't always make use of
the pydantic models, so that's why there
are model_dump in some places. To avoid bigger changes.
This commit cleans up most of the code related to v1 route that
is used only by visio.
This is first step before introducing an update to the v2 route.
Add react-aria 3.49.0 + react-stately 3.47.0 as direct deps and bump
react-aria-components to 1.18.0 so react-stately resolves to one version.
Fixes the nominal Timer type clash and missing @react-stately/toast
imports introduced by the React Aria v1.17 monopackage consolidation.
Rarely media files may have one or multiple
empty streams when they are badly formatted.
The extract metadata code would crash when that happened.
We now avoid crashing and create a clean file
from the bad one to make sure API calls with that data
works properly (observed some failures otherwise
in my tests).
The management command that merges users with duplicate emails was
comparing emails in a case-insensitive manner, which left some
duplicates in the database when their emails only differed by case.
When using Makefile to launch tests, passing flags as well as specific
classes (using "::") was broken. This PR fixes this issue, by adding
an `ARGS` argument allowing to do
`make test ARGS="core/tests/foo.py::Test::x -vv"`.
joserfc versions before 1.6.8 accept an empty or nil HMAC key when
verifying HS256/HS384/HS512 tokens, which is a cross-language
sibling of CVE-2026-45363.
Bump joserfc from 1.6.4 to 1.6.8 to pick up the fix.
Ref: https://avd.aquasec.com/nvd/cve-2026-49852
Passing a list of hosts in the ingress was broken: the template
helpers were called with the current loop context (`.`) instead of
the root context (`$`), so the service name was rendered incorrectly
inside the hosts loop.
Track events whenever a new meeting link is generated, both through
the public API and through the external API.
The goal is twofold:
* Identify where the most links are generated from, so we can assess
which integration or entry point works best.
* Measure how many links are generated per user, so we can consider
a user truly active when they generate a link, rather than only
when they participate in a meeting.
Add an analytics abstraction that allows configuring which analytics
solution the app uses. PostHog is implemented as one backend, but by
default no analytics backend is activated.
The goal is to track events in a sufficiently organized way and to
let any developer implement their own backend as long as it follows
the same protocol.
Add the PostHog SDK to the backend so we can send analytics events
from server-side code. We currently lack data on events triggered
from the backend, and this closes that gap.
Ad blockers recently started blocking requests to our PostHog
feature flag API, leading to undesired behavior in the app.
Following PostHog's documentation, allow configuring a dedicated
domain for feature flags, isolated from the main PostHog domain.
The original implementation, introduced two years ago, was incorrect
and exposed the API to an undesired authentication mode: any user
access token obtained for a given user was being accepted as valid
credentials on the external API.
Restrict authentication to the intended mode so that user access
tokens are no longer accepted on this API.
Thanks @lunika spotting this.
Following internal feedback, rename the "Premium" wording to
"Advanced" across the transcription and recording sidepanels, so the
label no longer implies a paid tier.
Add a note in the documentation about La Suite Meet being
generalized by the French Prime Minister, along with a link to an
English-language source article.
Update the contribution documentation to make the conditions of
contribution to the project clearer.
Also encourage potential contributors to connect with the community
and get involved.
The favicon is bundled into the frontend image as static files served from
/usr/share/nginx/html. Rather than add a runtime config knob, document how to
overlay custom icons with a volume (ConfigMap + frontend.extraVolumes/
extraVolumeMounts). This serves the right icon from the first byte — no
rebuild, no favicon flash — and covers every variant, including the iOS
home-screen and Android/PWA icons.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Recording lifecycle previously relied exclusively on the MinIO storage-hook
endpoint to transition from STOPPED to SAVED, which made the system dependent
on MinIO bucket notifications and lifecycle configuration. Introduce support
for LiveKit egress_ended webhook (EGRESS_COMPLETE, EGRESS_LIMIT_REACHED) as
an alternative finalization mechanism for self-hosted deployments that do
not use MinIO / S3 hooks.
Change behavior of existing configuation RECORDING_STORAGE_EVENT_ENABLE.
When the LiveKit mechanism is enabled (RECORDING_STORAGE_EVENT_ENABLE=False),
RecordingEventsService.handle_complete is triggered from
LiveKitEventsService._handle_egress_ended.
We should be able to use other transcription services,
those usually relie on response_format="diarized_json"
to produce what we need.
Note that as part of this change, we stop using
openai library for making this call to avoid
casting the result to a payload that doesn't
contain the elements we used to rely on.
(setting this specific format auto cast the
results in openai lib). We keep the old
result class used.
When going through AlbertAPI, timestamp are not provided at the word level.
This adds default values so that the summary external contract stays the same,
while giving us compatibility wisht AlbertAPI.
Updates the help chart to be able to run a list of CronJobs.
By defaults it runs the clean_pending_files
and purge_deleted_files commands during the night.
Since we have added custom backgrounds, they could be
soft deleted but were never really deleted.
This commits introduces a command to actually delete
this files.
In introduces also a command to purge pending upload
.
Add a footer to transcription outputs linking to an external satisfaction
survey. The survey URL is built from TRANSCRIPTION_SATISFACTION_FORM_BASE_URL.
When TRANSCRIPTION_SATISFACTION_FORM_BASE_URL is unset or None, the
footer is omitted.
When joining a meeting that already has many participants, new
participants are now muted by default.
This is an empirical change, not directly requested by users but
informed by user experience: a lot of people joining large meetings
arrive with their microphone and/or camera open, which can be
painful for the host, who otherwise has to mute everyone or ask
everyone to mute. Many of these participants are inattentive but
still noisy.
If you are joining a room that is already at a certain size, you are
probably not the one expected to speak; presenters typically join
among the very first participants.
In particularly large rooms, with more than 5 or 10 participants,
the entry notification sound can quickly feel spammy.
A configuration to disable it exists, but new users do not discover
it easily. The sound notification is most useful while waiting for
the first few participants to arrive; once a few people are in, the
meeting usually starts and the sound becomes more disruptive than
helpful.
The visual notification remains in place, so users are still aware
when newcomers join. Roll this out and check whether users find the
change helpful.
Required by some self-hosters who want to present a link to
participants that are guests only, without access to their SSO. For
example, a meeting between a citizen and a public servant where the
guest is known not to have an account.
Also useful when rendering the join page inside an iframe.
Several clients ran into issues with the silent login, leading to
redirections that were not appropriate for their use case.
Offer a URL parameter to control this behavior and disable silent
login when needed.
The noise-suppression processor is now imported on demand with a
dynamic import() instead of a top-level static import, so it lands in
its own code-split chunk rather than the main bundle.
Why this is necessary:
* @libreaudio/la-call is fully self-contained. It inlines *everything*
as JavaScript: two WASM binaries (SIMD and non-SIMD variants), the
Emscripten glue, the worklet processor source, and the noise model
plus its weights — the model is compiled into the .wasm, so we ship
effectively two copies of it.
* The WASM is inlined as numeric array literals (new Uint8Array([...])),
the least compact representation possible (~2-4 source chars/byte) and
not meaningfully minifiable. The result is a large module that also
can't be stream-compiled the way an external .wasm asset would be.
* A static import would pull all of that into the initial bundle,
inflating critical-path download size and lengthening build time
(parsing/minifying the big literal) — for a feature that's only used
when the user actually turns on noise suppression.
* Dynamic import() isolates the whole payload in a separate,
content-hashed, browser-cached chunk. The cost (chunk fetch + WASM
instantiation) becomes a one-time hit deferred to first activation,
and is fully off the page's initial load path.
Notes:
* The library self-bundles its AudioWorklet at runtime from a Blob URL
and inlines the WASM, so it needs no build-tool asset plumbing
(no ?worker&url / ?url, no Vite asset config). The dynamic import is
therefore the only splitting mechanism required.
* Audio processing still runs off the main thread on the AudioWorklet
path (desktop/most browsers); only the Android ScriptProcessor
fallback runs on the main thread. Import style does not affect this.
* To hide the first-activation latency, the chunk can be prefetched
(e.g. import() on idle or <link rel="modulepreload">) so it's warm
before the user enables suppression.
Replace the basic RNN noise processor with a more advanced audio
pipeline powered by Big Blue Better Audio (BBBA), a project
supported by the Prototype Fund.
The new WASM-based pipeline introduced by @falkTX and
@trummerschlunk adds:
- voice isolation
- high-pass filtering
- spectral balancing
- multiband compression
- low-pass filtering
This significantly improves overall audio quality and speech
clarity.
More information about the pipeline is available on the
trummerschlunk/BigBlueBetterAudio repo.
Voice isolation still relies on RNNNoise, a widely used deep
learning-based denoising model. Audio quality could be further
improved in the future if DeepFilterNet becomes usable directly
in the browser.
Their code has been released in an NPM package under GPL license.
A previous CSP change suggested by CodeRabbit was not properly
tested and broke inline styling as well as the loading of the
ProConnect image.
Adjust the CSP directives to allow these resources again.
The CSP was missing a default-src directive (flagged by CodeRabbit).
Without it, styles, images, fonts, and media are completely
unrestricted, which undercuts the otherwise strict policy.
Set default-src to 'self' and add an explicit style-src directive
since we rely on inline styles (e.g. #close-msg and the view
toggling use inline style attributes), so style-src needs to allow
'self' and 'unsafe-inline'.
Mount the nginx configuration used by the frontend image as a volume
so the default one can be overridden by a custom configuration at
deployment time.
Based on suggestions from @rouja to help parametrize the
configuration at deployment time.
Add the possibility to display a feedback form link in the footer to
help collect feedback from the first users of the plugin.
Once the plugin is in production at scale, this will be replaced
with a link to the support page.
Offer the possibility to append a query parameter at the end of the
meeting URL to flag links created from the Outlook plugin, inspired
by Zoom.
The behavior is toggled via an environment variable.
From the task panel, change the "add" button to "remove" when a
meeting link is already present.
This will be updated to "update" once the external API supports
updating an existing room.
Detect whether a meeting link is already present in the calendar
event item or its body, and prevent generating a new link if one is
found.
Detection is based on the presence of the app URL in the text. This
is imperfect but covers the most naive scenarios.
Refactor the plugin to support internationalization and ship it in
three languages.
Internationalizing requires updating the manifest, which involves
touching the Helm chart and releasing a new version. Ship a beta
version of the plugin once the i18n work is done to bundle these
changes together.
In the ribbon, the "create a meeting link" action was displayed with
the generic add icon, which is fitting in the context of a menu but
not explicit enough on its own.
Following feedback from the social ministries, replace the add icon
with the app logo to make it clear that this button creates a
videoconference link.
Instead of appending the meeting link at the end of the email, insert
it where the user's cursor is. This ensures the link is not placed
after the email signature, or below the quoted thread in a reply.
Refactor the message builder so that rendered messages are displayed
in a nicer way. Based on user feedback from the social ministries.
Go for a hybrid approach: on the desktop client, insert plain text so
it picks up the default font configured by the client theme; on the
web client, insert HTML, as the editor there is rendered using HTML.
The dialog window does not close itself on Outlook Desktop, which
opens links in a webview that behaves like a different browser.
Calling window.close() fails because, for security reasons, JS is
prevented from closing the window. The browser considers that the JS
script is not the one that opened the dialog.
The dialog also loses its reference to the opener due to the
redirection, so there is no way to message the parent to trigger a
close.
Keeping the dialog reference on the parent side does not help either:
since the opener is lost, the parent cannot call close on the dialog.
After investigation, go with a temporary solution that shows an
explicit message hinting the user to close the dialog manually if it
does not close automatically.
Restrict arm64 builds to release tags only, instead of running them on
every build.
The arm64 pipeline was significantly slowing down CI with little
practical usage, degrading developer experience for regular workflows.
Keep arm64 builds for release validation, and handle occasional failures
manually if needed.
The LiveKit integration was still using RoomInputOptions and
RoomOutputOptions, which emit deprecation warnings.
Update the implementation to use the unified RoomOptions API.
Pin urllib3 to >=2.7.0 via uv constraint-dependencies to fix a moderate
severity decompression DoS vulnerability. Affected versions (2.6.0 to
<2.7.0) could fully decode a compressed response body in a single
operation, leading to excessive CPU and memory consumption.
Pin idna to >=3.15 via uv constraint-dependencies to fix a moderate
severity (CVSS 5.3) information disclosure vulnerability. The flaw is
network-exploitable with no authentication required.
Pin idna to >=3.15 via uv constraint-dependencies to fix a moderate
severity (CVSS 5.3) information disclosure vulnerability. The flaw is
network-exploitable with no authentication required.
Uninstall `vite-tsconfig-paths` as path resolution is now supported
natively by Vite's built-in configuration.
Remove the unused dependency and simplify the frontend tooling setup.
The upgrade introduced around 50 new linting errors. Fix the
low-hanging fruit and address straightforward violations.
Several of the new rules appear to target patterns intended for newer
React compiler capabilities. Since the project currently runs on
React 18 rather than React 19, disable these rules for now instead of
applying potentially inappropriate changes.
Follow-up work can address the new rules in a dedicated PR, potentially
alongside a future React version upgrade.
Removes Node.js type dependency from browser/React code by substituting
the non-portable `NodeJS.Timeout` type with the standard
`ReturnType<typeof setTimeout>` equivalent across all affected files.
Also replaces `process.env.NODE_ENV` in Icon.tsx with
`import.meta.env.MODE` for Vite compatibility.
With the addition of the ANALYSING state
files could be accessed in the short time they
were in that state.
We now require files to be in ready.
Also adds missing frontend types (no impact).
Manually upgrade the frontend codebase to ESLint 9.
Update the linting configuration and related code where required to
maintain compatibility with the new major version.
When analysing a file, the previous commit introduced a row level lock
to make sure we would analyse and promote a single file.
This commit changes the locking mechanism so that it happens with
the upload state which avoids long running db locks and potential
perf issues.
Before this commit, post a file check, the policy could be
reused to change the verified file.
Now, files are uplaoded to a temporary location, then inside
a transaction that prevents concurrent calls, the file is
copied to its final destination and the checks are run on that one.
A new file can still be updated with the policy but it will never be read, etc.
As part of this change, all files in the new tmp directory
on s3 should have an expiration policy.
Introduce a Kubernetes job to run the `merge_duplicate_users`
management command on the backend, as suggested by @rouja.
This provides a standard and reproducible way to execute the user merge
process in deployed environments without requiring manual access to
application containers.
The job can be used alongside the command's dry-run mode to validate
the impact of a merge operation before applying changes.
Add an email substring filter to the user merge management command,
allowing selection of a subset of users concerned by the merge process.
This enables safer incremental execution of the command by testing it
on a controlled group of users before applying it globally.
The goal is to validate behavior and ensure the merge process does not
introduce unexpected side effects or inconsistencies at scale.
Add a management command to merge duplicate users and reassign all
granted resources to the most recent user account in the database.
Support a dry-run mode to estimate the impact of the operation before
applying any changes. This helps validate the command and identify
potential issues in environments where realistic testing is difficult.
Add unit tests to verify the command behavior and ensure database
integrity is preserved during the merge process.
Move user provisioning logic out of the external token viewset into a
dedicated service to keep the viewset lightweight and easier to
maintain.
While extracting the logic, refactor user object handling to improve
robustness and make the provisioning workflow easier to reason about.
Defend against race conditions when concurrent requests attempt to
provision the same user. Rely on the existing database constraints to
guarantee uniqueness and gracefully handle integrity errors raised by
concurrent creations.
Update the test to document the actual contract without modifying the
underlying model behavior.
These tests act as non-regression coverage and explicitly assert that
users may have a null sub or email. They are intended to document the
current behavior of the initial user model rather than evolve or
constrain it.
Ghost rows have not been reported as an operational issue. For the sake
of simplicity, avoid changing the model unless required by a concrete
issue or unless the benefits clearly outweigh the added complexity.
Fix a race condition in the external viewset where concurrent requests
could create multiple pending users with the same email address.
No database constraint enforced email uniqueness for pending users,
allowing duplicates to be created under load.
This caused issues during user reconciliation, which expects a single
matching pending user and raises a warning when multiple records are
found.
Add a narrowly scoped migration to enforce the uniqueness constraint
and address the identified issue.
Backport the logging configuration from docs to LaSuite Meet after
discussion with @lunika.
Add a proper base logging setup to restore usable logs in production.
The initial repository bootstrap lacked a complete logging
configuration, resulting in limited operational visibility.
* Removed constraint on file extension
* Infer audio/video streams from the media with ffmpeg
* Infer the correct processed audio file extension based on actual
codec to avoid ffmpeg errors
We need to support more extensions and make audio extraction dynamic,
as we shipped transcript in production and it led to user complaints
requesting more formats.
Avoid stacking too many components in route/home. Keep only the
responsibility of orchestration and layout in the home route, and
move stateful components to their appropriate folder.
The homepage previously relied on the userChoices store, which loads
the persisted user choices from localStorage. However, this store is
widely used in the room feature, so code splitting ended up loading
the chunk containing the userChoices store at the initial render.
That chunk was bundled with unrelated parts that proved to be heavy.
Switch to a more straightforward approach reading the cached username
and verify the bundle output + intial loading.
Saves a few hundred ko on the initial load, now bundled in another
chunk that is loaded only when entering a room.
Material icons (and Symbols, the most recent ones from Google
Material) were initially adopted to align with the UI kit. Before
that, we relied on Remixicon, which loads only the icons used and
totals 40ko for the whole library.
To benefit from Material icons, the implementation matched the UI
kit, which uses two fonts: 150ko and 750ko. Unlike the UI kit, the
fonts were preloaded to prevent icon render blinks. On products using
UI kit, the fonts are loaded the first time an icon renders,
and because they are heavy, the icon swap is visible to the user.
Icons are replaced by their text label in the meantime, which also
caused screen-reader vocalization or i18n issues that had to be mitigated.
Preloading the fonts was a quick win that worked fine on high-speed
connections where fonts get loaded fast then cached. However, while
optimizing LCP for users on poor connections (a separate concern that
already affects videoconference joining), preloading proved harmful:
it stole network bandwidth at the most critical moment,
when the main JS chunk was loading.
Switch to a frugal approach: import only the SVG icons actually
needed from Material, treated as components so they are tree-shaken
and customizable (color, etc.). Icons do not change often, so the DX
remains good. The UI kit also supports custom icons not available in
the font, so this approach lets us define them and works like a
charm.
Removal of Remixicon is planned in a follow-up PR.
It save user loading almost 1Mo of assets for few icons.
posthog-js was bundled in the main chunk. Loading it dynamically saves
200ko on the initial load and around 4s of loading time on slow 4G
connections.
PostHog, and analytics in general, should not block the initial
rendering. The risk is that some feature-flagged features may not be
instantaneously available to the user, but for most users on
high-speed connections this will be imperceptible.
Other places in the code rely on a direct import from posthog, I only
verified the delay import of posthog for the home page, and did
not for the /room route. I'm pretty sure there is room for
improvements on other parts of the app.
The Valtio actions on the store were initially defined inside a hook,
which was a bad idea: it does not follow Valtio's recommendation of
defining actions at the module level, and the function definitions
were re-created every time the hook re-rendered, which happened on
every state update.
These action has nothing related to React.
Additionally, the snapshot should be used more directly so Valtio can
understand and optimize which parts of the proxy are of interest to
the snapshot being made.
Use the Python port of Google's phone number library on the backend
instead of the JS one on the client. Saves 140Kb of unnecessary JS
and avoids a complex dynamic import that would have been required to
optimize loading.
The transformation is static: the phone number lives in the Django
settings, so the backend can format it once and pass the result to
the client. This avoids every client loading the 140Kb library and
re-computing the same information.
Moreover, within a single client, the transformation was previously
re-computed several times during a webapp lifecycle.
Avoid re-rendering the whole home page when interacting with the later
meeting creation flow.
Maintaining the modal open/close state at the parent level was causing
the whole home page to re-render on every menu open/close state
change.
livekit-client is a 400ko package that was wrongly bundled in the main
index.js chunk.
Every page was loading this enormeous vendor package only necessary
to join and participate to a room.
It made sense to set the LiveKit log level at app init, however the
performance tradeoff forces us to move it as close as possible to
where it is actually needed.
The library is rarely used, so load it dynamically. It is only 50ko,
which might not have been worth the effort, but these 50ko were
bundled in the main chunk and are not needed on the homepage nor when
launching a room.
The static placeholder is good enough to be acceptable. On very slow
connections, the 50ko might take a second to load, after which the
text is updated with the right content.
Also improves renders across the components touched, especially the
idle modal with the countdown.
Introduce a component that renders its children only if the logged-in
user is considered Admin or User. This helps avoid mounting the hooks
and logic of components that previously early-returned when the user
was not admin or owner, but were still re-rendering whenever their
internal logic updated.
One of the best ways to reduce the size of the initially loaded chunk
(index.js, around 2Mo before optimization) is to lazy load routes and
features so their JS gets isolated in dedicated chunks and is loaded
only when needed.
For example, most of the JS under the legal terms page is never
consulted by users. Likewise, when loading the home page, there is no
need for all the feature-related JS required to display a
videoconference.
After this lazy-loading optimization, the initial chunk is now around
1.2Mo, a significant improvement.
While attempting to lazy-load routes, Vite/Rollup warnings revealed
that the Dialog and Form primitives were causing circular imports
through their use of the barrel file.
Easy to address: when using primitive elements inside those
primitives, rely on direct imports instead of the barrel file.
Auth-related code is used across the codebase and imported by many
different features. The barrel file, while convenient for importing
utilities, was harming code splitting by pulling unrelated modules
into shared chunks.
Verified by running `npm run build:debug` before and after: chunking
and code splitting are better without the barrel.
Install tooling to visualize the Rollup bundle output. It helps
understand precisely what composes our bundle and what is included in
each chunk, which is a great help when trying to enhance code
splitting.
You can see its output running npm run build:debug.
Note: the library is not compatible with Node 20, which is the build
image we use in Docker to build the frontend, so it will raise a
warning in CI. This does not matter much, as it is a dev-only
dependency used locally without Docker.
Mark JS imports as type-only when applicable so they are stripped at
build time and ignored during chunk splitting, ensuring we take full
advantage of Rollup's code-splitting optimizations.
Following the ProConnect documentation leads to inlining almost 40ko of
SVG assets in the JS bundle. Moreover, each SVG asset (around 20ko) is
not optimized in size.
As the ProConnect button is loaded in the main index.js chunk at app
launch, these few Ko are critical.
Optimize index.js by 40ko and preload the hovered variant to avoid any
blink on hover.
Could be further optimized by handling the button background with CSS,
but leaving that as an improvement for later.
Inspired by commit eb23aef in suitenumerique/docs. The same base
route path is used everywhere, which helps when the backend sits
behind an ingress serving it with a regex like `api/*`.
Ensure the meet-backend createsuperuser and migrate jobs run only
once and do not rebuild the image by default. When the backend
updates, resources should focus on updating the backend pod itself.
Align enabled/disabled mature features across the dev environments.
Also reorganize backend env variables with a clearer scope and add
comments that help understand the responsibility of each setting and
why it exists.
Maintaining the two dev stacks is a nightmare. Start factorizing them
to make sure they stay updated easily, and to highlight differences
between the two at a glance.
Document the changes introduced in #128333, which allows passing
configuration to the external API when creating a room.
Warning: this documentation was generated in a rush using an LLM and
may contain minor errors.
Plan to factorize these YAML files into a common and shared
structure.
The securityContext and podSecurityContext were harming hot reloading
on the dev stack. The file permission system prevented source updates
because the user running the image was not root.
Allow passing configuration and access level when creating a room
through the external API.
Also add a few guardrails:
* control whether public rooms are accepted on the external API
* set the default access level when creating a new room
Ensure the new room configuration and access level are returned by
the serializer when listing rooms.
Surface connection state changes (reconnecting, disconnected,
etc.) directly in the PiP window so the user stays informed
without needing to switch back to the main window.
Co-authored-by: Cyril <c.gromoff@gmail.com>
Replace the hard-coded tooltip offset with a semantic CSS token,
making it possible to override the spacing per context (e.g.
tighter spacing in the PiP window where vertical room is scarce).
Co-authored-by: Cyril <c.gromoff@gmail.com>
react-aria computes overlay positions against the main window's
dimensions, which produces incorrect placement when the overlay
is rendered into a PiP document. Tooltips were the most visible
symptom, but the issue affects overlays in general.
Introduce a context that lets our primitives know when they're
rendering across documents. When set, the primitives use the
host document's window for positioning instead of falling back
to react-aria's default.
Original work by @ovgod. No cleaner approach seems feasible in
the short term, react-aria doesn't expose a clean way to
override the positioning target, so this works around it at our
primitive layer.
Co-authored-by: Cyril <c.gromoff@gmail.com>
The PiP window can be resized to a wide range of dimensions, so
the control bar needs to adapt. Introduce a collapsible mechanism
that hides or condenses controls as available width shrinks,
keeping the UX usable at small sizes.
Original works by @ovgdd
Co-authored-by: Cyril <c.gromoff@gmail.com>
The toolbar's onFocus handler called focusManager.focusFirst() whenever
focus arrived from outside the toolbar, which included clicks on
reaction buttons. This caused focus (and the scroll viewport) to snap
back to the first button on every click.
Use :focus-visible to distinguish keyboard focus from pointer focus.
Only redirect to the first button when focus arrives via keyboard,
leaving click-induced focus untouched.
Co-authored-by: Cyril <c.gromoff@gmail.com>
Render the notification region from the main window into the PiP
document via a portal, rather than duplicating it. This keeps a
single listener, a single store, and a single component instance —
so notifications stay consistent across both windows without extra
synchronization.
Simplest approach I could find. Good enough as a first pass;
worth revisiting if the requirements grow.
Co-authored-by: Cyril <c.gromoff@gmail.com>
The core component of the PiP layout. Adapted from a simplified version
of @ovgodd's work in #890 — see that PR for context on the original design.
Co-authored-by: Cyril <c.gromoff@gmail.com>
Adding a menu turned out to be tricky. Our usual menu primitive
is built on react-aria, which positions overlays based on the
`window` object. Since the JS runs in the main window, the
computed position refers to the main window's coordinates, and
the menu renders in the wrong place inside the PiP document.
After investigating, we couldn't find a clean way to make
react-aria target the PiP window's `window`/`document` without
significant rework. Agreed with @ovgodd to duplicate the menu in
the PiP window as a pragmatic workaround until a better approach
emerges.
Co-authored-by: Cyril <c.gromoff@gmail.com>
The PiP control bar shares state with the main window's control
bar — most importantly, the reaction toolbar and the mic/camera
toggles, which are fully stateful.
Sharing state works naturally here because the PiP content is
rendered through a portal into the same React tree, so the
controls in both windows read from and write to the same stores.
Co-authored-by: Cyril <c.gromoff@gmail.com>
The main window computes a JS offset to align the reaction toolbar
with the (off-center) reaction toggle. PiP and mobile don't need
this — centering the toolbar is enough. Add `adjustedCentering` to
opt out.
Not a great seam: the component shouldn't know about its host.
A cleaner fix would move the alignment concern to the parent.
Leaving for now to unblock PiP.
Co-authored-by: Cyril <c.gromoff@gmail.com>
When the PiP window is active, replace the corresponding content
in the main window with a lightweight placeholder. This avoids
rendering the camera feed (and other expensive media) twice when
the user is only watching the PiP window.
Co-authored-by: Cyril <c.gromoff@gmail.com>
Add a hook that manages the logic needed to open a document
picture-in-picture window, stores a ref to the window in a global
store, and—once ready—mounts a portal to duplicate content into
the PiP document's container.
Mounting the PiP content via a portal lets us share the same React
tree as the main app, and therefore share application state. This
comes with some trade-offs, particularly around components that
rely on the DOM hierarchy: react-aria popovers and overlays may
not behave correctly across documents.
The logic is kept to a minimum here. This first commit does nothing
more than open a window containing a loading spinner. The topic is
new to us, so plenty is likely to be refined in follow-ups.
Co-authored-by: Cyril <c.gromoff@gmail.com>
Refine keyboard behavior so left and right arrow keys no longer
control toolbar navigation, allowing users to directly tab into
individual reaction buttons for faster and more intuitive access.
Remove the reaction toolbar from accessibility tree to avoid
unnecessary focus and announcements, keeping assistive technology
focused on the actual reaction controls.
This ensures the UX centers on reaction actions rather than
auxiliary navigation elements.
Also, not related to this topic, I've reworked the scroll viewport styles.
Implement the missing disabled state styling for the
primaryDarkText button variant to ensure consistent UI feedback
when the button is not interactive.
Allow reaction interactions on mobile.
Adding the toolbar toggle currently impacts the responsive layout
of the control bar on very small screens. This will be improved in
a future PR with an auto-collapsing mobile control bar.
Replace the previous hardcoded offset approach with a dedicated
hook that dynamically centers the reaction toolbar relative to the
reaction toggle.
This improves layout accuracy and removes reliance on fixed values.
Note: ResizeObserver is used for positioning updates and may not
be supported in older browsers. Additional testing will be
performed before production release.
Replace direct DOM queries using hardcoded IDs with proper constants.
Add horizontal navigation support using left/right arrow controls
to scroll through available reactions on smaller screens.
This work is inspired by Cyril's implementation.
Rename strip-related components into a more generic container
abstraction.
Extract the buttons container into a dedicated component file and
update related naming to improve readability and maintainability.
Ensure the participant requesting a mute action is still present
in the room before processing the request.
This mitigates scenarios where a previously issued token could be
reused after the meeting has ended.
Current token lifetime is intentionally long-lived and will be
refactored in the future to better align with LiveKit session
constraints. In the meantime, add this extra validation step to
reduce the attack surface.
Fix unreachable code when notifying participants that they were
muted.
Prevent unnecessary function re-creations when props remain
unchanged.
Also guard against missing tokens by logging an error and
returning early when the token is undefined.
Extend the existing live synchronization mechanism beyond room
configuration to also include lobby access level changes.
This ensures that all owners and admins sharing a room maintain a
consistent and up-to-date view of room state in the frontend,
including configuration and access control updates.
Listen to room metadata change events and synchronize the React
Query cache with the latest room data fetched from the API.
This ensures clients react to live configuration updates, such as
showing or hiding mute controls when `everyone_can_mute` changes.
Update room serialization to include room configuration for all
users fetching the API response, not only room owners.
This behavior was inherited from the original upstream project.
At the moment, exposing this configuration does not appear to
introduce meaningful security concerns or provide attackers with
additional capabilities.
The decision will continue to be reviewed from a security
perspective, but sharing the configuration improves frontend
consistency and synchronization.
Simplify source serialization and validation logic while improving
type safety around room configuration handling.
Introduce a dedicated TypeScript type matching the backend
Pydantic model more precisely.
Also harmonize track source casing between frontend and backend to
remove redundant conversion logic and resolve#1282.
Introduce synchronization of room configuration changes across
active participants.
When a room configuration is updated through a PUT operation, the
backend now performs an additional LiveKit API call to notify room
participants through a room metadata update event.
This ensures admins and owners quickly see up-to-date settings in
their administration panel. It also prepares the frontend for
automatic updates of unprivileged participants room’s data without
refetching it from the API.
An event-driven design was chosen instead of storing the full room
configuration in LiveKit metadata. While embedding the state
directly in metadata would provide immediate synchronization, it
would also require initializing and maintaining configuration
state during room creation or webhook handling, increasing the
risk of operational failures and regressions.
Instead, the backend emits lightweight synchronization events and
active clients update their React Query cache, which remains the
single source of truth for room configuration data.
Introduce a new room setting controlling whether all participants,
including non-privileged users, can mute others.
Update API validation accordingly and add the frontend controls
allowing administrators to toggle the option and persist the
configuration through the API.
Allow non-privileged users to mute others when the
everyone_can_mute configuration is unset or true.
This setting is not yet customizable by room owners and will be
introduced in a future update.
Pass the LiveKit token when calling the mute-participant endpoint
to authenticate the request.
This enables non-authenticated participants to mute others through
the API while preserving proper authorization checks.
Enable any participant to mute others when the room configuration
allows it. This is enabled by default for all meetings unless
explicitly disabled by an administrator.
Privileged users retain the ability to mute any participant
regardless of the room configuration.
Fix two issues. 1: Missmatch between commands in dev and production in
Dockerfile, leading to unexpected behaviors. 2: Naming of
multi-user-transcriber -> multi-user-transcriber-dev for coherence.
Rollback the mail package upgrade after identifying multiple
breaking changes introduced in v5 that were not fully accounted
for.
Local testing initially missed the issue because the mail Docker
image had not been rebuilt automatically, causing broken emails to
go unnoticed.
Speaker-to-participant assignment relie on WhisperX word timings, but
incorrect word durations in the output can lead to inaccurate overlap
scoring and wrong user attribution. Add a custom heuristic to trim
overly long word durations before computing assignments.
Added a hyperlink to the "Open" text in step 1 of the recording
notification email instructions. Previously, "Open" was plain text
and users could only access their recording via the button below.
Now the text itself is a clickable link, improving accessibility
for email clients that may not render the button properly.
Updated MJML source template and all 4 locale files (en, fr, de, nl).
When duration is not reported in the files metadata,
we directly infer the duration from the audio packets.
This prevents errors on webm files.
Very simple audio & video test files have been added
that cover relevant usecases to prevent regressions.
Update dependencies to the latest minor versions fixed that
by re-resolving the fields.
This is needed for packaging as many distribution retrieve
node modules into the npm cache and then tries to install
node modules into the project without any internet connection.
Since there is no resolved/integrity field, it fails to
get packages from the cache.
Introduce a new user assignment mechanism to for more friendly output
than the current (SPEAKER_0, SPEAKER_1, ...). Use the VAD metadata to
compare speech intervals with those returned by WhisperX. User with the
highest overlap score above a defined threshold is assigned to each segment.
This method allows for multi-speaker scenarios for a single account.
Fix compatibility issues with the DINUM frontend image, which
overrides the default `font-sans` value.
Simplify the implementation by having the JavaScript layer only
toggle well-scoped CSS classes responsible for accessibility font
overrides. This makes the behavior more predictable and restoring
default styles straightforward.
Also clarify the intent of the hook by making its accessibility
purpose explicit and moving its usage to the App component, where
it better fits the application lifecycle.
The tasks endpoint used non-timezone-aware date and time values and split
them into separate variables, which is unconventional. Refactor the
implementation to use timezone-aware datetime objects and align transcription
formatting with the user-declared timezone. Update the source of truth for
recording start time to FileInfo.started_at for improved precision. Adjust
the task signature in preparation for upcoming user assignment work, which
will require `started_at`, `ended_at`, and `metadata_filename`.
Expose RECORDING_ENCODING_* settings to override the default LiveKit
Egress preset (H264_720P_30). When RECORDING_ENCODING_ENABLED is True,
the provided width/height/framerate/bitrate/keyframe values are passed
as advanced EncodingOptions. Lowering framerate and bitrate reduces
recording file size and egress worker CPU load.
Disabled by default, preserving current behaviour.
Udate the helm chart to support multiple transcribe worker in
the summary service.
This is useful when using multiple WhisperX instances to have one deployment
for each endpoint. This enables some kind of horizontal scaling (we still
keep one call per WhisperX endpoint but can have multiple WhisperX endpoints)
Room identifiers are created with `Math.random()`, which is predictable
and not suitable for security-sensitive identifiers. Predictable
room IDs increase the risk of room enumeration and unauthorized
access attempts, especially when IDs are part of join URLs.
Affected files: generateRoomId.ts
Signed-off-by: tuanaiseo <221258316+tuanaiseo@users.noreply.github.com>
Wrap Recording and RecordingAccess creation in a single transaction so a
partial failure does not leave orphan rows, and return 409 instead of 500
when a recording is already in progress for the room.
When the worker fails to start, transition the Recording to
FAILED_TO_START so the unique partial constraint on (room, status) no
longer blocks future recording attempts on the same room.
Fixes#1126 - Inconsistent role terminology in localization files.
Standardize on 'host' as the primary role term across en, de, and nl
locales, replacing mixed usage of 'administrator', 'organizer', 'admin',
'Organisator:in', 'Organisierende', and 'organisator'.
Room.configuration accepted arbitrary JSON without validation, allowing unsafe
or malformed payloads to be stored and creating a security risk. Define a
Pydantic schema to enforce structure and constraints, and add validation
at the serializer level to reject invalid inputs.
Updating connectionObserverSnapshot triggered page re-renders,
causing participants to reconnect due to a race condition.
Read directly from the underlying store instead of using the
snapshot to avoid unnecessary re-renders.
Ensure screen recording is properly disabled when the feature
flag is set to false.
Previously, no feature flag was enforced, allowing unintended
access.
Also update the blocked-access message to be more generic and not
specific to public sector users.
Integrate the add-ons authentication backend into the externalviewset.
This allows third-party integrations (e.g. calendar add-ins) to
obtain a JWT for a user and use it to call the API (e.g. create a
room) via a Bearer authorization header.
Introduce a configuration flag to enable or disable the
application token exchange (service account) mechanism.
This allows activating alternative authentication backends
without requiring full application token configuration.
Required to support the upcoming add-ons authentication backend.
The external viewset expected a plural "rooms:x" scope instead of
the incorrect singular "room:x".
Update it to accept the proper scope and emit tokens accordingly.
Provide the minimal components required to support an Outlook
add-in: user authentication, JWT retrieval, and API calls to
generate meeting links.
This implementation is an early alpha: developer experience is
limited, documentation is incomplete, and the solution is not
white-labeled.
It's too early to consider these parts ready to ship into
production.
As a result, it is currently only available within the DINUM
frontend image.
Add a new authentication backend for add-ons, with a core service
managing session state in cache and exposing three API endpoints.
Microsoft Add-ins recommend launching authentication from the
side panel via a dialog, then returning the JWT to the parent
context using postMessage. However, due to Django and SSO security
constraints (window.opener not preserved), this approach is not
viable.
Implement a three-step authentication flow:
- `/init`: create a session, return a short-lived transit token
and CSRF token. Store session ID in a secure HttpOnly cookie.
- `/poll`: allow the add-on to poll until authentication is
complete, then consume the session and clear the cookie.
- `/exchange`: exchange the transit token for a JWT, which is
later retrieved via the `/poll` endpoint.
The add-on opens an authentication dialog, stores the transit
token in sessionStorage, and performs the exchange after login.
This approach works within iframe constraints and provides a
sufficiently secure v0 despite known limitations.
A 1–7 scale is not commonly used in software products. Feedback
from both users and the support team suggests reducing the number
of options to simplify usage and analysis.
Adopting a 1–5 scale improves usability and makes responses
easier to interpret and process.
The scale has to be odd.
Allow controlling when the metadata collector agent is started,
enabling users to try the feature and disable it if needed.
Introduce a user-level feature flag to toggle the agent for the
initial release.
Introduce MetadataCollector and MetadataCollectorService classes to
centralize the collection and storage of user connections, VAD events,
and chat messages. This creates a structured foundation for future speaker
assignment logic based on voice activity detection. Add tests for this new
feature.
Capture selected ICE candidates for both subscriber and publisher
peer connections.
This enables correlation between survey feedback and connectivity
setup, helping identify problematic network configurations.
Bump the base image to Alpine 3.23 to resolve most vulnerabilities
reported by Cyberwatch and Trivy.
Remaining issues require manual updates:
- musl / musl-utils: upgrade to 1.2.5-r11 (CVE-2026-40200)
- zlib: upgrade to 1.3.2-r0 (CVE-2026-22184)
Run `uv lock --upgrade` to update transitive dependencies and
resolve the vulnerability in Pillow.
Upgrade Pillow from 12.1.1 to 12.2.0 to address the FITS GZIP
decompression bomb issue.
Replace costly PostHog surveys with basic surveys, which better
fit our headless usage and avoid short data retention limits.
Enhance emitted events with additional metadata, including a
unique session ID and room ID, to correlate survey responses with
specific sessions.
This lays the groundwork for further enrichment with participant
connection data.
Automatically copy required Kubernetes secrets when using the
Tilt development stack, as `make bootstrap` is not documented
as a prerequisite.
Feedback from Arnaud Robin
Tests were incorrectly mocking Twirp errors using HTTP status
codes instead of the meaningful error codes returned by the
LiveKit SDK.
Update mocks to reflect actual SDK behavior.
Replace passing the LiveKit JWT in the request body with the
Authorization header, following standard authentication practices.
Extend the LiveKit authentication backend usage across additional
endpoints.
This also raises questions about how clients should securely
retrieve LiveKit tokens, to be addressed later.
Avoid mapping all Twirp errors to generic 500 responses.
Explicitly handle the case where a participant is no longer in
the room, as this may indicate suspicious behavior or a client
state issue.
Improve error discrimination to provide more accurate responses.
Introduce toggle-hand and rename endpoints in RoomViewSet,
secured with LiveKit token authentication.
Remove direct permission for clients to update their own metadata
via LiveKit tokens to prevent spoofing (e.g. faking admin status).
Proxy participant metadata updates through the backend to enforce
proper validation and authorization.
Signed-off-by: lebaudantoine <lebaud.antoine131@gmail.com>
The multi-user transcriber service was not integrated into Docker Compose,
limiting its usability in local and development environments.
Add the necessary configuration to enable running and orchestrating the
multi-user transcriber via Compose.
This commit aim at adding a securityContext for
pod and container in Deployment and Job,
it include livekit pods as well
It adds 2 values :
- podSecurityContext : for pods
- securityContext : for containers
Please note that `celeryBackend` Deployment does
not have any values defined in `values.meet.yaml` at the moment.
Updated taskV2 API contract to be closer to the target gateway contract.
GET operations return the same things as the webhook payload.
Also store the summary on S3 to be iso with transcript.
Add multitenancy support to Summary sub-app. The V1 routes / tasks
behave like before, with the default tenant being "meet".
V2 routes / tasks support being called frm any tenant, and don't have
meet related logic.
V2 tasks are created in separate queues to avoid mix / match,i
Resolve warnings raised in jwt/api_jwt.py:365 by ensuring test
keys meet the required security length.
Align test configuration with expected cryptographic standards.
Mark the more options area as an explicit navigation
region for screen readers.
Update each sidepanel toggle to use `aria-expanded` to indicate
whether the panel is open, improving accessibility feedback.
Also, avoid render for screen reader the number of participants
as it's already added the the aria label
Declare a dedicated ARIA region for call controls
to improve accessibility.
Extract this region into a reusable component for better
consistency and maintainability.
Add an aria-label to clearly describe the purpose of the toolbar.
Avoid redundant wording in reaction button labels to improve
clarity for screen reader users.
Inspired by proprietary solutions.
Replace the dialog-based reaction UI with a toolbar integrated
directly into the DOM.
Allow it to remain open and support proper keyboard interaction,
improving accessibility and user experience.
Move hardcoded values used for layout alignment and animations
between the videoconference and side panel into shared constants.
This improves readability and makes future adjustments easier to
maintain.
Remove unnecessary wrapper divs to reduce layout complexity.
Explicitly name components to better reflect their
responsibilities, including RoomContentArea which handles the
video track viewport.
Group layout-related components under a single feature directory
to improve structure and readability.
This is a first step toward cleaning up the project and clarifying
the organization of the video call layout.
Use a single store, hook, and portal system to handle both local
and remote emoji reactions.
Improve code quality and reduce duplication through better
factorization of shared logic.
Reduce duplication by introducing YAML anchors for configurations
shared across multiple services.
Most settings were nearly identical across the three summary
services, making them easier to maintain and update.
The dev values are no longer in use and have not been used for over a year.
We primarily rely on the dev-keycloak values, and occasionally
the dev-dinum ones for testing on the Dinum-labeled frontend.
As a result, the unused dev values should be removed to reduce clutter
and simplify maintenance.
Serialization hardening introduced a breaking change between the
frontend and backend. Adjust the Pydantic model to restore
compatibility.
Reinstate support for can_subscribe_metric, which is passed by
default from the frontend.
Email addresses are PII and should not appear in technical or
error logs.
Sanitize logging to avoid exposing sensitive user data when
invitation sending fails.
Prevent invited participants from seeing each other's email
addresses when sending room invitations.
Ensure invitations are sent with proper isolation to avoid
mass PII disclosure.
This mitigates risks of email harvesting, spam, and phishing
through the platform.
After migrating to uv-build only the module matching the project name was
included in sdist/wheel packages. Without a src layout additional modules
need to be tracked manually to ship them in built packages.
Make SESSION_ENGINE configurable through environment variable,
following the same values.Value() pattern already used for
SESSION_COOKIE_AGE. This enables OIDC backchannel-logout by
allowing users to set SESSION_ENGINE to db backend.
Closes#1037
Update the link label to use explicit text "Download your recording"
instead of generic "following this link."
This ensures blind users understand the purpose of the link
and the behavior of opening a new window.
Update the logo alternative text to include the brand name instead
of a generic "logo image" description.
Use a more descriptive value such as "Logo LaSuite Meet"
to better convey the content to screen reader users.
It closes#1092
When uploading an image, depending on the available network
there might be a bit of wait while the image is being uploaded.
In this commit we add a preview (grayscale + spinner) to have
UI feedback that the upload is in progress.
The "Transcribe" and "Record" buttons had unclear and misleading
descriptions, both using the verb "record," which caused confusion,
especially for screen reader users.
Update descriptions to clearly reflect each action:
- Transcribe: generate a written transcript of the conversation
- Record: save the meeting as a video
This improves accessibility (RGAA 11.9) and reduces the risk of
users triggering the wrong action.
Closes#1173
The aria-label only announced the presence of a sidepanel without
including its title.
Append the sidepanel title to improve accessibility and context
for screen readers.
Closes#1176.
Add a custom virtual background feature.
If the backend supports uploading files, backgrounds are stored
in the backend for the user.
Otherwise, only one background image can be selected.
The getImageData call was using PROCESSING_WIDTH for both dimensions
instead of PROCESSING_WIDTH and PROCESSING_HEIGHT. This caused the
source image data to be 256x256 instead of the expected 256x144, leading
to buffer overflow when writing to the segmentation mask and potential
visual artifacts in Firefox background effects.
Sanitize error handling to prevent leaking internal details when
invalid or malicious requests are sent to the API.
Return generic error responses to reduce the risk of information
disclosure during probing attempts.
The dot before (?P<extension>...) was not escaped and matched any
character instead of a literal period.
Escape it to align with MEDIA_STORAGE_URL_PATTERN, which correctly
uses \. for the file extension separator.
Update the response message to explicitly state that certain
notifications are ignored on purpose, avoiding confusion
during debugging or log inspection.
With the introduction of background file uploads, a misconfigured
MinIO webhook could trigger the storage hook endpoint for unrelated
files.
While the dev setup now filters events via the MinIO lifecycle
configuration, add a safeguard at the application level.
Enforce a stricter filepath regex when parsing storage hook events
and ignore files outside the recording output directory.
Return a clean 200 response to acknowledge the webhook while
avoiding unnecessary processing.
Avoid duplicating patterns that match the same logical unit by
sharing common parts between the two regexes.
This logical factorization improves maintainability and ensures
consistent parsing behavior across S3 event handlers.
Initially I avoided coupling the recording path regex with the
setting that provides the Django static URL. However the new
file viewset already relies on it, and Drive does as well.
For consistency, update the recording regex to use the same
STATIC_URL-based approach.
While the coupling may not feel ideal, having two different
regex strategies for similar file paths would be worse.
Combine the regex used in Drive with the one from the recording
feature and centralize them in the enums module.
Although the module name suggests only enums, it also hosts
shared constants used across the codebase.
Add FILE_UPLOAD_ENABLED setting (default to False, to avoid
a breaking change). Also adds a max_count_by_user sub setting
to restrict the number of uploaded files per user.
InitialDelaySeconds was set to 30s which caused the tilt stack
startup to be very slow. In this commit I split the initial delay
and the period arg.
5s seems to be relevant given the nature of the app.
Authenticate the application secret before checking whether the
application is inactive.
This avoids revealing the inactive status of an application when
an incorrect secret is provided, preventing an authentication
state oracle and client_id enumeration.
Currently the inactive status is revealed before verifying the
secret, creating an authentication state oracle.
Introduce a failing test to capture the issue before
applying the fix.
With the introduction of file background uploads, only trigger the
webhook for files related to recordings.
Avoid firing the "recording saved" event for other file uploads,
preventing unnecessary queries and false triggers.
Add minimal admin support to allow administrators to mark an
application account as inactive if it becomes rogue or compromised.
This capability was missing when the application concept was
initially introduced in the backend.
Most models in the project use `is_active` rather than `active`.
The Application model was not aligned with this convention.
Rename the field and add a migration to standardize the naming.
Update related tests accordingly.
This change should not introduce breaking changes for external
applications.
Remove the user-provided filename while preserving the extension,
and switch to the format `uuid.extension`.
This is more natural than the previous `uuid/.extension` format
and avoids confusion.
Update related tests accordingly.
Migrate main meet app to use UV for dependancy management.
Also optimized the backend image build sequence for faster rebuilds
when dependencies don't change.
Also removed compiled django translations files are they are done in the build
process now.
Changes inspired by drive repo.
When support manually marks a recording as "failed to stop," enable
users to download the recording from the frontend.
This ensures users can recover their recordings even if the
automatic stop process fails.
Making the status read-only improved security and enforced least
privilege, but in production some recordings could not be properly
stopped, leaving them in an active state.
Introduce an admin action to allow support staff to mark recordings
as "failed to stop" or update their status when necessary.
Throttle the request-entry endpoint for authenticated users also
to guard against accidental hammering from buggy clients.
Authenticated users are throttled via RequestEntryUserRateThrottle.
Anonymous users are throttled using the lobby participant cookie
through RequestEntryAnonRateThrottle.
use the lobby participant cookie ID as the throttle cache key
rather than the client IP address.
This avoids penalising multiple users behind the same NAT or proxy
and aligns throttling with how LobbyService identifies participants.
This bug was spotted in production where users from ministries behind
NAT was blocked by throttling while using visio.
If no cookie is present yet, skip throttling for the request. The
cookie will be set on the first response and throttling will apply
from subsequent requests.
This throttle is intended to protect against accidental hammering
from buggy clients, not as a security control against DoS attacks.
This is not a security measure, we should use a WAF.
Enhancements to the German localization include:
- Refine phrases to sound more natural
- Ensure consistency in words usage
- Adapt phrases to be inclusive and gender-neutral
- Standardize to the "du" form of address, aligning with modern
practices and other open-source projects like GitLab and the Android
Open Source Project
The wrong port was configured, causing CORS issues
when uploading files directly to the MinIO bucket.
Port 9001 is reserved for the MinIO console;
use the correct API port instead.
* Add a simple celery-backend deployment to helm chart, which
uses the same docker image as the backend, and runs a single
worker.
* Update dev keycloack values accordingly
* Update Tiltfile to properly set service dependencies
* Update minio deployment to increase the default max body size limit
(relevant to uploading files)
Improve celery configuration from env variables and set
meet backend related tasks to run in the `meet-backend` queue
to ease sharing a single redis instance for multiple celery.
Add media_files_svc and ingress_media_files to helm chart
for serving medias related to the new files.
This setup is more straightforward than merging the
existing recording related media ones.
Also updated values to enable file upload by default in dev.
Mostly done by @lebaudantoine.
For the coming features we will need to store files on the meet side.
(for instance user backgrounds).
This commits adds a new Model to manage files, and the associated
serializers & viewsets. All are tested.
This work was heavily inspired by the work done by our friends at
https://github.com/suitenumerique/drive
It build on the same architecture design (upload directly to S3 but
download goes through our proxy), but model is much much simplier
(no folders, no file sharing, etc.).
Reused the logic developed by the team working on drive.
This is usefull for our own upload file backend (that will
come in later commits).
Dockefile was updated to add a required system dependency.
We might want to put this shared logic in a lib.
Recent styling changes introduced an overflow, causing the network
indicator to be pushed outside of the participant tile.
Remove width: 100% and add a minimal gap to prevent metadata
elements from being too close to each other.
Refactor styles to leverage PandaCSS inline capabilities for
better clarity and consistency.
Remove an unnecessary div wrapper that was causing a layout shift.
Create a separate Ingress resource to isolate traffic targeting the
webhook-livekit endpoint and allow applying specific NGINX
annotations to this route.
Use an exact path match to take precedence over the default /api
regex rule defined in the base Ingress.
No similar change is made for the S3 webhook endpoint, as this
dependency will be removed from the project.
Remove incorrect reference to ProConnect (DINUM SSO) from content
literals, where it should not be mentioned by default in the
white labeled version.
It closes#1075
The regex was being recreated on every function call, causing
unnecessary performance overhead.
Hoist the RegExp to a module-level constant to reuse the compiled
pattern.
The items array was defined inline, creating a new reference on
every render.
Hoist the array to a module-level constant or memoize it with
useMemo to prevent unnecessary re-renders.
The empty object literal created a new reference every render,
potentially triggering unnecessary re-renders.
Hoist an EMPTY_PROPS constant to the module level and reuse it
instead of allocating a new object.
Replace sequential await inside the loop with Promise.all, since
each enterRoom call is independent.
This prevents unnecessary delays when multiple participants are
waiting (e.g. 10 participants previously resulted in ~10x longer
execution time).
Improve validation of parameters accepted when starting a
recording to prevent unsupported or unexpected values.
Language validation will be further tightened to only accept
languages supported by the transcribe microservice.
Add extensive API validation tests to cover these scenarios.
Refactor the summary service to better separate concerns, making components
easier to isolate and test. Unify logging logic to ensure consistent
behavior and reduce duplication across the service layer. These changes
set up the codebase for granular testing.
Use settings directly in migrations to avoid noop
migrations. This might have undisered side effects
if we change the config over time 'invalid' data will be
in the database.
It's a simple quick fix.
Keeping some migrations that are no useless to avoid changing
too much the migration history for users.
Similar to https://github.com/suitenumerique/people/commit/
469014ac415b25be0ceed08b31a87d2d40d743cd
These fields previously triggered a suspicious operation exception
when passed to the API.
Make the list configurable so the serializer behavior can be
adjusted without requiring a new release.
During the bug bounty, attempts were made to pass unexpected hidden
fields to manipulate room behavior and join as a ghost.
Treat these parameters as suspicious. They are not sent by the
frontend, so their presence likely indicates tampering.
Explicitly allow the parameters but emit warning logs to help detect
and investigate suspicious activity.
Super useful for validation when handling unstructured dictionaries.
Follow qbey's recommendation and align with the
suitenumerique/conversation project approach to improve schema
validation and data integrity.
The Trivy GitHub repository was wiped over the weekend, raising
suspicions of a potential supply chain attack.
Temporarily disable the scan until the situation is clarified.
Add automated security review on new pull requests to strengthen
early detection of potential vulnerabilities.
Leverage Claude to help identify security issues and highlight
areas requiring special attention.
The previously pinned version (July release) did not support
passing the aria-disabled prop to React Aria Button.
A more recent release (August) introduced this capability.
Upgrade is required to make Cyril's proposal work.
Under some shells echo doesn't work as expected with the special formatting.
Using printf when creating the variables make it work and should be more robust.
Transcription and summarization results were always generated
using a French text structure (e.g. "Réunion du..."), regardless
of user preference or meeting language. Introduced basic localization
support to adapt generated string languages.
German was missing from the frontend/backend language list in the
sync hook, causing user preference updates to be ignored.
Add the language to ensure preference changes are properly applied.
Update default Renovate configuration to open PRs on
the first day of each month instead of weekly.
Security updates remain handled immediately by Dependabot, while
Renovate manages regular dependency updates to keep the project
up to date with third-party packages.
Refactor external API authentication classes to inherit from a
common base authentication backend.
Prepare the introduction of a new authentication class responsible
for verifying tokens provided to calendar integrations.
Move token decoding responsibility to the new token service so it
can both generate and validate tokens.
Encapsulate external exceptions and expose a clear interface by
defining custom Python exceptions raised during token validation.
Taken from #897.
Encapsulate token generation logic for authenticating to the
external API in a well-scoped service.
This service can later be reused in other parts of the codebase,
especially for providing tokens required by calendar integrations.
Commit was cherry picked from #897
Work adapted from PR #859 and partially extracted to ship as a
smaller, focused PR.
This allows users to view the full list of available shortcuts.
An editor to customize these shortcuts may be introduced later.
Pip was removed before copying the builder stage output, which caused
it to be reinstalled unintentionally. Adjust the order to align with
the backend image behavior.
Upgrade OpenSSL and related dependencies to address CVE-2025-15467
in meet-agents.
This vulnerability was blocking the image signature workflow, as it
is classified as a critical dependency.
Reduce surface area and keep the runtime image minimal.
Alpine 3.22 provides ffmpeg v6 as the latest version.
Alpine 3.23 does not include ffmpeg v7, so upgrade directly to v8.
Install pip temporarily for build steps, then remove it from the
production image.
Use the same user as in production to facilitate local testing with
the production image.
Assign group 127 to the docker user to mirror CI and match production
practices, even though the rationale for this group mapping is unclear.
Update all GitHub Actions to their latest major versions for improved
performance, security patches, and Node.js runtime compatibility.
Signed-off-by: Stephan Meijer <me@stephanmeijer.com>
2026-02-20 11:49:14 +01:00
642 changed files with 62133 additions and 10868 deletions
Powered by [LiveKit](https://livekit.io/), La Suite Meet offers Zoom-level performance with high-quality video and audio. No installation required—simply join calls directly from your browser. Check out LiveKit's impressive optimizations in their [blog post](https://blog.livekit.io/livekit-one-dot-zero/).
> [!TIP]
> New here? Start by introducing yourself in our Matrix channel:
> We’re happy to discuss ideas, answer questions, and help to deploy LaSuite Meet.
### Features
- Optimized for stability in large meetings (+100 p.)
- Support for multiple screen sharing streams
@@ -52,7 +63,7 @@ We’re continuously adding new features to enhance your experience, with the la
### 🚀 Major roll out to all French public servants
On the 25th of January 2026, David Amiel, France’s Minister for Civil Service and State Reform, announced the full deployment of Visio—the French government’s dedicated Meet platform—to all public servants. ([Source in French](https://www.latribune.fr/article/la-tribune-dimanche/politique/73157688099661/david-amiel-ministre-delegue-de-la-fonction-publique-nous-allons-sortir-de-la-dependance-aux-outils-americains))
On the 29th of January 2026, Prime Minister Sébastien Lecornu, announced the full deployment of Visio—the French government’s dedicated Meet platform—to all public servants. ([Source in English](https://www.nytimes.com/2026/01/29/world/europe/france-zoom-alternative-visio.html))
## Table of Contents
@@ -84,22 +95,57 @@ We use Kubernetes for our [production instance](https://visio.numerique.gouv.fr/
#### Known instances
We hope to see many more, here is an incomplete list of public La Suite Meet instances. Feel free to make a PR to add ones that are not listed below🙏
| [visio.numerique.gouv.fr](https://visio.numerique.gouv.fr/) | DINUM | French public agents working for the central administration and the extended public sphere. ProConnect is required to login in or sign up|
| [visio.suite.anct.gouv.fr](https://visio.suite.anct.gouv.fr/) | ANCT | French public agents working for the territorial administration and the extended public sphere. ProConnect is required to login in or sign up|
| [visio.lasuite.coop](https://visio.lasuite.coop/) | lasuite.coop | Free and open demo to all. Content and accounts are reset after one month |
| [mosacloud.cloud](https://mosa.cloud/) | mosa.cloud | Demo instance of mosa.cloud, a dutch company providing services around La Suite apps. |
| [visio.numerique.gouv.fr](https://visio.numerique.gouv.fr/) | DINUM | French public agents working for the central administration and the extended public sphere. ProConnect is required to login in or sign up|
| [visio.suite.anct.gouv.fr](https://visio.suite.anct.gouv.fr/) | ANCT | French public agents working for the territorial administration and the extended public sphere. ProConnect is required to login in or sign up|
| [visio.lasuite.coop](https://visio.lasuite.coop/) | lasuite.coop | Free and open demo to all. Content and accounts are reset after one month |
| [mosacloud.cloud](https://mosa.cloud/) | mosa.cloud | Demo instance of mosa.cloud, a dutch company providing services around La Suite apps. |
| [Clever Cloud](https://www.clever.cloud/product/visio/) | clever cloud | Openvisio is a sovereign video conferencing solution based on LaSuite Meet offered by [Clever Cloud](https://www.clever.cloud/). |
| [Email.eu](https://email.eu/) | Email.eu | Sovereign business workspace. |
## Contributing
# Contributing
We <3 contributions of any kind, big and small:
We <3 contributions of all kinds **big or small** and we’re genuinely glad you’re here. 🌱
- Vote on features or get early access to beta functionality in our [roadmap](https://github.com/orgs/suitenumerique/projects/11/views/4)
- Open a PR (see our instructions on [developing La Suite Meet locally](https://github.com/suitenumerique/meet/blob/main/docs/developping_locally.md))
- Submit a [feature request](https://github.com/suitenumerique/meet/issues/new?assignees=&labels=enhancement&template=Feature_request.md) or [bug report](https://github.com/suitenumerique/meet/issues/new?assignees=&labels=bug&template=Bug_report.md)
### Start by saying hi
**The best first contribution is simply to come say hi.**
Before opening a PR, especially a larger one, or one written with the help of AI, we encourage you to reach out to a maintainer on our [Matrix channel](https://matrix.to/#/#meet-official:matrix.org) (@antoine.lebaud:matrix.org).
Getting in touch early helps us align on goals, avoid duplicated or wasted effort, and build a community that stays active, welcoming, and fun to be part of. There are no silly questions here: whether you’ve shipped hundreds of PRs or you’re just getting started, you’re welcome.
### AI contributions
AI-assisted contributions are welcome. But code is never the end goal. What matters most is building relationships, sharing knowledge, and growing a sustainable community over time.
If your contribution has been heavily generated with AI, please be transparent about it. This helps maintainers review it with the right context and respects the time they invest in the project.
Using AI does not transfer ownership of the contribution: you should still fully understand the code, the problem it solves, and the reasoning behind the approach you propose. In short, even if AI helped write it, the why should still be yours.
### Contributions beyond code
**Not technical? We need you too.**
Open source is much more than code. Writing documentation, improving onboarding, translating content, answering questions, reporting bugs, or simply helping others feel welcome all make a huge difference.
### Ways to contribute
When you’re ready, here are a few ways to get involved:
* 👋 **Say hello** and share your ideas with the community and maintainers on our [Matrix channel](https://matrix.to/#/#meet-official:matrix.org)
* 🛠️ **Open a PR** by following our guide to [develop La Suite Meet locally](https://github.com/suitenumerique/meet/blob/main/docs/developping_locally.md)
* 💡 **Suggest an idea** by opening a [feature request](https://github.com/suitenumerique/meet/issues/new?assignees=&labels=enhancement&template=Feature_request.md)
* 🐛 **Report a bug** by opening a [bug report](https://github.com/suitenumerique/meet/issues/new?assignees=&labels=bug&template=Bug_report.md)
Thank you for helping build something open, useful, and human. 💙
### Community call
We host a community call on the first Friday of every month to share updates, discuss ideas, and connect with contributors.
Whether you’re actively contributing or just curious about the project, you’re welcome to join. More details are shared on the [Matrix channel](https://matrix.to/#/#meet-official:matrix.org).
@@ -15,3 +15,15 @@ the following command inside your docker container:
(Note : in your development environment, you can `make migrate`.)
## [Unreleased]
## v1.23.0
As part of the 1.23.0 release, the legacy `api/v1` implementation has been removed from the _experimental_ Summary service and Meet has been migrated to the new `api/v2`.
**To avoid a breaking change, the Meet backend continues to use the Summary service's v1-compatible API format by default (`SUMMARY_SERVICE_VERSION` setting defaults to `1`).**
If you are deploying both Meet and Summary from this repository, you must configure the Meet backend to use the v2 API by setting the following environment variable `SUMMARY_SERVICE_VERSION=2`.
If you are upgrading only the Meet deployment while keeping an older Summary v1 compatible deployment, no action is required, as the v1-compatible API remains the default.
Note that we plan on removing the legacy `v1` summary compatibility in a future major version. If you have your own implementation for the summary service, we recommend updating its API contract and setting `SUMMARY_SERVICE_VERSION=2`.
Your keycloak instance is now available on https://doc.yourdomain.tld
Your keycloak instance is now available on https://id.yourdomain.tld
> [!CAUTION]
> Version of the images are set to latest, you should pin it to the desired version to avoid unwanted upgrades when pulling latest image. You can find available versions on [Keycloak registry](https://quay.io/repository/keycloak/keycloak?tab=tags).
```
## Creating an OIDC Client for Meet Application
@@ -76,7 +75,7 @@ Your keycloak instance is now available on https://doc.yourdomain.tld
3. Enter the name of the realm - `meet`.
4. Click "Create".
#### Step 2: Create a New Client
### Step 2: Create a New Client
1. Navigate to the "Clients" tab.
2. Click on the "Create client" button.
@@ -86,7 +85,7 @@ Your keycloak instance is now available on https://doc.yourdomain.tld
1. Set the "Web Origins" to the URL of your meet application - e.g. `https://meet.example.com`.
1. Click "Save".
#### Step 3: Get Client Credentials
### Step 3: Get Client Credentials
1. Go to the "Credentials" tab.
2. Copy the client ID (`meet` in this example) and the client secret.
| **RECORDING_WORKER_CLASSES** | Dict | `{ "screen_recording": "core.recording.worker.services.VideoCompositeEgressService", "transcript": "core.recording.worker.services.AudioCompositeEgressService" }` | Maps recording types to their worker service classes. |
| **RECORDING_EVENT_PARSER_CLASS** | String | `"core.recording.event.parsers.MinioParser"` | Class responsible for parsing storage events and updating the backend. |
| **RECORDING_STORAGE_EVENT_ENABLE** | Boolean | `False` | Enable handling of storage events (must configure webhook in storage). |
| **RECORDING_STORAGE_EVENT_ENABLE** | Boolean | `False` | Enable handling of storage events (must configure webhook in storage). If `False`, fallback to LiveKit egress complete webhook. |
| **RECORDING_STORAGE_EVENT_TOKEN** | Secret/File | `None` | Token used to authenticate storage webhook requests, if `RECORDING_ENABLE_STORAGE_EVENT_AUTH` is enabled. |
| **RECORDING_EXPIRATION_DAYS** | Integer | `None` | Number of days before recordings expire. Should match bucket lifecycle policy. Set to `None` for no expiration. |
| **RECORDING_MAX_DURATION** | Integer | `None` | Maximum duration of a recording in milliseconds. Must be synced with the LiveKit Egress configuration. Set to None for unlimited duration. When the maximum duration is reached, the recording is automatically stopped and saved, and the user is prompted in the frontend with an alert message. |
| **RECORDING_ENCODING_ENABLED** | Boolean | `False` | When `False`, LiveKit Egress uses its built-in `H264_720P_30` preset. When `True`, the `RECORDING_ENCODING_*` values below are sent to LiveKit as advanced `EncodingOptions`. See [Tuning recording encoding](#tuning-recording-encoding). |
| **RECORDING_ENCODING_WIDTH** | Integer | `1280` | Recording video width in pixels. Only applied when `RECORDING_ENCODING_ENABLED` is `True`. |
| **RECORDING_ENCODING_HEIGHT** | Integer | `720` | Recording video height in pixels. Only applied when `RECORDING_ENCODING_ENABLED` is `True`. |
| **RECORDING_ENCODING_FRAMERATE** | Integer | `30` | Recording video framerate (fps). Directly impacts egress worker CPU (roughly linear). Only applied when `RECORDING_ENCODING_ENABLED` is `True`. |
| **RECORDING_ENCODING_VIDEO_BITRATE_KBPS** | Integer | `3000` | H.264 MAIN video bitrate in kbps. Only applied when `RECORDING_ENCODING_ENABLED` is `True`. |
| **RECORDING_ENCODING_AUDIO_BITRATE_KBPS** | Integer | `128` | AAC audio bitrate in kbps. Only applied when `RECORDING_ENCODING_ENABLED` is `True`. |
| **RECORDING_ENCODING_KEY_FRAME_INTERVAL_S** | Float | `4.0` | Keyframe interval in seconds. Drives seek granularity in the recorded MP4 (a player can only seek to keyframe boundaries). Larger values give the encoder slightly more bits for non-keyframe content at a fixed bitrate. `4.0` is a standard VOD value. Only applied when `RECORDING_ENCODING_ENABLED` is `True`. |
### Manual Storage Webhook
@@ -141,3 +148,54 @@ Using default project meet
This allows you to verify which recordings are in progress, troubleshoot egress issues, and confirm that recordings are being processed correctly.
## Tuning recording encoding
By default, LiveKit Egress records with the built-in `H264_720P_30` preset: 1280×720 at 30 fps, 3000 kbps H.264 MAIN video and 128 kbps AAC audio. For a one-hour meeting this produces a file of roughly **1.4 GB**, which is often heavier than necessary for talking-head content and screen sharing.
The `RECORDING_ENCODING_*` settings let operators override this preset without modifying the source. Values are passed straight through LiveKit's `EncodingOptions.advanced` to the GStreamer pipeline (`x264enc` for video, `faac` for audio), so there are no hidden conversions — what you set is what the encoder receives.
The H.264 profile is fixed to MAIN and the x264 `speed-preset` to `veryfast` by LiveKit (real-time constraint) — lowering the framerate is therefore the main lever to save CPU, while lowering the bitrate is the main lever to shrink the output file.
### Reference profiles
Rough 30-minute file-size estimates assume video + audio bitrate multiplied by duration. Actual sizes vary with content (static talking heads compress better than heavy screen motion). Egress CPU figures are indicative, measured on a single Ryzen laptop core saturated by the default preset (= 100 %); scaling is roughly linear with `framerate × bitrate` but the absolute numbers depend on the host hardware.
| Profile | Resolution | FPS | Video (kbps) | Audio (kbps) | Keyframe (s) | ~ size / 30 min | Egress CPU (vs. default) | Suitable for |
★ Recommended starting point for typical LaSuite Meet usage.
Environment variables for the **Low CPU / small file** profile:
```bash
RECORDING_ENCODING_ENABLED=True
RECORDING_ENCODING_WIDTH=1280
RECORDING_ENCODING_HEIGHT=720
RECORDING_ENCODING_FRAMERATE=15
RECORDING_ENCODING_VIDEO_BITRATE_KBPS=600
RECORDING_ENCODING_AUDIO_BITRATE_KBPS=64
RECORDING_ENCODING_KEY_FRAME_INTERVAL_S=4.0
```
### Caveats
- **Screen-share readability — think bits/frame, not bitrate**: at 720p, text legibility starts to break down below ~40 kbits/frame (= `bitrate ÷ framerate`). The recommended preset (600 kbps × 15 fps) sits at exactly that threshold, comfortable for talking heads with occasional slide sharing. The same 600 kbps at 30 fps would only deliver 20 kbits/frame and visibly blur dense slides — which is why **lowering framerate is a more screen-share-friendly lever than lowering bitrate**. For deck-heavy or IDE-share meetings, prefer the **Slide-heavy** profile (900 kbps × 15 fps ≈ 60 kbits/frame).
- **Motion handling**: the `veryfast` x264 preset is set by LiveKit and cannot be overridden here. Low-bitrate settings will therefore show more artefacts on fast motion than an offline re-encode with a slower preset would. This is the other reason FPS reduction is the safer tuning lever for meeting recordings.
- **Audio**: AAC at 64 kbps stereo is transparent for voice but starts to compress music noticeably. Keep 128 kbps if you expect music playback in meetings.
- **Codec choice**: H.264 MAIN is hardcoded on purpose. Switching to HEVC or VP9 would increase egress CPU cost 2×–5×, defeating the goal of this tuning.
@@ -87,12 +87,12 @@ If you are using an external service, you need to set `REDIS_URL` environment va
Generate a secure key for `LIVEKIT_API_SECRET` in `env.d/common`.
We provide a minimal recommanded config for production environment in `livekit-server.yaml`. Set the previously generated API secret key in the config file.
We provide a minimal recommended config for production environment in `livekit-server.yaml`. Set the previously generated API secret key in the config file.
To view other customization options, see [config-sample.yaml](https://github.com/livekit/livekit/blob/master/config-sample.yaml)
> [!NOTE]
> In this example, we configured multiplexing on a single UDP port. For better performances, you can configure a range of UDP ports.
> In this example, we configured multiplexing on a single UDP port. For better performance, you can configure a range of UDP ports.
@@ -122,11 +122,11 @@ If you haven't run the script **bin/start-kind.sh**, you'll need to manually cre
$ kubectl create namespace meet
```
If you have already run the script, you can skip this step and proceed to the next instruction. NOTE: Before you proceed, and is using the kind method, make sure you download this repo examples/ directory and its contents to the location where you will be executing the helm command. Helm will look for "examples/<name>values.yaml" from based on the path it is being executed.
If you have already run the script, you can skip this step and proceed to the next instruction. NOTE: Before you proceed, and is using the kind method, make sure you download this repo examples/helm directory and its contents to the location where you will be executing the helm command. Helm will look for "examples/helm/<name>values.yaml" from based on the path it is being executed.
LaSuite Meet uses a postgresql db as backend so if you have a provider, obtain the necessary information to use it. If you do not have, you can install a postgresql testing environment as follows:
@@ -11,14 +11,14 @@ There are two ways to customize LaSuite Meet:
### How to Use
To use this feature, simply set the `FRONTEND_CSS_URL` environment variable to the URL of your custom CSS file. For example:
To use this feature, simply set the `FRONTEND_CUSTOM_CSS_URL` environment variable (of the **backend** service) to the URL of your custom CSS file. For example:
> If you serve your CSS file on the same domain as LaSuite Meet, paths are supported, i.e. `FRONTEND_CSS_URL=/custom/style.css` will load `https://your-domain.com/custom/style.css`.
> If you serve your CSS file on the same domain as LaSuite Meet, paths are supported, i.e. `FRONTEND_CUSTOM_CSS_URL=/custom/style.css` will load `https://your-domain.com/custom/style.css`.
Setting this variable makes the app load your CSS at runtime, adding a `<link>` to `<head>` so you can override CSS variables and customize the frontend without rebuilding.
@@ -37,7 +37,7 @@ Let's say you want to change the font of our application to a custom font. You c
}
```
Then, set the `FRONTEND_CSS_URL` environment variable to the URL of your custom CSS file. Once you've done this, our application will load your custom CSS file and apply the styles, changing the default font to the one you specified.
Then, set the `FRONTEND_CUSTOM_CSS_URL` environment variable to the URL of your custom CSS file. Once you've done this, our application will load your custom CSS file and apply the styles, changing the default font to the one you specified.
> [!IMPORTANT]
> You can override any CSS token—semantic or palette. See [panda.config.ts](../src/frontend/panda.config.ts) for all defined semantic tokens.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.