Przyznanie i odebranie glosu oraz oba tryby (otwarty mikrofon, wideo dla wszystkich) zmieniaja uprawnienia uczestnika przez updateParticipant, bez ponownego wejscia na sale. Odebranie najpierw wycisza sciezke na serwerze, wiec dziala takze u klienta, ktory nie odebral zdarzenia. Sala LiveKit ma staly key; mikrofon wlacza sie sam po przyznaniu glosu. Rola uczestnika liczona w jednym miejscu (VideoService.participantRole) dla biletu i zmiany na zywo. RoomPermissionTest pilnuje jawnej listy zrodel publikacji. Blokada pisania na czacie ma ikone przekreslonego dymku i znacznik "bez czatu" zamiast przekreslonego mikrofonu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,9 +3,10 @@ import {
|
||||
LiveKitRoom as LiveKitRoomProvider,
|
||||
RoomAudioRenderer,
|
||||
useLocalParticipant,
|
||||
useLocalParticipantPermissions,
|
||||
} from '@livekit/components-react'
|
||||
import { RoomOptions } from 'livekit-client'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { useEffect, useMemo, useRef } from 'react'
|
||||
import { BackgroundBlurToggle } from './BackgroundBlurToggle'
|
||||
import { WebinarStage } from './WebinarStage'
|
||||
|
||||
@@ -43,6 +44,7 @@ export function LiveKitRoom({
|
||||
initialAudio = false,
|
||||
initialVideo = false,
|
||||
onDevicesChange,
|
||||
speakGranted = false,
|
||||
}: {
|
||||
serverUrl: string
|
||||
token: string
|
||||
@@ -59,6 +61,12 @@ export function LiveKitRoom({
|
||||
initialVideo?: boolean
|
||||
/** Zglasza wlaczenie i wylaczenie urzadzen, zeby stan przezyl przemontowanie. */
|
||||
onDevicesChange?: (state: { audio: boolean; video: boolean }) => void
|
||||
/**
|
||||
* Prowadzacy przyznal glos na prosbe uczestnika. Mikrofon wlacza sie wtedy
|
||||
* sam, gdy serwer nada prawo publikacji. Otwarty mikrofon dla calej sali tego
|
||||
* nie robi: tam nikt o glos nie prosil.
|
||||
*/
|
||||
speakGranted?: boolean
|
||||
}) {
|
||||
const options = useMemo<RoomOptions>(
|
||||
() => ({
|
||||
@@ -103,6 +111,7 @@ export function LiveKitRoom({
|
||||
<WebinarStage />
|
||||
{/* sledzi stan mikrofonu i kamery, zeby przezyl przemontowanie sali */}
|
||||
<DeviceStateReporter onChange={onDevicesChange} />
|
||||
<MicOnGrant granted={speakGranted} />
|
||||
{/*
|
||||
Rozmycie tla. Przycisk sam sie ukrywa, gdy nie ma wlasnej kamery, wiec
|
||||
uczestnik bez prawa publikacji obrazu nigdy go nie zobaczy. Lezy nad
|
||||
@@ -139,3 +148,34 @@ function DeviceStateReporter({
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Wlacza mikrofon po przyznaniu glosu.
|
||||
*
|
||||
* Uczestnik prosil o glos, wiec po zgodzie ma od razu mowic, a nie szukac
|
||||
* przycisku. Czeka na UPRAWNIENIE od serwera, a nie na samo zdarzenie
|
||||
* o przyznaniu: to drugie potrafi przyjsc szybciej, a proba publikacji przed
|
||||
* nadaniem prawa konczy sie odmowa.
|
||||
*
|
||||
* Raz na przyznanie: jesli uczestnik sam sie wyciszy, nie wlaczamy mu
|
||||
* mikrofonu z powrotem przy kolejnej zmianie uprawnien.
|
||||
*/
|
||||
function MicOnGrant({ granted }: { granted: boolean }) {
|
||||
const { localParticipant } = useLocalParticipant()
|
||||
const permissions = useLocalParticipantPermissions()
|
||||
const done = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!granted) {
|
||||
done.current = false
|
||||
return
|
||||
}
|
||||
if (done.current || !permissions?.canPublish) return
|
||||
done.current = true
|
||||
localParticipant.setMicrophoneEnabled(true).catch(() => {
|
||||
// brak zgody przegladarki na mikrofon: uczestnik wlaczy go przyciskiem
|
||||
})
|
||||
}, [granted, permissions, localParticipant])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -217,6 +217,8 @@ function SpeakButton({
|
||||
/*
|
||||
* Napis o mikrofonie idzie za UPRAWNIENIEM, a nie za stanem zgloszenia:
|
||||
* mowi o tym, co uczestnik faktycznie moze, a nie o tym, o co prosil.
|
||||
* Nie twierdzi, ze mikrofon jest wlaczony: sala wlacza go sama po
|
||||
* przyznaniu glosu, ale uczestnik moze go zaraz wyciszyc przyciskiem.
|
||||
* Przycisk "Oddaj glos" zostaje przy zgloszeniu, bo dotyczy wlasnie jego.
|
||||
*/
|
||||
if (speak.granted) {
|
||||
@@ -224,7 +226,7 @@ function SpeakButton({
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="inline-flex items-center gap-1.5 rounded-md bg-ok-50 px-3 py-1.5 text-sm font-semibold text-ok-600">
|
||||
<Icon name="check-circle" size={16} />
|
||||
{canPublishAudio ? 'Masz głos, mikrofon jest włączony' : 'Masz głos, trwa włączanie mikrofonu...'}
|
||||
{canPublishAudio ? 'Masz głos, możesz mówić' : 'Masz głos, trwa włączanie mikrofonu...'}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -303,7 +303,7 @@ function MeetingRoom({
|
||||
a nie w jednym uniwersalnym iframe. Panel interakcji, pasek akcji
|
||||
i banery zostaja nasze niezaleznie od tego wyboru.
|
||||
*/}
|
||||
<VideoArea join={join} src={src} />
|
||||
<VideoArea join={join} src={src} speakGranted={speak.granted} />
|
||||
|
||||
{/*
|
||||
OVERLAY: warstwa w rogu obrazu. Zamkniety baner nie wraca do konca
|
||||
@@ -396,7 +396,15 @@ function MeetingRoom({
|
||||
* da sie ich sprowadzic do wspolnego iframe. Daily zostaje przy iframe, bo tak
|
||||
* dziala jego gotowy komponent.
|
||||
*/
|
||||
function VideoArea({ join, src }: { join: JoinView; src: string | null }) {
|
||||
function VideoArea({
|
||||
join,
|
||||
src,
|
||||
speakGranted,
|
||||
}: {
|
||||
join: JoinView
|
||||
src: string | null
|
||||
speakGranted: boolean
|
||||
}) {
|
||||
/*
|
||||
* Stan urzadzen przezywajacy przemontowanie sali.
|
||||
*
|
||||
@@ -417,30 +425,21 @@ function VideoArea({ join, src }: { join: JoinView; src: string | null }) {
|
||||
<div className="min-h-[38vh] flex-1 lg:min-h-0">
|
||||
<Suspense fallback={<VideoLoading />}>
|
||||
{/*
|
||||
PRZEMONTOWANIE WYLACZNIE PRZY ZMIANIE WLASNYCH UPRAWNIEN
|
||||
BEZ PRZEMONTOWANIA PRZY ZMIANIE UPRAWNIEN
|
||||
---------------------------------------------------------------
|
||||
`LiveKitRoomProvider` laczy sie tokenem podanym przy montowaniu
|
||||
i nie renegocjuje uprawnien na zywo, wiec po przyznaniu glosu
|
||||
trzeba go przemontowac: sama podmiana propa `token` nie otworzy
|
||||
mikrofonu, dopoki uczestnik nie przeladuje strony.
|
||||
Uprawnienia zmienia na zywo serwer pokoju (`RoomPermissionSync`
|
||||
po stronie backendu), a biblioteka sama pokazuje albo chowa
|
||||
przycisk mikrofonu i kamery. Wczesniej sala byla przemontowywana
|
||||
przy kazdej zmianie, co oznaczalo ponowne wejscie: chwile
|
||||
ciemnego ekranu, zerwany dzwiek i wyciszenie mowiacego.
|
||||
|
||||
Ale przemontowanie jest PONOWNYM WEJSCIEM: provider startuje
|
||||
z `audio={false}`, wiec kazda opublikowana sciezka przepada
|
||||
i mowiacy zostaje wyciszony. Klucz nie moze wiec zalezec od
|
||||
samego tokenu, bo backend wydaje przy kazdym odswiezeniu inny
|
||||
ciag (JWT niesie czas wydania), takze gdy uprawnienia sa te same.
|
||||
|
||||
Zdarzylo sie to na zywo: prowadzacy wylaczyl "wideo dla wszystkich"
|
||||
przy wlaczonym otwartym mikrofonie, front pobral token i przemontowal
|
||||
sale CALEJ grupie, wyciszajac ludzi w trakcie wypowiedzi. Prawo do
|
||||
mikrofonu wcale sie wtedy nie zmienilo.
|
||||
|
||||
Klucz sklada wiec faktyczne uprawnienia tego uczestnika, ktore
|
||||
backend wylicza z roli, obu trybow i przyznanego glosu. Zmiana
|
||||
trybu, ktora go nie dotyczy, nie rusza jego sali.
|
||||
Nowy token nadal przychodzi przy kazdej zmianie, ale na polaczonej
|
||||
sali podmiana nic nie robi (`room.connect` przy stanie Connected
|
||||
konczy sie od razu). Uzyty zostaje dopiero przy ponownym wejsciu
|
||||
po zerwaniu polaczenia, i wtedy niesie aktualne uprawnienia.
|
||||
*/}
|
||||
<LiveKitRoom
|
||||
key={`${join.canPublishAudio ? 'a' : '-'}${join.canPublishVideo ? 'v' : '-'}`}
|
||||
key="sala"
|
||||
serverUrl={join.url}
|
||||
token={join.token}
|
||||
/*
|
||||
@@ -454,6 +453,7 @@ function VideoArea({ join, src }: { join: JoinView; src: string | null }) {
|
||||
onDevicesChange={(state) => {
|
||||
devicesRef.current = state
|
||||
}}
|
||||
speakGranted={speakGranted}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user