feat(admin): рычаги нагрузки медиа — потолок качества публикации и лимит плиток
instance_settings.media_limits (publish_quality_cap: off/720p/360p/180p, stage_max_tiles: 4/9/16/25) — новая вкладка «Нагрузка» в админке, дефолты (off/25) сохраняют текущее поведение существующих инсталляций. Настройка отдаётся не только GET /admin/settings, но и в join-ответе (JoinOut) — участнику нужно иметь её на руках ДО публикации трека, а /admin/settings доступен только администратору. Потолок качества применяется через RoomOptions.publishDefaults (videoEncoding + videoSimulcastLayers на пресетах VideoPresets LiveKit) — режет битрейт верхнего слоя симулкаста, реальное разрешение WebRTC подстраивает сам. Лимит плиток — фильтрация STAGE_GRID_LAYOUTS по columns*rows в StageGrid, лишние участники уходят на страницу пагинации вместо подписки. Значение приезжает в joinState вместе с токеном ДО первого рендера LiveKitRoom (RoomPage не рендерит его, пока joinState не заполнен целиком), поэтому смена настройки не переподключает уже вошедшего участника — roomOptions пересчитывается по стабильной ссылке на joinState, которая после подключения не меняется. Проверено вживую на локальном стенде (docker compose --profile media): сохранение/персист настроек, join отдаёт актуальные значения, уже подключённый участник не разрывается при смене настройки в другом окне.
This commit is contained in:
@@ -71,6 +71,7 @@ _MANAGED_KEYS = (
|
||||
"registration_team_choice",
|
||||
"registration_email_domain",
|
||||
"contact_email",
|
||||
"media_limits",
|
||||
)
|
||||
|
||||
|
||||
@@ -126,6 +127,7 @@ async def test_ensure_bootstrapped_imports_yaml_defaults(
|
||||
"registration_team_choice",
|
||||
"registration_email_domain",
|
||||
"contact_email",
|
||||
"media_limits",
|
||||
}
|
||||
cfg = await service.get()
|
||||
assert cfg.transcriber.provider == "faster_whisper_cpu"
|
||||
@@ -137,6 +139,11 @@ async def test_ensure_bootstrapped_imports_yaml_defaults(
|
||||
assert cfg.registration_email_domains == []
|
||||
assert cfg.contact_email_enabled is False
|
||||
assert cfg.contact_email is None
|
||||
# Дефолты сохраняют текущее (до появления настройки) поведение —
|
||||
# без ограничения качества и с максимумом сетки, равным фактическому
|
||||
# потолку `StageGrid` (5×5).
|
||||
assert cfg.media_limits.publish_quality_cap == "off"
|
||||
assert cfg.media_limits.stage_max_tiles == 25
|
||||
|
||||
|
||||
async def test_ensure_bootstrapped_is_idempotent_and_keeps_admin_edits(
|
||||
@@ -538,6 +545,28 @@ async def test_transcription_enabled_flag_toggles_both_transcriber_and_summarize
|
||||
assert cfg.summarizer.enabled is False
|
||||
|
||||
|
||||
async def test_media_limits_partial_update_keeps_untouched_field(
|
||||
db_session: AsyncSession, clean_instance_settings: None
|
||||
) -> None:
|
||||
"""Патч только одного поля `media_limits` не сбрасывает соседнее — оба поля
|
||||
живут в одной строке JSON, `update()` обязан подставлять текущее значение
|
||||
несменённого поля, а не дефолт модели."""
|
||||
service = InstanceSettingsService(db_session)
|
||||
await service.ensure_bootstrapped(PLUGINS_YAML)
|
||||
|
||||
cfg = await service.update(SettingsUpdateIn(publish_quality_cap="360p"))
|
||||
assert cfg.media_limits.publish_quality_cap == "360p"
|
||||
assert cfg.media_limits.stage_max_tiles == 25 # дефолт не тронут
|
||||
|
||||
cfg = await service.update(SettingsUpdateIn(stage_max_tiles=9))
|
||||
assert cfg.media_limits.stage_max_tiles == 9
|
||||
assert cfg.media_limits.publish_quality_cap == "360p" # предыдущая правка сохранилась
|
||||
|
||||
reloaded = await service.get()
|
||||
assert reloaded.media_limits.publish_quality_cap == "360p"
|
||||
assert reloaded.media_limits.stage_max_tiles == 9
|
||||
|
||||
|
||||
async def test_update_rejects_unavailable_ai_level(
|
||||
db_session: AsyncSession, clean_instance_settings: None
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user