Compare commits

..

2 Commits

Author SHA1 Message Date
kaelvar 1ac1778521 🐛(frontend) keep the sending resolution picked while the camera is off
`handleVideoResolutionChange` did all of its work inside `if (videoTrack)`,
including `saveVideoPublishResolution`. With the camera off there is no camera
publication, so choosing a resolution did nothing at all: it was neither applied
nor recorded, while the selector went on showing the value the user had just
picked. Turning the camera back on then published at the old resolution, and so
did the next session.

Found on a self-hosted instance: a user set the sending resolution with the
camera off, turned it back on, and the publisher kept sending 720p. Nothing in
the UI suggested the choice had been dropped.

Persist the choice first and unconditionally, then restart the track only when
there is one to restart.

Persisting alone is not enough within a session. `roomOptions` is only read by
`new Room(...)`, so a store update never reaches a room that is already built.

Sync the VideoDeviceControl with the userChoiesStore resolution, as we did for
the device id and the processor configuration.

The early return is the honest shape here: with no live track there is nothing
to await, and the defaults above already cover what happens next.
2026-09-02 00:11:43 +02:00
lebaudantoine fcc58065d2 🩹(changelog) fix changelog entry ordering
Restore the correct order of entries in the CHANGELOG, which got
shuffled somewhere between rebases.
2026-09-01 22:08:37 +02:00
3 changed files with 24 additions and 14 deletions
+4
View File
@@ -8,6 +8,10 @@ and this project adheres to
## [Unreleased] ## [Unreleased]
### Fixed
- 🐛(frontend) keep the sending resolution picked while the camera is off #1667
## [1.30.0] - 2026-09-01 ## [1.30.0] - 2026-09-01
### Added ### Added
@@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'
import { useTrackToggle, UseTrackToggleProps } from '@livekit/components-react' import { useTrackToggle, UseTrackToggleProps } from '@livekit/components-react'
import { Button, Popover } from '@/primitives' import { Button, Popover } from '@/primitives'
import { RiArrowUpSLine, RiImageCircleAiFill } from '@remixicon/react' import { RiArrowUpSLine, RiImageCircleAiFill } from '@remixicon/react'
import { Track, type VideoCaptureOptions } from 'livekit-client' import { Track, type VideoCaptureOptions, VideoPresets } from 'livekit-client'
import { ToggleDevice } from './ToggleDevice' import { ToggleDevice } from './ToggleDevice'
import { css } from '@/styled-system/css' import { css } from '@/styled-system/css'
@@ -57,7 +57,8 @@ export const VideoDeviceControl = ({
}: VideoDeviceControlProps) => { }: VideoDeviceControlProps) => {
const { t } = useTranslation('rooms', { keyPrefix: 'selectDevice' }) const { t } = useTranslation('rooms', { keyPrefix: 'selectDevice' })
const { videoDeviceId, processorConfig } = useSnapshot(userChoicesStore) const { videoDeviceId, processorConfig, videoPublishResolution } =
useSnapshot(userChoicesStore)
const onChange = React.useCallback( const onChange = React.useCallback(
(enabled: boolean, isUserInitiated: boolean) => (enabled: boolean, isUserInitiated: boolean) =>
@@ -97,6 +98,9 @@ export const VideoDeviceControl = ({
await toggle(!trackProps.enabled, { await toggle(!trackProps.enabled, {
processor: processor, processor: processor,
...(videoPublishResolution && {
resolution: VideoPresets[videoPublishResolution].resolution,
}),
} as VideoCaptureOptions) } as VideoCaptureOptions)
} }
@@ -32,7 +32,8 @@ const EMPTY_PROPS = {}
export const VideoTab = ({ id }: VideoTabProps) => { export const VideoTab = ({ id }: VideoTabProps) => {
const { t } = useTranslation('settings', { keyPrefix: 'video' }) const { t } = useTranslation('settings', { keyPrefix: 'video' })
const { localParticipant, remoteParticipants } = useRoomContext() const room = useRoomContext()
const { localParticipant, remoteParticipants } = room
const { const {
videoDeviceId, videoDeviceId,
@@ -70,12 +71,14 @@ export const VideoTab = ({ id }: VideoTabProps) => {
} }
const handleVideoResolutionChange = async (key: 'h720' | 'h360' | 'h180') => { const handleVideoResolutionChange = async (key: 'h720' | 'h360' | 'h180') => {
const videoPublication = localParticipant.getTrackPublication(
Track.Source.Camera
)
const videoTrack = videoPublication?.track
if (videoTrack) {
saveVideoPublishResolution(key) saveVideoPublishResolution(key)
const videoTrack = localParticipant.getTrackPublication(
Track.Source.Camera
)?.track
if (!videoTrack) {
return
}
await videoTrack.restartTrack({ await videoTrack.restartTrack({
resolution: VideoPresets[key].resolution, resolution: VideoPresets[key].resolution,
deviceId: { exact: videoDeviceId }, deviceId: { exact: videoDeviceId },
@@ -83,7 +86,6 @@ export const VideoTab = ({ id }: VideoTabProps) => {
BackgroundProcessorFactory.fromProcessorConfig(processorConfig), BackgroundProcessorFactory.fromProcessorConfig(processorConfig),
}) })
} }
}
/** /**
* Updates video quality for all existing remote video tracks when user preference changes. * Updates video quality for all existing remote video tracks when user preference changes.