Files
vidconf/frontend/vite.config.ts
Max Ronzhin 17437880b1 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 и фон молча не включался.
2026-08-10 08:59:47 +03:00

84 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import fs from 'node:fs'
import path from 'path'
import { defineConfig, type Plugin } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
/**
* Копирует wasm-рантайм MediaPipe (`@mediapipe/tasks-vision`) в `public/mediapipe/wasm/`,
* откуда его забирает замена фона (`lib/virtualBackground.ts`).
*
* Зачем вообще: библиотека `@livekit/track-processors` по умолчанию тянет и
* wasm, и модель сегментации с ВНЕШНИХ CDN (jsdelivr + storage.googleapis.com).
* VidConf ставят в закрытых контурах, где внешнего интернета нет, — там фича
* молча не заработала бы. Поэтому все ассеты отдаются со своего домена.
*
* Зачем копировать, а не коммитить: два варианта рантайма (simd/nosimd) весят
* ~19 МБ — в репозитории им не место, а версия обязана совпадать с
* установленной в `node_modules` (иначе рантайм и `vision_bundle.mjs`
* разъедутся). Копия делается и в dev (`vite`), и в сборке (`vite build`,
* который потом переносит весь `public/` в `dist/`), каталог назначения
* gitignored. Модель сегментации (`selfie_segmenter.tflite`, ~244 КБ) npm-пакетом
* не поставляется — она лежит в репозитории рядом, см. `public/mediapipe/NOTICE.txt`.
*/
function mediapipeWasm(): Plugin {
const source = path.resolve(__dirname, 'node_modules/@mediapipe/tasks-vision/wasm')
const target = path.resolve(__dirname, 'public/mediapipe/wasm')
return {
name: 'vidconf-mediapipe-wasm',
buildStart() {
if (!fs.existsSync(source)) {
// Пакет не установлен — падать сборкой не за что: замена фона просто
// не сможет инициализироваться, остальное приложение работает.
this.warn(`не найден wasm-рантайм MediaPipe: ${source}`)
return
}
fs.mkdirSync(target, { recursive: true })
for (const name of fs.readdirSync(source)) {
const from = path.join(source, name)
const to = path.join(target, name)
// Пропускаем уже скопированное с тем же размером — иначе каждый
// перезапуск dev-сервера переписывал бы 19 МБ.
const src = fs.statSync(from)
const dst = fs.existsSync(to) ? fs.statSync(to) : null
if (dst && dst.size === src.size) continue
fs.copyFileSync(from, to)
}
},
}
}
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss(), mediapipeWasm()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
server: {
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
// WS-чат конференции ходит через тот же префикс /api
ws: true,
},
// Загруженные файлы (аватары, картинки фона). На проде их раздаёт nginx
// напрямую из тома (`location /media/`), минуя backend; в dev такого
// слоя нет, и без этого прокси все `/media/...` упирались бы в SPA
// и отдавали index.html вместо картинки.
//
// ⚠️ Ключ обязан быть со СЛЭШЕМ на конце. Vite матчит префиксом, и
// ключ `/media` перехватывал заодно `/mediapipe/...` — ассеты замены
// фона уходили на backend и получали 404, из-за чего фон молча не
// включался. У nginx та же семантика: там `location /media/` тоже
// со слэшем, поэтому прод этим не задет.
'/media/': {
target: 'http://localhost:8000',
changeOrigin: true,
},
},
},
})