Cover desktop local preview and multiview navigation

This commit is contained in:
evgeny committed 2026-10-20 20:39:24 +02:00
1 parent e5510e9f62
commit 64064c2cbf
2 files changed
+69 -1

No files matched your search

+4
View File
@@ -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()
+65 -1
View File
@@ -13,6 +13,12 @@
#include <QSlider>
#include <QPainter>
#include <QWheelEvent>
#ifdef HAVE_STREAM_CAPTURE
#include <QVideoWidget>
#include <QVideoSink>
#include <QVideoFrame>
#include <QVideoFrameFormat>
#endif
#include <atomic>
#include <cmath>
#include <chrono>
@@ -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<uint64_t>(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<uint8_t*>(bytes.data()), group, 8); bytes[8] = valid;
bytes += QJsonDocument(sources).toJson(QJsonDocument::Compact);
grid.onSources(reinterpret_cast<const uint8_t*>(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<VideoRenderWidget*>(), 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<uint8_t*>(bytes.data()), group, 8); bytes[8] = 1;
bytes += QJsonDocument(sources).toJson(QJsonDocument::Compact);
grid.onSources(reinterpret_cast<const uint8_t*>(bytes.constData()), bytes.size());
};
snapshot(false);
auto* ownTile = grid.findChild<QWidget*>(QStringLiteral("streamPreview_%1").arg(own)); QVERIFY(ownTile);
auto* preview = ownTile->findChild<QVideoWidget*>(); QVERIFY(preview);
QVERIFY(grid.findChildren<StreamPlayerWindow*>().isEmpty()); QVERIFY(ownTile->findChildren<QSlider*>().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<StreamPlayerWindow> remote = grid.findChild<StreamPlayerWindow*>();
QVERIFY(remote); auto* video = remote->findChild<VideoRenderWidget*>(); 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<QSlider*>(); 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<StreamPlayerWindow*>(), 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 получает отдельную эпоху. */