zhuizhu 7 месяцев назад
Родитель
Сommit
8005e0c998

+ 1 - 1
LearningSmartClient.pro

@@ -2,7 +2,7 @@ QT       += core gui network svg xml
 QT += multimedia websockets
 QT += winextras
 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
-CONFIG += console
+# CONFIG += console
 CONFIG += c++17
 LIBS += -lshell32
 QT += opengl

+ 27 - 8
MainPanel.cpp

@@ -451,20 +451,39 @@ void MainPanel::handleDebouncedPlay()
     const QString id = m_pendingRoomId;
     if (id.isEmpty()) return;
     qDebug() << "[MainPanel] Debounced startPlay for room" << id;
-
+    RecorderWidget *rec = qobject_cast<RecorderWidget *>(playerWidget);
+    if (rec) {
+        rec->setLiveName(id);
+    }
     AVPlayerWidget *av = qobject_cast<AVPlayerWidget*>(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->startPlay();
+        av->startPlayAsync();  // 使用异步播放方法
         return;
     }
-
-    RecorderWidget *rec = qobject_cast<RecorderWidget*>(playerWidget);
-    if (rec) {
-        // rec->startLive();
-        // -        RecorderWidget::Settings s = rec->m_settings; // 需要通过公共接口设置,避免直接访问成员
-    }
 }
 
 void MainPanel::initAudioDeviceSelectors()

+ 167 - 1
libs/AVPlayer/avplayerwidget.cpp

@@ -4,6 +4,10 @@
 
 #include "av_player.h"
 #include "vframe.h"
+#include <QtConcurrent>
+#include <QApplication>
+#include <QDebug>
+#include <QElapsedTimer>
 
 AVPlayerWidget::AVPlayerWidget(QWidget *parent)
     : QWidget{parent}
@@ -11,6 +15,11 @@ AVPlayerWidget::AVPlayerWidget(QWidget *parent)
     , m_openglWidget(new AVOpenGLWidget(this))
     , m_isPlaying(false)
     , m_isPaused(false)
+    , m_isLoading(false)
+    , m_playWatcher(new QFutureWatcher<bool>(this))
+    , m_loadingLabel(nullptr)
+    , m_loadingProgress(nullptr)
+    , m_loadingMovie(nullptr)
 {
     // 设置尺寸策略,确保能够正确填充空间
     setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
@@ -42,6 +51,17 @@ void AVPlayerWidget::setupUI()
     // 添加OpenGL视频渲染组件
     m_mainLayout->addWidget(m_openglWidget, 1);
     
+    // 创建加载状态UI组件(初始隐藏)
+    m_loadingLabel = new QLabel("正在连接...", this);
+    m_loadingLabel->setAlignment(Qt::AlignCenter);
+    m_loadingLabel->setStyleSheet("QLabel { background-color: rgba(0, 0, 0, 180); color: white; font-size: 16px; padding: 10px; border-radius: 5px; }");
+    m_loadingLabel->hide();
+    
+    m_loadingProgress = new QProgressBar(this);
+    m_loadingProgress->setRange(0, 0);  // 无限进度条
+    m_loadingProgress->setStyleSheet("QProgressBar { border: 2px solid grey; border-radius: 5px; text-align: center; } QProgressBar::chunk { background-color: #05B8CC; width: 20px; }");
+    m_loadingProgress->hide();
+    
     // 创建控制面板容器
     QWidget *controlWidget = new QWidget(this);
     controlWidget->setFixedHeight(60);  // 设置控制面板固定高度
@@ -121,6 +141,9 @@ void AVPlayerWidget::connectSignals()
     connect(m_stopButton, &QPushButton::clicked, this, &AVPlayerWidget::onStopButtonClicked);
     connect(m_testPlayButton, &QPushButton::clicked, this, &AVPlayerWidget::onTestPlayButtonClicked);
     connect(m_volumeSlider, &QSlider::valueChanged, this, &AVPlayerWidget::onVolumeChanged);
+    
+    // 连接异步播放信号
+    connect(m_playWatcher, &QFutureWatcher<bool>::finished, this, &AVPlayerWidget::onAsyncPlayFinished);
 }
 
 void AVPlayerWidget::play(const QString &url)
@@ -267,6 +290,149 @@ void AVPlayerWidget::startPlay()
     const QString url = m_urlEdit->text().trimmed();
     if (!url.isEmpty()) {
         qDebug() << "startPlay" << url;
-        play(url);
+        playAsync(url);  // 使用异步播放
+    }
+}
+
+void AVPlayerWidget::playAsync(const QString &url)
+{
+    if (m_isLoading) {
+        qDebug() << "Already loading, ignoring new play request";
+        return;
+    }
+    
+    m_pendingUrl = url;
+    m_isLoading = true;
+    
+    // 显示加载状态
+    showLoadingUI();
+    emit playLoadingStarted();
+    
+    // 在后台线程执行播放逻辑
+    QFuture<bool> future = QtConcurrent::run([this, url]() -> bool {
+        try {
+            // 创建临时播放器用于测试连接
+            AVPlayer testPlayer;
+            
+            // 设置超时时间(10秒)
+            QElapsedTimer timer;
+            timer.start();
+            const int timeoutMs = 10000;
+            
+            bool success = testPlayer.play(url);
+            
+            // 检查是否超时
+            if (timer.elapsed() > timeoutMs) {
+                qDebug() << "Connection timeout after" << timeoutMs << "ms";
+                testPlayer.clearPlayer();
+                return false;
+            }
+            
+            if (!success) {
+                qDebug() << "Failed to connect to stream:" << url;
+                return false;
+            }
+            
+            // 连接成功,清理测试播放器
+            testPlayer.clearPlayer();
+            return true;
+            
+        } catch (const std::exception &e) {
+            qDebug() << "Exception during async play:" << e.what();
+            return false;
+        } catch (...) {
+            qDebug() << "Unknown exception during async play";
+            return false;
+        }
+    });
+    
+    m_playWatcher->setFuture(future);
+}
+
+void AVPlayerWidget::startPlayAsync()
+{
+    const QString url = m_urlEdit->text().trimmed();
+    if (!url.isEmpty()) {
+        playAsync(url);
+    }
+}
+
+void AVPlayerWidget::onAsyncPlayFinished()
+{
+    m_isLoading = false;
+    hideLoadingUI();
+    
+    bool success = m_playWatcher->result();
+    if (success) {
+        // 连接成功,使用主线程播放器开始播放
+        if (!m_player->play(m_pendingUrl)) {
+            QString errorMsg = "播放失败:无法初始化播放器";
+            qDebug() << errorMsg;
+            emit playError(errorMsg);
+            return;
+        }
+        m_isPlaying = true;
+        m_isPaused = false;
+        m_playButton->setEnabled(false);
+        m_pauseButton->setEnabled(true);
+        m_stopButton->setEnabled(true);
+        emit playStateChanged(true);
+        emit playLoadingFinished();
+        qDebug() << "Successfully started playing:" << m_pendingUrl;
+    } else {
+        QString errorMsg;
+        if (m_pendingUrl.startsWith("rtmp://")) {
+            errorMsg = "RTMP连接失败:请检查流地址是否正确,网络是否畅通";
+        } else if (m_pendingUrl.startsWith("http://") || m_pendingUrl.startsWith("https://")) {
+            errorMsg = "HTTP流连接失败:请检查网络连接和流地址";
+        } else {
+            errorMsg = "连接失败:不支持的协议或无效的流地址";
+        }
+        qDebug() << "Play failed for URL:" << m_pendingUrl;
+        emit playError(errorMsg);
+    }
+}
+
+void AVPlayerWidget::onAsyncPlayError(const QString &error)
+{
+    m_isLoading = false;
+    hideLoadingUI();
+    emit playError(error);
+}
+
+void AVPlayerWidget::showLoadingUI()
+{
+    if (m_loadingLabel && m_loadingProgress) {
+        // 计算居中位置
+        QRect rect = m_openglWidget->geometry();
+        int labelWidth = 200;
+        int labelHeight = 50;
+        int progressWidth = 300;
+        int progressHeight = 20;
+        
+        m_loadingLabel->setGeometry(
+            rect.x() + (rect.width() - labelWidth) / 2,
+            rect.y() + (rect.height() - labelHeight) / 2 - 30,
+            labelWidth, labelHeight
+        );
+        
+        m_loadingProgress->setGeometry(
+            rect.x() + (rect.width() - progressWidth) / 2,
+            rect.y() + (rect.height() - progressHeight) / 2 + 30,
+            progressWidth, progressHeight
+        );
+        
+        m_loadingLabel->show();
+        m_loadingProgress->show();
+        m_loadingLabel->raise();
+        m_loadingProgress->raise();
+    }
+}
+
+void AVPlayerWidget::hideLoadingUI()
+{
+    if (m_loadingLabel && m_loadingProgress) {
+        m_loadingLabel->hide();
+        m_loadingProgress->hide();
     }
 }

+ 27 - 0
libs/AVPlayer/avplayerwidget.h

@@ -9,6 +9,9 @@
 #include <QSlider>
 #include <QLineEdit>
 #include <QSharedPointer>
+#include <QFutureWatcher>
+#include <QProgressBar>
+#include <QMovie>
 
 class AVPlayer;
 class AVOpenGLWidget;
@@ -31,6 +34,10 @@ public:
     void setPlayRoomId(const QString &id);
     void startPlay();
     
+    // 异步播放接口
+    void playAsync(const QString &url);
+    void startPlayAsync();
+    
 private Q_SLOTS:
     void ptsChangedSlot(unsigned int duration);
     void durationChangedSlot(unsigned int pts);
@@ -46,12 +53,21 @@ private Q_SLOTS:
     // 视频帧处理
     void onFrameChanged(QSharedPointer<VideoFrame> frame);
     
+    // 异步播放相关槽函数
+    void onAsyncPlayFinished();
+    void onAsyncPlayError(const QString &error);
+    
 signals:
     void playStateChanged(bool isPlaying);
+    void playLoadingStarted();
+    void playLoadingFinished();
+    void playError(const QString &error);
 
 private:
     void setupUI();
     void connectSignals();
+    void showLoadingUI();
+    void hideLoadingUI();
     
     AVPlayer *m_player;
     AVOpenGLWidget *m_openglWidget;
@@ -69,6 +85,17 @@ private:
     
     bool m_isPlaying;
     bool m_isPaused;
+    bool m_isLoading;
+    
+    // 异步播放相关
+    QFutureWatcher<bool> *m_playWatcher;
+    
+    // 加载状态UI
+    QLabel *m_loadingLabel;
+    QProgressBar *m_loadingProgress;
+    QMovie *m_loadingMovie;
+    
+    QString m_pendingUrl;
 };
 
 #endif // AVPLAYERWIDGET_H

+ 0 - 7
widgets/meetingselectionwidget.cpp

@@ -216,7 +216,6 @@ void MeetingSelectionWidget::applyStyles()
                   "    border: none;"
                   "    border-radius: 15px;"
                   "    margin: 5px;"
-                  "    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);"
                   "}"
 
                   "#statsCardFrame {"
@@ -224,7 +223,6 @@ void MeetingSelectionWidget::applyStyles()
                   "    border: none;"
                   "    border-radius: 15px;"
                   "    margin: 5px;"
-                  "    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);"
                   "}"
 
                   "#userProfileCard {"
@@ -241,7 +239,6 @@ void MeetingSelectionWidget::applyStyles()
                   "    background-color: white;"
                   "    border: none;"
                   "    border-radius: 15px;"
-                  "    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);"
                   "}"
 
                   "#joinMeetingBtn {"
@@ -258,13 +255,11 @@ void MeetingSelectionWidget::applyStyles()
                   "#joinMeetingBtn:hover {"
                   "    background: qlineargradient(x1:0, y1:0, x2:0, y2:1, "
                   "        stop:0 #29b6f6, stop:1 #0288d1);"
-                  "    transform: translateY(-2px);"
                   "}"
 
                   "#joinMeetingBtn:pressed {"
                   "    background: qlineargradient(x1:0, y1:0, x2:0, y2:1, "
                   "        stop:0 #0288d1, stop:1 #0277bd);"
-                  "    transform: translateY(0px);"
                   "}"
 
                   "#createMeetingBtn {"
@@ -281,13 +276,11 @@ void MeetingSelectionWidget::applyStyles()
                   "#createMeetingBtn:hover {"
                   "    background: qlineargradient(x1:0, y1:0, x2:0, y2:1, "
                   "        stop:0 #66bb6a, stop:1 #4caf50);"
-                  "    transform: translateY(-2px);"
                   "}"
 
                   "#createMeetingBtn:pressed {"
                   "    background: qlineargradient(x1:0, y1:0, x2:0, y2:1, "
                   "        stop:0 #4caf50, stop:1 #43a047);"
-                  "    transform: translateY(0px);"
                   "}"
 
                   "#logoutBtn {"