当前位置: 首页 > news >正文

开源 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# 快速开发(一)基础知识

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

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

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

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

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

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

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

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

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

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

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

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

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

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

开源 C# 快速开发(十六)数据库--sqlserver增删改查

本章节主要内容是:使用qml编写了媒体播放器,使用了K-Lite Codec Pack Mega作为解码包,实现mp4的播放。

1.代码分析

2.所有源码

3.效果演示

一、代码分析1. 主窗口和属性定义
 

ApplicationWindow {id: mainWindowwidth: 1024height: 720visible: truetitle: "MP4播放器 - 完全兼容版"minimumWidth: 800minimumHeight: 600property string currentVideo: ""  // 当前视频文件路径property bool hasVideo: videoPlayer.hasVideo  // 绑定到Video组件的hasVideo属性property bool isSeekable: videoPlayer.seekable  // 是否支持跳转property string currentStatus: "等待选择文件"  // 当前状态显示文本
}


2. 核心视频播放组件
Video组件及其信号处理
 

Video {id: videoPlayeranchors.fill: parentanchors.margins: 2source: 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 = "无媒体文件"breakcase MediaPlayer.Loading:currentStatus = "加载中..."breakcase MediaPlayer.Loaded:currentStatus = "已加载"breakcase MediaPlayer.Stalling:currentStatus = "缓冲中..."breakcase MediaPlayer.Buffering:currentStatus = "缓冲中"breakcase MediaPlayer.Buffered:currentStatus = "就绪"breakcase MediaPlayer.EndOfMedia:currentStatus = "播放结束"breakcase MediaPlayer.InvalidMedia:currentStatus = "格式不支持"breakcase MediaPlayer.UnknownStatus:currentStatus = "未知状态"breakdefault:currentStatus = "就绪"}console.log("状态更新:", currentStatus)
}


播放按钮更新函数
 

function updatePlayButton() {switch(videoPlayer.playbackState) {case MediaPlayer.PlayingState:playButton.text = "⏸️ 暂停"  // 播放中显示暂停breakcase MediaPlayer.PausedState:playButton.text = "▶️ 继续"  // 暂停中显示继续breakdefault: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:SSreturn hours.toString().padStart(2, '0') + ":" +minutes.toString().padStart(2, '0') + ":" +seconds.toString().padStart(2, '0')} else {// 无时长格式:MM:SSreturn 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: fileDialogtitle: "选择视频文件"nameFilters: [ /* 文件过滤器列表 */ ]onAccepted: {currentVideo = fileDialog.fileUrl  // 获取选择的文件URLconsole.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.14ApplicationWindow {id: mainWindowwidth: 1024height: 720visible: truetitle: "MP4播放器 - 完全兼容版"minimumWidth: 800minimumHeight: 600property string currentVideo: ""property bool hasVideo: videoPlayer.hasVideoproperty bool isSeekable: videoPlayer.seekableproperty string currentStatus: "等待选择文件"// 背景渐变Rectangle {anchors.fill: parentgradient: Gradient {GradientStop { position: 0.0; color: "#2c3e50" }GradientStop { position: 1.0; color: "#3498db" }}}ColumnLayout {anchors.fill: parentanchors.margins: 10spacing: 10// 标题栏Rectangle {Layout.fillWidth: trueheight: 60color: "transparent"RowLayout {anchors.fill: parentText {text: "🎬 MP4播放器"color: "white"font.pixelSize: 24font.bold: true}Item { Layout.fillWidth: true }Text {text: "Powered by Qt5.14"color: "lightgray"font.pixelSize: 12}}}// 视频显示区域Rectangle {id: videoContainerLayout.fillWidth: trueLayout.fillHeight: truecolor: "black"radius: 8clip: trueVideo {id: videoPlayeranchors.fill: parentanchors.margins: 2source: currentVideofillMode: VideoOutput.PreserveAspectFitvolume: volumeSlider.valueautoPlay: 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: loadingIndicatoranchors.centerIn: parentrunning: videoPlayer.status === MediaPlayer.Loading ||videoPlayer.status === MediaPlayer.Stalling ||videoPlayer.status === MediaPlayer.Bufferingvisible: running}// 居中状态信息Column {id: centerInfoanchors.centerIn: parentspacing: 10visible: currentVideo === "" || videoPlayer.status === MediaPlayer.InvalidMedia || !hasVideoText {id: statusIconanchors.horizontalCenter: parent.horizontalCentercolor: "white"font.pixelSize: 48text: getStatusIcon()}Text {id: statusMessageanchors.horizontalCenter: parent.horizontalCentercolor: "white"font.pixelSize: 18text: getStatusMessage()horizontalAlignment: Text.AlignHCenter}Text {id: errorDetailsanchors.horizontalCenter: parent.horizontalCentercolor: "yellow"font.pixelSize: 12text: getErrorDetails()visible: text !== ""horizontalAlignment: Text.AlignHCenterwidth: 400wrapMode: Text.WordWrap}}// 点击控制播放/暂停MouseArea {anchors.fill: parentonClicked: togglePlayPause()enabled: hasVideo && currentVideo !== ""}}// 进度控制区域ColumnLayout {Layout.fillWidth: truespacing: 5// 进度条Slider {id: progressSliderLayout.fillWidth: truefrom: 0to: 100value: 0enabled: isSeekable && hasVideoonMoved: {if (isSeekable) {videoPlayer.seek(value)}}// 自定义样式background: Rectangle {implicitHeight: 6color: "#5a5a5a"radius: 3}handle: Rectangle {x: progressSlider.leftPadding + progressSlider.visualPosition * (progressSlider.availableWidth - width)y: progressSlider.topPadding + progressSlider.availableHeight / 2 - height / 2implicitWidth: 16implicitHeight: 16radius: 8color: progressSlider.pressed ? "#f0f0f0" : "#ffffff"border.color: "#bdbebf"}}// 时间信息RowLayout {Layout.fillWidth: trueText {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: trueheight: 70color: "transparent"RowLayout {anchors.fill: parentspacing: 10// 文件操作按钮Button {text: "📁 打开文件"onClicked: fileDialog.open()background: Rectangle {color: "#27ae60"radius: 5}contentItem: Text {text: parent.textcolor: "white"horizontalAlignment: Text.AlignHCenterverticalAlignment: Text.AlignVCenterfont.bold: true}}// 播放控制按钮Button {id: playButtontext: "▶️ 播放"onClicked: togglePlayPause()enabled: hasVideo && currentVideo !== ""background: Rectangle {color: enabled ? "#2980b9" : "#7f8c8d"radius: 5}contentItem: Text {text: parent.textcolor: "white"horizontalAlignment: Text.AlignHCenterverticalAlignment: Text.AlignVCenterfont.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.textcolor: "white"horizontalAlignment: Text.AlignHCenterverticalAlignment: Text.AlignVCenter}}Item { Layout.fillWidth: true }// 音量控制RowLayout {spacing: 5Text {text: "🔊"color: "white"font.pixelSize: 16}Slider {id: volumeSliderfrom: 0to: 1value: 0.8stepSize: 0.1Layout.preferredWidth: 100background: Rectangle {implicitHeight: 4color: "#5a5a5a"radius: 2}}Text {color: "white"text: Math.round(volumeSlider.value * 100) + "%"font.pixelSize: 12Layout.preferredWidth: 40}}}}// 状态信息栏Rectangle {Layout.fillWidth: trueheight: 30color: "#34495e"radius: 4RowLayout {anchors.fill: parentanchors.margins: 5Text {color: "white"text: "文件: " + (currentVideo ? getFileName(currentVideo) : "未选择")font.pixelSize: 11elide: Text.ElideMiddleLayout.fillWidth: true}Text {color: getStatusColor()text: currentStatusfont.pixelSize: 11font.bold: true}Text {color: hasVideo ? "lightgreen" : "red"text: hasVideo ? "🎥 有视频" : "❌ 无视频"font.pixelSize: 11}}}}FileDialog {id: fileDialogtitle: "选择视频文件"nameFilters: ["MP4文件 (*.mp4)","AVI文件 (*.avi)","MKV文件 (*.mkv)","MOV文件 (*.mov)","WMV文件 (*.wmv)","所有文件 (*.*)"]onAccepted: {currentVideo = fileDialog.fileUrlconsole.log("加载文件:", currentVideo)}}// 功能函数function togglePlayPause() {if (!hasVideo || currentVideo === "") returnif (videoPlayer.playbackState === MediaPlayer.PlayingState) {videoPlayer.pause()} else {videoPlayer.play()}}function updateStatusDisplay() {var status = videoPlayer.statusswitch(status) {case MediaPlayer.NoMedia:currentStatus = "无媒体文件"breakcase MediaPlayer.Loading:currentStatus = "加载中..."breakcase MediaPlayer.Loaded:currentStatus = "已加载"breakcase MediaPlayer.Stalling:currentStatus = "缓冲中..."breakcase MediaPlayer.Buffering:currentStatus = "缓冲中"breakcase MediaPlayer.Buffered:currentStatus = "就绪"breakcase MediaPlayer.EndOfMedia:currentStatus = "播放结束"breakcase MediaPlayer.InvalidMedia:currentStatus = "格式不支持"breakcase MediaPlayer.UnknownStatus:currentStatus = "未知状态"breakdefault:currentStatus = "就绪"}console.log("状态更新:", currentStatus)}function updatePlayButton() {switch(videoPlayer.playbackState) {case MediaPlayer.PlayingState:playButton.text = "⏸️ 暂停"breakcase MediaPlayer.PausedState:playButton.text = "▶️ 继续"breakdefault: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 % 60if (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 <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QMediaPlayer>
#include <QDebug>
#include <QDir>
#include <QStandardPaths>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文件,进行播放,可以拖动和暂停。需要注意的是必须要安装解码器才行。

http://www.dtcms.com/a/490693.html

相关文章:

  • PMBT2222A,215 开关晶体管功率二极管 NXP安世半导体 音频放大电路 LED驱动 应用
  • 大语言模型(LLM)入门笔记:嵌入向量与位置信息
  • 网站设计济南做网站的一定要开80或8080端口
  • 【Spring Boot从入门到精通】原理、实战与最佳实践
  • uni-app 入门学习教程,从入门到精通,uni-app 基础知识详解 (2)
  • Pyspark分布式访问NebulaGraph图数据库
  • FPGA----petalinux的Ubuntu文件系统移植
  • 宜昌网站建设厂家wordpress 扁担
  • TensorFlow2 Python深度学习 - 卷积神经网络示例2-使用Fashion MNIST识别时装示例
  • Eureka: Human-Level Reward Design via Coding Large Language Models 译读笔记
  • 随时随地看监控:我的UptimeKuma远程访问改造记
  • 关于网站篡改应急演练剧本编写(模拟真实场景)
  • 河北省企业网站建设公司企业管理系统软件有哪些
  • JVM的classpath
  • RVO优化
  • ethercat 环型拓扑(Ring Topology)
  • 颠覆PD快充、工业控制与智能家电等领域高CTR,高隔离电压高可靠性光电耦合器OCT1018/OCT1019
  • 【机器学习入门】8.1 降维的概念和意义:一文读懂降维的概念与意义 —— 从 “维度灾难” 到低维嵌入
  • 黄骅市旅游景点有哪些盐城网站关键词优化
  • 对于网站建设的调查问卷爱南宁app官网下载
  • 一文读懂 YOLOv1 与 YOLOv2:目标检测领域的早期里程碑
  • 在 Windows 10/11 LTSC等精简系统中安装Winget和微软应用商店,Windows Server安装Microsoft Store的应用
  • A2A架构详解
  • 基础 - SQL命令速查
  • logo图片素材大全sem和seo都包括什么
  • 把 AI“缝”进布里:生成式编织神经网络让布料自带摄像头
  • 岳阳建网站长沙网站优化价格
  • [Sora] 分布式训练 | 并行化策略 | `plugin_type` | `booster.boost()`
  • Linux系统函数link、unlink与dentry的关系及使用注意事项
  • 安卓手机 IP 切换指南:告别卡顿,轻松换 IP