MainPanel.cpp 36 KB

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