Compare commits

..

1 Commits

Author SHA1 Message Date
lebaudantoine f4c315bdbf wip connection tester updated 2026-08-25 23:14:05 +02:00
28 changed files with 197 additions and 191 deletions
-1
View File
@@ -75,7 +75,6 @@ db.sqlite3
# IDEs # IDEs
.idea/ .idea/
.vscode/ .vscode/
.cursor/
*.iml *.iml
.devcontainer .devcontainer
+1 -7
View File
@@ -10,12 +10,6 @@ and this project adheres to
### Added ### Added
- ✨(frontend) share OneToOneFocusLayout between PiP and main room
## [1.29.0] - 2026-08-25
### Added
- ✨(any) let any authenticated user manage the lobby on trusted rooms - ✨(any) let any authenticated user manage the lobby on trusted rooms
### Changed ### Changed
@@ -30,7 +24,6 @@ and this project adheres to
- ⬆️(frontend) upgrade posthog-js from 1.404.1 to 1.409.5 - ⬆️(frontend) upgrade posthog-js from 1.404.1 to 1.409.5
- ⚡️(frontend) apply frugal constraint to the active meeting audio track - ⚡️(frontend) apply frugal constraint to the active meeting audio track
- ⚡️(backend) replace blocking Redis KEYS with cursor-based SCAN - ⚡️(backend) replace blocking Redis KEYS with cursor-based SCAN
- ✨(summary) add hostname to analytics properties
## [1.28.0] - 2026-08-24 ## [1.28.0] - 2026-08-24
@@ -69,6 +62,7 @@ and this project adheres to
- 🔥(frontend) drop unused vendored ConnectionObserver - 🔥(frontend) drop unused vendored ConnectionObserver
- 🐛(frontend) vendor formatChatMessageLinks and trim surrounding newlines - 🐛(frontend) vendor formatChatMessageLinks and trim surrounding newlines
- ✨(summary) add hostname to analytics properties
### Fixed ### Fixed
+1 -1
View File
@@ -1,7 +1,7 @@
[project] [project]
name = "agents" name = "agents"
version = "1.29.0" version = "1.28.0"
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = [ dependencies = [
"livekit-agents==1.6.7", "livekit-agents==1.6.7",
+1 -1
View File
@@ -9,7 +9,7 @@ resolution-markers = [
[[package]] [[package]]
name = "agents" name = "agents"
version = "1.29.0" version = "1.28.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "livekit-agents" }, { name = "livekit-agents" },
+1 -1
View File
@@ -7,7 +7,7 @@ build-backend = "uv_build"
[project] [project]
name = "meet" name = "meet"
version = "1.29.0" version = "1.28.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }] authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [ classifiers = [
"Development Status :: 5 - Production/Stable", "Development Status :: 5 - Production/Stable",
+1 -1
View File
@@ -1187,7 +1187,7 @@ wheels = [
[[package]] [[package]]
name = "meet" name = "meet"
version = "1.29.0" version = "1.28.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "aiohttp" }, { name = "aiohttp" },
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "meet", "name": "meet",
"version": "1.29.0", "version": "1.28.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "meet", "name": "meet",
"version": "1.29.0", "version": "1.28.0",
"dependencies": { "dependencies": {
"@fontsource-variable/atkinson-hyperlegible-next": "5.3.0", "@fontsource-variable/atkinson-hyperlegible-next": "5.3.0",
"@fontsource-variable/lexend": "5.2.11", "@fontsource-variable/lexend": "5.2.11",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "meet", "name": "meet",
"private": true, "private": true,
"version": "1.29.0", "version": "1.28.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "panda codegen && vite", "dev": "panda codegen && vite",
@@ -42,6 +42,23 @@ export type IceCandidateReport = {
working: IceCandidatePair[] working: IceCandidatePair[]
} }
/**
* True when the selected pair does not carry media over plain UDP: the browser
* fell back to a TURN relay over TCP or TLS, or even the direct route is not
* UDP. Media still flows, but quality usually degrades under load.
*
* Accepts the loosely typed `data` stored on the step result; anything that is
* not an IceCandidateReport simply yields false.
*/
export const isSuboptimalRoute = (data: unknown): boolean => {
const selected = (data as IceCandidateReport | null | undefined)?.selected
if (!selected) return false
const transport = selected.local.relayProtocol ?? selected.local.protocol
// An unreported transport is not evidence of a bad route.
if (!transport) return false
return transport.toLowerCase() !== 'udp'
}
const PROBE_WIDTH = 320 const PROBE_WIDTH = 320
const PROBE_HEIGHT = 180 const PROBE_HEIGHT = 180
const PROBE_FPS = 15 const PROBE_FPS = 15
@@ -2,18 +2,33 @@ import type { ReactNode } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { ProgressBar } from 'react-aria-components' import { ProgressBar } from 'react-aria-components'
import { css, cx } from '@/styled-system/css' import { css, cx } from '@/styled-system/css'
import { A } from '@/primitives'
import type { ConnectionTestStats } from '../types' import type { ConnectionTestStats } from '../types'
import { statusSquareClass } from './stepAppearance' import { statusSquareClass } from './stepAppearance'
type SummaryState = 'idle' | 'running' | 'passed' | 'partial' | 'failed' /**
* Network prerequisites (protocols, ports, TURN fallbacks), written for the
* reader's IT department rather than for the end user.
*/
const NETWORK_REQUIREMENTS_DOC_URL =
'https://docs.numerique.gouv.fr/docs/f2baa1b9-f29e-4d58-959d-65d4376fc6b8/'
/** Only a failure earns a colour: everything else stays near-black. */ type SummaryState =
| 'idle'
| 'running'
| 'passed'
| 'partial'
| 'failed'
| 'warning'
/** Only a failure or a degraded route earns a colour: everything else stays near-black. */
const stateColorClass: Record<SummaryState, string> = { const stateColorClass: Record<SummaryState, string> = {
idle: css({ color: 'greyscale.1000' }), idle: css({ color: 'greyscale.1000' }),
running: css({ color: 'greyscale.1000' }), running: css({ color: 'greyscale.1000' }),
passed: css({ color: 'greyscale.1000' }), passed: css({ color: 'greyscale.1000' }),
partial: css({ color: 'greyscale.1000' }), partial: css({ color: 'greyscale.1000' }),
failed: css({ color: 'danger.600' }), failed: css({ color: 'danger.600' }),
warning: css({ color: 'warning' }),
} }
const cardClass = css({ const cardClass = css({
@@ -176,23 +191,31 @@ const Counter = ({
export const ConnectionTestSummary = ({ export const ConnectionTestSummary = ({
stats, stats,
isRunning, isRunning,
routeWarning = false,
children, children,
}: { }: {
stats: ConnectionTestStats stats: ConnectionTestStats
isRunning: boolean isRunning: boolean
/** The selected ICE route is usable but not UDP (e.g. TURN over TCP/TLS). */
routeWarning?: boolean
children?: ReactNode children?: ReactNode
}) => { }) => {
const { t } = useTranslation('connectionTest') const { t } = useTranslation('connectionTest')
// A hard failure still outranks the route warning; the warning outranks
// 'partial' because a measured degraded route matters more than skipped
// camera or microphone checks.
const state: SummaryState = isRunning const state: SummaryState = isRunning
? 'running' ? 'running'
: !stats.hasStarted : !stats.hasStarted
? 'idle' ? 'idle'
: stats.failed > 0 : stats.failed > 0
? 'failed' ? 'failed'
: stats.skipped > 0 : routeWarning
? 'partial' ? 'warning'
: 'passed' : stats.skipped > 0
? 'partial'
: 'passed'
return ( return (
<section className={cardClass}> <section className={cardClass}>
@@ -206,7 +229,22 @@ export const ConnectionTestSummary = ({
: t(`summary.${state}`)} : t(`summary.${state}`)}
</p> </p>
<p className={hintClass}>{t(`summary.${state}Hint`)}</p> <p className={hintClass}>
{t(`summary.${state}Hint`)}
{state === 'warning' && (
<>
{' '}
<A
href={NETWORK_REQUIREMENTS_DOC_URL}
target="_blank"
rel="noreferrer"
size="sm"
>
{t('summary.warningDocLink')}
</A>
</>
)}
</p>
</div> </div>
{stats.hasStarted && ( {stats.hasStarted && (
@@ -53,6 +53,9 @@ const fromCheckInfo = (info: CheckInfo): Partial<ConnectionTestStepResult> => ({
status: CHECK_STATUS_TO_STEP[info.status] ?? 'failed', status: CHECK_STATUS_TO_STEP[info.status] ?? 'failed',
summary: info.description, summary: info.description,
logs: info.logs, logs: info.logs,
// Only SelectedCandidateCheck sets `data` (the ICE candidate report); for
// the stock LiveKit checks it is undefined and this is a no-op.
data: info.data as Record<string, unknown> | undefined,
}) })
const groupDevicesByKind = (devices: MediaDeviceInfo[]) => { const groupDevicesByKind = (devices: MediaDeviceInfo[]) => {
@@ -13,6 +13,7 @@ import { css } from '@/styled-system/css'
import { Center, VStack } from '@/styled-system/jsx' import { Center, VStack } from '@/styled-system/jsx'
import { Permissions } from '@/features/rooms/components/Permissions' import { Permissions } from '@/features/rooms/components/Permissions'
import { useConnectionTestRunner } from '../hooks/useConnectionTestRunner' import { useConnectionTestRunner } from '../hooks/useConnectionTestRunner'
import { isSuboptimalRoute } from '../checks/selectedCandidate'
import { ConnectionTestStepRow } from '../components/ConnectionTestStepRow' import { ConnectionTestStepRow } from '../components/ConnectionTestStepRow'
import { ConnectionTestSummary } from '../components/ConnectionTestSummary' import { ConnectionTestSummary } from '../components/ConnectionTestSummary'
import { CONNECTION_TEST_GROUPS, summarizeSteps } from '../types' import { CONNECTION_TEST_GROUPS, summarizeSteps } from '../types'
@@ -70,6 +71,13 @@ const ConnectionTest = () => {
() => new Map(steps.map((step) => [step.id, step] as const)), () => new Map(steps.map((step) => [step.id, step] as const)),
[steps] [steps]
) )
// The summary headline downgrades to a warning when media flows but over a
// fallback route: TURN over TCP/TLS, or anything else that is not UDP.
const routeWarning = useMemo(() => {
const step = stepsById.get('selectedCandidate')
return step?.status === 'success' && isSuboptimalRoute(step.data)
}, [stepsById])
const isPublishVideoRunning = const isPublishVideoRunning =
stepsById.get('publishVideo')?.status === 'running' stepsById.get('publishVideo')?.status === 'running'
@@ -99,7 +107,11 @@ const ConnectionTest = () => {
<CenteredContent withBackButton> <CenteredContent withBackButton>
<Center> <Center>
<VStack gap="1.5rem" maxWidth="40rem" width="100%"> <VStack gap="1.5rem" maxWidth="40rem" width="100%">
<ConnectionTestSummary stats={stats} isRunning={isRunning}> <ConnectionTestSummary
stats={stats}
isRunning={isRunning}
routeWarning={routeWarning}
>
{isRunning ? ( {isRunning ? (
// A disabled "run" button while the test runs is dead weight: // A disabled "run" button while the test runs is dead weight:
// cancelling is the only thing left to do. // cancelling is the only thing left to do.
@@ -1,119 +0,0 @@
import { memo } from 'react'
import type { TrackReferenceOrPlaceholder } from '@livekit/components-core'
import { styled } from '@/styled-system/jsx'
import { cva } from '@/styled-system/css'
import { ParticipantTile } from '@/features/participantTile/components/ParticipantTile'
import { getTrackKey } from '@/features/layout/utils/trackSelection'
type OneToOneFocusLayoutProps = {
mainTrack?: TrackReferenceOrPlaceholder
thumbnailTrack?: TrackReferenceOrPlaceholder
disableTileControls?: boolean
/** Controls thumbnail dimensions 'pip' for small PiP window, 'room' for the main viewport. */
context?: 'pip' | 'room'
}
/**
* Focus layout for 1-to-1 calls: one main tile filling the area (letterboxed)
* with an optional thumbnail overlay at the bottom-right.
*
* Shared between PiP and the main room pass `disableTileControls` in PiP
* where hover controls should be hidden.
*/
export const OneToOneFocusLayout = memo(
({
mainTrack,
thumbnailTrack,
disableTileControls,
context = 'room',
}: OneToOneFocusLayoutProps) => {
return (
<FocusContainer>
{mainTrack && (
<MainSlot>
<ParticipantTile
key={getTrackKey(mainTrack)}
trackRef={mainTrack}
disableTileControls={disableTileControls}
/>
</MainSlot>
)}
{thumbnailTrack && (
<Thumbnail context={context}>
<ParticipantTile
key={getTrackKey(thumbnailTrack)}
trackRef={thumbnailTrack}
disableTileControls={disableTileControls}
/>
</Thumbnail>
)}
</FocusContainer>
)
}
)
OneToOneFocusLayout.displayName = 'OneToOneFocusLayout'
const FocusContainer = styled('div', {
base: {
position: 'relative',
width: '100%',
height: '100%',
borderRadius: '8px',
overflow: 'hidden',
backgroundColor: 'primaryDark.100',
boxSizing: 'border-box',
},
})
const MainSlot = styled('div', {
base: {
width: '100%',
height: '100%',
borderRadius: '8px',
overflow: 'hidden',
'& .lk-participant-tile': {
width: '100%',
height: '100%',
},
'& .lk-participant-media-video': {
objectFit: 'contain',
},
},
})
const Thumbnail = styled(
'div',
cva({
base: {
position: 'absolute',
right: '1.25rem',
bottom: '1.25rem',
aspectRatio: '16 / 9',
borderRadius: '8px',
overflow: 'hidden',
boxShadow: 'md',
zIndex: 2,
'& .lk-participant-tile': {
width: '100%',
height: '100%',
},
},
variants: {
context: {
pip: {
width: '42%',
maxWidth: '220px',
minWidth: '140px',
},
room: {
width: '20%',
maxWidth: '320px',
minWidth: '180px',
},
},
},
defaultVariants: {
context: 'room',
},
})
)
@@ -12,8 +12,7 @@ import {
import { Track } from 'livekit-client' import { Track } from 'livekit-client'
import { useSnapshot } from 'valtio' import { useSnapshot } from 'valtio'
import { clearPinnedTrack, layoutStore, setPinnedTrack } from '@/stores/layout' import { clearPinnedTrack, layoutStore, setPinnedTrack } from '@/stores/layout'
import { useEffect, useMemo, useRef } from 'react' import { useEffect, useRef } from 'react'
import { OneToOneFocusLayout } from '@/features/layout/components/OneToOneFocusLayout'
export const StageLayout = () => { export const StageLayout = () => {
const lastAutoFocusedScreenShareTrack = const lastAutoFocusedScreenShareTrack =
@@ -37,29 +36,6 @@ export const StageLayout = () => {
(track) => !isEqualTrackRef(track, pinnedTrackRef) (track) => !isEqualTrackRef(track, pinnedTrackRef)
) )
const cameraTracks = useMemo(
() => tracks.filter((t) => t.source === Track.Source.Camera),
[tracks]
)
const isOneToOne =
!pinnedTrackRef &&
screenShareTracks.length === 0 &&
cameraTracks.length <= 2
const oneToOneMainTrack = useMemo(() => {
if (!isOneToOne) return undefined
const remote = cameraTracks.find((t) => !t.participant?.isLocal)
const local = cameraTracks.find((t) => t.participant?.isLocal)
return remote ?? local
}, [isOneToOne, cameraTracks])
const oneToOneThumbnailTrack = useMemo(() => {
if (!isOneToOne) return undefined
const local = cameraTracks.find((t) => t.participant?.isLocal)
return oneToOneMainTrack === local ? undefined : local
}, [isOneToOne, cameraTracks, oneToOneMainTrack])
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
// Code duplicated from LiveKit; this warning will be addressed in the refactoring. // Code duplicated from LiveKit; this warning will be addressed in the refactoring.
useEffect(() => { useEffect(() => {
@@ -111,12 +87,7 @@ export const StageLayout = () => {
return ( return (
<> <>
{isOneToOne ? ( {!pinnedTrackRef ? (
<OneToOneFocusLayout
mainTrack={oneToOneMainTrack}
thumbnailTrack={oneToOneThumbnailTrack}
/>
) : !pinnedTrackRef ? (
<div className="lk-grid-layout-wrapper" style={{ height: 'auto' }}> <div className="lk-grid-layout-wrapper" style={{ height: 'auto' }}>
<GridLayout tracks={tracks} style={{ padding: 0 }}> <GridLayout tracks={tracks} style={{ padding: 0 }}>
<ParticipantTile /> <ParticipantTile />
@@ -0,0 +1,86 @@
import { memo } from 'react'
import type { TrackReferenceOrPlaceholder } from '@livekit/components-core'
import { styled } from '@/styled-system/jsx'
import { ParticipantTile } from '@/features/participantTile/components/ParticipantTile'
import { getTrackKey } from '../../utils/pipTrackSelection'
type PipFocusLayoutProps = {
mainTrack?: TrackReferenceOrPlaceholder
thumbnailTrack?: TrackReferenceOrPlaceholder
}
/**
* Focus layout used when 1-2 tracks are visible in the PiP window.
*
* The main tile is letterboxed (object-fit: contain) so the camera is
* never stretched to a non-video aspect and leaves dark padding
* above/below when the window shape doesn't match the source.
* The thumbnail keeps the usual cover fill.
*/
export const PipFocusLayout = memo(
({ mainTrack, thumbnailTrack }: PipFocusLayoutProps) => {
return (
<FocusContainer>
{mainTrack && (
<MainSlot>
<ParticipantTile
key={getTrackKey(mainTrack)}
trackRef={mainTrack}
disableTileControls
/>
</MainSlot>
)}
{thumbnailTrack && (
<Thumbnail>
<ParticipantTile
key={getTrackKey(thumbnailTrack)}
trackRef={thumbnailTrack}
disableTileControls
/>
</Thumbnail>
)}
</FocusContainer>
)
}
)
PipFocusLayout.displayName = 'PipFocusLayout'
const FocusContainer = styled('div', {
base: {
position: 'relative',
width: '100%',
height: '100%',
borderRadius: '8px',
overflow: 'hidden',
backgroundColor: 'primaryDark.100',
boxSizing: 'border-box',
},
})
const MainSlot = styled('div', {
base: {
width: '100%',
height: '100%',
borderRadius: '8px',
overflow: 'hidden',
'& .lk-participant-media-video': {
objectFit: 'contain',
},
},
})
const Thumbnail = styled('div', {
base: {
position: 'absolute',
right: '1.25rem',
bottom: '1.25rem',
width: '42%',
maxWidth: '220px',
minWidth: '140px',
aspectRatio: '16 / 9',
borderRadius: '8px',
overflow: 'hidden',
boxShadow: 'md',
zIndex: 2,
},
})
@@ -4,7 +4,7 @@ import { styled } from '@/styled-system/jsx'
import { ParticipantTile } from '@/features/participantTile/components/ParticipantTile' import { ParticipantTile } from '@/features/participantTile/components/ParticipantTile'
import { usePipElementSize } from '../../hooks/usePipElementSize' import { usePipElementSize } from '../../hooks/usePipElementSize'
import { computePipGridLayout } from '../../utils/pipGrid' import { computePipGridLayout } from '../../utils/pipGrid'
import { getTrackKey } from '@/features/layout/utils/trackSelection' import { getTrackKey } from '../../utils/pipTrackSelection'
type PipGridLayoutProps = { type PipGridLayoutProps = {
tracks: TrackReferenceOrPlaceholder[] tracks: TrackReferenceOrPlaceholder[]
@@ -2,7 +2,7 @@ import { memo } from 'react'
import type { TrackReferenceOrPlaceholder } from '@livekit/components-core' import type { TrackReferenceOrPlaceholder } from '@livekit/components-core'
import { styled } from '@/styled-system/jsx' import { styled } from '@/styled-system/jsx'
import { ParticipantTile } from '@/features/participantTile/components/ParticipantTile' import { ParticipantTile } from '@/features/participantTile/components/ParticipantTile'
import { getTrackKey } from '@/features/layout/utils/trackSelection' import { getTrackKey } from '../../utils/pipTrackSelection'
type PipScreenShareLayoutProps = { type PipScreenShareLayoutProps = {
screenShareTrack: TrackReferenceOrPlaceholder screenShareTrack: TrackReferenceOrPlaceholder
@@ -2,7 +2,7 @@ import React, { useMemo } from 'react'
import { usePagination, useTracks } from '@livekit/components-react' import { usePagination, useTracks } from '@livekit/components-react'
import { RoomEvent, Track } from 'livekit-client' import { RoomEvent, Track } from 'livekit-client'
import { styled } from '@/styled-system/jsx' import { styled } from '@/styled-system/jsx'
import { OneToOneFocusLayout } from '@/features/layout/components/OneToOneFocusLayout' import { PipFocusLayout } from './PipFocusLayout'
import { PipGridLayout } from './PipGridLayout' import { PipGridLayout } from './PipGridLayout'
import { PipPagination } from './PipPagination' import { PipPagination } from './PipPagination'
import { PipScreenShareLayout } from './PipScreenShareLayout' import { PipScreenShareLayout } from './PipScreenShareLayout'
@@ -59,11 +59,9 @@ export const PipStage = () => {
if (cameraTracks.length <= 1) { if (cameraTracks.length <= 1) {
return ( return (
<StageFrame> <StageFrame>
<OneToOneFocusLayout <PipFocusLayout
mainTrack={screenShareTrack} mainTrack={screenShareTrack}
thumbnailTrack={cameraTracks[0]} thumbnailTrack={cameraTracks[0]}
disableTileControls
context="pip"
/> />
</StageFrame> </StageFrame>
) )
@@ -101,12 +99,7 @@ export const PipStage = () => {
return ( return (
<StageFrame> <StageFrame>
<OneToOneFocusLayout <PipFocusLayout mainTrack={mainTrack} thumbnailTrack={thumbnailTrack} />
mainTrack={mainTrack}
thumbnailTrack={thumbnailTrack}
disableTileControls
context="pip"
/>
</StageFrame> </StageFrame>
) )
} }
@@ -46,6 +46,9 @@
"passedHint": "Ihr Browser, Ihre Geräte und Ihr Netzwerk sind für eine Besprechung bereit.", "passedHint": "Ihr Browser, Ihre Geräte und Ihr Netzwerk sind für eine Besprechung bereit.",
"partial": "Teilweiser Test", "partial": "Teilweiser Test",
"partialHint": "Einige Prüfungen wurden übersprungen. Erlauben Sie den Zugriff auf Ihre Kamera und Ihr Mikrofon, um diese zu testen.", "partialHint": "Einige Prüfungen wurden übersprungen. Erlauben Sie den Zugriff auf Ihre Kamera und Ihr Mikrofon, um diese zu testen.",
"warning": "Verbindung nicht optimal",
"warningHint": "Achtung: Sie befinden sich nicht in optimalen Bedingungen für die Nutzung des Tools. Ihre Medien werden nicht über UDP übertragen (Rückgriff auf ein TURN-Relay über TCP oder TLS), was die Audio- und Videoqualität beeinträchtigen kann.",
"warningDocLink": "Technische Dokumentation für Ihre IT-Abteilung",
"failed_one": "{{count}} Prüfung fehlgeschlagen", "failed_one": "{{count}} Prüfung fehlgeschlagen",
"failed_other": "{{count}} Prüfungen fehlgeschlagen", "failed_other": "{{count}} Prüfungen fehlgeschlagen",
"failedHint": "Öffnen Sie die fehlgeschlagenen Prüfungen für weitere Details und senden Sie den Bericht an Ihre IT-Abteilung." "failedHint": "Öffnen Sie die fehlgeschlagenen Prüfungen für weitere Details und senden Sie den Bericht an Ihre IT-Abteilung."
@@ -46,6 +46,9 @@
"passedHint": "Your browser, your devices and your network are ready for a meeting.", "passedHint": "Your browser, your devices and your network are ready for a meeting.",
"partial": "Partially tested", "partial": "Partially tested",
"partialHint": "Some checks were skipped. Allow access to your camera and microphone to test them.", "partialHint": "Some checks were skipped. Allow access to your camera and microphone to test them.",
"warning": "Suboptimal connection",
"warningHint": "Warning: you are not in optimal conditions to use the tool. Your media is not carried over UDP (it falls back to a TURN relay over TCP or TLS), which can degrade audio and video quality.",
"warningDocLink": "Technical documentation for your IT department",
"failed_one": "{{count}} check failed", "failed_one": "{{count}} check failed",
"failed_other": "{{count}} checks failed", "failed_other": "{{count}} checks failed",
"failedHint": "Open the failed checks below for details, then send the report to your IT department." "failedHint": "Open the failed checks below for details, then send the report to your IT department."
@@ -46,6 +46,9 @@
"passedHint": "Votre navigateur, vos périphériques et votre réseau sont prêts pour une réunion.", "passedHint": "Votre navigateur, vos périphériques et votre réseau sont prêts pour une réunion.",
"partial": "Test partiel", "partial": "Test partiel",
"partialHint": "Certaines vérifications ont été ignorées. Autorisez l'accès à votre caméra et à votre microphone pour les tester.", "partialHint": "Certaines vérifications ont été ignorées. Autorisez l'accès à votre caméra et à votre microphone pour les tester.",
"warning": "Connexion non optimale",
"warningHint": "Attention : vous n'êtes pas dans les conditions optimales pour accéder à l'outil. Vos flux ne transitent pas en UDP (repli sur un relais TURN en TCP ou TLS), ce qui peut dégrader la qualité audio et vidéo.",
"warningDocLink": "Documentation technique à destination de votre service informatique",
"failed_one": "{{count}} vérification en échec", "failed_one": "{{count}} vérification en échec",
"failed_other": "{{count}} vérifications en échec", "failed_other": "{{count}} vérifications en échec",
"failedHint": "Ouvrez les vérifications en échec pour voir le détail, puis transmettez le rapport à votre service informatique." "failedHint": "Ouvrez les vérifications en échec pour voir le détail, puis transmettez le rapport à votre service informatique."
@@ -46,6 +46,9 @@
"passedHint": "Je browser, apparaten en netwerk zijn klaar voor een vergadering.", "passedHint": "Je browser, apparaten en netwerk zijn klaar voor een vergadering.",
"partial": "Gedeeltelijke test", "partial": "Gedeeltelijke test",
"partialHint": "Sommige controles zijn overgeslagen. Geef toegang tot je camera en microfoon om deze te testen.", "partialHint": "Sommige controles zijn overgeslagen. Geef toegang tot je camera en microfoon om deze te testen.",
"warning": "Verbinding niet optimaal",
"warningHint": "Let op: je bevindt je niet in optimale omstandigheden om de tool te gebruiken. Je media loopt niet via UDP (terugval op een TURN-relay via TCP of TLS), wat de audio- en videokwaliteit kan verminderen.",
"warningDocLink": "Technische documentatie voor je IT-afdeling",
"failed_one": "{{count}} controle mislukt", "failed_one": "{{count}} controle mislukt",
"failed_other": "{{count}} controles mislukt", "failed_other": "{{count}} controles mislukt",
"failedHint": "Open de mislukte controles voor meer details en stuur het rapport door naar je IT-afdeling." "failedHint": "Open de mislukte controles voor meer details en stuur het rapport door naar je IT-afdeling."
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "mail_mjml", "name": "mail_mjml",
"version": "1.29.0", "version": "1.28.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mail_mjml", "name": "mail_mjml",
"version": "1.29.0", "version": "1.28.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@html-to/text-cli": "0.6.1", "@html-to/text-cli": "0.6.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "mail_mjml", "name": "mail_mjml",
"version": "1.29.0", "version": "1.28.0",
"description": "An util to generate html and text django's templates from mjml templates", "description": "An util to generate html and text django's templates from mjml templates",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "sdk", "name": "sdk",
"version": "1.29.0", "version": "1.28.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "sdk", "name": "sdk",
"version": "1.29.0", "version": "1.28.0",
"license": "ISC", "license": "ISC",
"workspaces": [ "workspaces": [
"./library", "./library",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "sdk", "name": "sdk",
"version": "1.29.0", "version": "1.28.0",
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"description": "", "description": "",
+1 -1
View File
@@ -1,7 +1,7 @@
[project] [project]
name = "summary" name = "summary"
version = "1.29.0" version = "1.28.0"
dependencies = [ dependencies = [
"fastapi[standard]>=0.105.0", "fastapi[standard]>=0.105.0",
"uvicorn>=0.24.0", "uvicorn>=0.24.0",