#include "avplayerwidget.h" #include "avopenglwidget.h" #include "av_player.h" #include "vframe.h" #include "../../config/networkconfig.h" #include #include #include #include AVPlayerWidget::AVPlayerWidget(QWidget *parent) : QWidget{parent} , m_player(new AVPlayer) , m_openglWidget(new AVOpenGLWidget(this)) , m_isPlaying(false) , m_isPaused(false) , m_isLoading(false) , m_playWatcher(new QFutureWatcher(this)) , m_loadingLabel(nullptr) , m_loadingProgress(nullptr) , m_loadingMovie(nullptr) { // 设置尺寸策略,确保能够正确填充空间 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setupUI(); connectSignals(); // 设置默认音量 m_player->setVolume(50); m_volumeSlider->setValue(50); // 设置默认测试URL m_urlEdit->setText(NetworkConfig::instance().getStreamUrl()); } AVPlayerWidget::~AVPlayerWidget() { if (m_player) { m_player->clearPlayer(); } } void AVPlayerWidget::setupUI() { m_mainLayout = new QVBoxLayout(this); m_mainLayout->setContentsMargins(5, 5, 5, 5); // 减少主布局边距 m_mainLayout->setSpacing(5); // 减少组件间距 // 添加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); // 设置控制面板固定高度 controlWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_controlLayout = new QHBoxLayout(controlWidget); m_controlLayout->setContentsMargins(5, 5, 5, 5); // 减少控制面板内边距 m_controlLayout->setSpacing(5); // 减少控件间距 // URL输入框 m_urlEdit = new QLineEdit(this); m_urlEdit->setPlaceholderText("输入视频文件路径或URL"); m_urlEdit->setFixedHeight(30); // 固定高度适应控制面板 m_urlEdit->setFont(QFont("Arial", 9)); m_controlLayout->addWidget(m_urlEdit, 2); // 测试播放按钮 m_testPlayButton = new QPushButton("测试播放", this); m_testPlayButton->setFixedSize(70, 30); // 固定尺寸 m_testPlayButton->setFont(QFont("Arial", 9, QFont::Bold)); m_controlLayout->addWidget(m_testPlayButton); // 播放控制按钮 m_playButton = new QPushButton("播放", this); m_playButton->setFixedSize(50, 30); // 固定尺寸 m_playButton->setFont(QFont("Arial", 9, QFont::Bold)); m_pauseButton = new QPushButton("暂停", this); m_pauseButton->setFixedSize(50, 30); // 固定尺寸 m_pauseButton->setFont(QFont("Arial", 9, QFont::Bold)); m_stopButton = new QPushButton("停止", this); m_stopButton->setFixedSize(50, 30); // 固定尺寸 m_stopButton->setFont(QFont("Arial", 9, QFont::Bold)); m_controlLayout->addWidget(m_playButton); m_controlLayout->addWidget(m_pauseButton); m_controlLayout->addWidget(m_stopButton); // 音量控制 QLabel *volumeLabel = new QLabel("音量:", this); volumeLabel->setFont(QFont("Arial", 9)); m_volumeSlider = new QSlider(Qt::Horizontal, this); m_volumeSlider->setRange(0, 100); m_volumeSlider->setValue(50); m_volumeSlider->setFixedSize(100, 20); // 固定尺寸 m_controlLayout->addWidget(volumeLabel); m_controlLayout->addWidget(m_volumeSlider); // 时间显示 m_timeLabel = new QLabel("00:00 / 00:00", this); m_timeLabel->setFont(QFont("Arial", 9)); m_timeLabel->setFixedWidth(80); m_controlLayout->addWidget(m_timeLabel); m_mainLayout->addWidget(controlWidget, 0); // 拉伸因子为0,不占用额外空间 // 设置初始状态 m_pauseButton->setEnabled(false); m_stopButton->setEnabled(false); controlWidget->hide(); } void AVPlayerWidget::connectSignals() { // 连接AVPlayer信号 connect(m_player, &AVPlayer::frameChanged, this, &AVPlayerWidget::onFrameChanged, Qt::QueuedConnection); connect(m_player, &AVPlayer::AVDurationChanged, this, &AVPlayerWidget::durationChangedSlot); connect(m_player, &AVPlayer::AVPtsChanged, this, &AVPlayerWidget::ptsChangedSlot); connect(m_player, &AVPlayer::AVTerminate, this, &AVPlayerWidget::terminateSlot, Qt::QueuedConnection); // 连接UI控件信号 connect(m_playButton, &QPushButton::clicked, this, &AVPlayerWidget::onPlayButtonClicked); connect(m_pauseButton, &QPushButton::clicked, this, &AVPlayerWidget::onPauseButtonClicked); 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::finished, this, &AVPlayerWidget::onAsyncPlayFinished); } void AVPlayerWidget::play(const QString &url) { if (!m_player->play(url)) { // 提示反馈? return; } m_isPlaying = true; m_isPaused = false; m_playButton->setEnabled(false); m_pauseButton->setEnabled(true); m_stopButton->setEnabled(true); emit playStateChanged(true); } void AVPlayerWidget::stop() { m_player->clearPlayer(); m_isPlaying = false; m_isPaused = false; m_playButton->setEnabled(true); m_pauseButton->setEnabled(false); m_stopButton->setEnabled(false); m_openglWidget->clearFrame(); emit playStateChanged(false); } void AVPlayerWidget::pause() { if (m_isPlaying && !m_isPaused) { m_player->pause(true); m_isPaused = true; m_playButton->setText("继续"); m_playButton->setEnabled(true); m_pauseButton->setEnabled(false); } } void AVPlayerWidget::resume() { if (m_isPlaying && m_isPaused) { m_player->pause(false); m_isPaused = false; m_playButton->setText("播放"); m_playButton->setEnabled(false); m_pauseButton->setEnabled(true); } } void AVPlayerWidget::onPlayButtonClicked() { if (m_isPaused) { resume(); } else { QString url = m_urlEdit->text().trimmed(); if (!url.isEmpty()) { play(url); } } } void AVPlayerWidget::onPauseButtonClicked() { pause(); } void AVPlayerWidget::onStopButtonClicked() { stop(); } void AVPlayerWidget::onTestPlayButtonClicked() { // 使用默认测试视频 QString testUrl = "C:/Users/zhuizhu/Videos/2.mp4"; m_urlEdit->setText(testUrl); play(testUrl); } void AVPlayerWidget::onVolumeChanged(int volume) { m_player->setVolume(volume); } void AVPlayerWidget::onFrameChanged(QSharedPointer frame) { m_openglWidget->onShowYUV(frame); // 注意:这里不要释放avFrame,因为Render方法会处理 } void AVPlayerWidget::ptsChangedSlot(unsigned int pts) { // 更新时间显示 int seconds = pts / 1000; int minutes = seconds / 60; seconds = seconds % 60; QString timeStr = QString("%1:%2").arg(minutes, 2, 10, QChar('0')).arg(seconds, 2, 10, QChar('0')); // 这里可以更新时间标签的当前时间部分 } void AVPlayerWidget::durationChangedSlot(unsigned int duration) { // 更新总时长显示 int seconds = duration / 1000; int minutes = seconds / 60; seconds = seconds % 60; QString durationStr = QString("%1:%2").arg(minutes, 2, 10, QChar('0')).arg(seconds, 2, 10, QChar('0')); m_timeLabel->setText(QString("00:00 / %1").arg(durationStr)); } void AVPlayerWidget::terminateSlot() { stop(); } // 兼容旧接口实现 void AVPlayerWidget::stopPlay() { stop(); } void AVPlayerWidget::setPlayRoomId(const QString &id) { // 以当前URL为基准,保留 app 与 streamKey(如 V1),仅替换/追加房间ID QString base = m_urlEdit->text().trimmed(); if (base.isEmpty()) { base = NetworkConfig::instance().getStreamUrl(); } const QString defaultBase = NetworkConfig::instance().getStreamUrl(); // 解析出前缀(协议+主机端口)与路径段 const int schemePos = base.indexOf("://"); const int pathStart = base.indexOf('/', schemePos >= 0 ? schemePos + 3 : 0); const QString prefix = pathStart >= 0 ? base.left(pathStart) : base; // rtmp://host:port const QString path = pathStart >= 0 ? base.mid(pathStart + 1) : QString(); // 去掉开头 '/' const QStringList segs = path.split('/', Qt::SkipEmptyParts); QString url; if (segs.size() >= 2) { // 保留前两段:app + streamKey(例如 stream/V1),将最后一段替换为 id url = prefix + "/" + segs.at(0) + "/" + segs.at(1) + "/" + id; } else { // 如果当前路径不足两段,退回到默认配置(包含 app 与 streamKey)再追加 id url = defaultBase + "/" + id; } qDebug() << "setPlayRoomId" << url; m_urlEdit->setText(url); } void AVPlayerWidget::startPlay() { const QString url = m_urlEdit->text().trimmed(); if (!url.isEmpty()) { qDebug() << "startPlay" << 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 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(); } }