| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- #include "avplayerwidget.h"
- #include "avopenglwidget.h"
- #include "av_player.h"
- #include "vframe.h"
- #include "../../config/networkconfig.h"
- #include <QtConcurrent>
- #include <QApplication>
- #include <QDebug>
- #include <QElapsedTimer>
- 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<bool>(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<bool>::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<VideoFrame> 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<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();
- }
- }
|