#include "MainPanel.h" #include "utils/iconutils.h" // Qt Core #include #include #include #include #include #include #include #include #include #include // Qt Widgets #include #include #include #include #include #include // Third Party #include #include #include // Project Includes #include "AVPlayer/avplayerwidget.h" #include "api/roomapi.h" #include "appevent.h" #include "network/websocketclient.h" #include "themesettingswidget.h" #include "widgets/bubbletip.h" #include "widgets/chatView/chatwindow.h" #include "widgets/framelessbase.h" #include "widgets/functionbutton.h" #include "widgets/maskoverlay.h" #include "widgets/recorderwidget.h" #include "widgets/userprofilewidget.h" #include MainPanel::MainPanel(QWidget *parent) : TWidget(parent) , m_chatTabWidget(nullptr) , m_chatWindow(nullptr) , m_onlineUsersWidget(nullptr) { setAttribute(Qt::WA_StyledBackground, true); // ========== 初始化播放控制组件 ========== initPlaybackControls(); // ========== 初始化聊天相关组件 ========== initChatComponents(); // ========== 初始化布局组件 ========== initLayoutComponents(); // ========== 初始化功能按钮 ========== initFunctionButtons(); // ========== 连接信号槽 ========== connectSignals(); // ========== 初始化音频设备 ========== initAudioDeviceSelectors(); } void MainPanel::initPlaybackControls() { m_debounceTimer = new QTimer(this); m_debounceTimer->setInterval(500); m_debounceTimer->setSingleShot(true); connect(m_debounceTimer, &QTimer::timeout, this, &MainPanel::handleDebouncedPlay); } void MainPanel::initChatComponents() { webSocketClient = new WebSocketClient(this); // 启用WebSocket自动重连,提升聊天室稳定性 if (webSocketClient) { webSocketClient->setAutoReconnect(true); } // 创建聊天窗口 m_chatWindow = new ChatWindow(webSocketClient); m_chatWindow->setMinimumWidth(400); // 防御:明确不随关闭销毁,避免父窗口关闭时误删 m_chatWindow->setAttribute(Qt::WA_DeleteOnClose, false); // 连接聊天窗口关闭请求信号 connect(m_chatWindow, &ChatWindow::windowCloseRequested, this, &MainPanel::onChatWindowCloseRequested); // 创建在线用户管理widget m_onlineUsersWidget = new OnlineUsersWidget(this); // 连接在线用户widget的信号 connect(m_onlineUsersWidget, &OnlineUsersWidget::userDoubleClicked, [this](const QString &userId, const QString &username) { qDebug() << "用户双击:" << username << "ID:" << userId; // TODO: 实现私聊功能 }); connect(m_onlineUsersWidget, &OnlineUsersWidget::privateMessageRequested, [this](const QString &userId, const QString &username) { qDebug() << "请求私信:" << username << "ID:" << userId; // TODO: 实现私信功能 }); // 创建Tab容器 m_chatTabWidget = new QTabWidget(this); m_chatTabWidget->addTab(m_chatWindow, "聊天"); m_chatTabWidget->addTab(m_onlineUsersWidget, "在线用户"); m_chatTabWidget->setMinimumWidth(400); } void MainPanel::initLayoutComponents() { // 创建右侧面板 m_rightWidget = new QWidget; QVBoxLayout *vbox = new QVBoxLayout(m_rightWidget); vbox->setContentsMargins(0, 0, 0, 0); // 创建聊天窗口容器 m_chatContainer = new QWidget(m_rightWidget); QVBoxLayout *chatLayout = new QVBoxLayout(m_chatContainer); chatLayout->setContentsMargins(0, 0, 0, 0); chatLayout->addWidget(m_chatTabWidget); vbox->addWidget(m_chatContainer, 1); // 创建主分割器 splitter = new QSplitter(Qt::Horizontal, this); playerContainer = new QWidget(this); splitter->addWidget(playerContainer); splitter->addWidget(m_rightWidget); splitter->setStretchFactor(0, 60); splitter->setStretchFactor(1, 30); this->mainLayout()->addWidget(splitter, 1); this->mainLayout()->setContentsMargins(0, 0, 0, 0); this->mainLayout()->setSpacing(0); // 为playerContainer设置初始布局 QVBoxLayout *playerLayout = new QVBoxLayout(playerContainer); playerLayout->setContentsMargins(0, 0, 0, 0); playerLayout->setSpacing(0); } void MainPanel::initFunctionButtons() { buttonGroup = new PopoverButtonGroup(Qt::Horizontal, playerContainer); // 添加功能按钮 // FunctionButton *settingsBtn = new FunctionButton(IconUtils::createSettingsIcon(), "设置", this); // // 移除 Popover 箭头,改为直接打开设置窗口 // buttonGroup->addButton(settingsBtn, nullptr); // connect(settingsBtn, &QPushButton::clicked, this, &MainPanel::onSettingsButtonClicked); // 移除:搜索按钮(暂不使用) // FunctionButton *searchBtn = new FunctionButton(IconUtils::createSearchIcon(), "搜索", this); // Popover *searchPopover = new Popover(this); // buttonGroup->addButton(searchBtn, searchPopover); // FunctionButton *userBtn = new FunctionButton(IconUtils::createUserIcon(), "用户", this); // Popover *userPopover = new Popover(this); // // 构建用户Popover内容:提供“退出”操作 // { // QWidget *userContent = new QWidget(userPopover); // QVBoxLayout *userLayout = new QVBoxLayout(userContent); // userLayout->setContentsMargins(8, 8, 8, 8); // userLayout->setSpacing(8); // QPushButton *logoutBtn = new QPushButton(tr("退出"), userContent); // logoutBtn->setMinimumWidth(120); // connect(logoutBtn, &QPushButton::clicked, this, [this]() { // emit logoutClicked(); // }); // userLayout->addWidget(logoutBtn); // userPopover->setContentWidget(userContent); // } // buttonGroup->addButton(userBtn, userPopover); // 添加音频设备选择按钮 // FunctionButton *audioDeviceBtn = new FunctionButton(IconUtils::createAudioDeviceIcon(), "音频设备", this); // Popover *audioDevicePopover = new Popover(this); // // // 使用解耦合版本的音频设备选择器 // // m_audioDeviceSelectorDecoupled = new AudioDeviceSelectorIconDecoupled(this); // // audioDevicePopover->setContentWidget(m_audioDeviceSelectorDecoupled); // // 使用 RecorderAudioWidget 作为 Popover 内容(麦克风 + 扬声器) // QWidget *audioContent = new QWidget(audioDevicePopover); // QVBoxLayout *audioLayout = new QVBoxLayout(audioContent); // audioLayout->setContentsMargins(8, 8, 8, 8); // audioLayout->setSpacing(8); // // 麦克风区域 // QLabel *micTitle = new QLabel(tr("麦克风"), audioContent); // micTitle->setStyleSheet("font-weight:600;"); // m_micWidget = new QComboBox(audioContent); // m_micWidget->setEditable(false); // audioLayout->addWidget(micTitle); // audioLayout->addWidget(m_micWidget); // // 扬声器区域 // QLabel *speakerTitle = new QLabel(tr("扬声器"), audioContent); // speakerTitle->setStyleSheet("font-weight:600;"); // m_speakerWidget = new QComboBox(audioContent); // m_speakerWidget->setEditable(false); // audioLayout->addWidget(speakerTitle); // audioLayout->addWidget(m_speakerWidget); // // 视频编码器区域 // QLabel *encoderTitle = new QLabel(tr("视频编码器"), audioContent); // encoderTitle->setStyleSheet("font-weight:600;"); // m_encoderWidget = new QComboBox(audioContent); // m_encoderWidget->setEditable(false); // audioLayout->addWidget(encoderTitle); // audioLayout->addWidget(m_encoderWidget); // audioDevicePopover->setContentWidget(audioContent); // buttonGroup->addButton(audioDeviceBtn, audioDevicePopover); // 新增:独立的推流按钮(无弹层) m_streamButton = new FunctionButton(IconUtils::createStreamIcon(), tr("推流"), this); buttonGroup->addButton(m_streamButton); // 新增:聊天按钮(推流:显示/隐藏;非推流:弹出/嵌入) m_chatButton = new FunctionButton(IconUtils::createChatIcon(), tr("聊天"), this); buttonGroup->addButton(m_chatButton); // 移除:执行操作按钮(暂不使用) // FunctionButton *actionButton = new FunctionButton(IconUtils::createSettingsIcon(), // "执行操作", // this); // buttonGroup->addButton(actionButton, nullptr); // 将buttonGroup添加到playerContainer的布局中 QVBoxLayout *playerLayout = qobject_cast(playerContainer->layout()); if (!playerLayout) { playerLayout = new QVBoxLayout(playerContainer); playerLayout->setContentsMargins(0, 0, 0, 0); playerLayout->setSpacing(0); } // 移除addStretch,让buttonGroup紧贴播放器组件,消除空白区域 playerLayout->addWidget(buttonGroup, 0); // 添加buttonGroup,不拉伸 buttonGroup->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); // 使用固定大小策略 } void MainPanel::connectSignals() { // 连接功能按钮信号 connect(m_streamButton, &QPushButton::clicked, this, &MainPanel::onStreamButtonClicked); connect(m_chatButton, &QPushButton::clicked, this, &MainPanel::onChatButtonClicked); // 连接WebSocket相关信号 // 已移除与 UserProfileWidget 相关的在线状态更新 // StatsWidget 已移除,统计功能已迁移到 OnlineUsersWidget // 连接用户列表更新信号到在线用户widget connect(webSocketClient, &WebSocketClient::userListUpdate, m_onlineUsersWidget, &OnlineUsersWidget::onUserListUpdate); // 连接统计更新信号到在线用户widget connect(webSocketClient, &WebSocketClient::statsUpdate, m_onlineUsersWidget, &OnlineUsersWidget::updateStats); connect(webSocketClient, &WebSocketClient::liveStatus, this, [this](const QString &msg) { // 这里可以处理 liveStatus 相关逻辑 QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(msg.toUtf8(), &err); if (err.error != QJsonParseError::NoError || !doc.isObject()) { qDebug() << "[MainPanel] liveStatus: 解析失败" << err.errorString(); return; } QJsonObject obj = doc.object(); int liveStatus = obj.value("liveStatus").toInt(0); // 默认-1 if (liveStatus == 1) { qDebug() << "[MainPanel] liveStatus: 直播中" << m_chatWindow; if (m_chatWindow) { const QString id = webSocketClient->roomId(); // 使用防抖机制处理频繁的请求 m_pendingRoomId = id; m_debounceTimer->start(); // 重新开始计时,如果在500ms内再次收到请求,会重置定时器 } } }); // 初始化音频设备列表 initAudioDeviceSelectors(); } MainPanel::~MainPanel() { if (m_avPlayerStandalone) { m_avPlayerStandalone->deleteLater(); m_avPlayerStandalone = nullptr; } if (m_recorderStandalone) { m_recorderStandalone->deleteLater(); m_recorderStandalone = nullptr; } } void MainPanel::setRole(const QStringList &roleList) { bool isRec = roleList.contains("role.admin") || roleList.contains("role.recorder") || roleList.contains("录制"); if (isRec) { auto rec = new RecorderWidget(this); setPlayerWidget(rec); } else { auto av = new AVPlayerWidget(this); setPlayerWidget(av); } if (m_streamButton) { m_streamButton->setVisible(isRec); m_streamButton->setText(tr("推流")); } // 更新在线用户组件的权限状态 if (m_onlineUsersWidget) { qDebug() << roleList; m_onlineUsersWidget->setUserRoles(roleList); } } void MainPanel::setPushRoomId(const QString &id) { if (!webSocketClient) return; // 初始化聊天室连接 webSocketClient->connectToRoom(id); if (m_chatWindow) { m_chatWindow->initWebsocket(id); } // 设置在线用户widget的房间ID if (m_onlineUsersWidget) { m_onlineUsersWidget->setCurrentRoomId(id); } // 若当前是播放器,使用防抖播放 m_pendingRoomId = id; m_debounceTimer->start(); } void MainPanel::setPlayerWidget(QWidget *newPlayer) { if (!newPlayer) return; if (!playerContainer) return; if (playerWidget) { playerWidget->deleteLater(); playerWidget = nullptr; } playerWidget = newPlayer; // 将新播放器添加到容器布局 if (auto layout = qobject_cast(playerContainer->layout())) { layout->insertWidget(0, playerWidget, 1); // 使用拉伸因子1,让播放器组件充分利用空间 } else { auto layout2 = new QVBoxLayout(playerContainer); layout2->setContentsMargins(0, 0, 0, 0); layout2->setSpacing(0); layout2->addWidget(playerWidget, 1); // 使用拉伸因子1,让播放器组件充分利用空间 } // 更新推流按钮可见性 if (m_streamButton) { const bool isRec = qobject_cast(playerWidget) != nullptr; m_streamButton->setVisible(isRec); if (!isRec) { m_isStreaming = false; m_streamButton->setText(tr("推流")); } } // 连接 RecorderWidget 的推流信号,保持 UI 状态同步 if (auto rec = qobject_cast(playerWidget)) { connect(rec, &RecorderWidget::streamingStarted, this, [this, rec]() { m_isStreaming = true; if (m_streamButton) m_streamButton->setText(tr("停止推流")); // 进入极简模式 if (QWidget *tlw = window()) { tlw->setUpdatesEnabled(false); } rec->hidePreview(); if (m_chatTabWidget) m_chatTabWidget->hide(); if (m_rightWidget) m_rightWidget->hide(); if (splitter) splitter->hide(); // 使用自定义标题的紧凑浮窗承载工具栏 if (!m_compactFrame) { m_compactFrame = new TMainWindow(); m_compactFrame->setWindowTitle(tr("共享控制")); // m_compactFrame->setWindowFlag(Qt::Tool, true); // m_compactFrame->setAttribute(Qt::WA_DeleteOnClose); if (buttonGroup) { if (auto layout = qobject_cast(playerContainer->layout())) { layout->removeWidget(buttonGroup); } auto container = new QWidget(m_compactFrame); auto lay = new QHBoxLayout(container); lay->setContentsMargins(12, 8, 12, 8); lay->setSpacing(10); buttonGroup->setParent(container); lay->addWidget(buttonGroup); m_compactFrame->setCentralWidget(container); // 使用 sizeHint 直接设置初始尺寸,避免反复 adjustSize 带来的重算 const QSize hint = buttonGroup->sizeHint(); int w = qMax(360, hint.width() + 24); int h = qMax(80, hint.height() + 16); int titleH = 0; if (auto mw = m_compactFrame->menuWidget()) { mw->adjustSize(); titleH = mw->sizeHint().height(); } m_compactFrame->setMinimumSize(w, h + titleH); m_compactFrame->resize(w, h + titleH); } } m_compactFrame->show(); m_compactFrame->raise(); m_compactFrame->activateWindow(); // 将窗口移动到屏幕中上位置 QRect screenGeometry = QApplication::primaryScreen()->availableGeometry(); int x = (screenGeometry.width() - m_compactFrame->width()) / 2; int y = screenGeometry.height() / 4; // 屏幕高度的1/4位置,即中上 m_compactFrame->move(screenGeometry.x() + x, screenGeometry.y() + y); m_compactMode = true; // 主窗口保持原几何但隐藏 if (QWidget *tlw = window()) { m_savedWindowGeometry = tlw->geometry(); tlw->hide(); } }); connect(rec, &RecorderWidget::streamingStopped, this, [this, rec]() { m_isStreaming = false; if (m_streamButton) m_streamButton->setText(tr("推流")); // 退出极简模式 if (QWidget *tlw = window()) { tlw->setUpdatesEnabled(false); } rec->showPreview(); if (splitter) splitter->show(); if (m_rightWidget) m_rightWidget->show(); // 销毁紧凑浮窗并将 buttonGroup 归还 if (m_compactFrame) { if (buttonGroup) { buttonGroup->hide(); buttonGroup->setParent(playerContainer); if (auto layout = qobject_cast(playerContainer->layout())) { layout->addWidget(buttonGroup, 0); } buttonGroup->show(); } m_compactFrame->close(); m_compactFrame->deleteLater(); m_compactFrame = nullptr; } m_compactMode = false; // 恢复主窗口显示 if (QWidget *tlw = window()) { tlw->setGeometry(m_savedWindowGeometry); tlw->show(); tlw->raise(); tlw->setUpdatesEnabled(true); } showChatEmbedded(); if (m_chatButton) m_chatButton->setText(tr("聊天")); applyModeLayout(); // 恢复布局为非推流状态 }); } // 根据当前模式(录制/播放)应用一次自适应布局 applyModeLayout(); } void MainPanel::handleDebouncedPlay() { // 在定时器触发时执行播放逻辑 const QString id = m_pendingRoomId; if (id.isEmpty()) return; qDebug() << "[MainPanel] Debounced startPlay for room" << id; RecorderWidget *rec = qobject_cast(playerWidget); if (rec) { rec->setLiveName(id); } AVPlayerWidget *av = qobject_cast(playerWidget); if (av) { // 连接异步播放的错误信号(如果尚未连接) static bool signalsConnected = false; if (!signalsConnected) { connect(av, &AVPlayerWidget::playError, this, [this](const QString &error) { qDebug() << "[MainPanel] Play error:" << error; // 显示错误提示 BubbleTip::showTip(this, error, BubbleTip::Bottom, 5000, BubbleTip::Error); }); connect(av, &AVPlayerWidget::playLoadingStarted, this, [this]() { qDebug() << "[MainPanel] Play loading started"; // 可以在这里添加全局加载状态指示 }); connect(av, &AVPlayerWidget::playLoadingFinished, this, [this]() { qDebug() << "[MainPanel] Play loading finished"; // 可以在这里隐藏全局加载状态指示 }); signalsConnected = true; } av->stopPlay(); av->setPlayRoomId(id); av->startPlayAsync(); // 使用异步播放方法 return; } } void MainPanel::initAudioDeviceSelectors() { // TODO: 枚举音频设备、填充 m_micWidget 和 m_speakerWidget;枚举编码器填充 m_encoderWidget } void MainPanel::showRecorderStandalone() { if (m_recorderFrame) { m_recorderFrame->raise(); m_recorderFrame->activateWindow(); return; } m_recorderFrame = new TMainWindow(); m_recorderFrame->setAttribute(Qt::WA_DeleteOnClose); m_recorderFrame->setWindowTitle(tr("录制器")); m_recorderStandalone = new RecorderWidget(m_recorderFrame); m_recorderFrame->setCentralWidget(m_recorderStandalone); m_recorderFrame->resize(960, 600); m_recorderFrame->show(); connect(m_recorderFrame, &QObject::destroyed, this, [this]() { m_recorderFrame = nullptr; m_recorderStandalone = nullptr; }); } void MainPanel::showPlayerStandalone() { if (m_playerFrame) { m_playerFrame->raise(); m_playerFrame->activateWindow(); return; } m_playerFrame = new TMainWindow(); m_playerFrame->setAttribute(Qt::WA_DeleteOnClose); m_playerFrame->setWindowTitle(tr("播放器")); m_avPlayerStandalone = new AVPlayerWidget(m_playerFrame); m_playerFrame->setCentralWidget(m_avPlayerStandalone); m_playerFrame->resize(960, 600); m_playerFrame->show(); connect(m_playerFrame, &QObject::destroyed, this, [this]() { m_playerFrame = nullptr; m_avPlayerStandalone = nullptr; }); } void MainPanel::showChatStandalone() { // 显示独立聊天窗口 - 调用 showChatInFrame 实现 showChatInFrame(); } void MainPanel::showChatInFrame() { if (!m_chatTabWidget) return; if (m_isStreaming) return; // 推流时保持仅浮窗 // - if (m_chatFrame) { // - m_chatFrame->raise(); // - m_chatFrame->activateWindow(); // - return; // - } if (m_chatFrame) { // 确保 m_chatTabWidget 已正确作为中央部件挂载 if (m_chatFrame->centralWidget() != m_chatTabWidget && m_chatTabWidget) { m_chatTabWidget->hide(); if (m_chatTabWidget->parent() != m_chatFrame) { m_chatTabWidget->setParent(m_chatFrame); } m_chatFrame->setCentralWidget(m_chatTabWidget); m_chatTabWidget->show(); } if (!m_chatFrame->isVisible()) m_chatFrame->show(); m_chatFrame->raise(); m_chatFrame->activateWindow(); return; } // 从嵌入容器移除 if (m_chatContainer) { if (auto l = qobject_cast(m_chatContainer->layout())) { l->removeWidget(m_chatTabWidget); } } m_chatFrame = new TMainWindow(); m_chatFrame->setAttribute(Qt::WA_DeleteOnClose); m_chatFrame->setWindowTitle(tr("聊天")); m_chatFrame->installEventFilter(this); m_chatTabWidget->setParent(m_chatFrame); m_chatFrame->setCentralWidget(m_chatTabWidget); m_chatFrame->resize(380, 540); m_chatFrame->show(); // - connect(m_chatFrame, &QObject::destroyed, this, [this]() { // - m_chatFrame = nullptr; // - onChatWindowCloseRequested(); // - }); connect(m_chatFrame, &QObject::destroyed, this, [this]() { m_chatFrame = nullptr; }); } void MainPanel::showChatEmbedded() { if (!m_chatTabWidget || !m_chatContainer) return; // 如存在独立窗口包装,先将 m_chatTabWidget 移回容器再关闭窗口,避免被父窗口销毁 if (m_chatFrame) { // 断开临时回调,避免 destroyed 中的副作用 disconnect(m_chatFrame, nullptr, this, nullptr); if (m_chatTabWidget->parent() == m_chatFrame) { // 使用 takeCentralWidget 而非 setCentralWidget(nullptr) 避免误删中央部件 if (m_chatFrame->centralWidget()) { QWidget *w = m_chatFrame->takeCentralWidget(); Q_UNUSED(w); } } m_chatTabWidget->hide(); m_chatTabWidget->setParent(m_chatContainer); m_chatTabWidget->setWindowTitle(QString()); if (auto l = qobject_cast(m_chatContainer->layout())) { if (l->indexOf(m_chatTabWidget) < 0) l->addWidget(m_chatTabWidget); } else { auto l2 = new QVBoxLayout(m_chatContainer); l2->setContentsMargins(0, 0, 0, 0); l2->addWidget(m_chatTabWidget); } m_chatTabWidget->show(); // 现在安全地关闭独立窗口 m_chatFrame->close(); m_chatFrame = nullptr; } else { // 无独立窗口,仅确保嵌入 m_chatTabWidget->hide(); m_chatTabWidget->setParent(m_chatContainer); m_chatTabWidget->setWindowTitle(QString()); if (auto l = qobject_cast(m_chatContainer->layout())) { if (l->indexOf(m_chatTabWidget) < 0) l->addWidget(m_chatTabWidget); } else { auto l2 = new QVBoxLayout(m_chatContainer); l2->setContentsMargins(0, 0, 0, 0); l2->addWidget(m_chatTabWidget); } m_chatTabWidget->show(); } } void MainPanel::onRecordButtonClicked() { // TODO: 录制控制逻辑 } void MainPanel::onStreamButtonClicked() { RecorderWidget *rec = qobject_cast(playerWidget); if (!rec) { qDebug() << "[MainPanel] 当前不处于录制器模式,无法推流"; return; } if (!m_isStreaming) { rec->startStreaming(); // 成功与否由信号驱动 UI } else { rec->stopStreaming(); } } void MainPanel::onChatWindowCloseRequested() { // 如果处在推流状态,认为是“隐藏聊天”而不是关闭程序 if (m_isStreaming) { // 显式隐藏独立聊天窗口,统一行为到 eventFilter 的隐藏逻辑 if (m_chatFrame && m_chatFrame->isVisible()) { m_chatFrame->hide(); } if (m_chatButton) m_chatButton->setText(tr("显示聊天")); return; // ChatWindow 本身不要销毁 } // 非推流:回归嵌入显示 showChatEmbedded(); if (m_chatButton) m_chatButton->setText(tr("聊天")); applyModeLayout(); } void MainPanel::onChatButtonClicked() { if (!m_chatTabWidget) return; if (m_isStreaming) { // 推流时:只在独立聊天窗口上 显示/隐藏 切换,避免频繁 reparent if (!m_chatFrame) { // 如果当前在嵌入容器里,先从布局移除 if (m_chatContainer) { if (auto l = qobject_cast(m_chatContainer->layout())) { l->removeWidget(m_chatTabWidget); } } m_chatFrame = new TMainWindow(); m_chatFrame->setAttribute(Qt::WA_DeleteOnClose); m_chatFrame->setWindowTitle(tr("聊天")); m_chatFrame->installEventFilter(this); m_chatTabWidget->setParent(m_chatFrame); m_chatFrame->setCentralWidget(m_chatTabWidget); m_chatTabWidget->show(); m_chatFrame->resize(380, 540); m_chatFrame->show(); m_chatFrame->raise(); m_chatFrame->activateWindow(); connect(m_chatFrame, &QObject::destroyed, this, [this]() { m_chatFrame = nullptr; }); if (m_chatButton) m_chatButton->setText(tr("隐藏聊天")); return; // 结束推流分支处理 } else { // 已有独立窗口:切换显示/隐藏 if (m_chatTabWidget->parent() != m_chatFrame) { m_chatTabWidget->setParent(m_chatFrame); if (m_chatFrame->centralWidget() != m_chatTabWidget) m_chatFrame->setCentralWidget(m_chatTabWidget); } m_chatTabWidget->show(); if (m_chatFrame->isVisible()) { m_chatFrame->hide(); if (m_chatButton) m_chatButton->setText(tr("显示聊天")); } else { m_chatFrame->show(); m_chatFrame->raise(); m_chatFrame->activateWindow(); if (m_chatButton) m_chatButton->setText(tr("隐藏聊天")); } return; // 结束推流分支处理 } } // 非推流:在弹出与嵌入间切换 if (m_chatFrame) { // 已经是弹出状态,切回嵌入 showChatEmbedded(); if (m_chatButton) m_chatButton->setText(tr("聊天")); applyModeLayout(); } else { // 目前为嵌入状态,弹出为独立窗口 showChatInFrame(); if (m_chatButton) m_chatButton->setText(tr("嵌入聊天")); applyModeLayout(); } } // ===== 浮动工具栏 ===== void MainPanel::showFloatingToolbar() { // 改为使用 m_compactFrame 承载,无需再在主窗口中悬浮 if (!m_compactFrame) return; m_compactFrame->show(); m_compactFrame->raise(); } void MainPanel::hideFloatingToolbar() { if (m_compactFrame) m_compactFrame->hide(); } // 新增:根据模式与窗口大小自适应布局 void MainPanel::applyModeLayout() { // 若控件未初始化,直接返回 if (!splitter || !playerContainer) return; // 推流期间,主窗口处于隐藏/极简状态,这里只保证分割区域隐藏 if (m_isStreaming) { if (m_rightWidget) m_rightWidget->hide(); splitter->hide(); return; } const bool isRecorder = qobject_cast(playerWidget) != nullptr; const bool isPlayer = qobject_cast(playerWidget) != nullptr; // 保证分割窗口可见,用于布局 splitter->show(); const int panelW = width() > 0 ? width() : 1200; if (isPlayer) { // 播放模式:根据聊天是否嵌入来决定右侧面板显示 const bool embeddedChat = (m_chatTabWidget && m_chatContainer && m_chatTabWidget->parent() == m_chatContainer); if (embeddedChat) { if (m_rightWidget) m_rightWidget->show(); int chatW = panelW / 3; chatW = qBound(300, chatW, 420); const int leftW = qMax(0, panelW - chatW); QList sizes; sizes << leftW << chatW; splitter->setSizes(sizes); } else { if (m_rightWidget) m_rightWidget->hide(); QList sizes; sizes << panelW << 0; splitter->setSizes(sizes); } return; } // 录制模式(非推流):展示右侧面板,根据聊天当前状态决定布局 if (m_rightWidget) m_rightWidget->show(); // 检查聊天当前是否为嵌入状态 const bool embeddedChat = (m_chatTabWidget && m_chatContainer && m_chatTabWidget->parent() == m_chatContainer); if (embeddedChat) { // 聊天为嵌入状态,设置分割布局 int chatW = panelW / 3; chatW = qBound(300, chatW, 420); const int leftW = qMax(0, panelW - chatW); QList sizes; sizes << leftW << chatW; splitter->setSizes(sizes); } else { // 聊天为独立窗口状态,隐藏右侧面板 if (m_rightWidget) m_rightWidget->hide(); QList sizes; sizes << panelW << 0; splitter->setSizes(sizes); } } void MainPanel::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); // 随窗口大小调整左右比例 applyModeLayout(); } bool MainPanel::eventFilter(QObject *watched, QEvent *event) { // 拦截聊天独立窗口的关闭事件 if (watched == m_chatFrame && event->type() == QEvent::Close) { // 推流期间:关闭操作改为隐藏窗口,避免销毁与频繁 reparent 导致异常 if (m_isStreaming) { event->ignore(); if (m_chatFrame) { m_chatFrame->hide(); } if (m_chatButton) m_chatButton->setText(tr("显示聊天")); return true; // 事件已处理,不再继续关闭 } // 非推流:允许关闭,但先把 m_chatTabWidget 放回嵌入容器,避免被父窗口销毁 if (m_chatTabWidget && m_chatContainer && m_chatTabWidget->parent() == m_chatFrame) { // 取走中央部件,避免 setCentralWidget(nullptr) 触发潜在删除 if (m_chatFrame->centralWidget()) { QWidget *w = m_chatFrame->takeCentralWidget(); Q_UNUSED(w); } m_chatTabWidget->hide(); m_chatTabWidget->setParent(m_chatContainer); m_chatTabWidget->setWindowTitle(QString()); if (auto l = qobject_cast(m_chatContainer->layout())) { if (l->indexOf(m_chatTabWidget) < 0) l->addWidget(m_chatTabWidget); } else { auto l2 = new QVBoxLayout(m_chatContainer); l2->setContentsMargins(0, 0, 0, 0); l2->addWidget(m_chatTabWidget); } m_chatTabWidget->show(); if (m_chatButton) m_chatButton->setText(tr("聊天")); applyModeLayout(); } return false; // 继续关闭流程 } return QWidget::eventFilter(watched, event); } void MainPanel::startStreamingTest(const QString &roomId, bool enableRecording) { qDebug() << "开始推流拉流测试,房间ID:" << roomId << ",启用录制:" << enableRecording; // 1. 创建或获取RecorderWidget实例用于推流 if (!m_recorderStandalone) { m_recorderStandalone = new RecorderWidget(); // 连接推流相关信号 connect(m_recorderStandalone, &RecorderWidget::streamingStarted, this, [this]() { qDebug() << "推流已开始"; }); connect(m_recorderStandalone, &RecorderWidget::streamingStopped, this, [this]() { qDebug() << "推流已停止"; }); connect(m_recorderStandalone, &RecorderWidget::errorOccurred, this, [this](const QString &error) { qDebug() << "推流错误:" << error; }); } // 2. 创建或获取AVPlayerWidget实例用于拉流 if (!m_avPlayerStandalone) { m_avPlayerStandalone = new AVPlayerWidget(); // 连接拉流相关信号 connect(m_avPlayerStandalone, &AVPlayerWidget::playStateChanged, this, [this](bool isPlaying) { qDebug() << "播放状态变化:" << (isPlaying ? "播放中" : "已停止"); }); connect(m_avPlayerStandalone, &AVPlayerWidget::playError, this, [this](const QString &error) { qDebug() << "播放错误:" << error; }); connect(m_avPlayerStandalone, &AVPlayerWidget::playLoadingStarted, this, [this]() { qDebug() << "开始加载流媒体"; }); connect(m_avPlayerStandalone, &AVPlayerWidget::playLoadingFinished, this, [this]() { qDebug() << "流媒体加载完成"; }); } // 3. 配置推流参数 m_recorderStandalone->setLiveName(roomId); // 4. 配置拉流参数 m_avPlayerStandalone->setPlayRoomId(roomId); // 5. 显示推流和拉流窗口 m_recorderStandalone->show(); m_recorderStandalone->resize(800, 600); m_recorderStandalone->move(0, 0); m_recorderStandalone->setWindowTitle(QString("推流测试 - 房间ID: %1").arg(roomId)); m_avPlayerStandalone->show(); m_avPlayerStandalone->resize(800, 600); m_recorderStandalone->move(800, 600); m_avPlayerStandalone->setWindowTitle(QString("拉流测试 - 房间ID: %1").arg(roomId)); // 6. 启动推流 if (m_recorderStandalone->startStreaming()) { qDebug() << "推流启动成功"; } else { qDebug() << "推流启动失败"; } // 7. 延迟启动拉流,等待推流稳定 QTimer::singleShot(5000, this, [this]() { if (m_avPlayerStandalone) { m_avPlayerStandalone->startPlayAsync(); qDebug() << "延迟启动拉流"; } }); // 8. 如果启用录制,同时开始录制 if (enableRecording) { QTimer::singleShot(1000, this, [this]() { if (m_recorderStandalone) { m_recorderStandalone->startRecording(); qDebug() << "同时启动录制功能"; } }); } } void MainPanel::stopStreamingTest() { qDebug() << "停止推流拉流测试"; // 1. 停止推流 if (m_recorderStandalone) { m_recorderStandalone->stopStreaming(); m_recorderStandalone->stopRecording(); // 同时停止录制 m_recorderStandalone->hide(); } // 2. 停止拉流 if (m_avPlayerStandalone) { m_avPlayerStandalone->stop(); m_avPlayerStandalone->hide(); } qDebug() << "推流拉流测试已停止"; }