From 872687e51b4cfe2b902f2a0ae1c9467cacd36f7d Mon Sep 17 00:00:00 2001 From: Maksim Sokolov <38921770+resokol@users.noreply.github.com> Date: Fri, 13 Nov 2026 07:12:15 +0000 Subject: [PATCH 1/5] feat(studio): show guest receive statistics with network quality guide --- CHANGELOG.md | 10 +- apps/web/components/GuestMiniStats.tsx | 78 +++++++ apps/web/components/GuestVideoCard.module.css | 78 +++++++ apps/web/components/GuestVideoCard.tsx | 4 +- apps/web/components/StudioWorkspace.tsx | 38 +++- apps/web/lib/guest-receive-bitrate.ts | 26 +++ apps/web/lib/i18n/workspaces.ts | 17 ++ .../fixtures/guest-mini-stats-refresh.tsx | 133 +++++++++++ .../guest-mini-stats-refresh.browser.cjs | 131 +++++++++++ apps/web/tests/guest-receive-bitrate.cjs | 27 +++ .../tests/network-quality-help.browser.cjs | 103 +++++++++ apps/web/tests/network-quality-help.cjs | 27 +++ apps/web/tests/run-ci.cjs | 2 + apps/web/tests/studio-console-a.cjs | 2 +- apps/web/tests/studio-guest-render.cjs | 23 +- distribution/RELEASE_NOTES_1_0_3.md | 29 +++ distribution/docs/en/RELEASE_NOTES_1_0_3.md | 28 +++ .../docs/zh-CN/RELEASE_NOTES_1_0_3.md | 22 ++ distribution/release-tracks.json | 2 +- docs/manual/FILE_MAP_RU.md | 214 ++++++++++++------ .../plans/2026-11-13-guest-network-help.md | 67 ++++++ .../2026-11-13-guest-network-help-design.md | 28 +++ 22 files changed, 1008 insertions(+), 81 deletions(-) create mode 100644 apps/web/components/GuestMiniStats.tsx create mode 100644 apps/web/lib/guest-receive-bitrate.ts create mode 100644 apps/web/tests/fixtures/guest-mini-stats-refresh.tsx create mode 100644 apps/web/tests/guest-mini-stats-refresh.browser.cjs create mode 100644 apps/web/tests/guest-receive-bitrate.cjs create mode 100644 apps/web/tests/network-quality-help.browser.cjs create mode 100644 apps/web/tests/network-quality-help.cjs create mode 100644 distribution/RELEASE_NOTES_1_0_3.md create mode 100644 distribution/docs/en/RELEASE_NOTES_1_0_3.md create mode 100644 distribution/docs/zh-CN/RELEASE_NOTES_1_0_3.md create mode 100644 docs/superpowers/plans/2026-11-13-guest-network-help.md create mode 100644 docs/superpowers/specs/2026-11-13-guest-network-help-design.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 0047dc29..7e17d814 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,18 @@ # Changelog -## 1.0.3 — обработка микрофона +## 1.0.3 server/web · 1.0.1 client — 2026-11-13 — статистика гостей / Guest statistics / 参与者统计 + +- RU: мини-статистика под карточкой гостя показывает измеренные битрейты приёма видео и микрофона и оценку сети. Маленькая стрелка открывает памятку с текущими числами и порогами; «Нет данных» тоже раскрывается. Обновления сохраняют видео и меню, одинаковые замеры не обновляют весь список. Обновите сервер и затем страницы вне эфира; Client 1.0.1 отображает интерфейс выбранного обновлённого сервера. [Примечания](distribution/RELEASE_NOTES_1_0_3.md). +- EN: guest cards show measured incoming camera/microphone bitrates and the network grade. A small arrow opens the current values and threshold guide, including No data. Updates preserve media and menus; identical samples preserve roster state. Update the server and reload pages off air; Client 1.0.1 displays its selected updated server's UI. [Release notes](distribution/docs/en/RELEASE_NOTES_1_0_3.md). +- 简体中文:参与者卡片显示摄像头和麦克风的实测接收码率及网络等级,小箭头可查看当前值和阈值,无数据时也可展开。更新保留视频和菜单,相同测量不更新整个列表。请在停播时更新服务器并刷新页面;Client 1.0.1 显示所选已更新服务器的界面。[发行说明](distribution/docs/zh-CN/RELEASE_NOTES_1_0_3.md)。 + +### Обработка микрофона - Шумоподавление и эхоподавление по умолчанию выключены. Ранее сохранённый явный выбор сохраняется. - В меню настроек микрофона — единый тумблер «Шумоподавление и эхо» с текущим состоянием. - Основная студия может включать и выключать обработку в карточке кастера без его ручного согласия. Приложение автоматически сообщает результат применения. - Переключение сохраняет выключенное состояние микрофона, не меняет камеру и не прерывает приватное обращение. -- Обновляются web и серверные пакеты Windows Server Setup/Linux; отдельная пересборка Windows/macOS Client не нужна для этого изменения web-интерфейса. После обновления сервера перезагрузите CastLink вне эфира. +- Обработка звука входит в общий web/Server Setup/Linux 1.0.3. Windows Client 1.0.1 показывает интерфейс выбранного сервера; перезагрузите страницы вне эфира. ## 1.0.2 — серверный выпуск / Server release / 服务器发行版 diff --git a/apps/web/components/GuestMiniStats.tsx b/apps/web/components/GuestMiniStats.tsx new file mode 100644 index 00000000..647af99e --- /dev/null +++ b/apps/web/components/GuestMiniStats.tsx @@ -0,0 +1,78 @@ +"use client"; + +import { useEffect, useId, useRef, useState } from "react"; +import { useI18n } from "./I18nProvider"; +import { StudioConsoleIcon } from "./StudioConsoleIcon"; +import { positionStudioConsolePopover } from "./studio-console-popover-position"; +import styles from "./GuestVideoCard.module.css"; + +export type GuestMiniStatsData = { + name?: string; + receiveVideoKbps?: number | null; + receiveAudioKbps?: number | null; + networkMeasured?: boolean; + statsSampledAt?: number; + statsSampledAtRef?: { current: number }; + stability: "Хорошая" | "Средняя" | "Плохая"; + packetLoss: number; + jitterMs: number; + rttMs: number; + camera: boolean; + microphone: boolean; +}; + +export function GuestMiniStats({ guest }: { guest: GuestMiniStatsData }) { + const { t, locale } = useI18n(); + const [now, setNow] = useState(Date.now); + const menuId = useId(); + const trigger = useRef(null), popup = useRef(null); + const [helpOpen, setHelpOpen] = useState(false); + const positionHelp = () => { if (popup.current && trigger.current) positionStudioConsolePopover(popup.current, trigger.current); }; + useEffect(() => { const timer = setInterval(() => setNow(Date.now()), 2000); return () => clearInterval(timer); }, []); + useEffect(() => { + if (!helpOpen) return; + window.addEventListener("resize", positionHelp); + window.addEventListener("scroll", positionHelp, true); + return () => { window.removeEventListener("resize", positionHelp); window.removeEventListener("scroll", positionHelp, true); }; + }, [helpOpen]); + const sampledAt = guest.statsSampledAtRef?.current ?? guest.statsSampledAt; + const fresh = sampledAt != null && sampledAt > 0 && now - sampledAt < 8000; + const measured = fresh && guest.networkMeasured; + const quality = !measured ? "unknown" : guest.stability === "Хорошая" ? "good" : guest.stability === "Средняя" ? "mid" : "bad"; + const bitrate = (value: number | null | undefined, enabled: boolean) => fresh && enabled && value != null && Number.isFinite(value) && value >= 0 + ? `${value.toLocaleString(locale)} ${t("кбит/с")}` : "—"; + const detail = measured ? t("Потери: {loss} пак. за всё время трека; джиттер: {jitter} мс; RTT: {rtt}", { + loss: guest.packetLoss, jitter: guest.jitterMs, rtt: guest.rttMs > 0 ? `${guest.rttMs} ${t("мс")}` : "—", + }) : t("Нет свежего замера соединения"); + return <>
+
{t("Приём видео")}
{bitrate(guest.receiveVideoKbps, guest.camera)}
+
{t("Приём аудио")}
{bitrate(guest.receiveAudioKbps, guest.microphone)}
+
{t("Сеть")}
+