|
@@ -122,6 +122,32 @@ QIcon createAudioDeviceIcon()
|
|
|
|
|
|
|
|
return QIcon(icon);
|
|
return QIcon(icon);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+QIcon createStreamIcon()
|
|
|
|
|
+{
|
|
|
|
|
+ QPixmap icon(24, 24);
|
|
|
|
|
+ icon.fill(Qt::transparent);
|
|
|
|
|
+
|
|
|
|
|
+ QPainter painter(&icon);
|
|
|
|
|
+ painter.setRenderHint(QPainter::Antialiasing);
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制推流图标(播放按钮+信号波纹)
|
|
|
|
|
+ painter.setPen(QPen(Qt::white, 1.5));
|
|
|
|
|
+ painter.setBrush(Qt::white);
|
|
|
|
|
+
|
|
|
|
|
+ // 播放按钮(三角形)
|
|
|
|
|
+ QPolygon triangle;
|
|
|
|
|
+ triangle << QPoint(6, 4) << QPoint(6, 20) << QPoint(18, 12);
|
|
|
|
|
+ painter.drawPolygon(triangle);
|
|
|
|
|
+
|
|
|
|
|
+ // 信号波纹
|
|
|
|
|
+ painter.setBrush(Qt::NoBrush);
|
|
|
|
|
+ painter.drawArc(16, 8, 4, 8, 0, 180 * 16);
|
|
|
|
|
+ painter.drawArc(18, 6, 4, 12, 0, 180 * 16);
|
|
|
|
|
+ painter.drawArc(20, 4, 4, 16, 0, 180 * 16);
|
|
|
|
|
+
|
|
|
|
|
+ return QIcon(icon);
|
|
|
|
|
+}
|
|
|
} // namespace IconUtils
|
|
} // namespace IconUtils
|
|
|
|
|
|
|
|
MainPanel::MainPanel(QWidget *parent)
|
|
MainPanel::MainPanel(QWidget *parent)
|
|
@@ -139,6 +165,8 @@ MainPanel::MainPanel(QWidget *parent)
|
|
|
webSocketClient = new WebSocketClient(this);
|
|
webSocketClient = new WebSocketClient(this);
|
|
|
chatView = new ChatWindow(webSocketClient);
|
|
chatView = new ChatWindow(webSocketClient);
|
|
|
chatView->setMinimumWidth(400);
|
|
chatView->setMinimumWidth(400);
|
|
|
|
|
+ // 连接聊天窗口关闭请求信号
|
|
|
|
|
+ connect(chatView, &ChatWindow::windowCloseRequested, this, &MainPanel::onChatWindowCloseRequested);
|
|
|
statsWidget = new StatsWidget(this);
|
|
statsWidget = new StatsWidget(this);
|
|
|
|
|
|
|
|
QWidget *rightWidget = new QWidget;
|
|
QWidget *rightWidget = new QWidget;
|
|
@@ -146,7 +174,13 @@ MainPanel::MainPanel(QWidget *parent)
|
|
|
vbox->setContentsMargins(0, 0, 0, 0);
|
|
vbox->setContentsMargins(0, 0, 0, 0);
|
|
|
vbox->addWidget(userProfile, 0);
|
|
vbox->addWidget(userProfile, 0);
|
|
|
vbox->addWidget(statsWidget, 0);
|
|
vbox->addWidget(statsWidget, 0);
|
|
|
- vbox->addWidget(chatView, 1);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 创建聊天窗口容器
|
|
|
|
|
+ m_chatContainer = new QWidget(rightWidget);
|
|
|
|
|
+ QVBoxLayout *chatLayout = new QVBoxLayout(m_chatContainer);
|
|
|
|
|
+ chatLayout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
|
+ chatLayout->addWidget(chatView);
|
|
|
|
|
+ vbox->addWidget(m_chatContainer, 1);
|
|
|
|
|
|
|
|
splitter = new QSplitter(Qt::Horizontal, this);
|
|
splitter = new QSplitter(Qt::Horizontal, this);
|
|
|
playerContainer = new QWidget(this);
|
|
playerContainer = new QWidget(this);
|
|
@@ -235,19 +269,22 @@ MainPanel::MainPanel(QWidget *parent)
|
|
|
// 按钮区域
|
|
// 按钮区域
|
|
|
QHBoxLayout *buttonLayout = new QHBoxLayout();
|
|
QHBoxLayout *buttonLayout = new QHBoxLayout();
|
|
|
m_recordButton = new QPushButton(tr("开始录制"), audioContent);
|
|
m_recordButton = new QPushButton(tr("开始录制"), audioContent);
|
|
|
- m_streamButton = new QPushButton(tr("开始推流"), audioContent);
|
|
|
|
|
m_settingsButton = new QPushButton(tr("设置"), audioContent);
|
|
m_settingsButton = new QPushButton(tr("设置"), audioContent);
|
|
|
|
|
|
|
|
buttonLayout->addWidget(m_recordButton);
|
|
buttonLayout->addWidget(m_recordButton);
|
|
|
- buttonLayout->addWidget(m_streamButton);
|
|
|
|
|
buttonLayout->addWidget(m_settingsButton);
|
|
buttonLayout->addWidget(m_settingsButton);
|
|
|
audioLayout->addLayout(buttonLayout);
|
|
audioLayout->addLayout(buttonLayout);
|
|
|
|
|
|
|
|
|
|
+ // 创建独立的推流按钮
|
|
|
|
|
+ m_streamButton = new FunctionButton(IconUtils::createStreamIcon(), "推流", this);
|
|
|
|
|
+
|
|
|
audioDevicePopover->setContentWidget(audioContent);
|
|
audioDevicePopover->setContentWidget(audioContent);
|
|
|
|
|
|
|
|
// 将按钮与 Popover 关联
|
|
// 将按钮与 Popover 关联
|
|
|
buttonGroup->addButton(audioDeviceBtn, audioDevicePopover);
|
|
buttonGroup->addButton(audioDeviceBtn, audioDevicePopover);
|
|
|
|
|
|
|
|
|
|
+ // 添加独立的推流按钮到buttonGroup
|
|
|
|
|
+ buttonGroup->addButton(m_streamButton);
|
|
|
// 设备枚举与填充逻辑
|
|
// 设备枚举与填充逻辑
|
|
|
auto populateAudioDevices = [this]() {
|
|
auto populateAudioDevices = [this]() {
|
|
|
// 清空缓存
|
|
// 清空缓存
|
|
@@ -481,10 +518,6 @@ MainPanel::~MainPanel()
|
|
|
m_avPlayerStandalone->deleteLater();
|
|
m_avPlayerStandalone->deleteLater();
|
|
|
m_avPlayerStandalone = nullptr;
|
|
m_avPlayerStandalone = nullptr;
|
|
|
}
|
|
}
|
|
|
- if (m_chatStandalone) {
|
|
|
|
|
- m_chatStandalone->deleteLater();
|
|
|
|
|
- m_chatStandalone = nullptr;
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|
|
|
void MainPanel::setRole(const QStringList &roleList)
|
|
void MainPanel::setRole(const QStringList &roleList)
|
|
|
{
|
|
{
|
|
@@ -513,9 +546,6 @@ void MainPanel::setPushRoomId(const QString &id)
|
|
|
|
|
|
|
|
// 重新进入房间
|
|
// 重新进入房间
|
|
|
chatView->initWebsocket(id);
|
|
chatView->initWebsocket(id);
|
|
|
- if (m_chatStandalone) {
|
|
|
|
|
- m_chatStandalone->initWebsocket(id);
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
if (AVPlayerWidget *playWidget = qobject_cast<AVPlayerWidget *>(playerWidget)) {
|
|
if (AVPlayerWidget *playWidget = qobject_cast<AVPlayerWidget *>(playerWidget)) {
|
|
|
MaskOverlay::instance()->show(nullptr, 0, MaskOverlay::ActiveWindow);
|
|
MaskOverlay::instance()->show(nullptr, 0, MaskOverlay::ActiveWindow);
|
|
@@ -689,36 +719,49 @@ void MainPanel::showPlayerStandalone()
|
|
|
|
|
|
|
|
void MainPanel::showChatStandalone()
|
|
void MainPanel::showChatStandalone()
|
|
|
{
|
|
{
|
|
|
- if (!m_chatStandalone) {
|
|
|
|
|
- m_chatStandalone = new ChatWindow(webSocketClient);
|
|
|
|
|
- m_chatStandalone->setAttribute(Qt::WA_DeleteOnClose, false);
|
|
|
|
|
- connect(m_chatStandalone, &QObject::destroyed, this, [this]() {
|
|
|
|
|
- m_chatStandalone = nullptr;
|
|
|
|
|
- });
|
|
|
|
|
- m_chatStandalone->setMinimumWidth(400);
|
|
|
|
|
- m_chatStandalone->setWindowTitle(tr("ChatWindow"));
|
|
|
|
|
- // 同步当前房间
|
|
|
|
|
- if (webSocketClient) {
|
|
|
|
|
- const QString rid = webSocketClient->roomId();
|
|
|
|
|
- if (!rid.isEmpty()) {
|
|
|
|
|
- m_chatStandalone->initWebsocket(rid);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (!chatView) {
|
|
|
|
|
+ return; // 如果聊天窗口不存在,直接返回
|
|
|
}
|
|
}
|
|
|
- m_chatStandalone->show();
|
|
|
|
|
- m_chatStandalone->raise();
|
|
|
|
|
- m_chatStandalone->activateWindow();
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 从容器中移除聊天窗口(如果在容器中)
|
|
|
|
|
+ if (m_chatContainer && chatView->parent() == m_chatContainer) {
|
|
|
|
|
+ chatView->setParent(nullptr);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 设置为独立窗口
|
|
|
|
|
+ chatView->setWindowFlags(Qt::Window);
|
|
|
|
|
+ chatView->setAttribute(Qt::WA_DeleteOnClose, false);
|
|
|
|
|
+ chatView->setMinimumWidth(400);
|
|
|
|
|
+ chatView->setWindowTitle(tr("ChatWindow"));
|
|
|
|
|
+ chatView->show();
|
|
|
|
|
+ chatView->raise();
|
|
|
|
|
+ chatView->activateWindow();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void MainPanel::showChatEmbedded()
|
|
void MainPanel::showChatEmbedded()
|
|
|
{
|
|
{
|
|
|
- if (chatView) {
|
|
|
|
|
|
|
+ if (!chatView) {
|
|
|
|
|
+ return; // 如果聊天窗口不存在,直接返回
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果当前是独立窗口模式,先隐藏
|
|
|
|
|
+ if (chatView->isWindow()) {
|
|
|
|
|
+ chatView->hide();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 重置窗口标志为普通widget
|
|
|
|
|
+ chatView->setWindowFlags(Qt::Widget);
|
|
|
|
|
+
|
|
|
|
|
+ // 将聊天窗口重新添加到容器中
|
|
|
|
|
+ if (m_chatContainer) {
|
|
|
|
|
+ chatView->setParent(m_chatContainer);
|
|
|
|
|
+ // 如果容器有布局,将聊天窗口添加到布局中
|
|
|
|
|
+ if (m_chatContainer->layout()) {
|
|
|
|
|
+ m_chatContainer->layout()->addWidget(chatView);
|
|
|
|
|
+ }
|
|
|
chatView->show();
|
|
chatView->show();
|
|
|
chatView->raise();
|
|
chatView->raise();
|
|
|
}
|
|
}
|
|
|
- if (m_chatStandalone) {
|
|
|
|
|
- m_chatStandalone->hide();
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 新增:录制控制按钮的槽函数实现
|
|
// 新增:录制控制按钮的槽函数实现
|
|
@@ -746,21 +789,36 @@ void MainPanel::onStreamButtonClicked()
|
|
|
{
|
|
{
|
|
|
if (RecorderWidget *rec = qobject_cast<RecorderWidget *>(playerWidget)) {
|
|
if (RecorderWidget *rec = qobject_cast<RecorderWidget *>(playerWidget)) {
|
|
|
// 同步选项设置到RecorderWidget
|
|
// 同步选项设置到RecorderWidget
|
|
|
-
|
|
|
|
|
- // 调用RecorderWidget的推流方法
|
|
|
|
|
|
|
+
|
|
|
rec->onStreamButtonClicked();
|
|
rec->onStreamButtonClicked();
|
|
|
-
|
|
|
|
|
- // 更新按钮文本
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 更新按钮文本和状态
|
|
|
if (m_streamButton) {
|
|
if (m_streamButton) {
|
|
|
- if (m_streamButton->text() == tr("开始推流")) {
|
|
|
|
|
|
|
+ if (!m_isStreaming) {
|
|
|
|
|
+ // 开始推流
|
|
|
m_streamButton->setText(tr("停止推流"));
|
|
m_streamButton->setText(tr("停止推流"));
|
|
|
|
|
+ m_isStreaming = true;
|
|
|
|
|
+
|
|
|
|
|
+ // 隐藏预览
|
|
|
|
|
+ rec->hidePreview();
|
|
|
|
|
+
|
|
|
|
|
+ // 显示独立聊天窗口
|
|
|
|
|
+ showChatStandalone();
|
|
|
|
|
+
|
|
|
} else {
|
|
} else {
|
|
|
- m_streamButton->setText(tr("开始推流"));
|
|
|
|
|
|
|
+ // 停止推流
|
|
|
|
|
+ m_streamButton->setText(tr("推流"));
|
|
|
|
|
+ m_isStreaming = false;
|
|
|
|
|
+
|
|
|
|
|
+ // 恢复预览显示
|
|
|
|
|
+ rec->showPreview();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
void MainPanel::onSettingsButtonClicked()
|
|
void MainPanel::onSettingsButtonClicked()
|
|
|
{
|
|
{
|
|
|
if (RecorderWidget *rec = qobject_cast<RecorderWidget *>(playerWidget)) {
|
|
if (RecorderWidget *rec = qobject_cast<RecorderWidget *>(playerWidget)) {
|
|
@@ -768,3 +826,18 @@ void MainPanel::onSettingsButtonClicked()
|
|
|
rec->onSettingsButtonClicked();
|
|
rec->onSettingsButtonClicked();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+void MainPanel::onChatWindowCloseRequested()
|
|
|
|
|
+{
|
|
|
|
|
+ if (!chatView) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (m_isStreaming) {
|
|
|
|
|
+ // 推流状态下,只隐藏窗口
|
|
|
|
|
+ chatView->hide();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 非推流状态下,切换回嵌入式显示
|
|
|
|
|
+ showChatEmbedded();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|