Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 64d5bc391d | |||
| ae696abff7 |
@@ -42,18 +42,41 @@ export function useSpeakRequest(token: string | undefined) {
|
||||
if (!token) return
|
||||
let cancelled = false
|
||||
|
||||
publicClient
|
||||
.get<{ request: SpeakRequestView | null }>(`/public/participation/${token}/speak-request`)
|
||||
.then((response) => {
|
||||
if (!cancelled) setRequest(response.data.request)
|
||||
})
|
||||
.catch(() => {
|
||||
// brak stanu nie moze zablokowac wejscia na spotkanie: uczestnik
|
||||
// zobaczy przycisk w stanie wyjsciowym i zglosi sie ponownie
|
||||
})
|
||||
function load() {
|
||||
publicClient
|
||||
.get<{ request: SpeakRequestView | null }>(`/public/participation/${token}/speak-request`)
|
||||
.then((response) => {
|
||||
if (!cancelled) setRequest(response.data.request)
|
||||
})
|
||||
.catch(() => {
|
||||
// brak stanu nie moze zablokowac wejscia na spotkanie: uczestnik
|
||||
// zobaczy przycisk w stanie wyjsciowym i zglosi sie ponownie
|
||||
})
|
||||
}
|
||||
|
||||
load()
|
||||
|
||||
/*
|
||||
* ODSWIEZANIE OKRESOWE JAKO SIATKA BEZPIECZENSTWA
|
||||
* -------------------------------------------------------------------------
|
||||
* WebSocket zostaje glowna droga: uczestnik ma zobaczyc przyznany glos od
|
||||
* razu, a nie po kilkunastu sekundach.
|
||||
*
|
||||
* Zdarzenie potrafi jednak nie dojsc, a polaczenie nie zawsze to
|
||||
* sygnalizuje: karta uspiona przez system, chwilowa utrata sieci,
|
||||
* przelaczenie z WiFi na komorke. Bez odpytywania uczestnik NIE DOWIE SIE,
|
||||
* ZE MA GLOS: przycisk zostaje w starym stanie, a mikrofon zamkniety,
|
||||
* chociaz uprawnienie jest juz przyznane po stronie serwera. Wyglada to na
|
||||
* niedzialajacy mikrofon, a nie na zgubione zdarzenie.
|
||||
*
|
||||
* Odpowiedz to jeden maly obiekt albo null, wiec dziesiec sekund nie kosztuje
|
||||
* nic zauwazalnego nawet przy duzej sali.
|
||||
*/
|
||||
const timer = window.setInterval(load, 10000)
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
window.clearInterval(timer)
|
||||
}
|
||||
}, [token])
|
||||
|
||||
|
||||
@@ -32,6 +32,19 @@ export function MeetingActionBar({
|
||||
isSpeaker,
|
||||
/** Tryb otwartego mikrofonu: kazdy mowi bez proszenia o glos. */
|
||||
openMic,
|
||||
/**
|
||||
* Faktyczne uprawnienie do mikrofonu, prosto z tokenu wydanego przez backend.
|
||||
*
|
||||
* JEDYNE ZRODLO PRAWDY O TYM, CZY UCZESTNIK MOZE MOWIC
|
||||
* ---------------------------------------------------------------------
|
||||
* Wczesniej pasek skladal te odpowiedz sam, ze stanu zgloszenia
|
||||
* (`speak.granted`) i trybu otwartego mikrofonu. Rozjezdzalo sie to
|
||||
* z rzeczywistoscia, bo prawo do audio daje takze rola ACTIVE_PARTICIPANT
|
||||
* przy wlaczonym "wideo dla wszystkich": odebranie glosu nie zmienialo wtedy
|
||||
* uprawnien, ale napis "Masz glos" znikal. Uczestnik widzial zamkniety
|
||||
* mikrofon przy dzialajacym i odwrotnie.
|
||||
*/
|
||||
canPublishAudio,
|
||||
/**
|
||||
* Temat wybrany klikiem w baner.
|
||||
*
|
||||
@@ -46,6 +59,7 @@ export function MeetingActionBar({
|
||||
speak: ReturnType<typeof useSpeakRequest>
|
||||
isSpeaker: boolean
|
||||
openMic: boolean
|
||||
canPublishAudio: boolean
|
||||
offerTopicFromBanner?: string | null
|
||||
onOfferHandled?: () => void
|
||||
}) {
|
||||
@@ -96,7 +110,7 @@ export function MeetingActionBar({
|
||||
Możesz mówić bez zgłaszania się
|
||||
</span>
|
||||
) : (
|
||||
<SpeakButton speak={speak} />
|
||||
<SpeakButton speak={speak} canPublishAudio={canPublishAudio} />
|
||||
))}
|
||||
|
||||
{/*
|
||||
@@ -193,13 +207,24 @@ export function MeetingActionBar({
|
||||
* oczekiwania, czyli dokladnie ten, w ktorym uczestnik zastanawia sie,
|
||||
* czy zgloszenie w ogole doszlo.
|
||||
*/
|
||||
function SpeakButton({ speak }: { speak: ReturnType<typeof useSpeakRequest> }) {
|
||||
function SpeakButton({
|
||||
speak,
|
||||
canPublishAudio,
|
||||
}: {
|
||||
speak: ReturnType<typeof useSpeakRequest>
|
||||
canPublishAudio: boolean
|
||||
}) {
|
||||
/*
|
||||
* 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.
|
||||
* Przycisk "Oddaj glos" zostaje przy zgloszeniu, bo dotyczy wlasnie jego.
|
||||
*/
|
||||
if (speak.granted) {
|
||||
return (
|
||||
<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} />
|
||||
Masz głos, mikrofon jest włączony
|
||||
{canPublishAudio ? 'Masz głos, mikrofon jest włączony' : 'Masz głos, trwa włączanie mikrofonu...'}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -240,6 +240,27 @@ function MeetingRoom({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [speak.granted, openMic, openVideo])
|
||||
|
||||
/*
|
||||
* Slad w konsoli po kazdej zmianie uprawnien.
|
||||
*
|
||||
* Rozjazd miedzy tym, co pokazuje interfejs, a tym, co faktycznie pozwala
|
||||
* serwer, jest trudny do zdiagnozowania po fakcie: uczestnik zglasza
|
||||
* "mikrofon nie dziala", a log serwera pokazuje poprawnie wydany token.
|
||||
* Ta linia pozwala porownac obie strony bez zgadywania - wystarczy otworzyc
|
||||
* konsole na sali. Nie jest to debugowanie do usuniecia: to jedyne miejsce,
|
||||
* w ktorym widac stan tokenu razem ze stanem zgloszenia.
|
||||
*/
|
||||
useEffect(() => {
|
||||
console.info('[sala] uprawnienia', {
|
||||
audio: join.canPublishAudio,
|
||||
video: join.canPublishVideo,
|
||||
rola: join.role,
|
||||
zgloszenieGlosu: speak.granted,
|
||||
otwartyMikrofon: openMic,
|
||||
wideoDlaWszystkich: openVideo,
|
||||
})
|
||||
}, [join.canPublishAudio, join.canPublishVideo, join.role, speak.granted, openMic, openVideo])
|
||||
|
||||
const src = useMemo(() => {
|
||||
if (!join.embedded) return null
|
||||
const url = new URL(join.url)
|
||||
@@ -337,6 +358,7 @@ function MeetingRoom({
|
||||
speak={speak}
|
||||
isSpeaker={isSpeaker}
|
||||
openMic={openMic}
|
||||
canPublishAudio={join.canPublishAudio}
|
||||
offerTopicFromBanner={offerTopicFromBanner}
|
||||
onOfferHandled={() => setOfferTopicFromBanner(null)}
|
||||
/>
|
||||
|
||||
@@ -127,8 +127,29 @@ export function HostConsolePage() {
|
||||
}
|
||||
}, [id])
|
||||
|
||||
/*
|
||||
* ODSWIEZANIE OKRESOWE JAKO SIATKA BEZPIECZENSTWA
|
||||
* ---------------------------------------------------------------------------
|
||||
* Kolejka odswieza sie zdarzeniem WebSocket i tak ma zostac: prowadzacy widzi
|
||||
* podniesiona reke od razu, a nie po kilkunastu sekundach.
|
||||
*
|
||||
* Zdarzenie potrafi jednak nie dojsc, a polaczenie nie zawsze to sygnalizuje:
|
||||
* karta uspiona przez system, chwilowa utrata sieci, przelaczenie WiFi na
|
||||
* telefonie. Klient wznawia wtedy polaczenie, ale zdarzenia wyslanego
|
||||
* w miedzyczasie nikt juz nie powtorzy, bo WebSocket nie ma historii.
|
||||
* Prowadzacy widzial przez to "Nikt nie prosi o glos" przy zgloszeniach
|
||||
* czekajacych juz kilka minut, i dowiadywal sie o nich dopiero po odswiezeniu
|
||||
* strony. Zdarzylo sie to na zywo.
|
||||
*
|
||||
* Odpytywanie jest wiec DRUGA droga, nie glowna. Piec sekund jest tanie:
|
||||
* kolejka to kilka wierszy, a odpowiedz przy pustej liscie wazy tyle co nic.
|
||||
* Ten sam wzorzec co przy banerach na sali, gdzie front takze nie polega
|
||||
* wylacznie na zdarzeniach.
|
||||
*/
|
||||
useEffect(() => {
|
||||
void loadSpeakQueue()
|
||||
const timer = window.setInterval(() => void loadSpeakQueue(), 5000)
|
||||
return () => window.clearInterval(timer)
|
||||
}, [loadSpeakQueue])
|
||||
|
||||
const webinar = useQuery({
|
||||
|
||||
Reference in New Issue
Block a user