feat(room): кнопка демонстрации экрана в мини-окне

В Document PiP (Chrome/Edge) своего тулбара нет, и начать демонстрацию,
не свернув мини-окно, было нельзя. Кнопка сделана по образцу кнопки
микрофона, добавленной в 0.0.15: тот же `useTrackToggle` через
`RoomContext`, поэтому она и кнопка основного тулбара — два вида одного
состояния и рассинхрону взяться неоткуда.

Главный вопрос задачи — пустит ли платформа `getDisplayMedia()`, вызванный
из кода основного окна по клику в ДРУГОМ окне. Замер в Chrome 150: после
клика в PiP `navigator.userActivation.isActive === true` в обоих окнах,
активация доезжает до опенера, вызов проходит, системный пикер выбора
экрана открывается отдельным окном поверх всего, а не прячется за
заглушкой основного окна.

Опции захвата (`SCREEN_SHARE_CAPTURE_OPTIONS`) вынесены из `RoomToolbar` в
`lib/screenShareOptions.ts`: кнопок демонстрации теперь две, и разойдись
они хотя бы в `audio`, демонстрация получалась бы разной в зависимости от
того, откуда её запустили.

Фокус в мини-окне не менялся: своя демонстрация показывается по тем же
правилам `pickStageFocus`, что и в основном окне.

В Safari мини-окно — нативный video-PiP без собственного DOM, кнопке там
негде жить; в Firefox мини-окна нет вовсе. Это ограничение платформы.
This commit is contained in:
2026-08-04 21:13:27 +03:00
parent aee76329c4
commit f89bf1ad64
4 changed files with 139 additions and 34 deletions

View File

@@ -0,0 +1,29 @@
import type { ScreenShareCaptureOptions } from 'livekit-client'
/**
* Опции захвата демонстрации экрана: `audio: true` — звук
* вкладки/экрана там, где браузер его отдаёт (Chrome/Edge — вкладка почти
* всегда, целый экран — только Windows); `selfBrowserSurface: 'exclude'`
* — не предлагать в списке
* источников собственную вкладку (зеркальный туннель самой конференции);
* `surfaceSwitching: 'include'` — разрешить переключать источник прямо во
* время демонстрации, не останавливая её; `systemAudio: 'include'` — не
* запрещать захват системного звука при выборе «весь экран».
*
* Вынесено в модульную константу — `useTrackToggle` держит
* `JSON.stringify(captureOptions)` в зависимостях внутреннего `useMemo`,
* инлайновый литерал был бы безвреден, но константа явнее фиксирует
* неизменность опций.
*
* Живёт в `lib/`, а не рядом с тулбаром, потому что кнопок демонстрации теперь
* ДВЕ: в основном тулбаре (`RoomToolbar`) и в мини-окне (`RoomStage`,
* `PipScreenShareToggle`). Опции у них обязаны совпадать: обе кнопки управляют
* одной и той же публикацией, и разойдись они хотя бы в `audio`, демонстрация
* получалась бы разной в зависимости от того, откуда её запустили.
*/
export const SCREEN_SHARE_CAPTURE_OPTIONS: ScreenShareCaptureOptions = {
audio: true,
selfBrowserSurface: 'exclude',
surfaceSwitching: 'include',
systemAudio: 'include',
}