Describe stream bridge ownership, asynchronous requests and snapshots

This commit is contained in:
evgeny committed 2026-10-28 00:58:59 +01:00
1 parent 3960e28638
commit 1f0c7483e0
2 files changed
+42 -12

No files matched your search

+2 -1
View File
@@ -22,7 +22,8 @@ public:
~StreamPortalCapture() override;
// Начать асинхронный выбор: showCursor=true требует Embedded=2, false — Hidden=1; fallback режима отсутствует.
// На служебные этапы отведено 30с, на первый кадр 8с; ожидание пользователя на Start не ограничено.
// Успех возвращается через frameReady; отказ/отмена выбора/повторный start — через errorOccurred.
// Успех — frameReady; отказ/отмена выбора — errorOccurred. Повтор активного start завершает захват ошибкой;
// после Failed start ничего не запускает и повторно не сообщает ошибку.
void start(bool showCursor);
signals:
// Скопированный QVideoFrame; ссылка заимствована на время сигнала, для сохранения скопировать объект.
+40 -11
View File
@@ -2,7 +2,8 @@
* Один общий publisher и независимые viewers; команды содержат IDs, не borrowed pointers ядра.
* Плеер запрашивает ограниченный батч; каждый трек имеет свой бюджет, новые данные будят worker.
* Watch до attach запоминает выбранную группу. Каталог идёт через CHAT_EVT_STREAM_SOURCES,
* media pull читает ближайшие кадры из общего RS-кольца; копия нужна только для передачи между потоками. */
* media pull читает ближайшие кадры из общего RS-кольца; QByteArray владеет копией payload.
* notify вызывается под внутренними mutex: только будить worker, не входить повторно в bridge/API ядра. */
#pragma once
#include <QByteArray>
@@ -16,35 +17,63 @@
struct UTUN_INSTANCE;
enum class StreamRecordKind { Video, Audio, Gap, Opened, End, Closed, VideoEnd, AudioEnd };
struct StreamRecord {
uint64_t id = 0, pts = 0;
StreamRecordKind kind = StreamRecordKind::Gap;
stream_media_format format{};
int flags = 0, reason = 0;
QByteArray data;
uint64_t id = 0, pts = 0; // ID viewer и PTS этого источника в микросекундах; PTS значим для media.
StreamRecordKind kind = StreamRecordKind::Gap; // Media либо событие открытия/разрыва/завершения трека.
stream_media_format format{}; // Формат VIDEO/AUDIO; для управляющих событий нули.
int flags = 0, reason = 0; // VIDEO: STREAM_MEDIA_FLAG_KEY; CLOSED: stream_close_reason.
QByteArray data; // Owned access unit/Opus; у управляющих событий пусто, живёт независимо от viewer/ядра.
};
/* Создать общий publisher/viewers для inst с работающим stream-сервисом; uasync, вне callbacks.
* true — managers готовы; false — неверный inst, повторный attach либо отказ создания, с логом нижнего слоя.
* Сохранённые watches применяются к новому ядру; отдельный отказ watch логируется и не отменяет attach. */
bool gui_stream_attach(UTUN_INSTANCE* inst);
/* Только совпавший inst: отменить pending watch, снять watches, уничтожить managers; NULL/старое ядро — no-op.
* uasync вне callbacks до destroy inst; mutex ждёт внешних callers. Выбор watches сохраняется для нового attach. */
void gui_stream_detach(UTUN_INSTANCE* inst);
/* owner=0 — выбранный чат; окно многопросмотра держит свой ненулевой owner. group=0 снимает только его. */
/* owner=0 — выбранный чат; окно многопросмотра держит свой ненулевой owner. group=0 снимает только его.
* Последнее значение каждого owner сохраняется до attach/через detach; работающий bridge объединяет изменения в post.
* true — выбор принят, не подтверждение доступности каталога. false — не выделен post, прежний выбор не меняется. */
bool gui_stream_watch(uint64_t group, uint64_t owner = 0);
/* notify только будит worker; arg живёт до gui_stream_stop/detach. Callback не вызывает bridge/API ядра. */
/* Ненулевые id/group/source/epoch; true — асинхронное открытие VIDEO|AUDIO принято, итог через Opened/Closed.
* id уникален до обработки stop actor. notify может вызываться сразу; arg живёт до gui_stream_stop/detach.
* Callback только будит worker, не вызывает bridge/API ядра. false — отказ открытия/установки notify. */
bool gui_stream_open(uint64_t id, uint64_t group, uint64_t source, uint64_t epoch,
void (*notify)(void*) = nullptr, void* arg = nullptr);
/* Синхронно разбудить/снять notify и асинхронно закрыть viewer; старый id допустим, полученные records сохраняются. */
void gui_stream_stop(uint64_t id);
/* true — одна готовая запись записана в record. false — record не меняется; при наличии manager запрошена выдача.
* position<0 прогревает звук до KEY; иначе позиция playback в мкс. audioAhead=-1 отключает звук, иначе 0..60000000 мкс.
* Это режим выдачи по одной записи: не смешивать с pull_batch на том же id без сброса reader. */
bool gui_stream_pull(uint64_t id, StreamRecord& record, int64_t position = -1,
bool wantVideo = true, int64_t audioAhead = STREAM_MEDIA_READ_AHEAD_US);
/* Добавляет готовый батч в records; при пустом ответе запрашивает до videoBudget кадров и audioAhead звука.
* Вызывать после расходования нужного трека: удерживаемые видеокадры не мешают запросить только звук. */
/* true — готовый батч добавлен в records, прежние записи не удаляются. false — новых записей пока нет либо viewer недоступен.
* При пустом ответе запрашивает до STREAM_VIEWER_BATCH_RECORDS записей: videoBudget=0..16 кадров и audioAhead звука.
* position/audioAhead как у pull. Вызывать после расходования нужного трека; удерживаемое видео не мешает запросить только звук.
* Запрос исполняется асинхронно, false не отличает ожидание от отказа demand; ошибки scheduling логируются ядром. */
bool gui_stream_pull_batch(uint64_t id, std::deque<StreamRecord>& records, int64_t position,
unsigned videoBudget, int64_t audioAhead);
/* true — последний снимок viewer, сохраняется после End/Closed до stop; до обновления actor/после resync — нули.
* false — ID/manager недоступен, выход не меняется. */
bool gui_stream_status(uint64_t id, stream_media_status& status);
/* true — снимок цепочки; раз в 200мс запрашивает обновление в uasync без ожидания его результата.
* До первого снимка нули; false не меняет telemetry. */
bool gui_stream_telemetry(uint64_t id, stream_viewer_telemetry& telemetry);
/* Очистить готовый ответ и запросить поиск ближайшего KEY только этого viewer; true — принято. */
bool gui_stream_resync(uint64_t id);
/* Есть прикреплённое ядро/managers; не означает открытие viewer/publisher или готовность сетевых маршрутов. */
bool gui_stream_ready();
/* Publisher независим от viewer; lifetime ядра ограничивает общий bridge mutex. */
/* Единственный publisher независим от viewers. Ненулевые id/group и допустимый format; true — принято OPENING.
* После Ended/Failed возможен новый id, результат открытия читается publish_status. */
bool gui_stream_publish_open(uint64_t id, uint64_t group, const stream_media_format& format);
/* true — mute/unmute принят в OPENING/LIVE без смены эпохи/аудиокодека; включение требует audio codec в format. */
bool gui_stream_publish_audio(uint64_t id, bool enabled);
/* 1 — data скопирован в очередь, 0 — нужен KEY/перегрузка, -1 — ошибка/старый id; приём не гарантирует доставку.
* pts<=INT64_MAX строго растёт отдельно по трекам; video требует полный access unit и format, audio — один Opus.
* Новый video revision принимается с KEY; format/key для audio игнорируются. data/format заимствованы только на вызов. */
int gui_stream_publish_feed(uint64_t id, bool video, uint64_t pts, bool key, const QByteArray& data,
const stream_media_format* format = nullptr);
/* Запретить feed, дренировать очередь с TTL и удержать FIN 2с; в OPENING отменить открытие. Повтор/старый id допустимы. */
void gui_stream_publish_stop(uint64_t id);
/* true — последний снимок, включая ENDED/FAILED; false — id/manager недоступен, status не меняется. */
bool gui_stream_publish_status(uint64_t id, stream_publisher_status& status);