diff --git a/doc/stream.txt b/doc/stream.txt index 1a9a8b31..4f3b431c 100644 --- a/doc/stream.txt +++ b/doc/stream.txt @@ -416,12 +416,12 @@ adm_tags — блок владельца, для самоуправляемых Отдельного STREAM_ANNOUNCE, broadcast-обновлений и собственных TTL/версий анонсов нет. Опции содержат: -- stream_source: активна ли собственная трансляция; если активна — её source_epoch и параметры FEC. +- stream_source: активна ли собственная трансляция; если активна — её source_epoch, параметры FEC и включён ли звук. - stream_subscriptions: список (source_node_id, source_epoch), на которые узел локально подписан. Формат — совместимые с существующим плоским userinfo строковые значения; список кодируется однозначно, со строгим разбором 64-битных IDs без потери точности через JSON numbers. Зафиксированный формат: - stream_source = ":::" + stream_source = "::::" stream_subscriptions = ":[:r],..." Суффикс :r означает внутренний приём только ради помощи, без локального зрителя. Локальный viewer на общем экземпляре снимает :r; закрытие последнего viewer возвращает :r, если помощь ещё используется. @@ -925,6 +925,21 @@ Android: завершается с диагностикой, приём рации сохраняется. Окно VAD/экран/разрешения не обходятся общим захватом. Звонок и voice recording по-прежнему исключают публикацию в обе стороны. +Звук трансляции и рация (Android и desktop): +- Во время публикации с микрофоном звук можно выключить и включить. Заглушается только копия PCM + трансляции перед кодированием; захват, формат аудиотрека, PTS и source_epoch не меняются. + Opus продолжает передавать тишину, рация получает исходный PCM. Для публикации без аудиотрека + включение микрофона требует нового запуска, как и раньше. +- audio в подписанном stream_source означает положение переключателя источника, а не наличие речи. + У локального viewer рация этого источника в том же канале не воспроизводится, пока audio=1. + Громкость зрителя, паузы речи, отдельный микрофон рации и буферизация правило не меняют. +- Raw readers и relay без media cursor рацию не подавляют. Последний закрытый viewer, + выключение звука источником, завершение публикации, новая эпоха и остановка сервиса снимают подавление. + Ручное отключение участника независимо сохраняется в БД; итог — ручное ИЛИ временное отключение. + Подписка рации, пересылка, индикация говорящего и занятость эфира продолжают работать. +- Изменение состояния не синхронизирует задержанные буферы рации и трансляции. Управление звуком + не запускает повторный захват, пересогласование видео или сброс шкалы воспроизведения. + Логи: stream — epoch/owners, очереди, GAP и teardown; video — codec/config, KEY, PTS и декодер. Kotlin progress — DEBUG раз в секунду; C packet-level диагностика — TRACE по запросу. Проверки: test_stream_media (все пары потерь 6+2, parser/границы/GAP), test_android_stream diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamScreen.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamScreen.kt index cc24bdcb..09026451 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamScreen.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamScreen.kt @@ -123,6 +123,13 @@ fun StreamScreen(channelId: String, channelName: String, sources: List Column(Modifier.fillMaxSize().padding(padding).padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { Text(channelName, style = MaterialTheme.typography.titleMedium) + if (local?.ongoing == true && (selected == null || selected.self)) { + Row { + Checkbox(checked = local.audioEnabled, onCheckedChange = StreamController::setAudioEnabled, + enabled = local.phase == StreamPhase.LIVE && local.audioAvailable) + Text(stringResource(R.string.stream_audio), Modifier.padding(top = 12.dp)) + } + } if (selected != null) { Text(selected.displayName, style = MaterialTheme.typography.titleSmall) if (selected.self && local?.ongoing == true) {