88 lines
7.8 KiB
C++
88 lines
7.8 KiB
C++
/* Окно всех активных трансляций одного канала. Собственный 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 и уничтожить плитки; повтор безопасен.
|
||
};
|