feat(room): замена фона видео на картинку — только на десктопе
Кнопка «Фон» в тулбаре комнаты и выбор фона в превью на входе: три готовые
сцены и свои картинки из профиля. Фон применяется процессором к самому
публикуемому треку (`LocalVideoTrack.setProcessor`), а НЕ пересозданием
`RoomOptions` — ссылка на них обязана оставаться стабильной, иначе
`LiveKitRoom` переподключается к комнате.
Фича только для ДЕСКТОПА, и «десктоп» определяется по возможностям устройства
(`pointer: fine` + `hover: hover` + `maxTouchPoints`), а НЕ по ширине окна:
узкое окно на десктопе — всё ещё десктоп, а широкий планшет — всё ещё планшет,
который сегментация греет. На мобильном кнопки нет вовсе, а не задизейбленной.
Ассеты сегментации отдаются СО СВОЕГО домена: библиотека по умолчанию тянет
wasm с jsdelivr, а модель с storage.googleapis.com, и в закрытом контуре фича
молча не работала бы. Модель (Apache 2.0, см. NOTICE.txt) лежит в репозитории,
wasm-рантайм (~19 МБ) копируется из node_modules плагином сборки. Сама
библиотека и модель грузятся ЛЕНИВО — только когда фон реально включают, вход
в конференцию не стал медленнее.
Три дефолтные сцены — собственные векторные рисунки (`design/backgrounds/`),
а не фотографии из интернета: у нарисованной сцены нет чужой лицензии, а
продукт расходится по инсталляциям, и проверять права на каждую копию некому.
Свои картинки — в профиле, до 10 штук, с уменьшением до 1280px и переводом в
WebP прямо в браузере перед отправкой. Удаление применённого сейчас фона
сбрасывает выбор на «без фона»: хранится ключ записи, а не URL картинки.
Смена камеры фон не теряет (`restartTrack` перезапускает процессор сам),
выключение и включение камеры — навешивает его на новый трек заново.
⚠️ Прокси dev-сервера для `/media/` — обязательно со слэшем: ключ `/media`
Vite матчит префиксом и перехватывает заодно `/mediapipe/...`, из-за чего
модель получала 404 и фон молча не включался.
This commit is contained in:
@@ -54,6 +54,9 @@ export interface SettingsOut {
|
||||
/** Проверка устройств на входе (сессия 33) — запрос доступа к камере/микрофону
|
||||
* и превью камеры на странице логина и в карточке «Как вас зовут?». */
|
||||
device_check_enabled: boolean
|
||||
/** Замена фона видео на картинку (сессия 35) — кнопка «Фон» в комнате,
|
||||
* выбор фона в превью на входе и раздел «Свои фоны» в профиле. */
|
||||
virtual_background_enabled: boolean
|
||||
}
|
||||
|
||||
/** Тело частичного обновления настроек инстанса — все поля опциональны. */
|
||||
@@ -78,6 +81,7 @@ export interface SettingsUpdateIn {
|
||||
consent_required?: boolean
|
||||
consent_policy_text?: string
|
||||
device_check_enabled?: boolean
|
||||
virtual_background_enabled?: boolean
|
||||
}
|
||||
|
||||
/** Тело запроса тестовой отправки письма (`POST /admin/settings/test-email`). */
|
||||
|
||||
@@ -67,6 +67,8 @@ export interface ConferenceJoinData {
|
||||
publish_quality_cap: PublishQualityCap
|
||||
/** Максимум одновременно видимых плиток сцены (`StageGrid`) на момент входа. */
|
||||
stage_max_tiles: number
|
||||
/** Включён ли модуль «замена фона» — при `false` кнопка «Фон» в тулбаре не рендерится. */
|
||||
virtual_background_enabled: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,9 @@ export interface PublicSettingsOut {
|
||||
/** Проверка устройств на входе (сессия 33) — запрос доступа к камере/микрофону
|
||||
* и превью камеры на LoginPage/JoinPage. */
|
||||
device_check_enabled: boolean
|
||||
/** Замена фона видео (сессия 35) — нужен превью на JoinPage; участнику в
|
||||
* комнате тот же флаг приезжает в join-ответе (`JoinOut`). */
|
||||
virtual_background_enabled: boolean
|
||||
}
|
||||
|
||||
export async function getPublicSettings(): Promise<PublicSettingsOut> {
|
||||
|
||||
@@ -62,6 +62,42 @@ export async function deleteMyAvatar(): Promise<void> {
|
||||
await apiRequest('/users/me/avatar', { method: 'DELETE' })
|
||||
}
|
||||
|
||||
/** Своя картинка пользователя для замены фона видео. */
|
||||
export interface MyBackground {
|
||||
id: string
|
||||
/** Путь на своём домене (`/media/backgrounds/...`), раздаёт nginx. */
|
||||
url: string
|
||||
}
|
||||
|
||||
/** Ответ списка своих картинок фона — вместе с серверным лимитом на их число. */
|
||||
export interface MyBackgroundsResponse {
|
||||
items: MyBackground[]
|
||||
limit: number
|
||||
}
|
||||
|
||||
/** Свои картинки фона текущего пользователя. Гостю недоступно (401) — у него нет профиля. */
|
||||
export async function listMyBackgrounds(): Promise<MyBackgroundsResponse> {
|
||||
return apiRequest<MyBackgroundsResponse>('/users/me/backgrounds')
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузить свою картинку фона; возвращается весь список заново.
|
||||
*
|
||||
* Картинка ужимается ДО отправки (`lib/imageResize.ts`) — сервер её не
|
||||
* пережимает (Pillow на backend нет). Коды ошибок: 409 — упёрлись в лимит,
|
||||
* 413 — файл больше 2 МБ, 415 — недопустимый тип.
|
||||
*/
|
||||
export async function uploadMyBackground(file: File | Blob): Promise<MyBackgroundsResponse> {
|
||||
const form = new FormData()
|
||||
form.append('file', file, 'background.webp')
|
||||
return apiRequest<MyBackgroundsResponse>('/users/me/backgrounds', { method: 'POST', body: form })
|
||||
}
|
||||
|
||||
/** Удалить свою картинку фона — 204 без тела; файл на диске удаляется вместе с записью. */
|
||||
export async function deleteMyBackground(id: string): Promise<void> {
|
||||
await apiRequest(`/users/me/backgrounds/${id}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
/** Тело смены пароля текущего пользователя. */
|
||||
export interface PasswordChangePayload {
|
||||
current_password: string
|
||||
|
||||
Reference in New Issue
Block a user