diff --git a/tools/chatgui/CMakeLists.txt b/tools/chatgui/CMakeLists.txt index 88189c55..10ebc9ff 100644 --- a/tools/chatgui/CMakeLists.txt +++ b/tools/chatgui/CMakeLists.txt @@ -675,6 +675,10 @@ add_executable(test_stream_view tests/test_stream_view.cpp src/streamplayer_wind transport/miniaudio_impl.c transport/audio_diagnostics.cpp) target_include_directories(test_stream_view PRIVATE ${UTUN_INCLUDE_DIRS} ${FFMPEG_INCLUDE_DIRS}) target_link_libraries(test_stream_view PRIVATE ${QT_LIBS} ${QT_TEST_LIB} utun_voice utun pthread ${CMAKE_DL_LIBS} ${FFMPEG_TARGET} ${FFMPEG_LIBRARIES}) +if(GUI_CAPTURE_ENABLED) + target_compile_definitions(test_stream_view PRIVATE HAVE_STREAM_CAPTURE) + target_link_libraries(test_stream_view PRIVATE Qt6::Multimedia Qt6::MultimediaWidgets) +endif() if(FFMPEG_FOUND) target_compile_definitions(test_stream_view PRIVATE HAVE_FFMPEG) endif() diff --git a/tools/chatgui/tests/test_stream_view.cpp b/tools/chatgui/tests/test_stream_view.cpp index 564569c1..5e3c8fd6 100644 --- a/tools/chatgui/tests/test_stream_view.cpp +++ b/tools/chatgui/tests/test_stream_view.cpp @@ -13,6 +13,12 @@ #include #include #include +#ifdef HAVE_STREAM_CAPTURE +#include +#include +#include +#include +#endif #include #include #include @@ -444,7 +450,7 @@ private slots: QJsonArray sources; for (int i = 0; i < count; ++i) sources.append(QJsonObject{ {"id", QString::number(inst->node_id ^ static_cast(i))}, - {"epoch", QString::number(epoch, 16)}, {"name", QStringLiteral("Source %1").arg(i)}, {"self", i == 0 ? 1 : 0}}); + {"epoch", QString::number(epoch, 16)}, {"name", QStringLiteral("Source %1").arg(i)}, {"self", 0}}); QByteArray bytes(9, '\0'); stream_write_be(reinterpret_cast(bytes.data()), group, 8); bytes[8] = valid; bytes += QJsonDocument(sources).toJson(QJsonDocument::Compact); grid.onSources(reinterpret_cast(bytes.constData()), bytes.size()); @@ -463,7 +469,10 @@ private slots: QCOMPARE(stream_publish_finish(publisher), STREAM_OK); stream_close(publisher); publisher = nullptr; QCOMPARE(stream_publish_open(inst, group, nullptr, &publisher), STREAM_OK); stream_info info{}; QCOMPARE(stream_get_info(publisher, &info), STREAM_OK); epoch = info.descriptor.source_epoch; + QTest::mouseClick(first->findChild(), Qt::LeftButton); + QTRY_COMPARE(grid.focusedSource(), inst->node_id); snapshot(1); QVERIFY(first.isNull()); QCOMPARE(grid.tileCount(), 1); + QCOMPARE(grid.focusedSource(), uint64_t(0)); QVERIFY(!grid.isFullScreen()); snapshot(0); QTRY_COMPARE_WITH_TIMEOUT(grid.tileCount(), 0, 3000); QVERIFY(grid.isVisible()); QCOMPARE(grid.gridSide(), 0); snapshot(1); QCOMPARE(grid.tileCount(), 1); @@ -474,6 +483,61 @@ private slots: #endif } + void gridLocalPreviewAndTileNavigation() { +#if defined(TEST_NULL_AUDIO) && defined(HAVE_STREAM_CAPTURE) + StreamGridWindow grid(group); grid.show(); grid.activateWindow(); + const auto own = inst->node_id ^ uint64_t(1); + auto snapshot = [&](bool remote) { + QJsonArray sources{QJsonObject{{"id", QString::number(own)}, {"epoch", QString::number(epoch, 16)}, + {"name", "Own preview"}, {"self", 1}}}; + if (remote) sources.append(QJsonObject{{"id", QString::number(inst->node_id)}, {"epoch", QString::number(epoch, 16)}, + {"name", "Remote fixture"}, {"self", 0}}); + QByteArray bytes(9, '\0'); stream_write_be(reinterpret_cast(bytes.data()), group, 8); bytes[8] = 1; + bytes += QJsonDocument(sources).toJson(QJsonDocument::Compact); + grid.onSources(reinterpret_cast(bytes.constData()), bytes.size()); + }; + snapshot(false); + auto* ownTile = grid.findChild(QStringLiteral("streamPreview_%1").arg(own)); QVERIFY(ownTile); + auto* preview = ownTile->findChild(); QVERIFY(preview); + QVERIFY(grid.findChildren().isEmpty()); QVERIFY(ownTile->findChildren().isEmpty()); + stream_info info{}; QTest::qWait(30); + QCOMPARE(stream_get_info(publisher, &info), STREAM_OK); QCOMPARE(info.owners, 1u); + QVideoFrame frame(QVideoFrameFormat(QSize(16, 16), QVideoFrameFormat::Format_BGRA8888)); + QVERIFY(frame.map(QVideoFrame::WriteOnly)); memset(frame.bits(0), 0xff, frame.mappedBytes(0)); frame.unmap(); + frame.setStartTime(123000); + grid.setLocalPreview(group, QVariant::fromValue(frame)); + QCOMPARE(preview->videoSink()->videoFrame().startTime(), qint64(123000)); + grid.setLocalPreview(group ^ 1, QVariant()); QVERIFY(preview->videoSink()->videoFrame().isValid()); + grid.setLocalPreview(group, QVariant()); QVERIFY(!preview->videoSink()->videoFrame().isValid()); + grid.setLocalPreview(group, QVariant::fromValue(frame)); + QSignalSpy manage(&grid, &StreamGridWindow::publishRequested); + QTest::mouseClick(preview, Qt::LeftButton); QTRY_COMPARE(manage.count(), 1); + QCOMPARE(manage.at(0).at(0).toULongLong(), group); QCOMPARE(grid.focusedSource(), uint64_t(0)); + snapshot(true); + QPointer remote = grid.findChild(); + QVERIFY(remote); auto* video = remote->findChild(); QVERIFY(video); + QTRY_VERIFY(stream_get_info(publisher, &info) == STREAM_OK && info.owners == 2); + QTest::mouseClick(video, Qt::LeftButton); + QTRY_COMPARE(grid.focusedSource(), inst->node_id); QVERIFY(grid.isFullScreen()); QVERIFY(ownTile->isHidden()); + auto* volume = remote->findChild(); QVERIFY(volume); volume->setFocus(); + QTest::keyClick(volume, Qt::Key_Escape); + QTRY_COMPARE(grid.focusedSource(), uint64_t(0)); + QVERIFY(!grid.isFullScreen()); QVERIFY(remote->isVisible()); QVERIFY(ownTile->isVisible()); + QCOMPARE(grid.findChild(), remote.data()); + QCOMPARE(stream_get_info(publisher, &info), STREAM_OK); QCOMPARE(info.owners, 2u); + QTest::mouseClick(video, Qt::LeftButton); QTRY_COMPARE(grid.focusedSource(), inst->node_id); + QTest::mouseClick(video, Qt::LeftButton); QTRY_COMPARE(grid.focusedSource(), uint64_t(0)); + video->setFrame(coordinateFrame()); zoomVideo(*video, 12000, video->rect().center()); + dragVideo(*video, video->rect().center(), video->rect().center() + QPoint(40, 20)); + QTest::qWait(20); QCOMPARE(grid.focusedSource(), uint64_t(0)); // Pan не считается кликом. + grid.close(); QVERIFY(remote.isNull()); + QTRY_VERIFY(stream_get_info(publisher, &info) == STREAM_OK && info.owners == 1); + DEBUG_INFO(DEBUG_CATEGORY_STREAM, "fixture multiview local preview: reader=0 audio=0; click/Esc/drag preserved remote owner"); +#else + QSKIP("Direct preview fixture requires Qt Multimedia and null audio"); +#endif + } + void realH264RenderingAndClose() { #ifdef HAVE_FFMPEG /* Фикстура протокола выше содержит фиктивный H264; реальный decoder получает отдельную эпоху. */