MainPanel.cpp 29 KB

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