mpv是啥?
mpv之前是mediaplayer,windwos之前好像都有预装这个,都来经过发展,现在是一个对跨平台开发很友好的播放器工具。
Qt是啥?
制作桌面应用程序的开发语言及工具,基于C++,跨平台。
基础开发步骤
1.动态壁纸需要一个播放视频的软件,那么可以借鉴mpv的官方demo
https://github.com/mpv-player/mpv-examples/tree/master/libmpv
可以使用里面的qt文件夹里面的demo或者qt_opengl里面的demo
(我是使用的opengl那个demo)
demo编译就会发现,可以播放视频了,那么就以这个demo作为我们的播放模块的代码
2.动态壁纸需要放到图标层以下(windows)
程序需要msvc编译,不能用mingw编译,怀疑是mingw用不了部分win32的部分api
需要在qt pro文件中增加api所需要的库文件
LIBS+=-luser32 //所需要的win32 api库
win32: LIBS += $$PWD/3rd/libmpv.dll.a //mpv的库文件
INCLUDEPATH += $$PWD/3rd/mpv //mpv头文件地址增加
3.关键代码:设置窗口放到图标下面(windows)
#ifdef Q_OS_WINDOWS
#include <windows.h>
WId viewId;
HHOOK hook;
HWND workerW;
LRESULT CALLBACK HookShoot(_In_ int nCode, _In_ WPARAM wParam,LPARAM lParam){
if(wParam == WM_MOUSEMOVE || wParam == WM_NCMOUSEMOVE){
MOUSEHOOKSTRUCT * mshook = (MOUSEHOOKSTRUCT *)lParam;
PostMessage((HWND)viewId,WM_MOUSEMOVE,0,MAKELPARAM(mshook->pt.x,mshook->pt.y));
};
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
HWND _WORKERW = nullptr;
inline BOOL CALLBACK EnumWindowsProc(_In_ HWND tophandle, _In_ LPARAM topparamhandle)
{
HWND defview = FindWindowEx(tophandle, 0, L"SHELLDLL_DefView", nullptr);
if (defview != nullptr)
{
_WORKERW = FindWindowEx(0, tophandle, L"WorkerW", 0);
}
return true;
}
HWND GetWorkerDesktop(){
int result;
HWND windowHandle = FindWindow(L"Progman", nullptr);
//使用 0x3e8 命令分割出两个 WorkerW
SendMessageTimeout(windowHandle, 0x052c, 0 ,0, SMTO_NORMAL, 0x3e8,(PDWORD_PTR)&result);
//遍历窗体获得窗口句柄
EnumWindows(EnumWindowsProc,(LPARAM)nullptr);
// 显示WorkerW
ShowWindow(_WORKERW,SW_HIDE);
return windowHandle;
}
void Wallpaper::registerDesktop()
{
//windwos设置
workerW = GetWorkerDesktop();
if(this){
viewId = this->winId();
// 返回workerW窗口句柄
//设置窗口样式
GetWindowLongA((HWND)viewId, GWL_STYLE);
//设置壁纸窗体的父窗体
SetParent((HWND)viewId,workerW);
SetWindowPos((HWND)viewId,HWND_TOP,0,0,0,0,WS_EX_LEFT|WS_EX_LTRREADING|WS_EX_RIGHTSCROLLBAR|WS_EX_NOACTIVATE);
// 设置全局鼠标事件钩子(会很卡)
// hook = SetWindowsHookEx(WH_MOUSE_LL,HookShoot,GetModuleHandle(NULL),0);
QDesktopWidget *desktop = QApplication::desktop();
this->move(QPoint(0,0));
int height = desktop->height();
int width = desktop->width();
this->resize(QSize(width,height));
this->showFullScreen();
}
4关键代码 linux,设置桌面属性
#include <QtX11Extras/QX11Info>
#include <xcb/xcb.h>
#include <xcb/xcb_ewmh.h>
#include <QtPlatformHeaders/QXcbWindowFunctions>
void Wallpaper::registerDesktop()
{
this->winId();
QWindow *window = this->windowHandle();
window->setOpacity(0.99);
if (QApplication::platformName() == "wayland") {
qDebug() << "wayland set desktop"; //wayland环境下没有做
} else {
//x11环境下直接设置这个即可
QXcbWindowFunctions::setWmWindowType(window, QXcbWindowFunctions::Desktop);
}
//随意激活下窗口,可能不需要
QTimer::singleShot(1, this, [=] {
show();
lower();
});
}
5 mpv播放器使用关键代码 (可以换用其他播放器)
头文件:
#ifndef PLAYERWINDOW_H
#define PLAYERWINDOW_H
#include <QtWidgets/QOpenGLWidget>
#include "mpv/client.h"
#include "mpv/render_gl.h"
#include "mpv/qthelper.hpp"
#include <QSize>
class MpvWidget Q_DECL_FINAL: public QOpenGLWidget
{
Q_OBJECT
public:
MpvWidget(QWidget *parent = 0, Qt::WindowFlags f = nullptr);
~MpvWidget();
void command(const QVariant ¶ms);
void setProperty(const QString &name, const QVariant &value);
QVariant getProperty(const QString &name) const;
QSize sizeHint() const Q_DECL_OVERRIDE { return QSize(480, 270);}
Q_SIGNALS:
void durationChanged(int value);
void positionChanged(int value);
protected:
void initializeGL() Q_DECL_OVERRIDE;
void paintGL() Q_DECL_OVERRIDE;
private Q_SLOTS:
void on_mpv_events();
void maybeUpdate();
private:
void handle_mpv_event(mpv_event *event);
static void on_update(void *ctx);
mpv_handle *mpv;
mpv_render_context *mpv_gl;
bool m_bScrrenShot{false};
};
#endif // PLAYERWINDOW_H
cpp:
#include "mpvwidget.h"
#include <stdexcept>
#include <QtGui/QOpenGLContext>
#include <QtCore/QMetaObject>
#include "application.h"
#include <QDateTime>
#include <QStandardPaths>
static void wakeup(void *ctx)
{
QMetaObject::invokeMethod((MpvWidget *)ctx, "on_mpv_events", Qt::QueuedConnection);
}
static void *get_proc_address(void *ctx, const char *name)
{
Q_UNUSED(ctx);
QOpenGLContext *glctx = QOpenGLContext::currentContext();
if (!glctx)
return nullptr;
return reinterpret_cast<void *>(glctx->getProcAddress(QByteArray(name)));
}
MpvWidget::MpvWidget(QWidget *parent, Qt::WindowFlags f)
: QOpenGLWidget(parent, f)
{
setAttribute(Qt::WA_TranslucentBackground);
mpv = mpv_create();
if (!mpv)
throw std::runtime_error("could not create mpv context");
// mpv_set_option_string(mpv, "terminal", "yes");
mpv_set_option_string(mpv, "msg-level", "all=v");
if (mpv_initialize(mpv) < 0)
throw std::runtime_error("could not initialize mpv context");
// Request hw decoding, just for testing.
// mpv::qt::set_option_variant(mpv, "hwdec", "auto");
mpv::qt::set_option_variant(mpv, "hwdec", "gpu");
mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
mpv_set_wakeup_callback(mpv, wakeup, this);
QList<QVariant> list;
list << "no-correct-pts";
// mpv::qt::command_variant(mpv, list);
mpv::qt::set_property_variant(mpv, "correct-pts", "no");
mpv::qt::set_property_variant(mpv, "fps", "0");
}
MpvWidget::~MpvWidget()
{
makeCurrent();
if (mpv_gl)
mpv_render_context_free(mpv_gl);
mpv_terminate_destroy(mpv);
}
void MpvWidget::command(const QVariant ¶ms)
{
mpv::qt::command_variant(mpv, params);
}
void MpvWidget::setProperty(const QString &name, const QVariant &value)
{
mpv::qt::set_property_variant(mpv, name, value);
}
QVariant MpvWidget::getProperty(const QString &name) const
{
return mpv::qt::get_property_variant(mpv, name);
}
void MpvWidget::initializeGL()
{
mpv_opengl_init_params gl_init_params{get_proc_address, nullptr, nullptr};
mpv_render_param params[] {
{MPV_RENDER_PARAM_API_TYPE, const_cast<char *>(MPV_RENDER_API_TYPE_OPENGL)},
{MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params},
{MPV_RENDER_PARAM_INVALID, nullptr}
};
if (mpv_render_context_create(&mpv_gl, mpv, params) < 0)
throw std::runtime_error("failed to initialize mpv GL context");
mpv_render_context_set_update_callback(mpv_gl, MpvWidget::on_update, reinterpret_cast<void *>(this));
}
void MpvWidget::paintGL()
{
int iwidth = width();
double dwidth = iwidth * devicePixelRatioF();
int iheight = height();
double dheight = iheight * devicePixelRatioF();
int deviceiwidth = dwidth;
int deviceiheight = dheight;
mpv_opengl_fbo mpfbo{static_cast<int>(defaultFramebufferObject()), deviceiwidth, deviceiheight, 0};
int flip_y{1};
mpv_render_param params[] = {
{MPV_RENDER_PARAM_OPENGL_FBO, &mpfbo},
{MPV_RENDER_PARAM_FLIP_Y, &flip_y},
{MPV_RENDER_PARAM_INVALID, nullptr}
};
// See render_gl.h on what OpenGL environment mpv expects, and
// other API details.
mpv_render_context_render(mpv_gl, params);
if (m_bScrrenShot) {
m_bScrrenShot = false;
QPixmap pix = QPixmap::fromImage(grabFramebuffer());
QDateTime::currentMSecsSinceEpoch();
QString path = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) + "/" + QString::number(QDateTime::currentMSecsSinceEpoch()) + ".png";
pix.save(path);
}
}
void MpvWidget::on_mpv_events()
{
// Process all events, until the event queue is empty.
while (mpv) {
mpv_event *event = mpv_wait_event(mpv, 0);
if (event->event_id == MPV_EVENT_NONE) {
break;
}
handle_mpv_event(event);
}
}
void MpvWidget::handle_mpv_event(mpv_event *event)
{
switch (event->event_id) {
case MPV_EVENT_PROPERTY_CHANGE: {
mpv_event_property *prop = (mpv_event_property *)event->data;
if (strcmp(prop->name, "time-pos") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
double time = *(double *)prop->data;
Q_EMIT positionChanged(time);
}
} else if (strcmp(prop->name, "duration") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
double time = *(double *)prop->data;
Q_EMIT durationChanged(time);
}
}
break;
}
default: ;
// Ignore uninteresting or unknown events.
}
}
// Make Qt invoke mpv_render_context_render() to draw a new/updated video frame.
void MpvWidget::maybeUpdate()
{
// If the Qt window is not visible, Qt's update() will just skip rendering.
// This confuses mpv's render API, and may lead to small occasional
// freezes due to video rendering timing out.
// Handle this by manually redrawing.
// Note: Qt doesn't seem to provide a way to query whether update() will
// be skipped, and the following code still fails when e.g. switching
// to a different workspace with a reparenting window manager.
if (window()->isMinimized()) {
makeCurrent();
paintGL();
context()->swapBuffers(context()->surface());
doneCurrent();
} else {
update();
}
}
void MpvWidget::on_update(void *ctx)
{
QMetaObject::invokeMethod((MpvWidget *)ctx, "maybeUpdate");
}
只要将激活窗口包含播放器,对应linux和windows设置就可以使用。
在windows下几乎完美,图标可点击,linux下只能显示视频(ubuntu18.04可以,其他似乎都不行)。
demo git地址(windows/linux)
windwos:
windwos源码
或者
https://download.csdn.net/download/qq_43081702/86741121
linux:
linux源码