diff --git a/frontend/src/components/room/RoomStage.tsx b/frontend/src/components/room/RoomStage.tsx index 4f4939e..1f85321 100644 --- a/frontend/src/components/room/RoomStage.tsx +++ b/frontend/src/components/room/RoomStage.tsx @@ -1,5 +1,5 @@ import { useEffect, useState, type ReactNode } from 'react' -import { EyeOff, Users } from 'lucide-react' +import { EyeOff, Mic, MicOff, Users } from 'lucide-react' import { Track, type Participant } from 'livekit-client' import { CarouselLayout, @@ -8,6 +8,7 @@ import { isTrackReference, useRoomContext, useSpeakingParticipants, + useTrackToggle, useTracks, type TrackReferenceOrPlaceholder, } from '@livekit/components-react' @@ -93,6 +94,29 @@ function hasLiveVideo(track: TrackReferenceOrPlaceholder): boolean { return isTrackReference(track) && !track.publication.isMuted } +/** + * Кнопка микрофона поверх единственной плитки мини-плеера (задача A5): в + * Document PiP своего тулбара нет вовсе, а звонок продолжается — без неё + * замьютить себя можно, только развернув основное окно. `useTrackToggle` — + * тот же хук, что и в `RoomToolbar`, читает/меняет состояние ЧЕРЕЗ + * `RoomContext`, а не через DOM, поэтому работает одинаково что в основном + * окне, что в портале, рендерящем эту сцену внутрь `pipWindow.document.body` + * (React-контекст порталом не разрывается, в отличие от DOM-дерева). + */ +function PipMicToggle() { + const mic = useTrackToggle({ source: Track.Source.Microphone }) + return ( + + ) +} + /** * Основная сцена конференции: превью остальных участников + крупная плитка * активного спикера (FocusLayoutContainer + CarouselLayout при нескольких @@ -366,6 +390,7 @@ export function RoomStage({ return (
{focusTrack && } +
) diff --git a/frontend/src/styles/room.css b/frontend/src/styles/room.css index c4806a2..3e2778c 100644 --- a/frontend/src/styles/room.css +++ b/frontend/src/styles/room.css @@ -133,9 +133,32 @@ video[data-lk-source='screen_share'] { object-fit: contain; background: #000; } * `.stage-tiles` в основном окне) плюс подстраховочный `min-height`, чтобы * плитка не схлопывалась, если видео ещё не выдало реальные размеры. */ -.room-single-tile { flex: 1; min-height: 0; display: flex; } +.room-single-tile { position: relative; flex: 1; min-height: 0; display: flex; } .room-single-tile .lk-participant-tile { flex: 1; min-height: 220px; width: 100%; } +/* Кнопка микрофона мини-плеера (задача A5) — своего тулбара в PiP нет, + кнопка лежит поверх единственной плитки левым нижним углом (правый занят + штатным `.lk-focus-toggle-button`/нашей булавкой закрепления — тут её нет, + в PiP закреплять нечего, но геометрия зеркалит `.room-pin-toggle`). */ +.room-pip-mic-toggle { + position: absolute; + left: 0.5rem; + bottom: 0.5rem; + z-index: 5; + width: 36px; + height: 36px; + border-radius: 50%; + border: none; + display: flex; + align-items: center; + justify-content: center; + background: var(--color-room-mic-on); + color: #10331f; + cursor: pointer; +} +.room-pip-mic-toggle svg { width: 18px; height: 18px; } +.room-pip-mic-toggle.is-off { background: var(--color-room-danger-bg); color: #fff; } + /* * ---------- Колонка миниатюр (кнопка «Скрыть» + карусель) ---------- * Прямой ребёнок `.lk-focus-layout` — вместо самой карусели, потому что над