文章的目的為了記錄使用QT QML開發學習的經歷。開發流程和要點有些記憶模糊,趕緊記錄,防止忘記。

本章節主要內容是:使用qml編寫了媒體播放器,使用了K-Lite Codec Pack Mega作為解碼包,實現mp4的播放。

1.代碼分析

2.所有源碼

3.效果演示

一、代碼分析1. 主窗口和屬性定義

ApplicationWindow {
    id: mainWindow
    width: 1024
    height: 720
    visible: true
    title: "MP4播放器 - 完全兼容版"
    minimumWidth: 800
    minimumHeight: 600
    property string currentVideo: ""  // 當前視頻文件路徑
    property bool hasVideo: videoPlayer.hasVideo  // 綁定到Video組件的hasVideo屬性
    property bool isSeekable: videoPlayer.seekable  // 是否支持跳轉
    property string currentStatus: "等待選擇文件"  // 當前狀態顯示文本
}

2. 核心視頻播放組件
Video組件及其信號處理

Video {
    id: videoPlayer
    anchors.fill: parent
    anchors.margins: 2
    source: currentVideo  // 綁定到currentVideo屬性
    fillMode: VideoOutput.PreserveAspectFit  // 保持寬高比
    volume: volumeSlider.value  // 綁定音量滑塊
    autoPlay: true  // 自動播放
    // 狀態變化信號處理
    onStatusChanged: {
        console.log("視頻狀態變化:", status)
        updateStatusDisplay()  // 更新狀態顯示
    }
    // 播放狀態變化信號處理
    onPlaybackStateChanged: {
        console.log("播放狀態變化:", playbackState)
        updatePlayButton()  // 更新播放按鈕文本
    }
    // 視頻時長變化信號處理
    onDurationChanged: {
        console.log("視頻時長:", duration, "ms")
        progressSlider.to = Math.max(1, duration)  // 設置進度條最大值
    }
    // 播放位置變化信號處理
    onPositionChanged: {
        if (!progressSlider.pressed) {  // 避免拖動時衝突
            progressSlider.value = position  // 更新進度條位置
        }
    }
}

3. 主要控制函數
播放/暫停切換函數

function togglePlayPause() {
    if (!hasVideo || currentVideo === "") return  // 安全檢查
    if (videoPlayer.playbackState === MediaPlayer.PlayingState) {
        videoPlayer.pause()  // 如果正在播放,則暫停
    } else {
        videoPlayer.play()  // 否則開始播放
    }
}

狀態顯示更新函數

function updateStatusDisplay() {
    var status = videoPlayer.status  // 獲取當前視頻狀態
    switch(status) {
        case MediaPlayer.NoMedia:
            currentStatus = "無媒體文件"
            break
        case MediaPlayer.Loading:
            currentStatus = "加載中..."
            break
        case MediaPlayer.Loaded:
            currentStatus = "已加載"
            break
        case MediaPlayer.Stalling:
            currentStatus = "緩衝中..."
            break
        case MediaPlayer.Buffering:
            currentStatus = "緩衝中"
            break
        case MediaPlayer.Buffered:
            currentStatus = "就緒"
            break
        case MediaPlayer.EndOfMedia:
            currentStatus = "播放結束"
            break
        case MediaPlayer.InvalidMedia:
            currentStatus = "格式不支持"
            break
        case MediaPlayer.UnknownStatus:
            currentStatus = "未知狀態"
            break
        default:
            currentStatus = "就緒"
    }
    console.log("狀態更新:", currentStatus)
}

播放按鈕更新函數

function updatePlayButton() {
    switch(videoPlayer.playbackState) {
        case MediaPlayer.PlayingState:
            playButton.text = "⏸️ 暫停"  // 播放中顯示暫停
            break
        case MediaPlayer.PausedState:
            playButton.text = "▶️ 繼續"  // 暫停中顯示繼續
            break
        default:
            playButton.text = "▶️ 播放"  // 默認顯示播放
    }
}

4. UI狀態顯示函數
狀態圖標獲取函數

function getStatusIcon() {
    if (currentVideo === "") return ""  // 無文件
    if (videoPlayer.status === MediaPlayer.InvalidMedia) return "❌"  // 格式錯誤
    if (videoPlayer.status === MediaPlayer.Loading) return "⏳"  // 加載中
    if (!hasVideo) return ""  // 無視頻流
    return "✅"  // 正常狀態
}

狀態消息獲取函數

function getStatusMessage() {
    if (currentVideo === "") return "請選擇視頻文件開始播放"  // 初始提示
    if (videoPlayer.status === MediaPlayer.InvalidMedia) return "無法播放此文件"  // 錯誤提示
    if (videoPlayer.status === MediaPlayer.Loading) return "正在加載..."  // 加載提示
    if (!hasVideo) return "準備播放"  // 準備狀態
    return "正在播放: " + getFileName(currentVideo)  // 播放狀態顯示文件名
}

錯誤詳情獲取函數

function getErrorDetails() {
    if (videoPlayer.status === MediaPlayer.InvalidMedia) {
        return "此文件需要H.264解碼器\n請安裝K-Lite Codec Pack\n下載: https://codecguide.com/download_kl.htm"
    }
    return ""  // 無錯誤時返回空字符串
}

5. 工具函數
狀態顏色獲取函數

function getStatusColor() {
    if (videoPlayer.status === MediaPlayer.InvalidMedia) return "#e74c3c"  // 紅色-錯誤
    if (videoPlayer.status === MediaPlayer.Buffered) return "#2ecc71"  // 綠色-就緒
    if (videoPlayer.status === MediaPlayer.Loading) return "#f39c12"  // 橙色-加載中
    return "#3498db"  // 藍色-默認
}

文件名提取函數

function getFileName(filePath) {
    var path = filePath.toString()  // 轉換為字符串
    var fileName = path.split('/').pop() || path.split('\\').pop()  // 從路徑中提取文件名
    return fileName || "未知文件"  // 返回文件名或默認值
}

時間格式化函數

function formatTime(milliseconds) {
    if (!milliseconds || isNaN(milliseconds)) return "00:00"  // 無效時間處理
    var totalSeconds = Math.floor(milliseconds / 1000)  // 轉換為秒
    var hours = Math.floor(totalSeconds / 3600)  // 計算小時
    var minutes = Math.floor((totalSeconds % 3600) / 60)  // 計算分鐘
    var seconds = totalSeconds % 60  // 計算秒數
    if (hours > 0) {
        // 有時長格式:HH:MM:SS
        return hours.toString().padStart(2, '0') + ":" +
               minutes.toString().padStart(2, '0') + ":" +
               seconds.toString().padStart(2, '0')
    } else {
        // 無時長格式:MM:SS
        return minutes.toString().padStart(2, '0') + ":" +
               seconds.toString().padStart(2, '0')
    }
}

6. 進度條控制函數
進度條拖動處理

Slider {
    id: progressSlider
    // ... 其他屬性
    onMoved: {
        if (isSeekable) {  // 檢查是否支持跳轉
            videoPlayer.seek(value)  // 跳轉到指定位置
        }
    }
}

7. C++主程序函數
環境設置函數

int main(int argc, char *argv[])
{
    // 關鍵環境變量設置
    qputenv("QT_MEDIA_BACKEND", "windows");  // 強制使用Windows媒體後端
    qputenv("QT_LOGGING_RULES", "qt.multimedia.*=true");  // 啓用多媒體調試日誌
    qputenv("MF_DEBUG", "1");  // 啓用Media Foundation調試
    // 應用程序屬性設置
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);  // 高DPI支持
    QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);  // 軟件渲染
    QGuiApplication app(argc, argv);
    // 應用程序信息
    app.setApplicationName("MP4播放器");
    app.setApplicationVersion("1.0");
    app.setOrganizationName("MyCompany");
}

系統診斷函數

// 詳細的多媒體支持信息
qDebug() << "=== 多媒體系統診斷信息 ===";
qDebug() << "應用程序目錄:" << QDir::currentPath();
qDebug() << "臨時目錄:" << QStandardPaths::writableLocation(QStandardPaths::TempLocation);
qDebug() << "支持的MIME類型數量:" << QMediaPlayer::supportedMimeTypes().count();
// 輸出支持的格式
auto mimeTypes = QMediaPlayer::supportedMimeTypes();
for (int i = 0; i < qMin(10, mimeTypes.count()); ++i) {
    qDebug() << "支持格式:" << mimeTypes[i];
}

8. 文件對話框處理

FileDialog {
    id: fileDialog
    title: "選擇視頻文件"
    nameFilters: [ /* 文件過濾器列表 */ ]
    onAccepted: {
        currentVideo = fileDialog.fileUrl  // 獲取選擇的文件URL
        console.log("加載文件:", currentVideo)  // 調試日誌
    }
}

二、所有源碼

1.安裝正確的解碼器包
推薦方案:K-Lite Codec Pack Mega
下載地址:https://codecguide.com/download_kl.htm

選擇版本:下載 Mega 版本(包含所有解碼器)

安裝步驟:運行安裝程序

2.pro文件

QT += quick quickcontrols2 multimedia multimediawidgets

3.main.qml文件源碼

import QtQuick 2.14
import QtQuick.Window 2.14
import QtMultimedia 5.14
import QtQuick.Controls 2.14
import QtQuick.Dialogs 1.3
import QtQuick.Layouts 1.14
ApplicationWindow {
    id: mainWindow
    width: 1024
    height: 720
    visible: true
    title: "MP4播放器 - 完全兼容版"
    minimumWidth: 800
    minimumHeight: 600
    property string currentVideo: ""
    property bool hasVideo: videoPlayer.hasVideo
    property bool isSeekable: videoPlayer.seekable
    property string currentStatus: "等待選擇文件"
    // 背景漸變
    Rectangle {
        anchors.fill: parent
        gradient: Gradient {
            GradientStop { position: 0.0; color: "#2c3e50" }
            GradientStop { position: 1.0; color: "#3498db" }
        }
    }
    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 10
        spacing: 10
        // 標題欄
        Rectangle {
            Layout.fillWidth: true
            height: 60
            color: "transparent"
            RowLayout {
                anchors.fill: parent
                Text {
                    text: " MP4播放器"
                    color: "white"
                    font.pixelSize: 24
                    font.bold: true
                }
                Item { Layout.fillWidth: true }
                Text {
                    text: "Powered by Qt5.14"
                    color: "lightgray"
                    font.pixelSize: 12
                }
            }
        }
        // 視頻顯示區域
        Rectangle {
            id: videoContainer
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: "black"
            radius: 8
            clip: true
            Video {
                id: videoPlayer
                anchors.fill: parent
                anchors.margins: 2
                source: currentVideo
                fillMode: VideoOutput.PreserveAspectFit
                volume: volumeSlider.value
                autoPlay: true
                // 狀態變化處理 - 使用正確的信號
                onStatusChanged: {
                    console.log("視頻狀態變化:", status)
                    updateStatusDisplay()
                }
                onPlaybackStateChanged: {
                    console.log("播放狀態變化:", playbackState)
                    updatePlayButton()
                }
                onDurationChanged: {
                    console.log("視頻時長:", duration, "ms")
                    progressSlider.to = Math.max(1, duration)
                }
                onPositionChanged: {
                    if (!progressSlider.pressed) {
                        progressSlider.value = position
                    }
                }
            }
            // 加載指示器
            BusyIndicator {
                id: loadingIndicator
                anchors.centerIn: parent
                running: videoPlayer.status === MediaPlayer.Loading ||
                         videoPlayer.status === MediaPlayer.Stalling ||
                         videoPlayer.status === MediaPlayer.Buffering
                visible: running
            }
            // 居中狀態信息
            Column {
                id: centerInfo
                anchors.centerIn: parent
                spacing: 10
                visible: currentVideo === "" || videoPlayer.status === MediaPlayer.InvalidMedia || !hasVideo
                Text {
                    id: statusIcon
                    anchors.horizontalCenter: parent.horizontalCenter
                    color: "white"
                    font.pixelSize: 48
                    text: getStatusIcon()
                }
                Text {
                    id: statusMessage
                    anchors.horizontalCenter: parent.horizontalCenter
                    color: "white"
                    font.pixelSize: 18
                    text: getStatusMessage()
                    horizontalAlignment: Text.AlignHCenter
                }
                Text {
                    id: errorDetails
                    anchors.horizontalCenter: parent.horizontalCenter
                    color: "yellow"
                    font.pixelSize: 12
                    text: getErrorDetails()
                    visible: text !== ""
                    horizontalAlignment: Text.AlignHCenter
                    width: 400
                    wrapMode: Text.WordWrap
                }
            }
            // 點擊控制播放/暫停
            MouseArea {
                anchors.fill: parent
                onClicked: togglePlayPause()
                enabled: hasVideo && currentVideo !== ""
            }
        }
        // 進度控制區域
        ColumnLayout {
            Layout.fillWidth: true
            spacing: 5
            // 進度條
            Slider {
                id: progressSlider
                Layout.fillWidth: true
                from: 0
                to: 100
                value: 0
                enabled: isSeekable && hasVideo
                onMoved: {
                    if (isSeekable) {
                        videoPlayer.seek(value)
                    }
                }
                // 自定義樣式
                background: Rectangle {
                    implicitHeight: 6
                    color: "#5a5a5a"
                    radius: 3
                }
                handle: Rectangle {
                    x: progressSlider.leftPadding + progressSlider.visualPosition * (progressSlider.availableWidth - width)
                    y: progressSlider.topPadding + progressSlider.availableHeight / 2 - height / 2
                    implicitWidth: 16
                    implicitHeight: 16
                    radius: 8
                    color: progressSlider.pressed ? "#f0f0f0" : "#ffffff"
                    border.color: "#bdbebf"
                }
            }
            // 時間信息
            RowLayout {
                Layout.fillWidth: true
                Text {
                    color: "white"
                    text: formatTime(videoPlayer.position)
                    font.pixelSize: 12
                }
                Item { Layout.fillWidth: true }
                Text {
                    color: "white"
                    text: formatTime(videoPlayer.duration)
                    font.pixelSize: 12
                }
            }
        }
        // 控制按鈕區域
        Rectangle {
            Layout.fillWidth: true
            height: 70
            color: "transparent"
            RowLayout {
                anchors.fill: parent
                spacing: 10
                // 文件操作按鈕
                Button {
                    text: " 打開文件"
                    onClicked: fileDialog.open()
                    background: Rectangle {
                        color: "#27ae60"
                        radius: 5
                    }
                    contentItem: Text {
                        text: parent.text
                        color: "white"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                        font.bold: true
                    }
                }
                // 播放控制按鈕
                Button {
                    id: playButton
                    text: "▶️ 播放"
                    onClicked: togglePlayPause()
                    enabled: hasVideo && currentVideo !== ""
                    background: Rectangle {
                        color: enabled ? "#2980b9" : "#7f8c8d"
                        radius: 5
                    }
                    contentItem: Text {
                        text: parent.text
                        color: "white"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                        font.bold: true
                    }
                }
                Button {
                    text: "⏹️ 停止"
                    onClicked: {
                        videoPlayer.stop()
                        progressSlider.value = 0
                    }
                    enabled: hasVideo && currentVideo !== ""
                    background: Rectangle {
                        color: enabled ? "#e74c3c" : "#7f8c8d"
                        radius: 5
                    }
                    contentItem: Text {
                        text: parent.text
                        color: "white"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                    }
                }
                Item { Layout.fillWidth: true }
                // 音量控制
                RowLayout {
                    spacing: 5
                    Text {
                        text: ""
                        color: "white"
                        font.pixelSize: 16
                    }
                    Slider {
                        id: volumeSlider
                        from: 0
                        to: 1
                        value: 0.8
                        stepSize: 0.1
                        Layout.preferredWidth: 100
                        background: Rectangle {
                            implicitHeight: 4
                            color: "#5a5a5a"
                            radius: 2
                        }
                    }
                    Text {
                        color: "white"
                        text: Math.round(volumeSlider.value * 100) + "%"
                        font.pixelSize: 12
                        Layout.preferredWidth: 40
                    }
                }
            }
        }
        // 狀態信息欄
        Rectangle {
            Layout.fillWidth: true
            height: 30
            color: "#34495e"
            radius: 4
            RowLayout {
                anchors.fill: parent
                anchors.margins: 5
                Text {
                    color: "white"
                    text: "文件: " + (currentVideo ? getFileName(currentVideo) : "未選擇")
                    font.pixelSize: 11
                    elide: Text.ElideMiddle
                    Layout.fillWidth: true
                }
                Text {
                    color: getStatusColor()
                    text: currentStatus
                    font.pixelSize: 11
                    font.bold: true
                }
                Text {
                    color: hasVideo ? "lightgreen" : "red"
                    text: hasVideo ? " 有視頻" : "❌ 無視頻"
                    font.pixelSize: 11
                }
            }
        }
    }
    FileDialog {
        id: fileDialog
        title: "選擇視頻文件"
        nameFilters: [
            "MP4文件 (*.mp4)",
            "AVI文件 (*.avi)",
            "MKV文件 (*.mkv)",
            "MOV文件 (*.mov)",
            "WMV文件 (*.wmv)",
            "所有文件 (*.*)"
        ]
        onAccepted: {
            currentVideo = fileDialog.fileUrl
            console.log("加載文件:", currentVideo)
        }
    }
    // 功能函數
    function togglePlayPause() {
        if (!hasVideo || currentVideo === "") return
        if (videoPlayer.playbackState === MediaPlayer.PlayingState) {
            videoPlayer.pause()
        } else {
            videoPlayer.play()
        }
    }
    function updateStatusDisplay() {
        var status = videoPlayer.status
        switch(status) {
            case MediaPlayer.NoMedia:
                currentStatus = "無媒體文件"
                break
            case MediaPlayer.Loading:
                currentStatus = "加載中..."
                break
            case MediaPlayer.Loaded:
                currentStatus = "已加載"
                break
            case MediaPlayer.Stalling:
                currentStatus = "緩衝中..."
                break
            case MediaPlayer.Buffering:
                currentStatus = "緩衝中"
                break
            case MediaPlayer.Buffered:
                currentStatus = "就緒"
                break
            case MediaPlayer.EndOfMedia:
                currentStatus = "播放結束"
                break
            case MediaPlayer.InvalidMedia:
                currentStatus = "格式不支持"
                break
            case MediaPlayer.UnknownStatus:
                currentStatus = "未知狀態"
                break
            default:
                currentStatus = "就緒"
        }
        console.log("狀態更新:", currentStatus)
    }
    function updatePlayButton() {
        switch(videoPlayer.playbackState) {
            case MediaPlayer.PlayingState:
                playButton.text = "⏸️ 暫停"
                break
            case MediaPlayer.PausedState:
                playButton.text = "▶️ 繼續"
                break
            default:
                playButton.text = "▶️ 播放"
        }
    }
    function getStatusIcon() {
        if (currentVideo === "") return ""
        if (videoPlayer.status === MediaPlayer.InvalidMedia) return "❌"
        if (videoPlayer.status === MediaPlayer.Loading) return "⏳"
        if (!hasVideo) return ""
        return "✅"
    }
    function getStatusMessage() {
        if (currentVideo === "") return "請選擇視頻文件開始播放"
        if (videoPlayer.status === MediaPlayer.InvalidMedia) return "無法播放此文件"
        if (videoPlayer.status === MediaPlayer.Loading) return "正在加載..."
        if (!hasVideo) return "準備播放"
        return "正在播放: " + getFileName(currentVideo)
    }
    function getErrorDetails() {
        if (videoPlayer.status === MediaPlayer.InvalidMedia) {
            return "此文件需要H.264解碼器\n請安裝K-Lite Codec Pack\n下載: https://codecguide.com/download_kl.htm"
        }
        return ""
    }
    function getStatusColor() {
        if (videoPlayer.status === MediaPlayer.InvalidMedia) return "#e74c3c"
        if (videoPlayer.status === MediaPlayer.Buffered) return "#2ecc71"
        if (videoPlayer.status === MediaPlayer.Loading) return "#f39c12"
        return "#3498db"
    }
    function getFileName(filePath) {
        var path = filePath.toString()
        var fileName = path.split('/').pop() || path.split('\\').pop()
        return fileName || "未知文件"
    }
    function formatTime(milliseconds) {
        if (!milliseconds || isNaN(milliseconds)) return "00:00"
        var totalSeconds = Math.floor(milliseconds / 1000)
        var hours = Math.floor(totalSeconds / 3600)
        var minutes = Math.floor((totalSeconds % 3600) / 60)
        var seconds = totalSeconds % 60
        if (hours > 0) {
            return hours.toString().padStart(2, '0') + ":" +
                   minutes.toString().padStart(2, '0') + ":" +
                   seconds.toString().padStart(2, '0')
        } else {
            return minutes.toString().padStart(2, '0') + ":" +
                   seconds.toString().padStart(2, '0')
        }
    }
    Component.onCompleted: {
        console.log("播放器初始化完成")
        console.log("視頻輸出支持:", videoPlayer.availability)
        console.log("是否有視頻:", videoPlayer.hasVideo)
        console.log("是否可跳轉:", videoPlayer.seekable)
    }
}

4. main.cpp文件源碼

#include 
#include 
#include 
#include 
#include 
#include 
int main(int argc, char *argv[])
{
    // 設置環境變量 - 在創建QGuiApplication之前
    qputenv("QT_MEDIA_BACKEND", "windows");
    qputenv("QT_LOGGING_RULES", "qt.multimedia.*=true");
    qputenv("MF_DEBUG", "1");
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
    QGuiApplication app(argc, argv);
    // 設置應用程序信息
    app.setApplicationName("MP4播放器");
    app.setApplicationVersion("1.0");
    app.setOrganizationName("MyCompany");
    // 詳細的多媒體支持信息
    qDebug() << "=== 多媒體系統診斷信息 ===";
    qDebug() << "應用程序目錄:" << QDir::currentPath();
    qDebug() << "臨時目錄:" << QStandardPaths::writableLocation(QStandardPaths::TempLocation);
    qDebug() << "支持的MIME類型數量:" << QMediaPlayer::supportedMimeTypes().count();
    // 輸出前10個支持的格式
    auto mimeTypes = QMediaPlayer::supportedMimeTypes();
    for (int i = 0; i < qMin(10, mimeTypes.count()); ++i) {
        qDebug() << "支持格式:" << mimeTypes[i];
    }
    qDebug() << "環境變量:";
    qDebug() << "QT_MEDIA_BACKEND =" << qgetenv("QT_MEDIA_BACKEND");
    qDebug() << "PATH =" << qgetenv("PATH");
    QQmlApplicationEngine engine;
    // 連接加載失敗信號
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [](QObject *obj, const QUrl &objUrl) {
        if (!obj) {
            qCritical() << "QML加載失敗:" << objUrl;
            QCoreApplication::exit(-1);
        } else {
            qDebug() << "QML加載成功";
        }
    }, Qt::QueuedConnection);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    return app.exec();
}

三、效果演示

選擇MP4文件,進行播放,可以拖動和暫停。需要注意的是必須要安裝解碼器才行。

實用指南:開源 C++ QT QML 開發(二十一)多媒體--視頻播放_快速開發