Browse Source

Show live sources and select a stream in desktop channel headers

master
evgeny 1 week ago
parent
commit
fe7734b9e0
  1. 63
      tools/chatgui/src/messagelist.cpp
  2. 7
      tools/chatgui/src/messagelist.h

63
tools/chatgui/src/messagelist.cpp

@ -1,5 +1,6 @@
#include <QCoreApplication>
#include "messagelist.h"
#include "../transport/stream_bridge.h"
#include "debug_ui.h"
#include "chatview.h"
#include "messagedelegate.h"
@ -426,6 +427,30 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent
connect(m_radioBtn, &QPushButton::toggled, this, &MessageList::onRadioToggled);
hl->addWidget(m_radioBtn);
m_streamBtn = new QPushButton(header); m_streamBtn->setObjectName("streamButton");
QPixmap streamIcon(24, 24); streamIcon.fill(Qt::transparent);
{ QPainter painter(&streamIcon); painter.setRenderHint(QPainter::Antialiasing); painter.setPen(QPen(QColor("#e53935"), 2));
painter.drawRoundedRect(QRectF(2, 6, 20, 14), 2, 2); painter.drawLine(8, 1, 12, 5); painter.drawLine(16, 1, 12, 5);
painter.setBrush(QColor("#e53935")); painter.drawEllipse(QPointF(12, 13), 3, 3); }
m_streamBtn->setIcon(QIcon(streamIcon)); m_streamBtn->setIconSize(QSize(24, 24)); m_streamBtn->hide();
m_streamBtn->setAccessibleName(QCoreApplication::translate("ChatGui", "Live streams"));
m_streamBtn->setCursor(Qt::PointingHandCursor);
connect(m_streamBtn, &QPushButton::clicked, this, [this] {
auto watch = [this](const LiveSource& source) {
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "desktop selected stream group=%s source=%llx epoch=%llx",
qPrintable(m_currentChannelId), (unsigned long long)source.id, (unsigned long long)source.epoch);
emit streamRequested(m_currentChannelId.toULongLong(), source.id, source.epoch, source.name);
};
if (m_streamSources.size() == 1) { watch(m_streamSources.front()); return; }
QMenu menu(this);
for (const auto& source : m_streamSources) {
auto* action = menu.addAction(QStringLiteral("%1 · %2").arg(source.name).arg(source.id));
connect(action, &QAction::triggered, this, [watch, source] { watch(source); });
}
menu.exec(m_streamBtn->mapToGlobal(QPoint(0, m_streamBtn->height())));
});
hl->addWidget(m_streamBtn);
m_groupMenuButton = new QPushButton(QStringLiteral("…"), header);
m_groupMenuButton->setObjectName("groupMenuButton");
m_groupMenuButton->setFixedSize(34, 30);
@ -749,6 +774,7 @@ void MessageList::loadChannel(const QString& channelId) {
m_avatarCache.clear();
if (m_currentChannelId != channelId) { m_feedback.clear(); m_pendingPoll.clear(); }
m_currentChannelId = channelId;
clearStreams(); watchStreams();
m_groupMenuButton->setVisible(channelId.toULongLong() != 0);
clearDmCallRoute();
m_callBtn->setVisible(isDmChannel(channelId));
@ -1451,6 +1477,43 @@ void MessageList::onRadioTalk(quint64 groupId, quint64 srcNodeId, bool on) {
}
}
/* Каталог выбранного канала не удерживает media owner; DM/пустой target снимает наблюдение. */
void MessageList::watchStreams() {
if (gui_stream_ready()) gui_stream_watch(isDmChannel(m_currentChannelId) ? 0 : m_currentChannelId.toULongLong());
}
void MessageList::clearStreams() { m_streamSources.clear(); m_streamBtn->hide(); }
/* Полный снимок применяется атомарно; ответы предыдущего канала не меняют текущую шапку. */
void MessageList::onStreamSources(const uint8_t* data, int len) {
if (!data || len < 11 || data[8] > 1) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop source event invalid len=%d", len); return; }
quint64 group = stream_read_be(data, 8);
if (isDmChannel(m_currentChannelId) || group != m_currentChannelId.toULongLong()) return;
QJsonParseError error;
auto document = QJsonDocument::fromJson(QByteArray(reinterpret_cast<const char*>(data + 9), len - 9), &error);
if (!data[8] || error.error != QJsonParseError::NoError || !document.isArray()) {
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop source list failed group=%llx valid=%u parse=%s",
(unsigned long long)group, data[8], qPrintable(error.errorString()));
clearStreams(); return;
}
QList<LiveSource> sources;
for (const auto& value : document.array()) {
auto member = value.toObject(); bool idOk = false, epochOk = false;
quint64 id = member.value("id").toString().toULongLong(&idOk);
quint64 epoch = member.value("epoch").toString().toULongLong(&epochOk, 16);
if (!idOk || !epochOk || !id || !epoch || !member.value("name").isString()) {
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop source identity malformed group=%llx", (unsigned long long)group);
clearStreams(); return;
}
QString name = member.value("name").toString(); if (name.isEmpty()) name = QString::number(id);
sources.push_back({id, epoch, name});
}
m_streamSources = std::move(sources); m_streamBtn->setVisible(!m_streamSources.isEmpty());
m_streamBtn->setText(m_streamSources.size() > 1 ? QString::number(m_streamSources.size()) : QString());
m_streamBtn->setToolTip(QCoreApplication::translate("ChatGui", "Live streams: %1").arg(m_streamSources.size()));
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "desktop stream header group=%llx sources=%d", (unsigned long long)group, (int)m_streamSources.size());
}
void MessageList::onRadioToggled(bool on) {
if (m_currentChannelId.isEmpty() || isDmChannel(m_currentChannelId)) return;

7
tools/chatgui/src/messagelist.h

@ -46,11 +46,15 @@ public:
void onFeedbackResult(const uint8_t* data, int len);
void onMessageSubmitted(const uint8_t* data, int len);
void onRadioTalk(quint64 groupId, quint64 srcNodeId, bool on); /* индикация говорящего */
void onStreamSources(const uint8_t* data, int len);
void clearStreams();
void watchStreams();
signals:
void readPositionChanged(const QString& channelId, qint64 lastReadMsgId);
void callRequested(const QString& target, quint64 peer, const QString& name);
void openVideoRequested(const QString& filePath);
void streamRequested(quint64 group, quint64 source, quint64 epoch, const QString& name);
void openImageRequested(const QString& filePath);
void inviteRequested(const QString& sourceChannel, quint64 peer, const QString& name);
void ownFilesRequested(const QString& channelId);
@ -83,6 +87,9 @@ private:
quint64 m_callPeer = 0;
quint64 m_callGroup = 0;
QPushButton* m_radioBtn = nullptr; /* шапка: переключатель рации */
QPushButton* m_streamBtn = nullptr;
struct LiveSource { quint64 id, epoch; QString name; };
QList<LiveSource> m_streamSources;
RadioPanel* m_radioPanel = nullptr; /* верхняя панель устройств, VAD и PTT */
bool m_radioOn = false;
QThreadPool m_thumbnailPool;

Loading…
Cancel
Save