diff --git a/tools/chatgui/tests/test_stream_publish.cpp b/tools/chatgui/tests/test_stream_publish.cpp index 4258eae7..9f94109f 100644 --- a/tools/chatgui/tests/test_stream_publish.cpp +++ b/tools/chatgui/tests/test_stream_publish.cpp @@ -3,10 +3,11 @@ #include #include #include -#include -#include #include -#include +#include +#include +#include +#include #include #include #include @@ -146,6 +147,19 @@ class StreamPublishTest : public QObject { QTimer poll; uint64_t group = 0; size_t allocations = 0; + static QMenu* menu(QWidget& controls, const char* name) { return controls.findChild(name); } + static QVariant chosen(QMenu* menu) { + auto* action = menu->findChild()->checkedAction(); return action ? action->data() : QVariant(); + } + static QList choices(QMenu* menu) { + QList result; + for (auto* action : menu->actions()) if (action->isCheckable()) result.append(action); + return result; + } + static void choose(QMenu* menu, const QVariant& data) { + for (auto* action : choices(menu)) if (action->data() == data) { action->trigger(); return; } + QFAIL("Missing menu choice"); + } static QByteArray keyframe(int codec, int width = 160, int height = 90, uint64_t pts = 100000) { #ifndef HAVE_FFMPEG Q_UNUSED(codec); Q_UNUSED(width); Q_UNUSED(height); Q_UNUSED(pts); @@ -201,8 +215,8 @@ private slots: QVERIFY(!StreamPublishEngine::supported()); QVERIFY(StreamPublishEngine::sources().isEmpty()); QVERIFY(engine.codecs().isEmpty()); QVERIFY(!engine.previewFrame().isValid()); StreamPublishWindow window(&engine, group); window.show(); - auto* source = window.findChild(QStringLiteral("streamVideoSource")); - QVERIFY(source); QCOMPARE(source->count(), 0); + auto* source = menu(window, "streamVideoSource"); + QVERIFY(source); QCOMPARE(choices(source).size(), 0); QPushButton* start = nullptr; for (auto* button : window.findChildren()) if (button->text() == QCoreApplication::translate("ChatGui", "Start stream")) start = button; @@ -264,60 +278,61 @@ private slots: } void captureSettingsSurviveReopening() { QString saved; - QVariantList selectedMode; + QSize selectedSize; { StreamPublishEngine engine; StreamPublishWindow controls(&engine, group); connect(&controls, &StreamPublishWindow::settingsChanged, this, [&saved](const QString& value) { saved = value; }); - auto boxes = controls.findChildren(); QCOMPARE(boxes.size(), 5); - if (StreamPublishEngine::supported()) QTRY_VERIFY_WITH_TIMEOUT(boxes[2]->count() > 0, 15000); + auto* codec = menu(controls, "streamVideoCodec"); + if (StreamPublishEngine::supported()) QTRY_VERIFY_WITH_TIMEOUT(!choices(codec).isEmpty(), 15000); auto sources = StreamPublishEngine::sources(); int screen = -1; for (int i = 0; i < sources.size(); i++) if (sources[i].kind == StreamVideoSource::Screen && !sources[i].size.isEmpty()) { screen = i; break; } if (screen < 0) QSKIP("No enumerated screen for capture settings"); - boxes[0]->setCurrentIndex(screen); + choose(menu(controls, "streamVideoSource"), screen); auto* cursor = controls.findChild("streamShowCursor"); QVERIFY(cursor); QVERIFY(cursor->isChecked()); cursor->setChecked(false); QSize maximum = stream_video_screen_size(sources[screen].size); - QCOMPARE(boxes[1]->currentData().toList(), (QVariantList{maximum.width(), maximum.height(), 30})); - QVERIFY(boxes[1]->count() >= 6); - boxes[1]->setCurrentIndex(boxes[1]->count() - 1); // Меньший размер сохраняется при повторном открытии. - selectedMode = boxes[1]->currentData().toList(); - boxes[3]->setCurrentIndex(boxes[3]->findData(4000000)); - controls.findChild("streamMaxQp")->setValue(25); + auto* resolution = menu(controls, "streamVideoResolution"); + QCOMPARE(chosen(resolution).toSize(), maximum); QCOMPARE(chosen(menu(controls, "streamVideoFps")).toInt(), 30); + QVERIFY(choices(resolution).size() >= 2); + choices(resolution).last()->trigger(); // Меньший размер сохраняется при повторном открытии. + selectedSize = chosen(resolution).toSize(); choose(menu(controls, "streamVideoFps"), 60); + choose(menu(controls, "streamVideoBitrate"), 4000000); + choose(menu(controls, "streamMaxQp"), 23); controls.findChild("streamAdaptiveQp")->setChecked(true); - controls.findChild()->button(270)->click(); - controls.findChildren().first()->setChecked(false); - if (boxes[2]->count() > 1) boxes[2]->setCurrentIndex(boxes[2]->count() - 1); + choose(menu(controls, "streamVideoRotation"), 270); + choose(menu(controls, "streamAudioSource"), QVariant()); + if (choices(codec).size() > 1) choices(codec).last()->trigger(); QVERIFY(!saved.isEmpty()); } const auto stored = QJsonDocument::fromJson(saved.toUtf8()).object(); - QCOMPARE(stored.value("width").toInt(), selectedMode[0].toInt()); QCOMPARE(stored.value("fps").toInt(), 60); + QCOMPARE(stored.value("width").toInt(), selectedSize.width()); QCOMPARE(stored.value("fps").toInt(), 60); QVERIFY(!stored.value("native_size").toBool()); QCOMPARE(stored.value("bitrate").toInt(), 4000000); QCOMPARE(stored.value("rotation").toInt(), 270); QVERIFY(!stored.value("audio").toBool()); QVERIFY(!stored.value("show_cursor").toBool(true)); - QCOMPARE(stored.value("max_qp").toInt(), 25); QVERIFY(stored.value("adaptive_qp").toBool()); + QCOMPARE(stored.value("max_qp").toInt(), 23); QVERIFY(stored.value("adaptive_qp").toBool()); StreamPublishEngine reopenedEngine; StreamPublishWindow reopened(&reopenedEngine, group, nullptr, saved); QSignalSpy changes(&reopened, &StreamPublishWindow::settingsChanged); if (StreamPublishEngine::supported()) QTRY_VERIFY_WITH_TIMEOUT(!reopenedEngine.codecs().isEmpty(), 15000); - auto boxes = reopened.findChildren(); - QCOMPARE(boxes[1]->currentData().toList(), selectedMode); - QCOMPARE(boxes[3]->currentData().toInt(), 4000000); - QCOMPARE(reopened.findChild("streamMaxQp")->value(), 25); + QCOMPARE(chosen(menu(reopened, "streamVideoResolution")).toSize(), selectedSize); + QCOMPARE(chosen(menu(reopened, "streamVideoFps")).toInt(), 60); + QCOMPARE(chosen(menu(reopened, "streamVideoBitrate")).toInt(), 4000000); + QCOMPARE(chosen(menu(reopened, "streamMaxQp")).toInt(), 23); QVERIFY(reopened.findChild("streamAdaptiveQp")->isChecked()); - if (StreamPublishEngine::supported()) QTRY_COMPARE(boxes[2]->currentData().toInt(), stored.value("codec").toInt()); - QCOMPARE(reopened.findChild()->checkedId(), 270); - QVERIFY(!reopened.findChildren().first()->isChecked()); + if (StreamPublishEngine::supported()) QTRY_COMPARE(chosen(menu(reopened, "streamVideoCodec")).toInt(), stored.value("codec").toInt()); + QCOMPARE(chosen(menu(reopened, "streamVideoRotation")).toInt(), 270); + QVERIFY(!chosen(menu(reopened, "streamAudioSource")).isValid()); QVERIFY(!reopened.findChild("streamShowCursor")->isChecked()); QVERIFY(changes.isEmpty()); // Асинхронный список кодеков не перезаписывает сохранённый выбор. - boxes[3]->setCurrentIndex(0); + choose(menu(reopened, "streamVideoBitrate"), 0); QCOMPARE(changes.size(), 1); auto automatic = changes.takeFirst().front().toString(); QCOMPARE(QJsonDocument::fromJson(automatic.toUtf8()).object().value("bitrate").toInt(-1), 0); StreamPublishWindow restoredAuto(&reopenedEngine, group, nullptr, automatic); - QCOMPARE(restoredAuto.findChildren()[3]->currentIndex(), 0); + QCOMPARE(chosen(menu(restoredAuto, "streamVideoBitrate")).toInt(), 0); } void qpAdaptationFollowsBitrateWithoutSpikes() { StreamQpControl qp; qp.reset(28, 1000000, true, 0); @@ -423,8 +438,8 @@ private slots: } void screenSelectionAndRefresh() { StreamPublishEngine engine; StreamPublishWindow controls(&engine, group); - auto* sourceBox = controls.findChild("streamVideoSource"); - auto* modeBox = controls.findChild("streamVideoMode"); + auto* sourceBox = menu(controls, "streamVideoSource"); + auto* resolution = menu(controls, "streamVideoResolution"); auto sources = StreamPublishEngine::sources(); QSignalSpy changes(&controls, &StreamPublishWindow::settingsChanged); QList screenIds; @@ -432,17 +447,52 @@ private slots: for (int i = 0; i < sources.size(); i++) { if (sources[i].kind != StreamVideoSource::Screen || sources[i].size.isEmpty()) continue; QVERIFY(!screenIds.contains(sources[i].id)); screenIds.append(sources[i].id); - screens++; sourceBox->setCurrentIndex(-1); changes.clear(); sourceBox->setCurrentIndex(i); + screens++; changes.clear(); choose(sourceBox, i); QSize size = stream_video_screen_size(sources[i].size); - auto mode = modeBox->currentData().toList(); - QCOMPARE(mode[0].toInt(), size.width()); QCOMPARE(mode[1].toInt(), size.height()); - QVERIFY(modeBox->currentData(Qt::UserRole + 1).toBool()); QCOMPARE(changes.size(), 1); - QVERIFY(sourceBox->currentText().contains(sources[i].name)); - modeBox->setCurrentIndex(modeBox->count() - 1); mode = modeBox->currentData().toList(); changes.clear(); - emit engine.sourcesChanged(); QCOMPARE(modeBox->currentData().toList(), mode); QVERIFY(changes.isEmpty()); + QCOMPARE(chosen(resolution).toSize(), size); QCOMPARE(changes.size(), 1); + QVERIFY(sourceBox->findChild()->checkedAction()->text().contains(sources[i].name)); + choices(resolution).last()->trigger(); QSize selectedSize = chosen(resolution).toSize(); changes.clear(); + emit engine.sourcesChanged(); QCOMPARE(chosen(resolution).toSize(), selectedSize); QVERIFY(changes.isEmpty()); } if (!screens) QSKIP("No enumerated screens"); } + void compactControlMenus() { + StreamPublishEngine engine; StreamPublishWindow controls(&engine, group); controls.show(); + controls.resize(controls.minimumSize()); QTest::qWait(20); + auto* bar = controls.findChild("streamControls"); QVERIFY(bar); + auto* start = controls.findChild("streamStart"); QVERIFY(start); + auto* sound = controls.findChild("streamSound"); QVERIFY(sound); + auto* buffer = controls.findChild("streamBufferIndicator"); QVERIFY(buffer); + QCOMPARE(buffer->height(), 20); QCOMPARE(buffer->width(), std::max(80, start->sizeHint().width())); + QCOMPARE(buffer->geometry().center().y(), start->geometry().center().y()); + QVERIFY(std::abs(bar->geometry().center().y() - start->geometry().center().y()) <= 1); + QVERIFY(bar->geometry().right() < sound->geometry().left()); QVERIFY(sound->geometry().right() < buffer->geometry().left()); + auto* qp = menu(controls, "streamMaxQp"); QCOMPARE(choices(qp).size(), 6); QCOMPARE(chosen(qp).toInt(), 28); + auto* audio = menu(controls, "streamAudioSource"); + choose(audio, QVariant()); QVERIFY(!chosen(audio).isValid()); QVERIFY(!sound->isChecked()); + choose(audio, QString()); QVERIFY(chosen(audio).isValid()); QVERIFY(sound->isChecked()); + auto* resolution = menu(controls, "streamVideoResolution"); auto* fps = menu(controls, "streamVideoFps"); + for (auto* submenu : {resolution, fps, qp, audio}) { + QVERIFY(!submenu->actions().first()->text().isEmpty()); QVERIFY(!submenu->actions().first()->isCheckable()); + QVERIFY(bar->rect().contains(bar->actionGeometry(submenu->menuAction()))); + } + QTest::mouseClick(bar, Qt::LeftButton, {}, bar->actionGeometry(resolution->menuAction()).center()); + QTRY_VERIFY(resolution->isVisible()); + QTest::mouseClick(bar, Qt::LeftButton, {}, bar->actionGeometry(fps->menuAction()).center()); + QTRY_VERIFY(fps->isVisible()); QVERIFY(!resolution->isVisible()); + QTest::mouseClick(bar, Qt::LeftButton, {}, bar->actionGeometry(fps->menuAction()).center()); + QTRY_VERIFY(!fps->isVisible()); + const auto sources = engine.sources(); + for (int i = 0; i < sources.size(); i++) { + choose(menu(controls, "streamVideoSource"), i); + const auto modes = sources[i].kind == StreamVideoSource::Camera ? sources[i].modes : stream_video_screen_modes(sources[i].size); + for (auto* sizeAction : choices(resolution)) { + sizeAction->trigger(); const QSize size = chosen(resolution).toSize(); + for (auto* rate : choices(fps)) QCOMPARE(rate->isEnabled(), modes.contains({size, rate->data().toInt()})); + if (!modes.isEmpty()) QVERIFY(modes.contains({size, chosen(fps).toInt()})); + } + } + } void nativeScreenCapture_data() { QTest::addColumn("codec"); QTest::newRow("h264") << int(STREAM_MEDIA_H264); @@ -606,9 +656,9 @@ private slots: if (source.kind != StreamVideoSource::Screen || source.screen != screen) continue; QVERIFY(StreamPublishEngine::cursorSupported(source)); StreamPublishWindow controls(&engine, group); - auto* sources = controls.findChild("streamVideoSource"); QVERIFY(sources); + auto* sources = menu(controls, "streamVideoSource"); QVERIFY(sources); auto all = engine.sources(); - for (int i = 0; i < all.size(); i++) if (all[i].sameSource(source)) sources->setCurrentIndex(i); + for (int i = 0; i < all.size(); i++) if (all[i].sameSource(source)) choose(sources, i); auto* toggle = controls.findChild("streamShowCursor"); QVERIFY(toggle); QVERIFY(!toggle->isHidden()); QVERIFY(toggle->isChecked()); } @@ -933,7 +983,7 @@ private slots: auto* preview = controls.findChild(); QVERIFY(preview); QTRY_VERIFY(preview->videoSink()->videoFrame().isValid()); QSignalSpy closed(&controls, &StreamPublishWindow::closed); - auto* choice = controls.findChild(); QVERIFY(choice); choice->setFocus(); + auto* choice = controls.findChild(); QVERIFY(choice); choice->setFocus(); QTest::keyClick(choice, Qt::Key_Escape); QTRY_COMPARE(closed.count(), 1); QVERIFY(!controls.isVisible()); QVERIFY(engine.running()); QVERIFY(record(205, StreamRecordKind::Video, out)); QVERIFY(out.pts > firstPts); @@ -980,7 +1030,7 @@ private slots: QTRY_VERIFY(preview->videoSink()->videoFrame().isValid()); QSignalSpy closed(&controls, &StreamPublishWindow::closed); controls.activateWindow(); QVERIFY(QTest::qWaitForWindowActive(&controls)); - auto* sourceChoice = controls.findChild(); QVERIFY(sourceChoice); sourceChoice->setFocus(); + auto* sourceChoice = controls.findChild(); QVERIFY(sourceChoice); sourceChoice->setFocus(); QTest::keyClick(sourceChoice, Qt::Key_Escape); QTRY_COMPARE(closed.count(), 1); QVERIFY(!controls.isVisible()); QVERIFY(engine.running()); uint64_t first = out.pts; unsigned audio = 0, video = 0; uint64_t latest = first, left = 0, right = 0; diff --git a/tools/chatgui/translations/chatgui_ru.ts b/tools/chatgui/translations/chatgui_ru.ts index 03f7c59f..b6e91bbb 100644 --- a/tools/chatgui/translations/chatgui_ru.ts +++ b/tools/chatgui/translations/chatgui_ru.ts @@ -154,6 +154,14 @@ Use CPU encoding instead? This may cause high CPU usage and slow down the comput Cannot read videoНе удалось прочитать видео Could not send the call commandНе удалось отправить команду звонка Video sourceИсточник видео + Video resolutionРазрешение видео + Frame rateЧастота кадров + Original resolutionИсходное разрешение + %1 fps%1 кадр/с + Audio sourceИсточник звука + No audioБез звука + Turn stream audio onВключить звук трансляции + Turn stream audio offВыключить звук трансляции Show mouse cursorПоказывать курсор мыши Change cursor visibility before starting screen sharingПоказ курсора можно изменить до начала трансляции экрана Screen capture failed: %1Ошибка захвата экрана: %1