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:
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
Hand,
|
||||
Image,
|
||||
LogOut,
|
||||
Maximize,
|
||||
MessageSquare,
|
||||
@@ -41,6 +42,15 @@ interface RoomToolbarProps extends StageViewProps {
|
||||
onTogglePiP: () => void
|
||||
/** `JoinOut.hand_queue_enabled` — при `false` кнопка «Рука» и очередь не рендерятся вовсе. */
|
||||
handQueueEnabled: boolean
|
||||
/**
|
||||
* Показывать ли кнопку «Фон». Уже учитывает ВСЁ сразу: включён ли модуль в
|
||||
* админке, десктопное ли это устройство и умеет ли браузер сегментацию —
|
||||
* см. `RoomPage`. При `false` кнопки нет вовсе (а не задизейбленной): на
|
||||
* телефоне фичи не существует, и мёртвая кнопка там только мешает.
|
||||
*/
|
||||
backgroundVisible: boolean
|
||||
backgroundOpen: boolean
|
||||
onToggleBackground: () => void
|
||||
/**
|
||||
* Очередь поднятых рук целиком (задача B1, `useChat().handQueue`) — сама
|
||||
* решает, поднята ли СВОЯ рука (сравнивая с `localParticipant.identity`
|
||||
@@ -100,6 +110,9 @@ export function RoomToolbar({
|
||||
pipActive,
|
||||
onTogglePiP,
|
||||
handQueueEnabled,
|
||||
backgroundVisible,
|
||||
backgroundOpen,
|
||||
onToggleBackground,
|
||||
handQueue,
|
||||
onRaiseHand,
|
||||
onLowerHand,
|
||||
@@ -130,6 +143,9 @@ export function RoomToolbar({
|
||||
2 /* микрофон, камера */ +
|
||||
(handQueueEnabled ? 2 : 0) /* рука, очередь */ +
|
||||
1 /* настройки */ +
|
||||
// На телефоне всегда 0 (фича десктопная), но десктопное окно бывает и уже
|
||||
// 600px — тогда кнопка «Фон» реально есть и должна попасть в подсчёт.
|
||||
(backgroundVisible ? 1 : 0) +
|
||||
(fullscreenSupported ? 1 : 0) +
|
||||
(chatVisible ? 1 : 0) +
|
||||
1 /* выйти */
|
||||
@@ -244,6 +260,23 @@ export function RoomToolbar({
|
||||
<span className="label">{isCompact ? 'Настройки' : 'Устройства'}</span>
|
||||
</button>
|
||||
|
||||
{/* Только десктоп — на мобильном `backgroundVisible` всегда false,
|
||||
поэтому кнопка не участвует и в `mobileButtonCount` выше. */}
|
||||
{backgroundVisible && (
|
||||
<button
|
||||
type="button"
|
||||
className={`tb-btn tb-btn--background${backgroundOpen ? ' is-panel-open' : ''}`}
|
||||
aria-label={backgroundOpen ? 'Закрыть выбор фона' : 'Выбрать фон'}
|
||||
aria-pressed={backgroundOpen}
|
||||
onClick={onToggleBackground}
|
||||
>
|
||||
<span className="icon-shell">
|
||||
<Image className="lucide" aria-hidden="true" />
|
||||
</span>
|
||||
<span className="label">Фон</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{fullscreenSupported && (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user