mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-22 08:07:26 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d8ce399b06 |
@@ -8,10 +8,6 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- 📈(frontend) track errors when starting or stopping a recording
|
||||
|
||||
### Changed
|
||||
|
||||
- ✨(backend) accept form-urlencoded on the user token endpoint
|
||||
@@ -19,18 +15,11 @@ and this project adheres to
|
||||
- ⬆️(frontend) upgrade i18next and react-i18next patch versions
|
||||
- ⬆️(frontend) upgrade posthog-js from 1.395.0 to 1.404.1
|
||||
- ⬆️(frontend) upgrade livekit-client and @livekit/components-react
|
||||
- 💄(frontend) increase the blur intensity
|
||||
|
||||
### Fixed
|
||||
|
||||
- 📝(docs) fix minor typos in comments and docstrings
|
||||
- ⬆️(backend) bump sqlparse from 0.5.5 to 0.6.0
|
||||
- ⬆️(mail) bump @html-to/text-cli from 0.6.0 to 0.6.1
|
||||
- 🐛(frontend) treat client-initiated connect aborts as events
|
||||
- 🐛(frontend) use state instead of a ref for MoreControls container
|
||||
- 🐛(frontend) stop init_virtual_background from firing on blur updates
|
||||
- 🐛(frontend) hoist mute confirmation dialog to VideoConference level
|
||||
- 🐛(frontend) fix joined notification tile no longer rendering properly
|
||||
|
||||
## [1.27.0] - 2026-08-14
|
||||
|
||||
|
||||
+1
-1
@@ -180,7 +180,7 @@ services:
|
||||
working_dir: /app
|
||||
|
||||
node:
|
||||
image: node:22
|
||||
image: node:18
|
||||
user: "${DOCKER_USER:-1000}"
|
||||
environment:
|
||||
HOME: /tmp
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useToast } from 'react-aria'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { useRef } from 'react'
|
||||
import { Button as RACButton } from 'react-aria-components'
|
||||
import { Track } from 'livekit-client'
|
||||
import Source = Track.Source
|
||||
@@ -11,7 +11,6 @@ import { Div } from '@/primitives'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { StyledToastContainer } from './StyledToastContainer'
|
||||
import { setPinnedTrack } from '@/stores/layout'
|
||||
import { useParticipantTracks } from '@livekit/components-react'
|
||||
|
||||
const ClickableToast = styled(RACButton, {
|
||||
base: {
|
||||
@@ -31,18 +30,14 @@ export function ToastJoined({ state, ...props }: Readonly<ToastProps>) {
|
||||
)
|
||||
const participant = props.toast.content.participant
|
||||
|
||||
const [cameraTrack] = useParticipantTracks(
|
||||
[Source.Camera],
|
||||
participant?.identity
|
||||
)
|
||||
|
||||
const trackReference = useMemo(
|
||||
() => cameraTrack ?? { participant, source: Source.Camera },
|
||||
[cameraTrack, participant]
|
||||
)
|
||||
|
||||
if (!participant) return
|
||||
|
||||
const trackReference = {
|
||||
participant,
|
||||
publication: participant.getTrackPublication(Source.Camera),
|
||||
source: Source.Camera,
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledToastContainer {...toastProps} ref={ref}>
|
||||
<ClickableToast
|
||||
|
||||
+17
-2
@@ -1,9 +1,11 @@
|
||||
import { Participant, Track } from 'livekit-client'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useTrackMutedIndicator } from '@livekit/components-react'
|
||||
import { useMuteParticipant } from '@/features/rooms/api/muteParticipant'
|
||||
import { useState } from 'react'
|
||||
import { Button } from '@/primitives'
|
||||
import { RiMicLine, RiMicOffLine } from '@remixicon/react'
|
||||
import { openMuteDialog } from '@/stores/muteDialog'
|
||||
import { MuteAlertDialog } from '@/features/rooms/livekit/components/MuteAlertDialog'
|
||||
|
||||
export const MuteButton = ({ participant }: { participant: Participant }) => {
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'participantTileFocus' })
|
||||
@@ -13,18 +15,31 @@ export const MuteButton = ({ participant }: { participant: Participant }) => {
|
||||
source: Track.Source.Microphone,
|
||||
})
|
||||
|
||||
const { muteParticipant } = useMuteParticipant()
|
||||
const [isAlertOpen, setIsAlertOpen] = useState(false)
|
||||
|
||||
const name = participant.name || participant.identity
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
isDisabled={isMuted}
|
||||
size={'sm'}
|
||||
variant={'primaryTextDark'}
|
||||
square
|
||||
onPress={() => openMuteDialog(participant)}
|
||||
onPress={() => setIsAlertOpen(true)}
|
||||
tooltip={t('muteParticipant', { name })}
|
||||
>
|
||||
{!isMuted ? <RiMicLine /> : <RiMicOffLine />}
|
||||
</Button>
|
||||
<MuteAlertDialog
|
||||
isOpen={isAlertOpen}
|
||||
onSubmit={() =>
|
||||
muteParticipant(participant).then(() => setIsAlertOpen(false))
|
||||
}
|
||||
onClose={() => setIsAlertOpen(false)}
|
||||
name={name}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,11 +19,13 @@ import {
|
||||
import Source = Track.Source
|
||||
import { RiMicFill, RiMicOffFill } from '@remixicon/react'
|
||||
import { Button } from '@/primitives'
|
||||
import { useState } from 'react'
|
||||
import { useMuteParticipant } from '@/features/rooms/api/muteParticipant'
|
||||
import { useCanMute } from '@/features/rooms/livekit/hooks/useCanMute'
|
||||
import { ParticipantMenuButton } from './menu/ParticipantMenuButton'
|
||||
import { PinBadge } from './PinBadge'
|
||||
import { UnauthenticatedBadge } from './UnauthenticatedBadge'
|
||||
import { openMuteDialog } from '@/stores/muteDialog'
|
||||
import { MuteAlertDialog } from '@/features/rooms/livekit/components/MuteAlertDialog'
|
||||
import { ParticipantName } from './ParticipantName'
|
||||
|
||||
type MicIndicatorProps = {
|
||||
@@ -32,6 +34,7 @@ type MicIndicatorProps = {
|
||||
|
||||
const MicIndicator = ({ participant }: MicIndicatorProps) => {
|
||||
const { t } = useTranslation('rooms')
|
||||
const { muteParticipant } = useMuteParticipant()
|
||||
const { isMuted } = useTrackMutedIndicator({
|
||||
participant: participant,
|
||||
source: Source.Microphone,
|
||||
@@ -39,6 +42,7 @@ const MicIndicator = ({ participant }: MicIndicatorProps) => {
|
||||
|
||||
const canMute = useCanMute(participant)
|
||||
const isSpeaking = useIsSpeaking(participant)
|
||||
const [isAlertOpen, setIsAlertOpen] = useState(false)
|
||||
const name = participant.name || participant.identity
|
||||
|
||||
const label = isLocal(participant)
|
||||
@@ -48,6 +52,7 @@ const MicIndicator = ({ participant }: MicIndicatorProps) => {
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
square
|
||||
variant="greyscale"
|
||||
@@ -57,8 +62,10 @@ const MicIndicator = ({ participant }: MicIndicatorProps) => {
|
||||
isDisabled={isMuted || !canMute}
|
||||
onPress={async () =>
|
||||
!isMuted && isLocal(participant)
|
||||
? await (participant as LocalParticipant)?.setMicrophoneEnabled(false)
|
||||
: openMuteDialog(participant)
|
||||
? await (participant as LocalParticipant)?.setMicrophoneEnabled(
|
||||
false
|
||||
)
|
||||
: setIsAlertOpen(true)
|
||||
}
|
||||
data-attr="participants-mute"
|
||||
>
|
||||
@@ -76,6 +83,15 @@ const MicIndicator = ({ participant }: MicIndicatorProps) => {
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
<MuteAlertDialog
|
||||
isOpen={isAlertOpen}
|
||||
onSubmit={() =>
|
||||
muteParticipant(participant).then(() => setIsAlertOpen(false))
|
||||
}
|
||||
onClose={() => setIsAlertOpen(false)}
|
||||
name={name}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
import { useStartRecording, useStopRecording } from '@/features/recording'
|
||||
import { recordingStore } from '@/stores/recording'
|
||||
import { captureEvent } from '@/features/analytics/telemetry'
|
||||
|
||||
export const useMutateRecording = () => {
|
||||
const { mutateAsync: startRecording, isPending: isPendingToStart } =
|
||||
useStartRecording({
|
||||
onError: () => {
|
||||
recordingStore.isErrorDialogOpen = 'start'
|
||||
captureEvent('error-starting-recording')
|
||||
},
|
||||
})
|
||||
const { mutateAsync: stopRecording, isPending: isPendingToStop } =
|
||||
useStopRecording({
|
||||
onError: () => {
|
||||
recordingStore.isErrorDialogOpen = 'stop'
|
||||
captureEvent('error-stopping-recording')
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ import {
|
||||
usePersistentUserChoices,
|
||||
} from '@livekit/components-react'
|
||||
import {
|
||||
ConnectionError,
|
||||
ConnectionErrorReason,
|
||||
DisconnectReason,
|
||||
MediaDeviceFailure,
|
||||
Room,
|
||||
@@ -27,11 +25,7 @@ import { VideoConference } from '../livekit/prefabs/VideoConference'
|
||||
import { css } from '@/styled-system/css'
|
||||
import { BackgroundProcessorFactory } from '../livekit/components/blur'
|
||||
import { LocalUserChoices } from '@/stores/userChoices'
|
||||
import {
|
||||
captureEvent,
|
||||
captureMediaEvent,
|
||||
reportError,
|
||||
} from '@/features/analytics/telemetry'
|
||||
import { captureMediaEvent, reportError } from '@/features/analytics/telemetry'
|
||||
import { useConfig } from '@/api/useConfig'
|
||||
import { isFireFox } from '@/utils/livekit'
|
||||
import { useIsMobile } from '@/utils/useIsMobile'
|
||||
@@ -233,16 +227,6 @@ export const Conference = ({
|
||||
onError={(e) => {
|
||||
const failure = MediaDeviceFailure.getFailure(e)
|
||||
if (failure && failure !== MediaDeviceFailure.Other) return
|
||||
|
||||
// connect() was aborted by a disconnect() before the join completed
|
||||
if (
|
||||
e instanceof ConnectionError &&
|
||||
e.reason === ConnectionErrorReason.Cancelled
|
||||
) {
|
||||
void captureEvent('connection-cancelled')
|
||||
return
|
||||
}
|
||||
|
||||
reportError('livekit_room_error', e, {
|
||||
path: 'connect_publish',
|
||||
})
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { useRef } from 'react'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { useMuteParticipant } from '@/features/rooms/api/muteParticipant'
|
||||
import { closeMuteDialog, muteDialogStore } from '@/stores/muteDialog'
|
||||
import { MuteAlertDialog } from './MuteAlertDialog'
|
||||
|
||||
export const MuteAlertDialogProvider = () => {
|
||||
const { participant } = useSnapshot(muteDialogStore)
|
||||
const { muteParticipant } = useMuteParticipant()
|
||||
|
||||
const lastNameRef = useRef('')
|
||||
if (participant) {
|
||||
lastNameRef.current = participant.name || participant.identity
|
||||
}
|
||||
|
||||
return (
|
||||
<MuteAlertDialog
|
||||
isOpen={!!participant}
|
||||
name={lastNameRef.current}
|
||||
onClose={closeMuteDialog}
|
||||
onSubmit={() => {
|
||||
const target = muteDialogStore.participant
|
||||
if (!target) return
|
||||
muteParticipant(target).then(closeMuteDialog)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
+3
-1
@@ -105,7 +105,9 @@ export class BackgroundCustomProcessor implements BackgroundProcessorInterface {
|
||||
|
||||
_initVirtualBackgroundImage() {
|
||||
if (this.options.type !== 'virtual') {
|
||||
return
|
||||
throw new Error(
|
||||
'Virtual background is only supported for virtual background'
|
||||
)
|
||||
}
|
||||
|
||||
const needsUpdate =
|
||||
|
||||
@@ -37,8 +37,8 @@ import { reportError } from '@/features/analytics/telemetry'
|
||||
|
||||
enum BlurRadius {
|
||||
NONE = 0,
|
||||
LIGHT = 10,
|
||||
NORMAL = 20,
|
||||
LIGHT = 5,
|
||||
NORMAL = 10,
|
||||
}
|
||||
|
||||
const isSupported = BackgroundProcessorFactory.isSupported()
|
||||
|
||||
@@ -9,7 +9,7 @@ import { SubtitlesToggle } from '../../components/controls/SubtitlesToggle'
|
||||
import { OptionsButton } from '../../components/controls/Options/OptionsButton'
|
||||
import { StartMediaButton } from '../../components/controls/StartMediaButton'
|
||||
import { MoreOptions } from './MoreOptions'
|
||||
import { RefObject, useMemo, useState } from 'react'
|
||||
import { useRef } from 'react'
|
||||
import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut'
|
||||
import { useFullScreen } from '../../hooks/useFullScreen'
|
||||
import { VideoDeviceControl } from '../../components/controls/Device/VideoDeviceControl'
|
||||
@@ -21,14 +21,7 @@ export function DesktopControlBar({
|
||||
onDeviceError,
|
||||
}: Readonly<ControlBarAuxProps>) {
|
||||
const browserSupportsScreenSharing = supportsScreenSharing()
|
||||
|
||||
const [controlBarElement, setControlBarElement] =
|
||||
useState<HTMLDivElement | null>(null)
|
||||
|
||||
const desktopControlBarEl = useMemo<RefObject<HTMLDivElement>>(
|
||||
() => ({ current: controlBarElement }),
|
||||
[controlBarElement]
|
||||
)
|
||||
const desktopControlBarEl = useRef<HTMLDivElement>(null)
|
||||
|
||||
const { toggleFullScreen, isFullscreenAvailable } = useFullScreen({})
|
||||
|
||||
@@ -52,7 +45,7 @@ export function DesktopControlBar({
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={setControlBarElement}
|
||||
ref={desktopControlBarEl}
|
||||
className={css({
|
||||
width: '100vw',
|
||||
display: 'flex',
|
||||
|
||||
@@ -19,7 +19,6 @@ import { RoomMetadataSynchronizer } from '../components/RoomMetadataSynchronizer
|
||||
import { useNoiseReduction } from '../hooks/useNoiseReduction'
|
||||
import { VideoResolutionSubscription } from '../components/VideoResolutionSubscription'
|
||||
import { SettingsDialogProvider } from '@/features/settings/components/SettingsDialogProvider'
|
||||
import { MuteAlertDialogProvider } from '@/features/rooms/livekit/components/MuteAlertDialogProvider'
|
||||
import { IsIdleDisconnectModal } from '../components/IsIdleDisconnectModal'
|
||||
import { ReactionPortals } from '@/features/reactions/components/ReactionPortals'
|
||||
import { RoomContentArea } from '@/features/layout/components/RoomContentArea'
|
||||
@@ -145,7 +144,6 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
|
||||
<ConnectionStateToast />
|
||||
<RecordingProvider />
|
||||
<SettingsDialogProvider />
|
||||
<MuteAlertDialogProvider />
|
||||
<ReactionPortals />
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { proxy, ref } from 'valtio'
|
||||
import type { Participant } from 'livekit-client'
|
||||
|
||||
type State = {
|
||||
participant: Participant | null
|
||||
}
|
||||
|
||||
export const muteDialogStore = proxy<State>({
|
||||
participant: null,
|
||||
})
|
||||
|
||||
export const openMuteDialog = (participant: Participant) => {
|
||||
muteDialogStore.participant = ref(participant)
|
||||
}
|
||||
|
||||
export const closeMuteDialog = () => {
|
||||
muteDialogStore.participant = null
|
||||
}
|
||||
Generated
+8
-18
@@ -9,7 +9,7 @@
|
||||
"version": "1.27.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@html-to/text-cli": "0.6.1",
|
||||
"@html-to/text-cli": "0.6.0",
|
||||
"mjml": "5.4.0"
|
||||
}
|
||||
},
|
||||
@@ -52,14 +52,14 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@html-to/text-cli": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@html-to/text-cli/-/text-cli-0.6.1.tgz",
|
||||
"integrity": "sha512-fnbLS8bra4BkGinXWzUKfalqLPYmz8E2ABT+TgHUZ4inhAUYF2rBEEctOKqZ6FgaJF1iZ//KBBLKVz6nm3RbhA==",
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@html-to/text-cli/-/text-cli-0.6.0.tgz",
|
||||
"integrity": "sha512-JwlrCBccUM/QkUpc37P+qG+hpkXRbWOVcHd9vM+5D+O82Ak2p8anGRxisr33//aJzlkYNKNg2I8LDh3Bx2tBPg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@selderee/plugin-htmlparser2": "~0.12.0",
|
||||
"aspargvs": "~0.7.0",
|
||||
"deepmerge-ts": "^8.0.1",
|
||||
"deepmerge-ts": "^7.1.5",
|
||||
"htmlparser2": "^10.1.0",
|
||||
"selderee": "~0.12.0"
|
||||
},
|
||||
@@ -656,19 +656,9 @@
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/deepmerge-ts": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-8.0.1.tgz",
|
||||
"integrity": "sha512-szCXE7YLCvLKR9bFPJcvsezOShdalctSvrgN/LM/QGUEPZQajwjmsMObZ6/DuANT5lxzM/wtO8Feubwdkz8myA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "ko-fi",
|
||||
"url": "https://ko-fi.com/rebeccastevens"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/deepmerge-ts"
|
||||
}
|
||||
],
|
||||
"version": "7.1.5",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz",
|
||||
"integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "An util to generate html and text django's templates from mjml templates",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@html-to/text-cli": "0.6.1",
|
||||
"@html-to/text-cli": "0.6.0",
|
||||
"mjml": "5.4.0"
|
||||
},
|
||||
"private": true,
|
||||
|
||||
Reference in New Issue
Block a user