开源 C++ QT QML 开发(十八)多媒体--音频播放

文章的目的为了记录使用QT QML开发学习的经历。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。

相关链接:

开源 C++ QT QML 开发(一)基本介绍

开源 C++ QT QML 开发(二)工程结构

开源 C++ QT QML 开发(三)常用控件

开源 C++ QT QML 开发(四)复杂控件--Listview

开源 C++ QT QML 开发(五)复杂控件--Gridview

开源 C++ QT QML 开发(六)自定义控件--波形图

开源 C++ QT QML 开发(七)自定义控件--仪表盘

开源 C++ QT QML 开发(八)自定义控件--圆环

开源 C++ QT QML 开发(九)文件--文本和二进制

开源 C++ QT QML 开发(十)通讯--串口

开源 C++ QT QML 开发(十一)通讯--TCP服务器端

开源 C++ QT QML 开发(十二)通讯--TCP客户端

开源 C++ QT QML 开发(十三)多线程

开源 C++ QT QML 开发(十四)进程用途

开源 C++ QT QML 开发(十五)通讯--http下载

开源 C++ QT QML 开发(十六)进程--共享内存

开源 C++ QT QML 开发(十七)进程--LocalSocket

开源 C++ QT QML 开发(十八)多媒体--音频播放

推荐链接:

开源 C# 快速开发(一)基础知识

开源 C# 快速开发(二)基础控件

开源 C# 快速开发(三)复杂控件

开源 C# 快速开发(四)自定义控件--波形图

开源 C# 快速开发(五)自定义控件--仪表盘

开源 C# 快速开发(六)自定义控件--圆环

开源 C# 快速开发(七)通讯--串口

开源 C# 快速开发(八)通讯--Tcp服务器端

开源 C# 快速开发(九)通讯--Tcp客户端

开源 C# 快速开发(十)通讯--http客户端

开源 C# 快速开发(十一)线程

开源 C# 快速开发(十二)进程监控

开源 C# 快速开发(十三)进程--管道通讯

开源 C# 快速开发(十四)进程--内存映射

开源 C# 快速开发(十五)进程--windows消息

开源 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事件循环

  1. 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可调用的接口

  1. 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);
}
  1. 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();
}

三、效果演示

点击启动,播放进度和操作日志开始更新。

相关推荐
XXYBMOOO3 小时前
如何自定义 Qt 日志处理并记录日志到文件
开发语言·数据库·qt
知南x3 小时前
【QT界面设计学习篇】qt快速开发技巧
开发语言·qt
宏笋3 小时前
Qt 绘制彩色文本,包括字符颜色分割、动画效果和渐变等多种花式效果
qt
BS_Li3 小时前
C++11(可变参数模板、新的类功能和STL中的一些变化)
c++·c++11·可变参数模板
奶茶树3 小时前
【C++】12.多态(超详解)
开发语言·c++
草莓熊Lotso3 小时前
《算法闯关指南:优选算法--二分查找》--17.二分查找(附二分查找算法简介),18. 在排序数组中查找元素的第一个和最后一个位置
开发语言·c++·算法
努力努力再努力wz3 小时前
【C++进阶系列】:万字详解特殊类以及设计模式
java·linux·运维·开发语言·数据结构·c++·设计模式
磨十三3 小时前
【C++进阶】从零实现一个支持动态扩容的 Vector 容器(含移动语义与内存管理详解)
开发语言·c++
bkspiderx3 小时前
C++设计模式之行为型模式:策略模式(Strategy)
c++·设计模式·策略模式