mirror of
https://github.com/suitenumerique/meet.git
synced 2026-09-02 13:48:26 +00:00
✨(frontend) add 1080p sending resolution option
The sending resolution selector stopped at 720p while `VideoPresets` already exposes `h1080` (1920x1080), so publishers on a good uplink could not make use of the capacity they had. Add "Very high definition (1080p)" above the existing entries, translated in the five supported locales. The default stays `h720`, so nothing changes unless a user goes and picks the new entry. Being explicit about what that costs, since 1080p roughly doubles a publisher's uplink: this is a per-user choice, and an instance operator has no way today to decline it. Whether that warrants a server-side setting alongside the existing `ApiConfig` flags is a call for maintainers — happy to add one if you want it, rather than change the API contract unasked in a frontend PR. While here, make the option list harder to get wrong. Resolutions now come from a single `VIDEO_RESOLUTIONS` tuple that `VideoResolution` derives from, the selector items are built by mapping over it against a `Record<VideoResolution, string>` of labels — so a resolution cannot be added to one and forgotten in the other — and a persisted value that is not in the tuple falls back to `h720` instead of reaching `VideoPresets[...]` as `undefined`, since `loadUserChoices` spreads localStorage without validating it. Known limitation, unchanged by this patch: `restartTrack` passes the resolution as an `ideal` constraint, so a camera that cannot reach the selected height degrades silently. That is already true of 720p on a 480p webcam; 1080p is the first step where the gap is the common case rather than the edge one.
This commit is contained in:
@@ -8,6 +8,10 @@ and this project adheres to
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- ✨(frontend) add 1080p sending resolution option #1660
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- 🐛(frontend) keep the sending resolution picked while the camera is off #1667
|
- 🐛(frontend) keep the sending resolution picked while the camera is off #1667
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
saveVideoPublishResolution,
|
saveVideoPublishResolution,
|
||||||
saveVideoSubscribeQuality,
|
saveVideoSubscribeQuality,
|
||||||
userChoicesStore,
|
userChoicesStore,
|
||||||
|
VIDEO_RESOLUTIONS,
|
||||||
VideoResolution,
|
VideoResolution,
|
||||||
} from '@/stores/userChoices'
|
} from '@/stores/userChoices'
|
||||||
import { RowWrapper } from './layout/RowWrapper'
|
import { RowWrapper } from './layout/RowWrapper'
|
||||||
@@ -70,7 +71,7 @@ export const VideoTab = ({ id }: VideoTabProps) => {
|
|||||||
isDisabled: true,
|
isDisabled: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleVideoResolutionChange = async (key: 'h720' | 'h360' | 'h180') => {
|
const handleVideoResolutionChange = async (key: VideoResolution) => {
|
||||||
saveVideoPublishResolution(key)
|
saveVideoPublishResolution(key)
|
||||||
const videoTrack = localParticipant.getTrackPublication(
|
const videoTrack = localParticipant.getTrackPublication(
|
||||||
Track.Source.Camera
|
Track.Source.Camera
|
||||||
@@ -124,20 +125,13 @@ export const VideoTab = ({ id }: VideoTabProps) => {
|
|||||||
}, [videoDeviceId, videoElement])
|
}, [videoDeviceId, videoElement])
|
||||||
|
|
||||||
const resolutionItems = useMemo(() => {
|
const resolutionItems = useMemo(() => {
|
||||||
return [
|
const labels: Record<VideoResolution, string> = {
|
||||||
{
|
h1080: `${t('resolution.publish.items.veryHigh')} (1080p)`,
|
||||||
value: 'h720',
|
h720: `${t('resolution.publish.items.high')} (720p)`,
|
||||||
label: `${t('resolution.publish.items.high')} (720p)`,
|
h360: `${t('resolution.publish.items.medium')} (360p)`,
|
||||||
},
|
h180: `${t('resolution.publish.items.low')} (180p)`,
|
||||||
{
|
}
|
||||||
value: 'h360',
|
return VIDEO_RESOLUTIONS.map((value) => ({ value, label: labels[value] }))
|
||||||
label: `${t('resolution.publish.items.medium')} (360p)`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'h180',
|
|
||||||
label: `${t('resolution.publish.items.low')} (180p)`,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}, [t])
|
}, [t])
|
||||||
|
|
||||||
const videoQualityItems = useMemo(() => {
|
const videoQualityItems = useMemo(() => {
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
"publish": {
|
"publish": {
|
||||||
"label": "Wähle die maximale Auflösung beim Senden",
|
"label": "Wähle die maximale Auflösung beim Senden",
|
||||||
"items": {
|
"items": {
|
||||||
|
"veryHigh": "Sehr hohe Auflösung",
|
||||||
"high": "Hohe Auflösung",
|
"high": "Hohe Auflösung",
|
||||||
"medium": "Mittlere Auflösung",
|
"medium": "Mittlere Auflösung",
|
||||||
"low": "Niedrige Auflösung"
|
"low": "Niedrige Auflösung"
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
"publish": {
|
"publish": {
|
||||||
"label": "Select your sending resolution (max.)",
|
"label": "Select your sending resolution (max.)",
|
||||||
"items": {
|
"items": {
|
||||||
|
"veryHigh": "Very high definition",
|
||||||
"high": "High definition",
|
"high": "High definition",
|
||||||
"medium": "Standard definition",
|
"medium": "Standard definition",
|
||||||
"low": "Low definition"
|
"low": "Low definition"
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
"publish": {
|
"publish": {
|
||||||
"label": "Selecciona tu resolución de envío (máx.)",
|
"label": "Selecciona tu resolución de envío (máx.)",
|
||||||
"items": {
|
"items": {
|
||||||
|
"veryHigh": "Muy alta definición",
|
||||||
"high": "Alta definición",
|
"high": "Alta definición",
|
||||||
"medium": "Definición estándar",
|
"medium": "Definición estándar",
|
||||||
"low": "Baja definición"
|
"low": "Baja definición"
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
"publish": {
|
"publish": {
|
||||||
"label": "Sélectionner votre résolution d'envoi (max.)",
|
"label": "Sélectionner votre résolution d'envoi (max.)",
|
||||||
"items": {
|
"items": {
|
||||||
|
"veryHigh": "Très haute définition",
|
||||||
"high": "Haute définition",
|
"high": "Haute définition",
|
||||||
"medium": "Définition standard",
|
"medium": "Définition standard",
|
||||||
"low": "Basse définition"
|
"low": "Basse définition"
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
"publish": {
|
"publish": {
|
||||||
"label": "Selecteer uw verzendresolutie (max.)",
|
"label": "Selecteer uw verzendresolutie (max.)",
|
||||||
"items": {
|
"items": {
|
||||||
|
"veryHigh": "Zeer hoge definitie",
|
||||||
"high": "Hoge definitie",
|
"high": "Hoge definitie",
|
||||||
"medium": "Standaarddefinitie",
|
"medium": "Standaarddefinitie",
|
||||||
"low": "Lage definitie"
|
"low": "Lage definitie"
|
||||||
|
|||||||
@@ -10,7 +10,12 @@ import {
|
|||||||
} from '@livekit/components-core'
|
} from '@livekit/components-core'
|
||||||
import { VideoQuality } from 'livekit-client'
|
import { VideoQuality } from 'livekit-client'
|
||||||
|
|
||||||
export type VideoResolution = 'h720' | 'h360' | 'h180'
|
export const VIDEO_RESOLUTIONS = ['h1080', 'h720', 'h360', 'h180'] as const
|
||||||
|
|
||||||
|
export type VideoResolution = (typeof VIDEO_RESOLUTIONS)[number]
|
||||||
|
|
||||||
|
const isVideoResolution = (value: unknown): value is VideoResolution =>
|
||||||
|
VIDEO_RESOLUTIONS.includes(value as VideoResolution)
|
||||||
|
|
||||||
export type LocalUserChoices = Omit<LocalUserChoicesLK, 'username'> & {
|
export type LocalUserChoices = Omit<LocalUserChoicesLK, 'username'> & {
|
||||||
processorConfig?: ProcessorConfig
|
processorConfig?: ProcessorConfig
|
||||||
@@ -21,13 +26,17 @@ export type LocalUserChoices = Omit<LocalUserChoicesLK, 'username'> & {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getUserChoicesState(): LocalUserChoices {
|
function getUserChoicesState(): LocalUserChoices {
|
||||||
return {
|
const stored: LocalUserChoices = {
|
||||||
noiseReductionEnabled: false,
|
noiseReductionEnabled: false,
|
||||||
audioOutputDeviceId: 'default', // Use 'default' to match LiveKit's standard device selection behavior
|
audioOutputDeviceId: 'default', // Use 'default' to match LiveKit's standard device selection behavior
|
||||||
videoPublishResolution: 'h720',
|
videoPublishResolution: 'h720',
|
||||||
videoSubscribeQuality: VideoQuality.HIGH,
|
videoSubscribeQuality: VideoQuality.HIGH,
|
||||||
...loadUserChoices(),
|
...loadUserChoices(),
|
||||||
}
|
}
|
||||||
|
if (!isVideoResolution(stored.videoPublishResolution)) {
|
||||||
|
stored.videoPublishResolution = 'h720'
|
||||||
|
}
|
||||||
|
return stored
|
||||||
}
|
}
|
||||||
|
|
||||||
export const userChoicesStore = proxy<LocalUserChoices>(getUserChoicesState())
|
export const userChoicesStore = proxy<LocalUserChoices>(getUserChoicesState())
|
||||||
|
|||||||
Reference in New Issue
Block a user