diff --git a/doc/stream.txt b/doc/stream.txt index 17d75304..605b52d4 100644 --- a/doc/stream.txt +++ b/doc/stream.txt @@ -919,6 +919,8 @@ Desktop: Callback копирует PCM в предвыделенную очередь; DSP/Opus в нём нет. Hardware PTS используется при наличии, иначе шкала строится по количеству отсчётов. Очередь/возраст ограничены; при разрыве сбрасываются только неполный PCM-пакет и история компрессора. Mute передаёт тишину. + Fatal ошибка audio encoder завершает публикацию по session ID, без отката видеонастроек; + причина сохраняется в окне управления после FIN. Поздняя ошибка старой сессии игнорируется. Публикация эксклюзивно занимает микрофон: звонок/рация/запись и публикация запрещают друг другу новый capture. Прежнее совместное владение микрофоном голосовыми функциями сохраняется. - transport/stream_bridge владеет независимым stream_publisher; shared C actor выполняет uAV2/RS, @@ -957,7 +959,8 @@ test_stream_publish проверяет реальные encoder/decoder H.264/H. с прежним decoder, общую публикацию/RS, смену revision, pressure/KEY recovery, FIN и concurrent detach с балансом tracked allocations. Дополнительный Xvfb/private D-Bus сценарий проверяет реальный экран/окно, стереомикрофон с двумя разными тонами, сырое превью, закрытие окна управления, неподвижный источник, -смену codec/bitrate/rotation и rollback без смены audio/source_epoch. +смену codec/bitrate/rotation и rollback без смены audio/source_epoch, затем отказ Opus после +подтверждённого KEY с завершением эфира и освобождением microphone lease. Статистика отправки на экране публикации: - Отдельный uasync timer общего stream_publisher каждые 100ms делает согласованный TX snapshot. diff --git a/tools/chatgui/src/stream_publish_window.cpp b/tools/chatgui/src/stream_publish_window.cpp index b1bcd9dc..c1718952 100644 --- a/tools/chatgui/src/stream_publish_window.cpp +++ b/tools/chatgui/src/stream_publish_window.cpp @@ -80,6 +80,7 @@ StreamPublishWindow::StreamPublishWindow(StreamPublishEngine* engine, uint64_t g m_stats = new QLabel(this); m_stats->setWordWrap(true); m_stats->setMinimumHeight(46); layout->addWidget(m_stats); m_start = new QPushButton(text("Start stream"), this); layout->addWidget(m_start); connect(m_start, &QPushButton::clicked, this, [this] { + if (!m_engine->active()) m_status->setProperty("streamError", QString()); if (m_engine->active()) m_engine->stop(); else m_engine->start(m_group, selection()); synchronize(); }); for (auto* box : {m_source, m_mode, m_codec, m_bitrate}) connect(box, QOverload::of(&QComboBox::currentIndexChanged), this, &StreamPublishWindow::apply); @@ -90,8 +91,13 @@ StreamPublishWindow::StreamPublishWindow(StreamPublishEngine* engine, uint64_t g #endif connect(engine, &StreamPublishEngine::codecsChanged, this, &StreamPublishWindow::refreshCodecs); connect(engine, &StreamPublishEngine::configurationChanged, this, &StreamPublishWindow::synchronize); - connect(engine, &StreamPublishEngine::stateChanged, this, [this](const QString& value) { m_status->setText(value); synchronize(); }); - connect(engine, &StreamPublishEngine::errorOccurred, this, [this](const QString& value) { m_status->setText(value); }); + connect(engine, &StreamPublishEngine::stateChanged, this, [this](const QString& value) { + auto error = m_status->property("streamError").toString(); + m_status->setText(error.isEmpty() ? value : value + "\n" + error); synchronize(); + }); + connect(engine, &StreamPublishEngine::errorOccurred, this, [this](const QString& value) { + m_status->setProperty("streamError", value); m_status->setText(value); + }); connect(engine, &StreamPublishEngine::audioChannelsChanged, this, [this](int channels) { m_inputChannels->setText(channels == 2 ? text("Stereo") : text("Mono")); }); connect(engine, &StreamPublishEngine::telemetryChanged, this, [this](const stream_publisher_status& stats) { m_stats->setText(text("Video queue: %1 · Inflight: %2 · Send buffer: %3\nACK, all connections, last second: %4")