lct-08: каркас фронта, типизированный сокет, захват микрофона
Направление канала зашито в тип: у наблюдателя Out = never, отправить нечего и нельзя; у преподавателя нет входящих. Разделение прав архитектурное и на фронте тоже. Пока соединения нет, события не копятся: истина на сервере, после переподключения придёт актуальное состояние. Ресемплинг 48 → 16 кГц усредняет по окну выходного сэмпла: без фильтра всё выше 8 кГц наложилось бы на речь и испортило распознавание. Проверено числами: секунда звука — ровно 50 кадров по 320 сэмплов из 48 и 44.1 кГц, помеха 15 кГц подавлена до 7.8%. Эхоподавление и автоусиление браузера выключены: гарнитура эхо не даёт, а обработка портит речь для STT. Бэкенд принимает бинарные кадры по тому же сокету, что и события; кадр не того размера отбрасывается с одним предупреждением, а не 50 в секунду. Логирование приложения не было настроено: uvicorn настраивает только свои логгеры, и весь INFO модулей app.* молча терялся — нашлось, когда в логе не оказалось строки о принятых кадрах. make lesson запускает занятие и печатает ссылки на экраны — пульта преподавателя ещё нет, а открыть АРМ курсанта без занятия не с чем. Путь из браузера с живым микрофоном не проверен: нужен человек, шаги записаны в карточке.
This commit is contained in:
parent
f01f2dab76
commit
f24031b5b2
21 changed files with 594 additions and 4 deletions
|
|
@ -168,3 +168,29 @@ def test_call_socket_refuses_session_that_was_not_started(client):
|
|||
with client.websocket_connect(f"/ws/call/{uuid4()}") as trainee:
|
||||
message = trainee.receive_json()
|
||||
assert message["type"] == "error" and message["code"] == "session_not_found"
|
||||
|
||||
|
||||
def test_audio_frames_reach_the_server(client):
|
||||
"""Веха lct-08: кадры PCM16 16 кГц по 20 мс долетают до бэкенда."""
|
||||
with lesson(client) as (session_id, _):
|
||||
state = hub.get(session_id)
|
||||
with client.websocket_connect(f"/ws/call/{session_id}") as trainee:
|
||||
for _ in range(50):
|
||||
trainee.send_bytes(b"\x00\x00" * 320) # секунда тишины
|
||||
trainee.send_bytes(b"\x00" * 100) # кадр не того размера
|
||||
wait_value(lambda: state.bad_frames or None)
|
||||
|
||||
assert state.audio_frames == 50
|
||||
assert state.bad_frames == 1, "кадр не того размера должен отбрасываться, а не считаться звуком"
|
||||
|
||||
|
||||
def test_events_still_work_between_audio_frames(client):
|
||||
"""Звук и события идут по одному сокету: бинарь не должен ломать разбор JSON."""
|
||||
with lesson(client) as (session_id, _):
|
||||
state = hub.get(session_id)
|
||||
with client.websocket_connect(f"/ws/call/{session_id}") as trainee:
|
||||
trainee.send_bytes(b"\x00\x00" * 320)
|
||||
trainee.send_json({"type": "kio.patch", "fields": {"floor": "5"}})
|
||||
trainee.send_bytes(b"\x00\x00" * 320)
|
||||
wait_for(lambda: state.kio.floor == "5")
|
||||
assert state.audio_frames == 2
|
||||
|
|
|
|||
Loading…
Reference in a new issue