fix(room): мини-окно открывается на плитке из основного окна
В Chrome мини-плеер показывал самого пользователя вместо того, что он видел крупно. Сцена в мини-окне — отдельный экземпляр RoomStage (портал в PiP-окно), и состояние фокуса он начинал с нуля: демонстрации нет, никто прямо сейчас не говорит — pickStageFocus доходил до последнего фолбэка localKey, то есть до «показать себя». В Safari бага не было видно: там Document PiP не используется, а video-фолбэк берёт <video> прямо из фокус-плитки основного окна. Ключ фокуса теперь передаётся наружу (onFocusKeyChange), живёт в состоянии RoomPage и достаётся следующему экземпляру сцены (initialFocusKey). Работает в обе стороны — возврат из мини-плеера тоже не сбрасывает фокус. Правила выбора фокуса (pickStageFocus) не тронуты.
This commit is contained in:
@@ -124,6 +124,17 @@ function hasLiveVideo(track: TrackReferenceOrPlaceholder): boolean {
|
||||
* `tiles` скрывать нечего (карусели нет), переключатель там заблокирован —
|
||||
* см. `StageViewOptions`.
|
||||
*
|
||||
* ФОКУС ПЕРЕЖИВАЕТ ПЕРЕЕЗД В МИНИ-ПЛЕЕР. Сцена в мини-плеере — ОТДЕЛЬНЫЙ
|
||||
* экземпляр этого компонента (портал в PiP-окно), и своё состояние фокуса он
|
||||
* начинал с нуля: демонстрации нет, никто прямо сейчас не говорит — и
|
||||
* `pickStageFocus` доходил до последнего фолбэка `localKey`, то есть мини-окно
|
||||
* открывалось на самом пользователе вместо того, что он видел крупно. В Safari
|
||||
* бага не было видно: там Document PiP не используется, а video-фолбэк
|
||||
* (`useRoomPiP`) берёт `<video>` прямо из фокус-плитки основного окна. Лечится
|
||||
* передачей ключа наружу и обратно: `onFocusKeyChange` → состояние в
|
||||
* `RoomPage` → `initialFocusKey` следующего экземпляра. Работает в обе стороны
|
||||
* — возврат из мини-плеера тоже не сбрасывает фокус.
|
||||
*
|
||||
* Раскладка — вертикальная колонка миниатюр слева от основной сцены (не
|
||||
* горизонтальная лента, см. design/mockups/room.html после правки: узкая
|
||||
* колонка сбоку, скролл по вертикали). Это штатное поведение самого
|
||||
@@ -166,6 +177,8 @@ export function RoomStage({
|
||||
hideOthers = false,
|
||||
onShowOthers,
|
||||
onHideOthers,
|
||||
initialFocusKey = null,
|
||||
onFocusKeyChange,
|
||||
}: {
|
||||
variant?: 'full' | 'pip'
|
||||
/** Выбранный пользователем режим показа; игнорируется при `variant="pip"`. */
|
||||
@@ -176,6 +189,10 @@ export function RoomStage({
|
||||
onShowOthers?: () => void
|
||||
/** Скрыть остальных — кнопка над каруселью (только широкий экран). */
|
||||
onHideOthers?: () => void
|
||||
/** Чем инициализировать фокус при монтировании — см. докстринг про мини-плеер. */
|
||||
initialFocusKey?: string | null
|
||||
/** Сообщать наружу текущий фокус, чтобы его пережил переезд сцены в мини-плеер и обратно. */
|
||||
onFocusKeyChange?: (key: string | null) => void
|
||||
}) {
|
||||
const room = useRoomContext()
|
||||
const isCompact = useIsCompactViewport()
|
||||
@@ -224,7 +241,10 @@ export function RoomStage({
|
||||
// `pickStageFocus` меняют результат без изменения самих треков.
|
||||
const [prevTracks, setPrevTracks] = useState(tracks)
|
||||
const [prevSpeakingParticipants, setPrevSpeakingParticipants] = useState(speakingParticipants)
|
||||
const [focusKey, setFocusKey] = useState<string | null>(null)
|
||||
// Стартовое значение — фокус, доставшийся от предыдущего экземпляра сцены
|
||||
// (см. `initialFocusKey` и докстринг про мини-плеер). Дальше живёт своей
|
||||
// жизнью: `pickStageFocus` пересчитывает его на каждое значимое изменение.
|
||||
const [focusKey, setFocusKey] = useState<string | null>(initialFocusKey)
|
||||
// Закрепление живёт в состоянии сцены (задача 3.1): ключ `identity:source`
|
||||
// плитки, которую пользователь закрепил булавкой; `null` — закрепления нет.
|
||||
// Только для основного окна — в PiP плитка одна и закреплять нечего.
|
||||
@@ -282,6 +302,14 @@ export function RoomStage({
|
||||
}
|
||||
}
|
||||
|
||||
// Отдаём фокус наружу (в `RoomPage`), чтобы он пережил размонтирование этой
|
||||
// сцены и достался следующей — см. `initialFocusKey`. Именно эффект, а не
|
||||
// вызов в теле рендера: setState ЧУЖОГО компонента во время рендера React
|
||||
// запрещает.
|
||||
useEffect(() => {
|
||||
onFocusKeyChange?.(focusKey)
|
||||
}, [focusKey, onFocusKeyChange])
|
||||
|
||||
const focusTrack = tracks.find((t) => stageTrackKey(t) === focusKey) ?? screenShareTracks[0] ?? cameraTracks[0]
|
||||
const focusTrackKey = focusTrack ? stageTrackKey(focusTrack) : null
|
||||
// При активной демонстрации карусель — ВСЕ камеры (включая демонстратора) И
|
||||
|
||||
@@ -168,6 +168,13 @@ export function RoomPage() {
|
||||
saveStageLayoutMode(mode)
|
||||
}, [])
|
||||
|
||||
// Ключ трека, который сцена показывает крупно. Живёт ЗДЕСЬ, а не только
|
||||
// внутри `RoomStage`, потому что при открытии мини-плеера сцена
|
||||
// размонтируется в основном окне и монтируется заново в PiP-окне (портал
|
||||
// ниже) — без этого мостика новый экземпляр начинал бы выбор фокуса с нуля и
|
||||
// открывал мини-окно на самом пользователе. Подробнее — докстринг `RoomStage`.
|
||||
const [stageFocusKey, setStageFocusKey] = useState<string | null>(null)
|
||||
|
||||
// Сохранённый выбор устройств — читаем через собственный вызов
|
||||
// usePersistentUserChoices (независимый от того, что использует
|
||||
// DeviceSettingsDialog: там свой вызов хука со своим состоянием). ВАЖНО:
|
||||
@@ -241,6 +248,8 @@ export function RoomPage() {
|
||||
hideOthers={hideOthers}
|
||||
onShowOthers={() => setHideOthers(false)}
|
||||
onHideOthers={() => setHideOthers(true)}
|
||||
initialFocusKey={stageFocusKey}
|
||||
onFocusKeyChange={setStageFocusKey}
|
||||
/>
|
||||
)}
|
||||
{chatVisible && chatOpen && (
|
||||
@@ -284,8 +293,13 @@ export function RoomPage() {
|
||||
рендерящийся в PiP-окне, продолжает читать RoomContext/треки.
|
||||
`variant="pip"` — мини-плеер
|
||||
показывает только активное окно (одну плитку), без карусели/грида
|
||||
основного окна. */}
|
||||
{pip.pipWindow && createPortal(<RoomStage variant="pip" />, pip.pipWindow.document.body)}
|
||||
основного окна. `initialFocusKey` — то, что было крупно в основном
|
||||
окне: без него мини-окно открывалось на самом пользователе. */}
|
||||
{pip.pipWindow &&
|
||||
createPortal(
|
||||
<RoomStage variant="pip" initialFocusKey={stageFocusKey} onFocusKeyChange={setStageFocusKey} />,
|
||||
pip.pipWindow.document.body,
|
||||
)}
|
||||
</LiveKitRoom>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user