文章目录
-
- 效果图
- 整体技术架构
- 一、先把功能入口接起来
- 二、媒体页挂载位置不能随便放
- [三、媒体目录必须跟 AppSettings 走](#三、媒体目录必须跟 AppSettings 走)
- [四、Android 和 Windows 的 file URL 不能硬共用一套逻辑](#四、Android 和 Windows 的 file URL 不能硬共用一套逻辑)
- 五、先把目录扫描做稳,再谈播放
- [六、`FolderListModel.get(...)` 返回值这个坑很容易忽略](#六、
FolderListModel.get(...)返回值这个坑很容易忽略) - [七、Android 到这里基本就已经通了](#七、Android 到这里基本就已经通了)
- [八、Windows 最终问题落到了 Qt5 + DirectShow](#八、Windows 最终问题落到了 Qt5 + DirectShow)
- 九、最后总结
大家好,这篇记录一下我最近在 QGC 二次开发里做本地媒体浏览功能时踩到的一个坑。
这次需求并不复杂,就是在飞行界面增加一个媒体浏览入口,用来查看软件保存下来的照片和视频。其中:
- 照片要支持预览
- 照片要支持上一张、下一张切换
- 视频要支持播放、暂停、停止
- Android 和 Windows 都要能用
结果实际做下来发现,Android 这条线基本没什么问题,但 Windows 上就开始出状况了:
- 图片能正常显示
- 视频列表也能正常出来
- 点击视频后却无法播放
- 控制台一直报
DirectShowPlayerService::doRender相关错误
所以这篇文章不讲虚的,直接把这次排查过程和关键代码记录下来。后面如果你也在做 QGC 二次开发,刚好要碰本地媒体浏览,这篇应该能帮你少绕点路。
效果图
整体效果图如下:

整体技术架构
先放一张这次媒体浏览功能的整体架构图,后面正文就是按这条链路一步一步排查下来的。
QGC 飞行界面
Media 入口按钮
媒体浏览页
目录层
AppSettings
photoSavePath / videoSavePath
列表层
FolderListModel
后缀分类 / 排序 / 筛选
预览层
图片预览 / 视频播放
Android
Windows
图片预览正常
视频播放正常
图片预览正常
视频列表正常
视频播放失败
DirectShowPlayerService::doRender
结论
Windows 问题不在媒体页 UI
而在 Qt5 + DirectShow 本地视频链路
一、先把功能入口接起来
本地媒体浏览页不是一个单独弹出来的窗口,而是飞行界面里的一层页面,所以第一步先把入口做好。
我这里的做法是,在顶部状态栏增加一个 Media 按钮,点击后切换媒体页显示状态。
关键代码如下:
qml
Button {
text: XGlobalProperty.mediaBrowserVisible ? qsTr("Back") : qsTr("Media")
onClicked: {
XGlobalProperty.mediaBrowserVisible = !XGlobalProperty.mediaBrowserVisible
if (XGlobalProperty.mediaBrowserVisible) {
XGlobalProperty.settingViewVisible = false
}
}
}
这里的思路很简单:
- 没进入媒体页时,按钮显示
Media - 进入媒体页后,按钮变成
Back - 返回时直接把媒体页状态关掉
也就是说,媒体浏览功能本质上就是飞行界面中的一个页面状态切换。
二、媒体页挂载位置不能随便放
这一步是我一开始踩到的第一个结构性问题。
最开始我以为媒体页只要挂在主 QML 页面里就行,结果实际验证发现不对。因为飞行界面本身已经有:
- 图传区域
- 飞行叠加控件
- 拍照录像控件
- PIP 小窗
如果媒体页挂载层级不对,就会出现"媒体页打开了,但别的飞行控件还浮在上面"的情况。
最后正确的挂载方式,是让媒体页直接覆盖到飞行界面顶层。
关键代码类似这样:
qml
XMediaBrowserView {
id: xMediaBrowserView
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
visible: XGlobalProperty.mediaBrowserVisible
z: QGroundControl.zOrderTopMost + 10
}
这段代码看起来很简单,但很关键。因为它决定了媒体页不是一个普通子控件,而是一个真正压在飞行界面上层的全屏页面。
三、媒体目录必须跟 AppSettings 走
页面起来以后,下一步就是读照片和视频目录。
这种功能很容易一开始写成这样:
qml
property string photoFolderPath: "C:/Users/xxx/Documents/QGroundControl/Photo"
property string videoFolderPath: "C:/Users/xxx/Documents/QGroundControl/Video"
开发阶段这么写似乎能跑,但这种方式很不稳。因为真正的保存路径应该跟软件设置保持一致,而不是开发者自己猜。
在 QGC 二次开发里,更合理的方式是直接读取应用设置中的照片和视频保存目录。
关键代码如下:
qml
readonly property string photoFolderPath: QGroundControl.settingsManager.appSettings.photoSavePath
readonly property string videoFolderPath: QGroundControl.settingsManager.appSettings.videoSavePath
这里我还踩了一个小坑,就是最开始把它们当成 Fact.value 去读了,写成类似下面这样:
qml
QGroundControl.settingsManager.appSettings.photoSavePath.value
QGroundControl.settingsManager.appSettings.videoSavePath.value
结果路径直接就拿错了。
后面确认以后才发现,这两个值本身就是直接给 QML 使用的字符串属性,不需要再套 .value。
四、Android 和 Windows 的 file URL 不能硬共用一套逻辑
目录路径拿对以后,下一步就是生成目录 URL 给 FolderListModel、Image 和 MediaPlayer 使用。
这一步一开始我也想偷懒,想着统一转成 file:// 就行。但实际做下来发现,Android 和 Windows 这里还是得分开处理。
我这里最后用的是一个很简单的转换函数:
qml
function toFolderUrl(path) {
var cleaned = cleanedPath(path)
if (cleaned === "") {
return ""
}
if (cleaned.charAt(0) === "/") {
return "file://" + cleaned
}
return "file:///" + cleaned
}
为什么要这样分?
因为两边原始路径长得不一样。
Android
Android 这边通常是:
text
/storage/emulated/0/...
如果这里 file:// 前缀拼错,结果通常不是"路径报错",而是更烦人的那种情况:
- 明明目录里有文件
- 但列表扫不到
- 图片不显示
- 视频也播不了
Windows
Windows 这边通常是:
text
C:/Users/xxx/Documents/...
这时就应该是 file:///C:/... 这种形式。
这一步看起来是小事,但实际就是这种小地方最容易把 Android 和 Windows 的行为拉开。
五、先把目录扫描做稳,再谈播放
路径处理对了以后,接着就是扫描目录。
我这里没有一开始就把照片和视频分成很多套扫描规则,而是先把目录里的文件全部扫出来,再由代码自己判断类型。
关键代码如下:
qml
FolderListModel {
id: photoFolderModel
folder: root.photoFolderUrl
nameFilters: [ "*" ]
showDirs: false
showDotAndDotDot: false
sortField: FolderListModel.Unsorted
}
再配合一个后缀判断函数:
qml
function mediaTypeFromName(fileName) {
var lower = (fileName || "").toLowerCase()
if (lower.match(/\.(jpg|jpeg|png|bmp|gif)$/)) {
return "photo"
}
if (lower.match(/\.(mp4|mov|avi|mkv)$/)) {
return "video"
}
return "unknown"
}
这样做的好处是比较直接:
- 目录先完整扫出来
- 再用代码自己控制哪些当照片、哪些当视频
- 后面调试时也更容易看清到底是哪一步出了问题
如果一开始就把过滤条件全压到 FolderListModel 上,出了问题有时候不太好定位。
六、FolderListModel.get(...) 返回值这个坑很容易忽略
目录能扫出来,不代表数据就已经可以放心往后用了。
我这次还有一个很典型的坑,就是从 FolderListModel 取值的时候,直接把结果当普通字符串用了。
比如最开始很容易写出这种代码:
qml
var fileName = folderModel.get(i, "fileName")
var fileUrl = folderModel.get(i, "fileURL")
表面看起来没问题,但运行时就会碰到这种报错:
text
Unable to assign QJSValue to QString
Unable to assign QJSValue to QUrl
原因就在于,FolderListModel.get(...) 返回出来的值很多时候不是你以为的纯字符串,而是 QJSValue。
所以更稳一点的写法应该是显式转换一下,比如:
qml
var nativeUrl = folderModel.get(i, "fileURL")
var fileUrl = nativeUrl ? nativeUrl.toString() : ""
这一步其实很细,但很真实。因为你会发现:
- 文件数没问题
- 列表似乎也差不多出来了
- 但运行时一直有奇怪类型错误
如果不注意这里,排查起来很浪费时间。
七、Android 到这里基本就已经通了
把目录、URL、列表模型和类型转换这些问题收掉以后,Android 这条线基本就通了。
最后 Android 上的效果大致就是:
- 能正常读取照片和视频目录
- 能显示媒体列表
- 照片能预览
- 照片支持上一张、下一张
- 视频点击后自动播放
- 支持暂停、停止和拖动进度
也就是说,从整体设计上看,这套媒体浏览页面本身是成立的。页面结构没有问题,列表逻辑没有问题,基础交互也没有问题。
这一点非常重要,因为这意味着:如果后面 Windows 还有问题,那就别再反复怀疑整套界面设计了。
八、Windows 最终问题落到了 Qt5 + DirectShow
前面的基础问题都排干净以后,Windows 上剩下的现象就比较集中:
- 图片可以看
- 视频列表可以显示
- 视频文件路径也没问题
- 点击视频后,播放器报错
当时最典型的报错就是:
text
DirectShowPlayerService::doRender: Unresolved error code 0x80040266
而视频播放部分的代码其实很普通,就是 Qt5 常见的写法:
qml
MediaPlayer {
id: mediaPlayer
source: root.fileUrl
autoPlay: false
onError: {
console.log("[MediaBrowser] video play error:", error, errorString, root.fileUrl)
}
}
VideoOutput {
anchors.fill: parent
source: mediaPlayer
fillMode: VideoOutput.PreserveAspectFit
}
也正因为这段代码没什么特别,所以问题反而更容易收束。
排查到这里,我基本就确认了:
- 不是页面层级问题
- 不是目录路径问题
- 不是媒体列表问题
- 不是图片预览逻辑问题
而是 Qt5 在 Windows 下这套本地多媒体链路,本质上还是走到了 DirectShow 这条路径上。而这条路径的老问题就是:不是所有本地 MP4 都能稳定播。
换句话说,同样一套 QML 播放逻辑:
- Android 上可以
- Windows 上不一定
这时候如果还继续在 QML 层面来回试,意义已经不大了。
九、最后总结
这次在 QGC 二次开发里做本地媒体浏览,整体结论比较清楚。
先说 Android,这条线基本没什么大问题。只要把照片和视频保存路径读取对、文件 URL 处理对、FolderListModel 取值时注意类型转换,图片预览和视频播放都能正常工作。
Windows 这边就不一样了。前面的目录、列表、图片预览其实都没问题,真正卡住的是本地视频播放。最后问题收束到 Qt5 在 Windows 下这套 MediaPlayer + VideoOutput 的本地多媒体链路,也就是 DirectShow 这一层不够稳。现象就是图片能看、视频列表能出,但视频不一定能播,哪怕文件本身是 MP4。
这次有几个点值得单独记一下:
- 照片和视频目录不要自己猜,直接从应用设置里读取。
photoSavePath和videoSavePath不是Fact.value,这一点很容易写错。- Android 和 Windows 的
file://路径不能偷懒用一套逻辑。 FolderListModel.get(...)取出来的值最好先做显式转换,否则很容易遇到QJSValue的类型问题。- 如果 Windows 一直报
DirectShowPlayerService::doRender,那基本就不是页面逻辑的问题了,而是本地视频后端本身的兼容性边界。
所以这篇文章的最终结论就是:
- Android 端,这套本地媒体浏览方案可以继续用 Qt5 自带播放器。
- Windows 端,如果只是浏览图片或者外部打开视频,到这里已经够了。
- 但如果项目要求"Windows 也要在软件内部稳定播放本地视频",那就不能继续停在 DirectShow 这条线上了。
下一篇我继续写,记录一下 Windows 下如何用一个 FFmpeg 最小系统,把本地视频播放真正接起来。