Files
utun2/tools/chatgui/src/streamgrid_window.h
T

88 lines
7.8 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Окно всех активных трансляций одного канала. Собственный catalogue owner не зависит от выбранного чата.
* Плитка сохраняет player/decoder/часы при перекладке; новая эпоха заменяет только соответствующий player.
* Своя плитка получает setLocalPreview до кодирования, без reader/audio; клик запрашивает управление.
* Чужая плитка разворачивается без пересоздания players; Esc/повторный клик возвращает сетку.
* Пропавшему источнику даётся дочитать FIN, затем плитка удаляется. Пустое окно ждёт новые публикации.
* Все методы — GUI-поток; диспетчер передаёт снимки через onSources, publisher — кадры через setLocalPreview.
* Закрытие терминально: останавливает таймер/плитки и снимает свой watch. WA_DeleteOnClose задаёт владелец окна. */
#pragma once
#include <QWidget>
#include <QTimer>
#include <QElapsedTimer>
#include <QPointer>
#include <QVariant>
#include <vector>
#include <cstdint>
class StreamPlayerWindow;
class QGridLayout;
class QLabel;
class QPushButton;
class QCloseEvent;
class StreamGridWindow : public QWidget {
Q_OBJECT
public:
// group — ненулевая CHAT-группа; parent владеет окном по правилам QWidget, конструктор начинает свой watch.
explicit StreamGridWindow(uint64_t group, QWidget* parent = nullptr);
// Идемпотентно остановить viewers/watch; уничтожение плиток завершает их workers.
~StreamGridWindow() override;
// Неизменная группа этого окна.
uint64_t group() const { return m_group; }
// Заимствованный CHAT_EVT_STREAM_SOURCES: ошибка/другая группа игнорируются; valid=false сохраняет все плитки.
// Новая эпоха или смена self пересоздаёт соответствующую плитку; новое имя обновляет подписи без сброса playback.
void onSources(const uint8_t* data, int length);
// Последний QVideoFrame локального publisher в QVariant; пустой кадр сбрасывает preview, другая группа игнорируется.
void setLocalPreview(uint64_t group, const QVariant& frame);
// Число плиток, включая исчезнувшие источники, пока они дочитывают остаток.
int tileCount() const { return static_cast<int>(m_tiles.size()); }
// Сторона сетки ceil(sqrt(tileCount)); 0 для пустого окна, не меняется при разворачивании одной плитки.
int gridSide() const { return m_side; }
// ID развёрнутой чужой плитки; 0 означает общий вид.
uint64_t focusedSource() const { return m_focused; }
signals:
void publishRequested(quint64 group); // Открыть управление своей публикацией, сохранив это окно.
protected:
// stop до штатного закрытия QWidget; повторное show не возобновляет просмотр.
void closeEvent(QCloseEvent* event) override;
// Отложенная активация по короткому клику; drag/колесо остаются у видео для pan/zoom.
bool eventFilter(QObject* watched, QEvent* event) override;
private:
struct Tile {
uint64_t source, epoch; // Узел источника и эпоха публикации из каталога.
QString name; // Имя заголовка/повторно открываемого player.
bool self = false, previewPresented = false; // Прямой локальный preview; последний переданный ему кадр был валиден.
QWidget* widget = nullptr; // Владеет player либо прямым preview, удаляется целиком.
QWidget* video = nullptr; // Поверхность изображения; сюда направлены клики.
StreamPlayerWindow* player = nullptr; // Только remote: владелец decoder, аудиочасов и viewer.
QLabel* title = nullptr; // Подпись self-preview; подпись remote принадлежит StreamPlayerWindow.
QLabel* status = nullptr; // Статус прямого preview; у remote им владеет player.
qint64 removeAt = -1, retryAt = 0; // Дедлайны m_clock в мс: -1 не удалять, 0 повтор не назначен.
unsigned failures = 0; // Backoff decoder: 500мс, 1/2/4/8с, далее максимум 10с.
};
uint64_t m_group, m_owner; // Группа и независимый process-local owner каталога.
std::vector<Tile> m_tiles; // Каждый widget принадлежит окну; удаляется целиком при смене идентичности/stop.
// Элементы интерфейса принадлежат QObject-дереву этого окна.
QGridLayout* m_grid = nullptr;
QLabel* m_status = nullptr;
QLabel* m_empty = nullptr;
QPushButton* m_close = nullptr;
QVariant m_preview; // Только последний кадр издателя; сеть/декодер для self не открываются.
QTimer m_timer; // 250мс: повтор watch, backoff decoder и завершение исчезнувших источников.
QElapsedTimer m_clock; // Общие монотонные часы дедлайнов плиток.
int m_side = 0; // Последняя рассчитанная сторона сетки.
bool m_watching = false, m_closed = false; // Watch принят bridge; окно терминально остановлено.
uint64_t m_focused = 0; // Source развёрнутой чужой плитки; 0 — сетка.
Qt::WindowStates m_gridWindowState; // Состояние окна до разворачивания remote-плитки.
QPointer<QObject> m_pressed; // Поверхность нажатия; удаление плитки безопасно обнуляет указатель.
QPoint m_pressPosition; // Глобальная позиция нажатия для отличия клика от drag.
bool m_dragged = false; // Пройден QApplication::startDragDistance.
void createTile(Tile& tile); // Создать self-preview либо embedded player, установить фильтры кликов.
void updatePreview(Tile& tile); // Передать m_preview поверхности self и обновить её статус.
void activateTile(uint64_t source); // Self запрашивает управление; remote разворачивается/возвращается в сетку.
void restoreGrid(); // Восстановить состояние окна и раскладку, сохранив playback всех плиток.
void arrange(); // Переложить/скрыть widgets, сохранив их decoder, reader и часы.
void tick(); // Повторить неудачный watch/decoder либо удалить дочитавшую/просроченную плитку.
void stop(); // Терминально остановить таймер/watch и уничтожить плитки; повтор безопасен.
};