MainPanel.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897
  1. #include "MainPanel.h"
  2. #include <QSplitter>
  3. #include <QVBoxLayout>
  4. #include <QIcon>
  5. #include <QPixmap>
  6. #include <QPainter>
  7. #include <QPolygon>
  8. #include <QSizePolicy>
  9. #include <QTimer>
  10. #include <QJsonDocument>
  11. #include <QJsonObject>
  12. #include <QJsonParseError>
  13. #include <QPen>
  14. #include <QResizeEvent>
  15. #include "network/websocketclient.h"
  16. #include <util/jsonmapper.h>
  17. #include <QDebug>
  18. #include <qtpromise/qpromise.h>
  19. #include <qtpromise/qpromisefuture.h>
  20. #include <qtpromise/qpromisehelpers.h>
  21. #include <QtConcurrent>
  22. #include "widgets/bubbletip.h"
  23. #include "widgets/chatView/chatwindow.h"
  24. #include <QComboBox>
  25. #include <QFrame>
  26. #include <QHBoxLayout>
  27. #include <QLabel>
  28. #include "AVPlayer/avplayerwidget.h"
  29. #include "api/roomapi.h"
  30. #include "appevent.h"
  31. #include "widgets/framelessbase.h"
  32. #include "widgets/functionbutton.h"
  33. #include "widgets/maskoverlay.h"
  34. #include "widgets/recorderwidget.h"
  35. #include "widgets/statswidget.h"
  36. // 新增:设置面板
  37. #include "themesettingswidget.h"
  38. namespace IconUtils {
  39. QIcon createSettingsIcon()
  40. {
  41. QPixmap pix(24, 24);
  42. pix.fill(Qt::transparent);
  43. QPainter p(&pix);
  44. p.setRenderHint(QPainter::Antialiasing);
  45. p.setPen(QPen(QColor(60,60,60), 2));
  46. p.drawEllipse(QPointF(12,12), 8, 8);
  47. p.drawLine(QPointF(12,2), QPointF(12,6));
  48. p.drawLine(QPointF(12,22), QPointF(12,18));
  49. p.end();
  50. return QIcon(pix);
  51. }
  52. QIcon createSearchIcon()
  53. {
  54. QPixmap pix(24, 24);
  55. pix.fill(Qt::transparent);
  56. QPainter p(&pix);
  57. p.setRenderHint(QPainter::Antialiasing);
  58. p.setPen(QPen(QColor(60,60,60), 2));
  59. p.drawEllipse(QPointF(10,10), 6, 6);
  60. p.drawLine(QPointF(14,14), QPointF(22,22));
  61. p.end();
  62. return QIcon(pix);
  63. }
  64. QIcon createUserIcon()
  65. {
  66. QPixmap pix(24, 24);
  67. pix.fill(Qt::transparent);
  68. QPainter p(&pix);
  69. p.setRenderHint(QPainter::Antialiasing);
  70. p.setPen(QPen(QColor(60,60,60), 2));
  71. p.drawEllipse(QPointF(12,9), 5, 5);
  72. p.drawRoundedRect(QRectF(6,14,12,8), 4, 4);
  73. p.end();
  74. return QIcon(pix);
  75. }
  76. QIcon createAudioDeviceIcon()
  77. {
  78. QPixmap pix(24, 24);
  79. pix.fill(Qt::transparent);
  80. QPainter p(&pix);
  81. p.setRenderHint(QPainter::Antialiasing);
  82. p.setPen(QPen(QColor(60,60,60), 2));
  83. p.drawRoundedRect(QRectF(5,6,14,12), 3, 3);
  84. p.drawLine(QPointF(12,18), QPointF(12,22));
  85. p.drawLine(QPointF(8,22), QPointF(16,22));
  86. p.end();
  87. return QIcon(pix);
  88. }
  89. QIcon createStreamIcon()
  90. {
  91. QPixmap pix(24, 24);
  92. pix.fill(Qt::transparent);
  93. QPainter p(&pix);
  94. p.setRenderHint(QPainter::Antialiasing);
  95. p.setPen(QPen(QColor(250,80,60), 2));
  96. p.setBrush(QColor(250,80,60));
  97. QPolygon tri; tri << QPoint(8,6) << QPoint(20,12) << QPoint(8,18);
  98. p.drawPolygon(tri);
  99. p.end();
  100. return QIcon(pix);
  101. }
  102. QIcon createChatIcon()
  103. {
  104. QPixmap pix(24, 24);
  105. pix.fill(Qt::transparent);
  106. QPainter p(&pix);
  107. p.setRenderHint(QPainter::Antialiasing);
  108. p.setPen(QPen(QColor(60,60,60), 2));
  109. p.setBrush(Qt::NoBrush);
  110. p.drawRoundedRect(QRectF(4,5,16,12), 3, 3);
  111. QPolygon tail; tail << QPoint(12,17) << QPoint(10,21) << QPoint(15,18);
  112. p.drawPolygon(tail);
  113. p.end();
  114. return QIcon(pix);
  115. }
  116. } // namespace IconUtils
  117. MainPanel::MainPanel(QWidget *parent)
  118. : QWidget(parent)
  119. , chatView(nullptr)
  120. {
  121. setAttribute(Qt::WA_StyledBackground, true);
  122. setStyleSheet("background: #f7f7f7;");
  123. m_debounceTimer = new QTimer(this);
  124. m_debounceTimer->setInterval(500);
  125. m_debounceTimer->setSingleShot(true);
  126. connect(m_debounceTimer, &QTimer::timeout, this, &MainPanel::handleDebouncedPlay);
  127. webSocketClient = new WebSocketClient(this);
  128. // 启用WebSocket自动重连,提升聊天室稳定性
  129. if (webSocketClient) {
  130. webSocketClient->setAutoReconnect(true);
  131. }
  132. chatView = new ChatWindow(webSocketClient);
  133. chatView->setMinimumWidth(400);
  134. // 防御:明确不随关闭销毁,避免父窗口关闭时误删
  135. chatView->setAttribute(Qt::WA_DeleteOnClose, false);
  136. // 连接聊天窗口关闭请求信号
  137. connect(chatView, &ChatWindow::windowCloseRequested, this, &MainPanel::onChatWindowCloseRequested);
  138. // statsWidget = new StatsWidget(this); // 暂时移除统计在此处的展示
  139. m_rightWidget = new QWidget;
  140. QVBoxLayout *vbox = new QVBoxLayout(m_rightWidget);
  141. vbox->setContentsMargins(0, 0, 0, 0);
  142. // vbox->addWidget(userProfile, 0);
  143. // vbox->addWidget(statsWidget, 0); // 暂时移除
  144. // 创建聊天窗口容器
  145. m_chatContainer = new QWidget(m_rightWidget);
  146. QVBoxLayout *chatLayout = new QVBoxLayout(m_chatContainer);
  147. chatLayout->setContentsMargins(0, 0, 0, 0);
  148. chatLayout->addWidget(chatView);
  149. vbox->addWidget(m_chatContainer, 1);
  150. splitter = new QSplitter(Qt::Horizontal, this);
  151. playerContainer = new QWidget(this);
  152. splitter->addWidget(playerContainer);
  153. splitter->addWidget(m_rightWidget);
  154. splitter->setStretchFactor(0, 60);
  155. splitter->setStretchFactor(1, 30);
  156. QVBoxLayout *mainLayout = new QVBoxLayout(this);
  157. mainLayout->addWidget(splitter, 1);
  158. mainLayout->setContentsMargins(0, 0, 0, 0);
  159. mainLayout->setSpacing(0);
  160. setLayout(mainLayout);
  161. // 为playerContainer设置初始布局
  162. QVBoxLayout *playerLayout = new QVBoxLayout(playerContainer);
  163. playerLayout->setContentsMargins(0, 0, 0, 0);
  164. buttonGroup = new PopoverButtonGroup(Qt::Horizontal, playerContainer);
  165. // 添加功能按钮
  166. FunctionButton *settingsBtn = new FunctionButton(IconUtils::createSettingsIcon(), "设置", this);
  167. // 移除 Popover 箭头,改为直接打开设置窗口
  168. buttonGroup->addButton(settingsBtn, nullptr);
  169. connect(settingsBtn, &QPushButton::clicked, this, &MainPanel::onSettingsButtonClicked);
  170. // 移除:搜索按钮(暂不使用)
  171. // FunctionButton *searchBtn = new FunctionButton(IconUtils::createSearchIcon(), "搜索", this);
  172. // Popover *searchPopover = new Popover(this);
  173. // buttonGroup->addButton(searchBtn, searchPopover);
  174. FunctionButton *userBtn = new FunctionButton(IconUtils::createUserIcon(), "用户", this);
  175. Popover *userPopover = new Popover(this);
  176. // 构建用户Popover内容:提供“退出”操作
  177. {
  178. QWidget *userContent = new QWidget(userPopover);
  179. QVBoxLayout *userLayout = new QVBoxLayout(userContent);
  180. userLayout->setContentsMargins(8, 8, 8, 8);
  181. userLayout->setSpacing(8);
  182. QPushButton *logoutBtn = new QPushButton(tr("退出"), userContent);
  183. logoutBtn->setMinimumWidth(120);
  184. connect(logoutBtn, &QPushButton::clicked, this, [this]() {
  185. emit logoutClicked();
  186. });
  187. userLayout->addWidget(logoutBtn);
  188. userPopover->setContentWidget(userContent);
  189. }
  190. buttonGroup->addButton(userBtn, userPopover);
  191. // 添加音频设备选择按钮
  192. FunctionButton *audioDeviceBtn = new FunctionButton(IconUtils::createAudioDeviceIcon(), "音频设备", this);
  193. Popover *audioDevicePopover = new Popover(this);
  194. // // 使用解耦合版本的音频设备选择器
  195. // m_audioDeviceSelectorDecoupled = new AudioDeviceSelectorIconDecoupled(this);
  196. // audioDevicePopover->setContentWidget(m_audioDeviceSelectorDecoupled);
  197. // 使用 RecorderAudioWidget 作为 Popover 内容(麦克风 + 扬声器)
  198. QWidget *audioContent = new QWidget(audioDevicePopover);
  199. QVBoxLayout *audioLayout = new QVBoxLayout(audioContent);
  200. audioLayout->setContentsMargins(8, 8, 8, 8);
  201. audioLayout->setSpacing(8);
  202. // 麦克风区域
  203. QLabel *micTitle = new QLabel(tr("麦克风"), audioContent);
  204. micTitle->setStyleSheet("font-weight:600;");
  205. m_micWidget = new QComboBox(audioContent);
  206. m_micWidget->setEditable(false);
  207. audioLayout->addWidget(micTitle);
  208. audioLayout->addWidget(m_micWidget);
  209. // 扬声器区域
  210. QLabel *speakerTitle = new QLabel(tr("扬声器"), audioContent);
  211. speakerTitle->setStyleSheet("font-weight:600;");
  212. m_speakerWidget = new QComboBox(audioContent);
  213. m_speakerWidget->setEditable(false);
  214. audioLayout->addWidget(speakerTitle);
  215. audioLayout->addWidget(m_speakerWidget);
  216. // 视频编码器区域
  217. QLabel *encoderTitle = new QLabel(tr("视频编码器"), audioContent);
  218. encoderTitle->setStyleSheet("font-weight:600;");
  219. m_encoderWidget = new QComboBox(audioContent);
  220. m_encoderWidget->setEditable(false);
  221. audioLayout->addWidget(encoderTitle);
  222. audioLayout->addWidget(m_encoderWidget);
  223. audioDevicePopover->setContentWidget(audioContent);
  224. buttonGroup->addButton(audioDeviceBtn, audioDevicePopover);
  225. // 新增:独立的推流按钮(无弹层)
  226. m_streamButton = new FunctionButton(IconUtils::createStreamIcon(), tr("推流"), this);
  227. buttonGroup->addButton(m_streamButton);
  228. // 新增:聊天按钮(推流:显示/隐藏;非推流:弹出/嵌入)
  229. m_chatButton = new FunctionButton(IconUtils::createChatIcon(), tr("聊天"), this);
  230. buttonGroup->addButton(m_chatButton);
  231. // 移除:执行操作按钮(暂不使用)
  232. // FunctionButton *actionButton = new FunctionButton(IconUtils::createSettingsIcon(),
  233. // "执行操作",
  234. // this);
  235. // buttonGroup->addButton(actionButton, nullptr);
  236. // 将buttonGroup添加到playerContainer的布局中
  237. playerLayout = qobject_cast<QVBoxLayout*>(playerContainer->layout());
  238. if (!playerLayout) {
  239. playerLayout = new QVBoxLayout(playerContainer);
  240. playerLayout->setContentsMargins(0, 0, 0, 0);
  241. }
  242. playerLayout->addStretch(1); // 添加弹性空间,将buttonGroup推到底部
  243. playerLayout->addWidget(buttonGroup, 0); // 添加buttonGroup到底部,不拉伸
  244. buttonGroup->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); // 使用固定大小策略
  245. // initConnect
  246. // 已移除与 UserProfileWidget 相关的在线状态更新
  247. // connect(webSocketClient, &WebSocketClient::statsUpdate, statsWidget, &StatsWidget::updateStats); // 暂时移除统计在主面板的更新
  248. connect(webSocketClient, &WebSocketClient::liveStatus, this, [this](const QString &msg) {
  249. // 这里可以处理 liveStatus 相关逻辑
  250. QJsonParseError err;
  251. QJsonDocument doc = QJsonDocument::fromJson(msg.toUtf8(), &err);
  252. if (err.error != QJsonParseError::NoError || !doc.isObject()) {
  253. qDebug() << "[MainPanel] liveStatus: 解析失败" << err.errorString();
  254. return;
  255. }
  256. QJsonObject obj = doc.object();
  257. int liveStatus = obj.value("liveStatus").toInt(0); // 默认-1
  258. if (liveStatus == 1) {
  259. qDebug() << "[MainPanel] liveStatus: 直播中" << chatView;
  260. if (chatView) {
  261. const QString id = webSocketClient->roomId();
  262. // 使用防抖机制处理频繁的请求
  263. m_pendingRoomId = id;
  264. m_debounceTimer->start(); // 重新开始计时,如果在500ms内再次收到请求,会重置定时器
  265. }
  266. }
  267. });
  268. // 连接按钮事件
  269. connect(m_streamButton, &QPushButton::clicked, this, &MainPanel::onStreamButtonClicked);
  270. connect(m_chatButton, &QPushButton::clicked, this, &MainPanel::onChatButtonClicked);
  271. // 初始化音频设备列表
  272. initAudioDeviceSelectors();
  273. }
  274. MainPanel::~MainPanel()
  275. {
  276. if (m_avPlayerStandalone) {
  277. m_avPlayerStandalone->deleteLater();
  278. m_avPlayerStandalone = nullptr;
  279. }
  280. if (m_recorderStandalone) {
  281. m_recorderStandalone->deleteLater();
  282. m_recorderStandalone = nullptr;
  283. }
  284. }
  285. void MainPanel::setRole(const QStringList &roleList)
  286. {
  287. bool isRec = roleList.contains("role.admin") || roleList.contains("role.recorder") || roleList.contains("录制");
  288. if (isRec) {
  289. auto rec = new RecorderWidget(this);
  290. setPlayerWidget(rec);
  291. } else {
  292. auto av = new AVPlayerWidget(this);
  293. setPlayerWidget(av);
  294. }
  295. if (m_streamButton) {
  296. m_streamButton->setVisible(isRec);
  297. m_streamButton->setText(tr("推流"));
  298. }
  299. }
  300. void MainPanel::setPushRoomId(const QString &id)
  301. {
  302. if (!webSocketClient) return;
  303. // 初始化聊天室连接
  304. webSocketClient->connectToRoom(id);
  305. if (chatView) {
  306. chatView->initWebsocket(id);
  307. }
  308. // 若当前是播放器,使用防抖播放
  309. m_pendingRoomId = id;
  310. m_debounceTimer->start();
  311. }
  312. void MainPanel::setPlayerWidget(QWidget *newPlayer)
  313. {
  314. if (!newPlayer) return;
  315. if (!playerContainer) return;
  316. if (playerWidget) {
  317. playerWidget->deleteLater();
  318. playerWidget = nullptr;
  319. }
  320. playerWidget = newPlayer;
  321. // 将新播放器添加到容器布局
  322. if (auto layout = qobject_cast<QVBoxLayout*>(playerContainer->layout())) {
  323. layout->insertWidget(0, playerWidget, 1);
  324. } else {
  325. auto layout2 = new QVBoxLayout(playerContainer);
  326. layout2->setContentsMargins(0, 0, 0, 0);
  327. layout2->setSpacing(0);
  328. layout2->addWidget(playerWidget, 1);
  329. }
  330. // 更新推流按钮可见性
  331. if (m_streamButton) {
  332. const bool isRec = qobject_cast<RecorderWidget*>(playerWidget) != nullptr;
  333. m_streamButton->setVisible(isRec);
  334. if (!isRec) {
  335. m_isStreaming = false;
  336. m_streamButton->setText(tr("推流"));
  337. }
  338. }
  339. // 连接 RecorderWidget 的推流信号,保持 UI 状态同步
  340. if (auto rec = qobject_cast<RecorderWidget*>(playerWidget)) {
  341. connect(rec, &RecorderWidget::streamingStarted, this, [this, rec]() {
  342. m_isStreaming = true;
  343. if (m_streamButton) m_streamButton->setText(tr("停止推流"));
  344. // 进入极简模式
  345. if (QWidget *tlw = window()) {
  346. tlw->setUpdatesEnabled(false);
  347. }
  348. rec->hidePreview();
  349. if (chatView) chatView->hide();
  350. if (m_rightWidget) m_rightWidget->hide();
  351. if (splitter) splitter->hide();
  352. // 使用自定义标题的紧凑浮窗承载工具栏
  353. if (!m_compactFrame) {
  354. m_compactFrame = new TMainWindow();
  355. m_compactFrame->setWindowTitle(tr("共享控制"));
  356. m_compactFrame->setWindowFlag(Qt::Tool, true);
  357. // m_compactFrame->setAttribute(Qt::WA_DeleteOnClose);
  358. if (buttonGroup) {
  359. if (auto layout = qobject_cast<QVBoxLayout*>(playerContainer->layout())) {
  360. layout->removeWidget(buttonGroup);
  361. }
  362. auto container = new QWidget(m_compactFrame);
  363. auto lay = new QHBoxLayout(container);
  364. lay->setContentsMargins(12, 8, 12, 8);
  365. lay->setSpacing(10);
  366. buttonGroup->setParent(container);
  367. lay->addWidget(buttonGroup);
  368. m_compactFrame->setCentralWidget(container);
  369. // 使用 sizeHint 直接设置初始尺寸,避免反复 adjustSize 带来的重算
  370. const QSize hint = buttonGroup->sizeHint();
  371. int w = qMax(360, hint.width() + 24);
  372. int h = qMax(80, hint.height() + 16);
  373. int titleH = 0;
  374. if (auto mw = m_compactFrame->menuWidget()) {
  375. mw->adjustSize();
  376. titleH = mw->sizeHint().height();
  377. }
  378. m_compactFrame->setMinimumSize(w, h + titleH);
  379. m_compactFrame->resize(w, h + titleH);
  380. }
  381. }
  382. m_compactFrame->show();
  383. m_compactFrame->raise();
  384. m_compactMode = true;
  385. // 主窗口保持原几何但隐藏
  386. if (QWidget *tlw = window()) {
  387. m_savedWindowGeometry = tlw->geometry();
  388. tlw->hide();
  389. }
  390. });
  391. connect(rec, &RecorderWidget::streamingStopped, this, [this, rec]() {
  392. m_isStreaming = false;
  393. if (m_streamButton) m_streamButton->setText(tr("推流"));
  394. // 退出极简模式
  395. if (QWidget *tlw = window()) {
  396. tlw->setUpdatesEnabled(false);
  397. }
  398. rec->showPreview();
  399. if (splitter) splitter->show();
  400. if (m_rightWidget) m_rightWidget->show();
  401. // 销毁紧凑浮窗并将 buttonGroup 归还
  402. if (m_compactFrame) {
  403. if (buttonGroup) {
  404. buttonGroup->hide();
  405. buttonGroup->setParent(playerContainer);
  406. if (auto layout = qobject_cast<QVBoxLayout*>(playerContainer->layout())) {
  407. layout->addWidget(buttonGroup, 0);
  408. }
  409. buttonGroup->show();
  410. }
  411. m_compactFrame->close();
  412. m_compactFrame->deleteLater();
  413. m_compactFrame = nullptr;
  414. }
  415. m_compactMode = false;
  416. // 恢复主窗口显示
  417. if (QWidget *tlw = window()) {
  418. tlw->setGeometry(m_savedWindowGeometry);
  419. tlw->show();
  420. tlw->raise();
  421. tlw->setUpdatesEnabled(true);
  422. }
  423. showChatEmbedded();
  424. if (m_chatButton) m_chatButton->setText(tr("聊天"));
  425. applyModeLayout(); // 恢复布局为非推流状态
  426. });
  427. }
  428. // 根据当前模式(录制/播放)应用一次自适应布局
  429. applyModeLayout();
  430. }
  431. void MainPanel::handleDebouncedPlay()
  432. {
  433. // 在定时器触发时执行播放逻辑
  434. const QString id = m_pendingRoomId;
  435. if (id.isEmpty()) return;
  436. qDebug() << "[MainPanel] Debounced startPlay for room" << id;
  437. AVPlayerWidget *av = qobject_cast<AVPlayerWidget*>(playerWidget);
  438. if (av) {
  439. av->stopPlay();
  440. av->setPlayRoomId(id);
  441. av->startPlay();
  442. return;
  443. }
  444. RecorderWidget *rec = qobject_cast<RecorderWidget*>(playerWidget);
  445. if (rec) {
  446. // rec->startLive();
  447. // - RecorderWidget::Settings s = rec->m_settings; // 需要通过公共接口设置,避免直接访问成员
  448. }
  449. }
  450. void MainPanel::initAudioDeviceSelectors()
  451. {
  452. // TODO: 枚举音频设备、填充 m_micWidget 和 m_speakerWidget;枚举编码器填充 m_encoderWidget
  453. }
  454. void MainPanel::showRecorderStandalone()
  455. {
  456. if (m_recorderFrame) {
  457. m_recorderFrame->raise();
  458. m_recorderFrame->activateWindow();
  459. return;
  460. }
  461. m_recorderFrame = new TMainWindow();
  462. m_recorderFrame->setAttribute(Qt::WA_DeleteOnClose);
  463. m_recorderFrame->setWindowTitle(tr("录制器"));
  464. m_recorderStandalone = new RecorderWidget(m_recorderFrame);
  465. m_recorderFrame->setCentralWidget(m_recorderStandalone);
  466. m_recorderFrame->resize(960, 600);
  467. m_recorderFrame->show();
  468. connect(m_recorderFrame, &QObject::destroyed, this, [this]() {
  469. m_recorderFrame = nullptr;
  470. m_recorderStandalone = nullptr;
  471. });
  472. }
  473. void MainPanel::showPlayerStandalone()
  474. {
  475. if (m_playerFrame) {
  476. m_playerFrame->raise();
  477. m_playerFrame->activateWindow();
  478. return;
  479. }
  480. m_playerFrame = new TMainWindow();
  481. m_playerFrame->setAttribute(Qt::WA_DeleteOnClose);
  482. m_playerFrame->setWindowTitle(tr("播放器"));
  483. m_avPlayerStandalone = new AVPlayerWidget(m_playerFrame);
  484. m_playerFrame->setCentralWidget(m_avPlayerStandalone);
  485. m_playerFrame->resize(960, 600);
  486. m_playerFrame->show();
  487. connect(m_playerFrame, &QObject::destroyed, this, [this]() {
  488. m_playerFrame = nullptr;
  489. m_avPlayerStandalone = nullptr;
  490. });
  491. }
  492. void MainPanel::showChatStandalone()
  493. {
  494. if (!chatView) return;
  495. if (m_isStreaming) return; // 推流时保持仅浮窗
  496. // - if (m_chatFrame) {
  497. // - m_chatFrame->raise();
  498. // - m_chatFrame->activateWindow();
  499. // - return;
  500. // - }
  501. if (m_chatFrame) {
  502. // 确保 chatView 已正确作为中央部件挂载
  503. if (m_chatFrame->centralWidget() != chatView && chatView) {
  504. chatView->hide();
  505. if (chatView->parent() != m_chatFrame) {
  506. chatView->setParent(m_chatFrame);
  507. }
  508. m_chatFrame->setCentralWidget(chatView);
  509. chatView->show();
  510. }
  511. if (!m_chatFrame->isVisible())
  512. m_chatFrame->show();
  513. m_chatFrame->raise();
  514. m_chatFrame->activateWindow();
  515. return;
  516. }
  517. // 从嵌入容器移除
  518. if (m_chatContainer) {
  519. if (auto l = qobject_cast<QVBoxLayout*>(m_chatContainer->layout())) {
  520. l->removeWidget(chatView);
  521. }
  522. }
  523. m_chatFrame = new TMainWindow();
  524. m_chatFrame->setAttribute(Qt::WA_DeleteOnClose);
  525. m_chatFrame->setWindowTitle(tr("聊天"));
  526. m_chatFrame->installEventFilter(this);
  527. chatView->setParent(m_chatFrame);
  528. m_chatFrame->setCentralWidget(chatView);
  529. m_chatFrame->resize(380, 540);
  530. m_chatFrame->show();
  531. // - connect(m_chatFrame, &QObject::destroyed, this, [this]() {
  532. // - m_chatFrame = nullptr;
  533. // - onChatWindowCloseRequested();
  534. // - });
  535. connect(m_chatFrame, &QObject::destroyed, this, [this]() { m_chatFrame = nullptr; });
  536. }
  537. void MainPanel::showChatEmbedded()
  538. {
  539. if (!chatView || !m_chatContainer) return;
  540. // 如存在独立窗口包装,先将 chatView 移回容器再关闭窗口,避免被父窗口销毁
  541. if (m_chatFrame) {
  542. // 断开临时回调,避免 destroyed 中的副作用
  543. disconnect(m_chatFrame, nullptr, this, nullptr);
  544. if (chatView->parent() == m_chatFrame) {
  545. // 使用 takeCentralWidget 而非 setCentralWidget(nullptr) 避免误删中央部件
  546. if (m_chatFrame->centralWidget()) {
  547. QWidget *w = m_chatFrame->takeCentralWidget();
  548. Q_UNUSED(w);
  549. }
  550. }
  551. chatView->hide();
  552. chatView->setParent(m_chatContainer);
  553. chatView->setWindowTitle(QString());
  554. if (auto l = qobject_cast<QVBoxLayout *>(m_chatContainer->layout())) {
  555. if (l->indexOf(chatView) < 0)
  556. l->addWidget(chatView);
  557. } else {
  558. auto l2 = new QVBoxLayout(m_chatContainer);
  559. l2->setContentsMargins(0, 0, 0, 0);
  560. l2->addWidget(chatView);
  561. }
  562. chatView->show();
  563. // 现在安全地关闭独立窗口
  564. m_chatFrame->close();
  565. m_chatFrame = nullptr;
  566. } else {
  567. // 无独立窗口,仅确保嵌入
  568. chatView->hide();
  569. chatView->setParent(m_chatContainer);
  570. chatView->setWindowTitle(QString());
  571. if (auto l = qobject_cast<QVBoxLayout *>(m_chatContainer->layout())) {
  572. if (l->indexOf(chatView) < 0)
  573. l->addWidget(chatView);
  574. } else {
  575. auto l2 = new QVBoxLayout(m_chatContainer);
  576. l2->setContentsMargins(0, 0, 0, 0);
  577. l2->addWidget(chatView);
  578. }
  579. chatView->show();
  580. }
  581. }
  582. void MainPanel::onRecordButtonClicked()
  583. {
  584. // TODO: 录制控制逻辑
  585. }
  586. void MainPanel::onStreamButtonClicked()
  587. {
  588. RecorderWidget *rec = qobject_cast<RecorderWidget*>(playerWidget);
  589. if (!rec) {
  590. qDebug() << "[MainPanel] 当前不处于录制器模式,无法推流";
  591. return;
  592. }
  593. if (!m_isStreaming) {
  594. rec->startStreaming(); // 成功与否由信号驱动 UI
  595. } else {
  596. rec->stopStreaming();
  597. }
  598. }
  599. void MainPanel::onSettingsButtonClicked()
  600. {
  601. // 使用 TMainWindow 包裹设置页面,保持无边框一致风格
  602. if (m_settingsFrame) {
  603. if (m_settingsFrame->isVisible()) {
  604. m_settingsFrame->raise();
  605. m_settingsFrame->activateWindow();
  606. } else {
  607. m_settingsFrame->show();
  608. m_settingsFrame->raise();
  609. m_settingsFrame->activateWindow();
  610. }
  611. return;
  612. }
  613. m_settingsFrame = new TMainWindow();
  614. m_settingsFrame->setAttribute(Qt::WA_DeleteOnClose);
  615. m_settingsFrame->setWindowTitle(tr("设置"));
  616. // 当设置窗口关闭时,清空指针
  617. connect(m_settingsFrame, &QObject::destroyed, this, [this]() { m_settingsFrame = nullptr; });
  618. auto *settingsWidget = new ThemeSettingsWidget(m_settingsFrame);
  619. m_settingsFrame->setCentralWidget(settingsWidget);
  620. m_settingsFrame->resize(560, 460);
  621. m_settingsFrame->show();
  622. }
  623. void MainPanel::onChatWindowCloseRequested()
  624. {
  625. // 如果处在推流状态,认为是“隐藏聊天”而不是关闭程序
  626. if (m_isStreaming) {
  627. // 显式隐藏独立聊天窗口,统一行为到 eventFilter 的隐藏逻辑
  628. if (m_chatFrame && m_chatFrame->isVisible()) {
  629. m_chatFrame->hide();
  630. }
  631. if (m_chatButton) m_chatButton->setText(tr("显示聊天"));
  632. return; // ChatWindow 本身不要销毁
  633. }
  634. // 非推流:回归嵌入显示
  635. showChatEmbedded();
  636. if (m_chatButton) m_chatButton->setText(tr("聊天"));
  637. }
  638. void MainPanel::onChatButtonClicked()
  639. {
  640. if (!chatView) return;
  641. if (m_isStreaming) {
  642. // 推流时:只在独立聊天窗口上 显示/隐藏 切换,避免频繁 reparent
  643. if (!m_chatFrame) {
  644. // 如果当前在嵌入容器里,先从布局移除
  645. if (m_chatContainer) {
  646. if (auto l = qobject_cast<QVBoxLayout*>(m_chatContainer->layout())) {
  647. l->removeWidget(chatView);
  648. }
  649. }
  650. m_chatFrame = new TMainWindow();
  651. m_chatFrame->setAttribute(Qt::WA_DeleteOnClose);
  652. m_chatFrame->setWindowTitle(tr("聊天"));
  653. m_chatFrame->installEventFilter(this);
  654. chatView->setParent(m_chatFrame);
  655. m_chatFrame->setCentralWidget(chatView);
  656. chatView->show();
  657. m_chatFrame->resize(380, 540);
  658. m_chatFrame->show();
  659. m_chatFrame->raise();
  660. m_chatFrame->activateWindow();
  661. connect(m_chatFrame, &QObject::destroyed, this, [this]() { m_chatFrame = nullptr; });
  662. if (m_chatButton) m_chatButton->setText(tr("隐藏聊天"));
  663. return; // 结束推流分支处理
  664. } else {
  665. // 已有独立窗口:切换显示/隐藏
  666. if (chatView->parent() != m_chatFrame) {
  667. chatView->setParent(m_chatFrame);
  668. if (m_chatFrame->centralWidget() != chatView)
  669. m_chatFrame->setCentralWidget(chatView);
  670. }
  671. chatView->show();
  672. if (m_chatFrame->isVisible()) {
  673. m_chatFrame->hide();
  674. if (m_chatButton) m_chatButton->setText(tr("显示聊天"));
  675. } else {
  676. m_chatFrame->show();
  677. m_chatFrame->raise();
  678. m_chatFrame->activateWindow();
  679. if (m_chatButton) m_chatButton->setText(tr("隐藏聊天"));
  680. }
  681. return; // 结束推流分支处理
  682. }
  683. }
  684. // 非推流:在弹出与嵌入间切换
  685. if (m_chatFrame) {
  686. // 已经是弹出状态,切回嵌入
  687. showChatEmbedded();
  688. if (m_chatButton)
  689. m_chatButton->setText(tr("聊天"));
  690. } else {
  691. // 目前为嵌入状态,弹出为独立窗口
  692. showChatStandalone();
  693. if (m_chatButton)
  694. m_chatButton->setText(tr("嵌入聊天"));
  695. }
  696. }
  697. // ===== 浮动工具栏 =====
  698. void MainPanel::showFloatingToolbar()
  699. {
  700. // 改为使用 m_compactFrame 承载,无需再在主窗口中悬浮
  701. if (!m_compactFrame)
  702. return;
  703. m_compactFrame->show();
  704. m_compactFrame->raise();
  705. }
  706. void MainPanel::hideFloatingToolbar()
  707. {
  708. if (m_compactFrame)
  709. m_compactFrame->hide();
  710. }
  711. // 新增:根据模式与窗口大小自适应布局
  712. void MainPanel::applyModeLayout()
  713. {
  714. // 若控件未初始化,直接返回
  715. if (!splitter || !playerContainer) return;
  716. // 推流期间,主窗口处于隐藏/极简状态,这里只保证分割区域隐藏
  717. if (m_isStreaming) {
  718. if (m_rightWidget) m_rightWidget->hide();
  719. splitter->hide();
  720. return;
  721. }
  722. const bool isRecorder = qobject_cast<RecorderWidget*>(playerWidget) != nullptr;
  723. const bool isPlayer = qobject_cast<AVPlayerWidget*>(playerWidget) != nullptr;
  724. // 保证分割窗口可见,用于布局
  725. splitter->show();
  726. const int panelW = width() > 0 ? width() : 1200;
  727. if (isPlayer) {
  728. // 播放模式:默认折叠右侧,让视频获得更大可视区域
  729. if (m_rightWidget) m_rightWidget->hide();
  730. // 如果聊天仍处于嵌入容器,将其弹出为独立窗口,避免占位
  731. if (chatView && m_chatContainer && chatView->parent() == m_chatContainer) {
  732. showChatStandalone();
  733. }
  734. // 左侧铺满
  735. QList<int> sizes; sizes << panelW << 0;
  736. splitter->setSizes(sizes);
  737. return;
  738. }
  739. // 录制模式(非推流):展示右侧面板并保持聊天嵌入,宽度自适应为总宽的 1/3(300~420 间)
  740. if (m_rightWidget) m_rightWidget->show();
  741. showChatEmbedded();
  742. int chatW = panelW / 3;
  743. chatW = qBound(300, chatW, 420);
  744. const int leftW = qMax(0, panelW - chatW);
  745. QList<int> sizes; sizes << leftW << chatW;
  746. splitter->setSizes(sizes);
  747. }
  748. void MainPanel::resizeEvent(QResizeEvent* event)
  749. {
  750. QWidget::resizeEvent(event);
  751. // 随窗口大小调整左右比例
  752. applyModeLayout();
  753. }
  754. bool MainPanel::eventFilter(QObject *watched, QEvent *event)
  755. {
  756. // 拦截聊天独立窗口的关闭事件
  757. if (watched == m_chatFrame && event->type() == QEvent::Close) {
  758. // 推流期间:关闭操作改为隐藏窗口,避免销毁与频繁 reparent 导致异常
  759. if (m_isStreaming) {
  760. event->ignore();
  761. if (m_chatFrame) {
  762. m_chatFrame->hide();
  763. }
  764. if (m_chatButton)
  765. m_chatButton->setText(tr("显示聊天"));
  766. return true; // 事件已处理,不再继续关闭
  767. }
  768. // 非推流:允许关闭,但先把 chatView 放回嵌入容器,避免被父窗口销毁
  769. if (chatView && m_chatContainer && chatView->parent() == m_chatFrame) {
  770. // 取走中央部件,避免 setCentralWidget(nullptr) 触发潜在删除
  771. if (m_chatFrame->centralWidget()) {
  772. QWidget *w = m_chatFrame->takeCentralWidget();
  773. Q_UNUSED(w);
  774. }
  775. chatView->hide();
  776. chatView->setParent(m_chatContainer);
  777. chatView->setWindowTitle(QString());
  778. if (auto l = qobject_cast<QVBoxLayout *>(m_chatContainer->layout())) {
  779. if (l->indexOf(chatView) < 0)
  780. l->addWidget(chatView);
  781. } else {
  782. auto l2 = new QVBoxLayout(m_chatContainer);
  783. l2->setContentsMargins(0, 0, 0, 0);
  784. l2->addWidget(chatView);
  785. }
  786. chatView->show();
  787. if (m_chatButton)
  788. m_chatButton->setText(tr("聊天"));
  789. }
  790. return false; // 继续关闭流程
  791. }
  792. return QWidget::eventFilter(watched, event);
  793. }