文章的目的为了记录使用QT QML开发学习的经历。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
开源 C++ QT QML 开发(四)复杂控件--Listview
开源 C++ QT QML 开发(五)复杂控件--Gridview
开源 C++ QT QML 开发(十一)通讯--TCP服务器端
开源 C++ QT QML 开发(十二)通讯--TCP客户端
开源 C++ QT QML 开发(十五)通讯--http下载
开源 C++ QT QML 开发(十七)进程--LocalSocket
推荐链接:
开源 C# 快速开发(十六)数据库--sqlserver增删改查
本章节主要内容是:实现多媒体控制中的音频播放,实时显示进度和控制。
1.代码分析
2.所有源码
3.效果演示
一、代码分析1. main.cpp 详细分析
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QIcon>
#include <QDebug>
#include "audioplayer.h"
int main(int argc, char *argv[])
{
// 启用高DPI缩放支持
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
// 创建GUI应用程序实例
QGuiApplication app(argc, argv);
app.setApplicationName("WAV音频播放器");
app.setApplicationVersion("1.0");
qDebug() << "应用程序启动...";
// 注册AudioPlayer类到QML系统,使其可以在QML中使用
// 参数说明:"AudioPlayer" - QML中的类型名,1,0 - 主版本和次版本,"AudioPlayer" - QML中的类名
qmlRegisterType<AudioPlayer>("AudioPlayer", 1, 0, "AudioPlayer");
// 创建QML引擎,负责加载和解释QML文件
QQmlApplicationEngine engine;
// 创建音频播放器实例并设置为QML上下文属性
// 这样在QML中可以直接通过"audioPlayer"访问C++对象
AudioPlayer *audioPlayer = new AudioPlayer(&app);
engine.rootContext()->setContextProperty("audioPlayer", audioPlayer);
// 定义要加载的QML文件URL(使用资源系统)
const QUrl url(QStringLiteral("qrc:/main.qml"));
// 连接对象创建信号,用于检测QML加载是否成功
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
// 回调函数:当QML对象创建完成后调用
if (!obj && url == objUrl) {
// 如果对象创建失败且URL匹配,输出错误并退出
qCritical() << "QML加载失败:" << url;
QCoreApplication::exit(-1);
} else {
// 加载成功,输出日志
qDebug() << "QML加载成功,窗口已创建";
}
}, Qt::QueuedConnection); // 使用队列连接确保在事件循环中执行
// 连接警告信号,捕获QML中的警告和错误
QObject::connect(&engine, &QQmlApplicationEngine::warnings,
[](const QList<QQmlError> &warnings) {
// 遍历所有警告并输出
for (const QQmlError &error : warnings) {
qWarning() << "QML警告:" << error.toString();
}
});
// 加载QML文件
engine.load(url);
// 检查是否成功创建了根对象
if (engine.rootObjects().isEmpty()) {
qCritical() << "没有创建任何QML根对象";
return -1;
}
qDebug() << "应用程序进入事件循环...";
// 启动事件循环,程序开始运行
return app.exec();
}
关键函数分析:
qmlRegisterType(): 将C++类注册到QML系统中
setContextProperty(): 将C++对象暴露给QML上下文
engine.load(): 加载并解析QML文件
app.exec(): 启动Qt事件循环
-
audioplayer.h 详细分析
#ifndef AUDIOPLAYER_H
#define AUDIOPLAYER_H#include <QObject>
#include <QMediaPlayer>
#include <QTimer>
#include <QFileInfo>
#include <QDir>
#include <QDateTime>class AudioPlayer : public QObject
{
Q_OBJECT
// Q_PROPERTY宏定义QML可访问的属性
Q_PROPERTY(QString filePath READ filePath NOTIFY filePathChanged)
Q_PROPERTY(QString statusText READ statusText NOTIFY statusTextChanged)
Q_PROPERTY(QString timeDisplay READ timeDisplay NOTIFY timeDisplayChanged)
Q_PROPERTY(int progress READ progress NOTIFY progressChanged)
Q_PROPERTY(int volume READ volume WRITE setVolume NOTIFY volumeChanged)
Q_PROPERTY(bool isPlaying READ isPlaying NOTIFY playingStateChanged)public:
explicit AudioPlayer(QObject *parent = nullptr);// 属性读取函数 QString filePath() const; QString statusText() const; QString timeDisplay() const; int progress() const; int volume() const; bool isPlaying() const; // Q_INVOKABLE宏标记的函数可以在QML中调用 Q_INVOKABLE void playPause(); // 播放/暂停切换 Q_INVOKABLE void stop(); // 停止播放 Q_INVOKABLE void setVolume(int volume); // 设置音量
signals:
// 信号定义,当属性改变时发射
void filePathChanged();
void statusTextChanged();
void timeDisplayChanged();
void progressChanged();
void volumeChanged();
void playingStateChanged();
void logMessageReceived(const QString &message); // 日志消息信号private slots:
// 私有槽函数,处理播放器状态变化
void handlePlayerStateChanged(QMediaPlayer::State state);
void handlePlayerError(QMediaPlayer::Error error);
void updatePlaybackProgress(); // 更新播放进度private:
// 私有辅助函数
bool checkAudioFile(); // 检查音频文件
void logMessage(const QString &message); // 记录日志// 成员变量 QMediaPlayer *m_player; // Qt多媒体播放器 QTimer *m_progressTimer; // 进度更新定时器 QString m_audioFilePath; // 音频文件路径 QString m_statusText; // 状态文本 QString m_timeDisplay; // 时间显示文本 int m_progress; // 播放进度(0-100) int m_volume; // 音量(0-100) bool m_isPlaying; // 是否正在播放
};
#endif // AUDIOPLAYER_H
关键设计模式:
属性绑定: 使用Q_PROPERTY实现C++与QML的数据绑定
信号槽机制: 实现对象间的松耦合通信
命令模式: Q_INVOKABLE方法提供QML可调用的接口
- audioplayer.cpp 详细分析
构造函数
AudioPlayer::AudioPlayer(QObject *parent)
: QObject(parent)
, m_player(new QMediaPlayer(this)) // 创建播放器,设置父对象用于自动内存管理
, m_progressTimer(new QTimer(this)) // 创建定时器
, m_progress(0) // 初始化进度为0
, m_volume(80) // 初始化音量为80%
, m_isPlaying(false) // 初始化播放状态为false
{
// 设置音频文件路径为当前目录下的Output.wav
m_audioFilePath = QDir::currentPath() + "/Output.wav";
// 配置播放器初始音量
m_player->setVolume(m_volume);
// 连接信号槽 - 使用旧式语法兼容Qt5.12
connect(m_player, SIGNAL(stateChanged(QMediaPlayer::State)),
this, SLOT(handlePlayerStateChanged(QMediaPlayer::State)));
connect(m_player, SIGNAL(error(QMediaPlayer::Error)),
this, SLOT(handlePlayerError(QMediaPlayer::Error)));
// 使用新式语法连接定时器超时信号
connect(m_progressTimer, &QTimer::timeout, this, &AudioPlayer::updatePlaybackProgress);
// 初始化状态文本
m_statusText = "就绪";
m_timeDisplay = "00:00 / 00:00";
// 检查音频文件可用性
if (checkAudioFile()) {
logMessage("音频文件检查正常,准备就绪");
} else {
m_statusText = "文件不可用";
emit statusTextChanged(); // 发射信号通知QML更新
}
}
属性读取函数
QString AudioPlayer::filePath() const { return m_audioFilePath; }
QString AudioPlayer::statusText() const { return m_statusText; }
QString AudioPlayer::timeDisplay() const { return m_timeDisplay; }
int AudioPlayer::progress() const { return m_progress; }
int AudioPlayer::volume() const { return m_volume; }
bool AudioPlayer::isPlaying() const { return m_isPlaying; }
核心控制函数
void AudioPlayer::playPause()
{
if (m_player->state() == QMediaPlayer::PlayingState) {
// 如果正在播放,则暂停
logMessage("用户暂停播放");
m_player->pause();
} else {
// 否则开始播放
if (!checkAudioFile()) {
logMessage("播放失败:音频文件不可用");
return;
}
logMessage("开始播放音频文件...");
// 设置媒体文件(使用本地文件URL)
m_player->setMedia(QUrl::fromLocalFile(m_audioFilePath));
// 检查媒体状态
if (m_player->mediaStatus() == QMediaPlayer::InvalidMedia) {
logMessage("错误:无法加载媒体文件");
m_statusText = "错误:无效的媒体文件";
emit statusTextChanged();
return;
}
m_player->play(); // 开始播放
// 启动进度更新定时器,每100ms更新一次
m_progressTimer->start(100);
logMessage("播放器启动完成");
}
}
void AudioPlayer::stop()
{
if (m_player->state() != QMediaPlayer::StoppedState) {
logMessage("用户停止播放");
m_player->stop();
m_progressTimer->stop(); // 停止进度更新
}
}
void AudioPlayer::setVolume(int volume)
{
if (m_volume != volume) {
m_volume = volume;
m_player->setVolume(volume); // 设置播放器音量
logMessage(QString("音量设置: %1").arg(volume));
emit volumeChanged(); // 通知QML音量已改变
}
}
状态处理槽函数
void AudioPlayer::handlePlayerStateChanged(QMediaPlayer::State state)
{
switch (state) {
case QMediaPlayer::StoppedState:
logMessage("播放停止");
m_statusText = "播放完成";
m_isPlaying = false;
m_progressTimer->stop(); // 停止定时器
m_progress = 100; // 设置进度为100%
emit progressChanged(); // 通知进度更新
break;
case QMediaPlayer::PlayingState:
logMessage("播放进行中...");
m_statusText = "正在播放";
m_isPlaying = true;
logMessage(QString("媒体时长: %1 毫秒").arg(m_player->duration()));
break;
case QMediaPlayer::PausedState:
logMessage("播放暂停");
m_statusText = "播放暂停";
m_isPlaying = false;
break;
}
// 发射状态改变信号,触发QML更新
emit statusTextChanged();
emit playingStateChanged();
}
错误处理槽函数
void AudioPlayer::handlePlayerError(QMediaPlayer::Error error)
{
QString errorMsg;
// 根据错误类型设置相应的错误消息
switch (error) {
case QMediaPlayer::NoError:
return; // 没有错误,直接返回
case QMediaPlayer::ResourceError:
errorMsg = "资源错误:无法访问媒体文件";
break;
case QMediaPlayer::FormatError:
errorMsg = "格式错误:不支持的媒体格式";
break;
case QMediaPlayer::NetworkError:
errorMsg = "网络错误";
break;
case QMediaPlayer::AccessDeniedError:
errorMsg = "访问被拒绝:没有足够的权限";
break;
default:
errorMsg = "未知错误";
break;
}
logMessage(errorMsg);
m_statusText = "播放错误";
m_isPlaying = false;
// 通知QML状态更新
emit statusTextChanged();
emit playingStateChanged();
}
进度更新函数
void AudioPlayer::updatePlaybackProgress()
{
if (m_player->duration() > 0) {
qint64 position = m_player->position(); // 当前播放位置(毫秒)
qint64 duration = m_player->duration(); // 总时长(毫秒)
int seconds = position / 1000; // 转换为秒
int totalSeconds = duration / 1000;
// 格式化时间显示:分:秒 / 分:秒
m_timeDisplay = QString("%1:%2 / %3:%4")
.arg(seconds / 60, 2, 10, QLatin1Char('0')) // 分钟,2位,十进制,用0填充
.arg(seconds % 60, 2, 10, QLatin1Char('0')) // 秒
.arg(totalSeconds / 60, 2, 10, QLatin1Char('0'))
.arg(totalSeconds % 60, 2, 10, QLatin1Char('0'));
// 计算播放进度百分比
int newProgress = (duration > 0) ? static_cast<int>(position * 100 / duration) : 0;
if (m_progress != newProgress) {
m_progress = newProgress;
emit progressChanged(); // 进度改变时发射信号
}
emit timeDisplayChanged(); // 时间显示更新
// 记录播放进度(每秒记录一次,避免过于频繁)
static int lastSecond = -1; // 静态变量保存上次记录的秒数
if (seconds != lastSecond) {
lastSecond = seconds;
logMessage(QString("播放进度: %1% (%2/%3 秒)")
.arg(m_progress)
.arg(seconds)
.arg(totalSeconds));
}
}
}
文件检查和日志函数
bool AudioPlayer::checkAudioFile()
{
QFileInfo fileInfo(m_audioFilePath);
if (!fileInfo.exists()) {
logMessage("错误:音频文件不存在 - " + m_audioFilePath);
m_statusText = "错误:文件不存在";
return false;
}
if (fileInfo.size() == 0) {
logMessage("警告:音频文件为空");
m_statusText = "警告:文件为空";
return false;
}
logMessage(QString("找到音频文件:%1 (%2 字节)")
.arg(m_audioFilePath)
.arg(fileInfo.size()));
return true;
}
void AudioPlayer::logMessage(const QString &message)
{
// 添加时间戳
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
QString logEntry = QString("[%1] %2").arg(timestamp).arg(message);
// 输出到控制台
qDebug() << logEntry;
// 发射信号通知QML更新日志显示
emit logMessageReceived(logEntry);
}
- main.qml 详细分析
窗口和基础布局
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtQuick.Window 2.12
ApplicationWindow {
id: window
width: 800
height: 600
minimumWidth: 600
minimumHeight: 400
title: "16K WAV音频播放器 - Qt5.12 QML"
visible: true // 关键:确保窗口可见
// 背景渐变 - 使用Rectangle实现渐变背景
Rectangle {
anchors.fill: parent // 填充整个父窗口
gradient: Gradient {
GradientStop { position: 0.0; color: "#2c3e50" } // 顶部颜色
GradientStop { position: 1.0; color: "#34495e" } // 底部颜色
}
}
// 主布局 - 使用ColumnLayout实现垂直排列
ColumnLayout {
anchors.fill: parent // 填充父窗口
anchors.margins: 20 // 外边距
spacing: 15 // 子项间距
标题区域
// 标题
Label {
text: "16K WAV音频播放器"
Layout.alignment: Qt.AlignHCenter // 水平居中
color: "white"
font.pixelSize: 24
font.bold: true
}
文件信息卡片
// 文件信息卡片
Rectangle {
Layout.fillWidth: true // 填充布局宽度
height: 80
color: "#34495e" // 卡片背景色
radius: 8 // 圆角半径
border.color: "#3498db" // 边框颜色
border.width: 1
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
Label {
text: "文件路径:"
color: "#bdc3c7" // 浅灰色
font.pixelSize: 12
}
// 文件路径显示 - 绑定到C++对象的filePath属性
Label {
text: audioPlayer.filePath
Layout.fillWidth: true
color: "white"
font.pixelSize: 14
elide: Text.ElideMiddle // 文本过长时中间显示省略号
}
// 状态显示 - 根据播放状态改变颜色
Label {
text: "状态: " + audioPlayer.statusText
color: audioPlayer.isPlaying ? "#2ecc71" : "#e74c3c" // 播放时绿色,停止时红色
font.pixelSize: 14
font.bold: true
}
}
}
播放控制区域
// 播放控制区域
Rectangle {
Layout.fillWidth: true
height: 120
color: "transparent" // 透明背景
ColumnLayout {
anchors.fill: parent
spacing: 10
// 时间显示 - 绑定到C++对象的timeDisplay属性
Label {
text: audioPlayer.timeDisplay
Layout.alignment: Qt.AlignHCenter
color: "white"
font.pixelSize: 18
font.bold: true
}
// 进度条 - 绑定到C++对象的progress属性
ProgressBar {
id: progressBar
Layout.fillWidth: true
value: audioPlayer.progress / 100 // 转换为0-1范围
background: Rectangle {
implicitHeight: 6
color: "#34495e" // 背景轨道颜色
radius: 3
}
contentItem: Item {
implicitHeight: 6
Rectangle {
width: progressBar.visualPosition * parent.width // 根据进度计算宽度
height: parent.height
radius: 3
gradient: Gradient { // 进度条渐变效果
GradientStop { position: 0.0; color: "#3498db" }
GradientStop { position: 1.0; color: "#2980b9" }
}
}
}
}
控制按钮
// 控制按钮
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 20
// 停止按钮
Button {
text: "停止"
onClicked: audioPlayer.stop() // 调用C++对象的stop方法
background: Rectangle {
color: "#e74c3c" // 红色
radius: 5
}
contentItem: Text {
text: parent.text
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
// 播放/暂停按钮 - 文本根据状态动态变化
Button {
text: audioPlayer.isPlaying ? "暂停" : "播放"
onClicked: audioPlayer.playPause() // 调用C++对象的playPause方法
background: Rectangle {
color: "#2ecc71" // 绿色
radius: 5
}
contentItem: Text {
text: parent.text
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.bold: true
}
}
}
音量控制
// 音量控制
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 10
Label {
text: "音量:"
color: "white"
font.pixelSize: 14
}
// 音量滑块
Slider {
id: volumeSlider
from: 0
to: 100
value: audioPlayer.volume // 绑定到C++对象的volume属性
onMoved: audioPlayer.setVolume(value) // 拖动时设置音量
background: Rectangle {
implicitWidth: 200
implicitHeight: 4
color: "#34495e"
radius: 2
}
handle: Rectangle { // 滑块手柄
x: volumeSlider.leftPadding + volumeSlider.visualPosition * (volumeSlider.availableWidth - width)
y: volumeSlider.topPadding + volumeSlider.availableHeight / 2 - height / 2
implicitWidth: 20
implicitHeight: 20
radius: 10 // 圆形手柄
color: volumeSlider.pressed ? "#3498db" : "white" // 按下时变色
border.color: "#3498db"
}
}
// 音量百分比显示
Label {
text: volumeSlider.value + "%"
color: "white"
font.pixelSize: 14
Layout.minimumWidth: 40
}
}
}
}
日志区域
// 日志区域
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true // 填充剩余高度
color: "#2c3e50"
radius: 8
border.color: "#34495e"
border.width: 2
ColumnLayout {
anchors.fill: parent
spacing: 5
Label {
text: "播放日志:"
color: "#bdc3c7"
font.pixelSize: 14
font.bold: true
Layout.leftMargin: 10
Layout.topMargin: 5
}
// 滚动视图,包含日志文本区域
ScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true // 裁剪超出部分
TextArea {
id: logTextArea
readOnly: true // 只读
color: "#ecf0f1"
font.pixelSize: 12
font.family: "Consolas, Monaco, monospace" // 等宽字体
background: Rectangle {
color: "transparent"
}
}
}
}
}
}
信号连接和初始化
// 连接C++对象的日志信号到QML
Connections {
target: audioPlayer
onLogMessageReceived: {
logTextArea.append(message) // 收到日志时添加到文本区域
}
}
// 组件完成时的初始化
Component.onCompleted: {
console.log("QML窗口初始化完成")
logTextArea.append("音频播放器初始化完成")
logTextArea.append("等待用户操作...")
}
}
二、所有源码
audioplayer.h文件源码
#ifndef AUDIOPLAYER_H
#define AUDIOPLAYER_H
#include <QObject>
#include <QMediaPlayer>
#include <QTimer>
#include <QFileInfo>
#include <QDir>
#include <QDateTime>
class AudioPlayer : public QObject
{
Q_OBJECT
Q_PROPERTY(QString filePath READ filePath NOTIFY filePathChanged)
Q_PROPERTY(QString statusText READ statusText NOTIFY statusTextChanged)
Q_PROPERTY(QString timeDisplay READ timeDisplay NOTIFY timeDisplayChanged)
Q_PROPERTY(int progress READ progress NOTIFY progressChanged)
Q_PROPERTY(int volume READ volume WRITE setVolume NOTIFY volumeChanged)
Q_PROPERTY(bool isPlaying READ isPlaying NOTIFY playingStateChanged)
public:
explicit AudioPlayer(QObject *parent = nullptr);
QString filePath() const;
QString statusText() const;
QString timeDisplay() const;
int progress() const;
int volume() const;
bool isPlaying() const;
Q_INVOKABLE void playPause();
Q_INVOKABLE void stop();
Q_INVOKABLE void setVolume(int volume);
signals:
void filePathChanged();
void statusTextChanged();
void timeDisplayChanged();
void progressChanged();
void volumeChanged();
void playingStateChanged();
void logMessageReceived(const QString &message);
private slots:
void handlePlayerStateChanged(QMediaPlayer::State state);
void handlePlayerError(QMediaPlayer::Error error);
void updatePlaybackProgress();
private:
bool checkAudioFile();
void logMessage(const QString &message);
QMediaPlayer *m_player;
QTimer *m_progressTimer;
QString m_audioFilePath;
QString m_statusText;
QString m_timeDisplay;
int m_progress;
int m_volume;
bool m_isPlaying;
};
#endif // AUDIOPLAYER_H
audioplayer.cpp文件源码
#include "audioplayer.h"
#include <QDebug>
AudioPlayer::AudioPlayer(QObject *parent)
: QObject(parent)
, m_player(new QMediaPlayer(this))
, m_progressTimer(new QTimer(this))
, m_progress(0)
, m_volume(80)
, m_isPlaying(false)
{
// 设置音频文件路径
m_audioFilePath = QDir::currentPath() + "/Output.wav";
// 配置播放器
m_player->setVolume(m_volume);
// 连接信号槽
connect(m_player, SIGNAL(stateChanged(QMediaPlayer::State)),
this, SLOT(handlePlayerStateChanged(QMediaPlayer::State)));
connect(m_player, SIGNAL(error(QMediaPlayer::Error)),
this, SLOT(handlePlayerError(QMediaPlayer::Error)));
connect(m_progressTimer, &QTimer::timeout, this, &AudioPlayer::updatePlaybackProgress);
// 初始化状态
m_statusText = "就绪";
m_timeDisplay = "00:00 / 00:00";
// 检查音频文件
if (checkAudioFile()) {
logMessage("音频文件检查正常,准备就绪");
} else {
m_statusText = "文件不可用";
emit statusTextChanged();
}
}
QString AudioPlayer::filePath() const
{
return m_audioFilePath;
}
QString AudioPlayer::statusText() const
{
return m_statusText;
}
QString AudioPlayer::timeDisplay() const
{
return m_timeDisplay;
}
int AudioPlayer::progress() const
{
return m_progress;
}
int AudioPlayer::volume() const
{
return m_volume;
}
bool AudioPlayer::isPlaying() const
{
return m_isPlaying;
}
void AudioPlayer::playPause()
{
if (m_player->state() == QMediaPlayer::PlayingState) {
// 暂停播放
logMessage("用户暂停播放");
m_player->pause();
} else {
// 开始播放
if (!checkAudioFile()) {
logMessage("播放失败:音频文件不可用");
return;
}
logMessage("开始播放音频文件...");
m_player->setMedia(QUrl::fromLocalFile(m_audioFilePath));
if (m_player->mediaStatus() == QMediaPlayer::InvalidMedia) {
logMessage("错误:无法加载媒体文件");
m_statusText = "错误:无效的媒体文件";
emit statusTextChanged();
return;
}
m_player->play();
// 启动进度更新计时器
m_progressTimer->start(100);
logMessage("播放器启动完成");
}
}
void AudioPlayer::stop()
{
if (m_player->state() != QMediaPlayer::StoppedState) {
logMessage("用户停止播放");
m_player->stop();
m_progressTimer->stop();
}
}
void AudioPlayer::setVolume(int volume)
{
if (m_volume != volume) {
m_volume = volume;
m_player->setVolume(volume);
logMessage(QString("音量设置: %1").arg(volume));
emit volumeChanged();
}
}
void AudioPlayer::handlePlayerStateChanged(QMediaPlayer::State state)
{
switch (state) {
case QMediaPlayer::StoppedState:
logMessage("播放停止");
m_statusText = "播放完成";
m_isPlaying = false;
m_progressTimer->stop();
m_progress = 100;
emit progressChanged();
break;
case QMediaPlayer::PlayingState:
logMessage("播放进行中...");
m_statusText = "正在播放";
m_isPlaying = true;
logMessage(QString("媒体时长: %1 毫秒").arg(m_player->duration()));
break;
case QMediaPlayer::PausedState:
logMessage("播放暂停");
m_statusText = "播放暂停";
m_isPlaying = false;
break;
}
emit statusTextChanged();
emit playingStateChanged();
}
void AudioPlayer::handlePlayerError(QMediaPlayer::Error error)
{
QString errorMsg;
switch (error) {
case QMediaPlayer::NoError:
return;
case QMediaPlayer::ResourceError:
errorMsg = "资源错误:无法访问媒体文件";
break;
case QMediaPlayer::FormatError:
errorMsg = "格式错误:不支持的媒体格式";
break;
case QMediaPlayer::NetworkError:
errorMsg = "网络错误";
break;
case QMediaPlayer::AccessDeniedError:
errorMsg = "访问被拒绝:没有足够的权限";
break;
default:
errorMsg = "未知错误";
break;
}
logMessage(errorMsg);
m_statusText = "播放错误";
m_isPlaying = false;
emit statusTextChanged();
emit playingStateChanged();
}
void AudioPlayer::updatePlaybackProgress()
{
if (m_player->duration() > 0) {
qint64 position = m_player->position();
qint64 duration = m_player->duration();
int seconds = position / 1000;
int totalSeconds = duration / 1000;
// 更新时间显示
m_timeDisplay = QString("%1:%2 / %3:%4")
.arg(seconds / 60, 2, 10, QLatin1Char('0'))
.arg(seconds % 60, 2, 10, QLatin1Char('0'))
.arg(totalSeconds / 60, 2, 10, QLatin1Char('0'))
.arg(totalSeconds % 60, 2, 10, QLatin1Char('0'));
// 更新进度
int newProgress = (duration > 0) ? static_cast<int>(position * 100 / duration) : 0;
if (m_progress != newProgress) {
m_progress = newProgress;
emit progressChanged();
}
emit timeDisplayChanged();
// 记录播放进度(每秒一次)
static int lastSecond = -1;
if (seconds != lastSecond) {
lastSecond = seconds;
logMessage(QString("播放进度: %1% (%2/%3 秒)")
.arg(m_progress)
.arg(seconds)
.arg(totalSeconds));
}
}
}
bool AudioPlayer::checkAudioFile()
{
QFileInfo fileInfo(m_audioFilePath);
if (!fileInfo.exists()) {
logMessage("错误:音频文件不存在 - " + m_audioFilePath);
m_statusText = "错误:文件不存在";
return false;
}
if (fileInfo.size() == 0) {
logMessage("警告:音频文件为空");
m_statusText = "警告:文件为空";
return false;
}
logMessage(QString("找到音频文件:%1 (%2 字节)")
.arg(m_audioFilePath)
.arg(fileInfo.size()));
return true;
}
void AudioPlayer::logMessage(const QString &message)
{
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
QString logEntry = QString("[%1] %2").arg(timestamp).arg(message);
qDebug() << logEntry;
emit logMessageReceived(logEntry);
}
main.qml文件源码
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtQuick.Window 2.12
ApplicationWindow {
id: window
width: 800
height: 600
minimumWidth: 600
minimumHeight: 400
title: "16K WAV音频播放器 - Qt5.12 QML"
visible: true // 确保窗口可见
// 背景渐变
Rectangle {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.0; color: "#2c3e50" }
GradientStop { position: 1.0; color: "#34495e" }
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
// 标题
Label {
text: "16K WAV音频播放器"
Layout.alignment: Qt.AlignHCenter
color: "white"
font.pixelSize: 24
font.bold: true
}
// 文件信息卡片
Rectangle {
Layout.fillWidth: true
height: 80
color: "#34495e"
radius: 8
border.color: "#3498db"
border.width: 1
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
Label {
text: "文件路径:"
color: "#bdc3c7"
font.pixelSize: 12
}
Label {
text: audioPlayer.filePath
Layout.fillWidth: true
color: "white"
font.pixelSize: 14
elide: Text.ElideMiddle
}
Label {
text: "状态: " + audioPlayer.statusText
color: audioPlayer.isPlaying ? "#2ecc71" : "#e74c3c"
font.pixelSize: 14
font.bold: true
}
}
}
// 播放控制区域
Rectangle {
Layout.fillWidth: true
height: 120
color: "transparent"
ColumnLayout {
anchors.fill: parent
spacing: 10
// 时间显示
Label {
text: audioPlayer.timeDisplay
Layout.alignment: Qt.AlignHCenter
color: "white"
font.pixelSize: 18
font.bold: true
}
// 进度条
ProgressBar {
id: progressBar
Layout.fillWidth: true
value: audioPlayer.progress / 100
background: Rectangle {
implicitHeight: 6
color: "#34495e"
radius: 3
}
contentItem: Item {
implicitHeight: 6
Rectangle {
width: progressBar.visualPosition * parent.width
height: parent.height
radius: 3
gradient: Gradient {
GradientStop { position: 0.0; color: "#3498db" }
GradientStop { position: 1.0; color: "#2980b9" }
}
}
}
}
// 控制按钮
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 20
Button {
text: "停止"
onClicked: audioPlayer.stop()
background: Rectangle {
color: "#e74c3c"
radius: 5
}
contentItem: Text {
text: parent.text
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
Button {
text: audioPlayer.isPlaying ? "暂停" : "播放"
onClicked: audioPlayer.playPause()
background: Rectangle {
color: "#2ecc71"
radius: 5
}
contentItem: Text {
text: parent.text
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.bold: true
}
}
}
// 音量控制
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 10
Label {
text: "音量:"
color: "white"
font.pixelSize: 14
}
Slider {
id: volumeSlider
from: 0
to: 100
value: audioPlayer.volume
onMoved: audioPlayer.setVolume(value)
background: Rectangle {
implicitWidth: 200
implicitHeight: 4
color: "#34495e"
radius: 2
}
handle: Rectangle {
x: volumeSlider.leftPadding + volumeSlider.visualPosition * (volumeSlider.availableWidth - width)
y: volumeSlider.topPadding + volumeSlider.availableHeight / 2 - height / 2
implicitWidth: 20
implicitHeight: 20
radius: 10
color: volumeSlider.pressed ? "#3498db" : "white"
border.color: "#3498db"
}
}
Label {
text: volumeSlider.value + "%"
color: "white"
font.pixelSize: 14
Layout.minimumWidth: 40
}
}
}
}
// 日志区域
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: "#2c3e50"
radius: 8
border.color: "#34495e"
border.width: 2
ColumnLayout {
anchors.fill: parent
spacing: 5
Label {
text: "播放日志:"
color: "#bdc3c7"
font.pixelSize: 14
font.bold: true
Layout.leftMargin: 10
Layout.topMargin: 5
}
ScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
TextArea {
id: logTextArea
readOnly: true
color: "#ecf0f1"
font.pixelSize: 12
font.family: "Consolas, Monaco, monospace"
background: Rectangle {
color: "transparent"
}
}
}
}
}
}
// 连接日志信号
Connections {
target: audioPlayer
onLogMessageReceived: {
logTextArea.append(message)
}
}
Component.onCompleted: {
console.log("QML窗口初始化完成")
logTextArea.append("音频播放器初始化完成")
logTextArea.append("等待用户操作...")
}
}
main.cpp文件源码
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QIcon>
#include <QDebug>
#include "audioplayer.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
app.setApplicationName("WAV音频播放器");
app.setApplicationVersion("1.0");
qDebug() << "应用程序启动...";
// 注册音频播放器到QML
qmlRegisterType<AudioPlayer>("AudioPlayer", 1, 0, "AudioPlayer");
QQmlApplicationEngine engine;
// 创建音频播放器实例并设置为上下文属性
AudioPlayer *audioPlayer = new AudioPlayer(&app);
engine.rootContext()->setContextProperty("audioPlayer", audioPlayer);
// 加载QML文件
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl) {
qCritical() << "QML加载失败:" << url;
QCoreApplication::exit(-1);
} else {
qDebug() << "QML加载成功,窗口已创建";
}
}, Qt::QueuedConnection);
// 处理引擎加载错误
QObject::connect(&engine, &QQmlApplicationEngine::warnings,
[](const QList<QQmlError> &warnings) {
for (const QQmlError &error : warnings) {
qWarning() << "QML警告:" << error.toString();
}
});
engine.load(url);
if (engine.rootObjects().isEmpty()) {
qCritical() << "没有创建任何QML根对象";
return -1;
}
qDebug() << "应用程序进入事件循环...";
return app.exec();
}
三、效果演示
点击启动,播放进度和操作日志开始更新。
