1 // Portions of code in this module came from the examples directory in mpv's
2 // git repo.  Reworked by me.
3 
4 #include <QtGlobal>
5 #if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
6 #include <QtX11Extras/QX11Info>
7 #include <qpa/qplatformnativeinterface.h>
8 #endif
9 #include <QLayout>
10 #include <QMainWindow>
11 #include <QGuiApplication>
12 #include <QThread>
13 #include <QTimer>
14 #include <QOpenGLContext>
15 #include <QMouseEvent>
16 #include <QMetaObject>
17 #include <QDir>
18 #include <QDebug>
19 #include <cmath>
20 #include <stdexcept>
21 #include "logger.h"
22 #include "mpvwidget.h"
23 #include "widgets/logowidget.h"
24 #include "platform/unify.h"
25 #include "storage.h"
26 
27 #ifndef Q_PROCESSOR_ARM
28     #ifndef GLAPIENTRY
29     // On Windows, GLAPIENTRY may sometimes conveniently go missing
30     #define GLAPIENTRY __stdcall
31     #endif
32 #endif
33 #ifndef GLAPIENTRY
34 #define GLAPIENTRY
35 #endif
36 
37 #define HANDLE_PROP(p, method, converter, dflt) \
38 { \
39     p, \
40     [](MpvObject *self, bool ok, const QVariant &v) -> void { \
41         if (ok && v.canConvert<decltype(dflt)>()) \
42             emit self->method(v.converter());  \
43         else \
44             emit self->method(dflt); \
45     } \
46 }
47 
48 MpvObject::PropertyDispatchMap MpvObject::propertyDispatch = {
49     HANDLE_PROP("time-pos", self_playTimeChanged, toDouble, -1.0),
50     HANDLE_PROP("duration", self_playLengthChanged, toDouble, -1.0),
51     HANDLE_PROP("seekable", seekableChanged, toBool, false),
52     HANDLE_PROP("pause", pausedChanged, toBool, true),
53     HANDLE_PROP("media-title", mediaTitleChanged, toString, QString()),
54     HANDLE_PROP("chapter-metadata", chapterDataChanged, toMap, QVariantMap()),
55     HANDLE_PROP("chapter-list", chaptersChanged, toList, QVariantList()),
56     HANDLE_PROP("track-list", tracksChanged, toList, QVariantList()),
57     HANDLE_PROP("estimated-vf-fps", fpsChanged, toDouble, 0.0),
58     HANDLE_PROP("avsync", avsyncChanged, toDouble, 0.0),
59     HANDLE_PROP("frame-drop-count", displayFramedropsChanged, toLongLong, 0ll),
60     HANDLE_PROP("decoder-frame-drop-count", decoderFramedropsChanged, toLongLong, 0ll),
61     HANDLE_PROP("audio-bitrate", audioBitrateChanged, toDouble, 0.0),
62     HANDLE_PROP("video-bitrate", videoBitrateChanged, toDouble, 0.0),
63     HANDLE_PROP("metadata", self_metadata, toMap, QVariantMap()),
64     HANDLE_PROP("audio-device-list", self_audioDeviceList, toList, QVariantList()),
65     HANDLE_PROP("filename", fileNameChanged, toString, QString()),
66     HANDLE_PROP("file-format", fileFormatChanged, toString, QString()),
67     HANDLE_PROP("file-date-created", fileCreationTimeChanged, toLongLong, 0ll),
68     HANDLE_PROP("file-size", fileSizeChanged, toLongLong, 0ll),
69     HANDLE_PROP("path", filePathChanged, toString, QString())
70 };
71 
MpvObject(QObject * owner,const QString & clientName)72 MpvObject::MpvObject(QObject *owner, const QString &clientName) : QObject(owner)
73 {
74     // Setup threads
75     worker = new QThread();
76     worker->start();
77 
78     // setup controller
79     ctrl = new MpvController();
80     ctrl->moveToThread(worker);
81 
82     // setup timer
83     hideTimer = new QTimer(this);
84     hideTimer->setSingleShot(true);
85     hideTimer->setInterval(1000);
86 
87     // Wire the basic mpv functions to avoid littering the codebase with
88     // QMetaObject::invokeMethod.
89     connect(this, &MpvObject::ctrlContinueHook,
90             ctrl, &MpvController::continueHook, Qt::QueuedConnection);
91     connect(this, &MpvObject::ctrlCommand,
92             ctrl, &MpvController::command, Qt::QueuedConnection);
93     connect(this, &MpvObject::ctrlSetOptionVariant,
94             ctrl, &MpvController::setOptionVariant, Qt::QueuedConnection);
95     connect(this, &MpvObject::ctrlSetPropertyVariant,
96             ctrl, &MpvController::setPropertyVariant, Qt::QueuedConnection);
97     connect(this, &MpvObject::ctrlSetLogLevel,
98             ctrl, &MpvController::setLogLevel, Qt::QueuedConnection);
99     connect(this, &MpvObject::ctrlShowStats,
100             ctrl, &MpvController::showStatsPage, Qt::QueuedConnection);
101 
102     // Wire up the event-handling callbacks
103     connect(ctrl, &MpvController::mpvPropertyChanged,
104             this, &MpvObject::ctrl_mpvPropertyChanged, Qt::QueuedConnection);
105     connect(ctrl, &MpvController::hookEvent,
106             this, &MpvObject::ctrl_hookEvent, Qt::QueuedConnection);
107     connect(ctrl, &MpvController::unhandledMpvEvent,
108             this, &MpvObject::ctrl_unhandledMpvEvent, Qt::QueuedConnection);
109     connect(ctrl, &MpvController::videoSizeChanged,
110             this, &MpvObject::ctrl_videoSizeChanged, Qt::QueuedConnection);
111 
112     // Wire up the mouse and timer-related callbacks
113     connect(this, &MpvObject::mouseMoved,
114             this, &MpvObject::self_mouseMoved);
115     connect(hideTimer, &QTimer::timeout,
116             this, &MpvObject::hideTimer_timeout);
117 
118     // Wire up the logging interface
119     connect(ctrl, &MpvController::logMessageByParts,
120             Logger::singleton(), &Logger::makeLogDescriptively);
121 
122     // Fetch installed scripts
123     QString scriptPath = Storage::fetchConfigPath() + "/scripts";
124     auto scriptInfoList = QDir(scriptPath).entryInfoList({"*.lua"}, QDir::Files);
125     QStringList scripts;
126     for (auto &info : scriptInfoList)
127         scripts.append(info.absoluteFilePath());
128 
129     // Initialize mpv playback instance
130     MpvController::OptionList earlyOptions = {
131         { "vo", "libmpv" },
132         { "ytdl", "yes" },
133         { "audio-client-name", clientName },
134         { "load-scripts", true },
135         { "scripts", scripts }
136     };
137     QMetaObject::invokeMethod(ctrl, "create", Qt::BlockingQueuedConnection,
138                               Q_ARG(MpvController::OptionList, earlyOptions));
139 
140     // clean up objects when the worker thread is deleted
141     connect(worker, &QThread::finished, ctrl, &MpvController::deleteLater);
142 
143     // Observe some properties
144     MpvController::PropertyList options = {
145         { "time-pos", 0, MPV_FORMAT_DOUBLE },
146         { "pause", 0, MPV_FORMAT_FLAG },
147         { "media-title", 0, MPV_FORMAT_STRING },
148         { "chapter-metadata", 0, MPV_FORMAT_NODE },
149         { "track-list", 0, MPV_FORMAT_NODE },
150         { "chapter-list", 0, MPV_FORMAT_NODE },
151         { "duration", 0, MPV_FORMAT_DOUBLE },
152         { "estimated-vf-fps", 0, MPV_FORMAT_DOUBLE },
153         { "avsync", 0, MPV_FORMAT_DOUBLE },
154         { "frame-drop-count", 0, MPV_FORMAT_INT64 },
155         { "decoder-frame-drop-count", 0, MPV_FORMAT_INT64 },
156         { "audio-bitrate", 0, MPV_FORMAT_DOUBLE },
157         { "video-bitrate", 0, MPV_FORMAT_DOUBLE },
158         { "paused-for-cache", 0, MPV_FORMAT_FLAG },
159         { "metadata", 0, MPV_FORMAT_NODE },
160         { "audio-device-list", 0, MPV_FORMAT_NODE },
161         { "filename", 0, MPV_FORMAT_STRING },
162         { "file-format", 0, MPV_FORMAT_STRING },
163         { "file-size", 0, MPV_FORMAT_STRING },
164         { "file-date-created", 0, MPV_FORMAT_NODE },
165         { "path", 0, MPV_FORMAT_STRING },
166         { "seekable", 0, MPV_FORMAT_FLAG }
167     };
168     QSet<QString> throttled = {
169         "time-pos", "avsync", "estimated-vf-fps", "frame-drop-count",
170         "decoder-frame-drop-count", "audio-bitrate", "video-bitrate"
171     };
172     QMetaObject::invokeMethod(ctrl, "observeProperties",
173                               Qt::QueuedConnection,
174                               Q_ARG(MpvController::PropertyList, options),
175                               Q_ARG(QSet<QString>, throttled));
176 
177     QMetaObject::invokeMethod(ctrl, "addHook",
178                               Qt::QueuedConnection,
179                               Q_ARG(QString, "on_unload"),
180                               Q_ARG(uint64_t, reinterpret_cast<uint64_t>(this)));
181 
182     QMetaObject::invokeMethod(ctrl, "setLogLevel",
183                               Qt::QueuedConnection,
184                               Q_ARG(QString, "info"));
185 }
186 
~MpvObject()187 MpvObject::~MpvObject()
188 {
189     if (widget)
190         delete widget;
191     if (hideTimer) {
192         delete hideTimer;
193         hideTimer = nullptr;
194     }
195     if (ctrl) {
196         // no explicit delete. deleteLater will be called at thread finish.
197         // Instead tell mpv to stop sending wake notifications so the
198         // can be reliably idle when we end it.
199         QMetaObject::invokeMethod(ctrl, "stop",
200                                   Qt::BlockingQueuedConnection);
201         ctrl = nullptr;
202     }
203     worker->quit();
204     worker->wait();
205     worker->deleteLater();
206 }
207 
setHostLayout(QLayout * hostLayout)208 void MpvObject::setHostLayout(QLayout *hostLayout)
209 {
210     if (!this->hostLayout)
211         this->hostLayout = hostLayout;
212 }
213 
setHostWindow(QMainWindow * hostWindow)214 void MpvObject::setHostWindow(QMainWindow *hostWindow)
215 {
216     if (!this->hostWindow)
217         this->hostWindow = hostWindow;
218 }
219 
setWidgetType(Helpers::MpvWidgetType widgetType,MpvWidgetInterface * customWidget)220 void MpvObject::setWidgetType(Helpers::MpvWidgetType widgetType, MpvWidgetInterface *customWidget)
221 {
222     if (this->widgetType == widgetType)
223         return;
224     this->widgetType = widgetType;
225 
226     if (widget) {
227         delete widget;
228         widget = nullptr;
229     }
230 
231     switch(widgetType) {
232     case Helpers::NullWidget:
233         widget = nullptr;
234         break;
235     case Helpers::EmbedWidget:
236         widget = new MpvEmbedWidget(this);
237         break;
238     case Helpers::GlCbWidget:
239         widget = new MpvGlWidget(this);
240         break;
241     case Helpers::VulkanCbWidget:
242         widget = new MpvVulkanCbWidget(this);
243         break;
244     case Helpers::CustomWidget:
245         widget = customWidget;
246         if (widget == nullptr)
247             widgetType = Helpers::NullWidget;
248         break;
249     }
250     if (!widget)
251         return;
252 
253     if (hostLayout)
254         hostLayout->addWidget(widget->self());
255     else if (hostWindow)
256         hostWindow->setCentralWidget(widget->self());
257     widget->setController(ctrl);
258     widget->initMpv();
259 }
260 
mpvVersion()261 QString MpvObject::mpvVersion()
262 {
263     return getMpvPropertyVariant("mpv-version").toString();
264 }
265 
controller()266 MpvController *MpvObject::controller()
267 {
268     return ctrl;
269 }
270 
mpvWidget()271 QWidget *MpvObject::mpvWidget()
272 {
273     return widget->self();
274 }
275 
audioDevices()276 QList<AudioDevice> MpvObject::audioDevices()
277 {
278     return AudioDevice::listFromVList(getMpvPropertyVariant("audio-device-list").toList());
279 }
280 
supportedProtocols()281 QStringList MpvObject::supportedProtocols()
282 {
283     return ctrl->protocolList();
284 }
285 
showMessage(QString message)286 void MpvObject::showMessage(QString message)
287 {
288     if (shownStatsPage == 0)
289         emit ctrlCommand(QVariantList({"show_text", message, "1000"}));
290 }
291 
showStatsPage(int page)292 void MpvObject::showStatsPage(int page)
293 {
294     emit ctrlShowStats(page);
295     shownStatsPage = page;
296 }
297 
cycleStatsPage()298 int MpvObject::cycleStatsPage()
299 {
300     showStatsPage(shownStatsPage < 2 ? shownStatsPage+1 : -1);
301     return shownStatsPage;
302 }
303 
selectedStatsPage()304 int MpvObject::selectedStatsPage()
305 {
306     return shownStatsPage;
307 }
308 
urlOpen(QUrl url)309 void MpvObject::urlOpen(QUrl url)
310 {
311     fileOpen(url.isLocalFile() ? url.toLocalFile()
312                                : url.fromPercentEncoding(url.toEncoded()));
313 }
314 
fileOpen(QString filename)315 void MpvObject::fileOpen(QString filename)
316 {
317     setSubFile("\n");
318     //setStartTime(0.0);
319     emit ctrlCommand(QStringList({"loadfile", filename}));
320     setMouseHideTime(hideTimer->interval());
321 }
322 
discFilesOpen(QString path)323 void MpvObject::discFilesOpen(QString path) {
324     QStringList entryList = QDir(path).entryList();
325     if (entryList.contains("VIDEO_TS") || entryList.contains("AUDIO_TS")) {
326         fileOpen(path + "/VIDEO_TS/VIDEO_TS.IFO");
327     } else if (entryList.contains("BDMV") || entryList.contains("AACS")) {
328         fileOpen("bluray://" + path);
329     }
330 }
331 
stopPlayback()332 void MpvObject::stopPlayback()
333 {
334     emit ctrlCommand("stop");
335 }
336 
stepBackward()337 void MpvObject::stepBackward()
338 {
339     emit ctrlCommand("frame_back_step");
340 }
341 
stepForward()342 void MpvObject::stepForward()
343 {
344     emit ctrlCommand("frame_step");
345 }
346 
seek(double amount,bool exact)347 void MpvObject::seek(double amount, bool exact)
348 {
349     QVariantList payload({"seek", amount});
350     if (exact)
351         payload.append("exact");
352     emit ctrlCommand(payload);
353 }
354 
screenshot(const QString & fileName,Helpers::ScreenshotRender render)355 void MpvObject::screenshot(const QString &fileName, Helpers::ScreenshotRender render)
356 {
357     static QMap <Helpers::ScreenshotRender,const char*> methods {
358         { Helpers::VideoRender, "video" },
359         { Helpers::SubsRender, "subtitles" },
360         { Helpers::WindowRender, "window" }
361     };
362     if (render == Helpers::WindowRender) {
363         widget->self()->grab().save(fileName);
364         return;
365     }
366     emit ctrlCommand(QStringList({"screenshot-to-file", fileName,
367                                   methods.value(render, "video")}));
368 }
369 
setMouseHideTime(int msec)370 void MpvObject::setMouseHideTime(int msec)
371 {
372     hideTimer->stop();
373     hideTimer->setInterval(msec);
374     showCursor();
375     if (msec > 0)
376         hideTimer->start();
377 }
378 
setLogoUrl(const QString & filename)379 void MpvObject::setLogoUrl(const QString &filename)
380 {
381     widget->setLogoUrl(filename);
382 }
383 
setLogoBackground(const QColor & color)384 void MpvObject::setLogoBackground(const QColor &color)
385 {
386     widget->setLogoBackground(color);
387 }
388 
setSubFile(QString filename)389 void MpvObject::setSubFile(QString filename)
390 {
391     emit ctrlSetOptionVariant("sub-files", filename);
392 }
393 
addSubFile(QString filename)394 void MpvObject::addSubFile(QString filename)
395 {
396     emit ctrlCommand(QStringList({"sub-add", filename}));
397 }
398 
chapter()399 int64_t MpvObject::chapter()
400 {
401     return getMpvPropertyVariant("chapter").toLongLong();
402 }
403 
setChapter(int64_t chapter)404 bool MpvObject::setChapter(int64_t chapter)
405 {
406     // As this requires knowledge of mpv's return value, it cannot be
407     // queued as a simple message.  The usual return values are:
408     // MPV_ERROR_PROPERTY_UNAVAILABLE: unchaptered file
409     // MPV_ERROR_PROPERTY_FORMAT: past-the-end value requested
410     // MPV_ERROR_SUCCESS: success
411     int r;
412     QMetaObject::invokeMethod(ctrl, "setPropertyVariant",
413                               Qt::BlockingQueuedConnection,
414                               Q_RETURN_ARG(int, r),
415                               Q_ARG(QString, "chapter"),
416                               Q_ARG(QVariant, QVariant(qlonglong(chapter))));
417     return r == MPV_ERROR_SUCCESS;
418 }
419 
mediaTitle()420 QString MpvObject::mediaTitle()
421 {
422     return getMpvPropertyVariant("media-title").toString();
423 }
424 
setMute(bool yes)425 void MpvObject::setMute(bool yes)
426 {
427     setMpvPropertyVariant("mute", yes);
428 }
429 
setPaused(bool yes)430 void MpvObject::setPaused(bool yes)
431 {
432     setMpvPropertyVariant("pause", yes);
433 }
434 
setSpeed(double speed)435 void MpvObject::setSpeed(double speed)
436 {
437     setMpvPropertyVariant("speed", speed);
438 }
439 
setTime(double position)440 void MpvObject::setTime(double position)
441 {
442     setMpvPropertyVariant("time-pos", position);
443 }
444 
setTimeSync(double position)445 void MpvObject::setTimeSync(double position)
446 {
447     ctrl->command(QVariantList() << "seek" << position << "absolute");
448 }
449 
setLoopPoints(double first,double end)450 void MpvObject::setLoopPoints(double first, double end)
451 {
452     setMpvPropertyVariant("ab-loop-a",
453                           first < 0 ? QVariant("no") : QVariant(first));
454     setMpvPropertyVariant("ab-loop-b",
455                           end < 0 ? QVariant("no") : QVariant(end));
456 }
457 
setAudioTrack(int64_t id)458 void MpvObject::setAudioTrack(int64_t id)
459 {
460     setMpvPropertyVariant("aid", qlonglong(id));
461 }
462 
setSubtitleTrack(int64_t id)463 void MpvObject::setSubtitleTrack(int64_t id)
464 {
465     setMpvPropertyVariant("sid", qlonglong(id));
466 }
467 
setVideoTrack(int64_t id)468 void MpvObject::setVideoTrack(int64_t id)
469 {
470     setMpvPropertyVariant("vid", qlonglong(id));
471 }
472 
setDrawLogo(bool yes)473 void MpvObject::setDrawLogo(bool yes)
474 {
475     widget->setDrawLogo(yes);
476 }
477 
setVolume(int64_t volume)478 void MpvObject::setVolume(int64_t volume)
479 {
480     static int64_t lastVolume = -1;
481     if (lastVolume == volume)
482         return;
483     lastVolume = volume;
484     setMpvPropertyVariant("volume", qlonglong(volume));
485 }
486 
eofReached()487 bool MpvObject::eofReached()
488 {
489     return getMpvPropertyVariant("eof-reached").toBool();
490 }
491 
setClientDebuggingMessages(bool yes)492 void MpvObject::setClientDebuggingMessages(bool yes)
493 {
494     debugMessages = yes;
495 }
496 
setMpvLogLevel(QString logLevel)497 void MpvObject::setMpvLogLevel(QString logLevel)
498 {
499     emit ctrlSetLogLevel(logLevel);
500 }
501 
playLength()502 double MpvObject::playLength()
503 {
504     return playLength_;
505 }
506 
playTime()507 double MpvObject::playTime()
508 {
509     return playTime_;
510 }
511 
videoSize()512 QSize MpvObject::videoSize()
513 {
514     return videoSize_;
515 }
516 
clientDebuggingMessages()517 bool MpvObject::clientDebuggingMessages()
518 {
519     return debugMessages;
520 }
521 
setCachedMpvOption(const QString & option,const QVariant & value)522 void MpvObject::setCachedMpvOption(const QString &option, const QVariant &value)
523 {
524     if (cachedState.contains(option) && cachedState.value(option) == value)
525         return;
526     cachedState.insert(option, value);
527     setMpvOptionVariant(option, value);
528 }
529 
blockingMpvCommand(QVariant params)530 QVariant MpvObject::blockingMpvCommand(QVariant params)
531 {
532     QVariant v;
533     QMetaObject::invokeMethod(ctrl, "command",
534                               Qt::BlockingQueuedConnection,
535                               Q_RETURN_ARG(QVariant, v),
536                               Q_ARG(QVariant, params));
537     return v;
538 }
539 
blockingSetMpvPropertyVariant(QString name,QVariant value)540 QVariant MpvObject::blockingSetMpvPropertyVariant(QString name, QVariant value)
541 {
542     int v;
543     QMetaObject::invokeMethod(ctrl, "setPropertyVariant",
544                               Qt::BlockingQueuedConnection,
545                               Q_RETURN_ARG(int, v),
546                               Q_ARG(QString, name),
547                               Q_ARG(QVariant, value));
548     return v == MPV_ERROR_SUCCESS ? QVariant()
549                                   : QVariant::fromValue(MpvErrorCode(v));
550 }
551 
blockingSetMpvOptionVariant(QString name,QVariant value)552 QVariant MpvObject::blockingSetMpvOptionVariant(QString name, QVariant value)
553 {
554     int v;
555     QMetaObject::invokeMethod(ctrl, "setOptionVariant",
556                               Qt::BlockingQueuedConnection,
557                               Q_RETURN_ARG(int, v),
558                               Q_ARG(QString, name),
559                               Q_ARG(QVariant, value));
560     return v == MPV_ERROR_SUCCESS ? QVariant()
561                                   : QVariant::fromValue(MpvErrorCode(v));
562 }
563 
getMpvPropertyVariant(QString name)564 QVariant MpvObject::getMpvPropertyVariant(QString name)
565 {
566     QVariant v;
567     QMetaObject::invokeMethod(ctrl, "getPropertyVariant",
568                               Qt::BlockingQueuedConnection,
569                               Q_RETURN_ARG(QVariant, v),
570                               Q_ARG(QString, name));
571     return v;
572 }
573 
574 
setMpvPropertyVariant(QString name,QVariant value)575 void MpvObject::setMpvPropertyVariant(QString name, QVariant value)
576 {
577     if (debugMessages)
578         LogStream("mpvobject") << name << " property set to " << value;
579     emit ctrlSetPropertyVariant(name, value);
580 }
581 
setMpvOptionVariant(QString name,QVariant value)582 void MpvObject::setMpvOptionVariant(QString name, QVariant value)
583 {
584     if (debugMessages)
585         LogStream("mpvobject") << name << " option set to " << value;
586     emit ctrlSetOptionVariant(name, value);
587 }
588 
showCursor()589 void MpvObject::showCursor()
590 {
591     widget->self()->setCursor(Qt::ArrowCursor);
592 }
593 
hideCursor()594 void MpvObject::hideCursor()
595 {
596     widget->self()->setCursor(Qt::BlankCursor);
597 }
598 
ctrl_mpvPropertyChanged(QString name,QVariant v)599 void MpvObject::ctrl_mpvPropertyChanged(QString name, QVariant v)
600 {
601     if (debugMessages)
602         LogStream("mpvobject") << name << " property changed to " << v;
603 
604     bool ok = v.type() < QVariant::UserType;
605     if (propertyDispatch.contains(name))
606         propertyDispatch[name](this, ok, v);
607     else
608         LogStream("mpvobject") << name << " property changed, but was not in dispatch list.";
609 }
610 
ctrl_hookEvent(QString name,uint64_t selfId,uint64_t mpvId)611 void MpvObject::ctrl_hookEvent(QString name, uint64_t selfId, uint64_t mpvId)
612 {
613     if (reinterpret_cast<MpvObject*>(selfId) != this)
614         return;
615 
616     if (name == "on_unload") {
617         QVariantList playlist = getMpvPropertyVariant("playlist").toList();
618         if (playlist.count() > 1)
619             emit playlistChanged(playlist);
620     }
621     emit ctrlContinueHook(mpvId);
622 }
623 
ctrl_unhandledMpvEvent(int eventLevel)624 void MpvObject::ctrl_unhandledMpvEvent(int eventLevel)
625 {
626     switch(eventLevel) {
627     case MPV_EVENT_START_FILE: {
628         if (debugMessages)
629             Logger::log("mpvobject", "start file");
630         emit playbackLoading();
631         break;
632     }
633     case MPV_EVENT_FILE_LOADED: {
634         if (debugMessages)
635             Logger::log("mpvobject", "file loaded");
636         emit playbackStarted();
637         break;
638     }
639     case MPV_EVENT_END_FILE: {
640         if (debugMessages)
641             Logger::log("mpvobject", "end file");
642         emit playbackFinished();
643         break;
644     }
645     case MPV_EVENT_IDLE: {
646         if (debugMessages)
647             Logger::log("mpvobject", "idling");
648         emit playbackIdling();
649         break;
650     }
651     case MPV_EVENT_SHUTDOWN: {
652         if (debugMessages)
653             Logger::log("mpvobject", "event shutdown");
654         emit playbackFinished();
655         break;
656     }
657     }
658 }
659 
ctrl_videoSizeChanged(QSize size)660 void MpvObject::ctrl_videoSizeChanged(QSize size)
661 {
662     videoSize_ = size;
663     emit videoSizeChanged(videoSize_);
664 }
665 
self_playTimeChanged(double playTime)666 void MpvObject::self_playTimeChanged(double playTime)
667 {
668     playTime_ = playTime;
669     emit playTimeChanged(playTime);
670 }
671 
self_playLengthChanged(double playLength)672 void MpvObject::self_playLengthChanged(double playLength)
673 {
674     playLength_ = playLength;
675     emit playLengthChanged(playLength);
676 }
677 
self_metadata(QVariantMap metadata)678 void MpvObject::self_metadata(QVariantMap metadata)
679 {
680     QVariantMap map;
681     for (auto it = metadata.begin(); it != metadata.end(); it++)
682         map.insert(it.key().toLower(), it.value());
683     emit metaDataChanged(map);
684 }
685 
self_audioDeviceList(const QVariantList & list)686 void MpvObject::self_audioDeviceList(const QVariantList &list)
687 {
688     emit audioDeviceList(AudioDevice::listFromVList(list));
689 }
690 
self_mouseMoved()691 void MpvObject::self_mouseMoved()
692 {
693     if (hideTimer->interval() > 0)
694         hideTimer->start();
695     showCursor();
696 }
697 
hideTimer_timeout()698 void MpvObject::hideTimer_timeout()
699 {
700     hideCursor();
701 }
702 
703 //----------------------------------------------------------------------------
704 
MpvWidgetInterface(MpvObject * object)705 MpvWidgetInterface::MpvWidgetInterface(MpvObject *object)
706     : mpvObject(object)
707 {
708     ctrl = object->controller();
709 }
710 
~MpvWidgetInterface()711 MpvWidgetInterface::~MpvWidgetInterface()
712 {
713     ctrl = nullptr;
714 }
715 
setController(MpvController * controller)716 void MpvWidgetInterface::setController(MpvController *controller)
717 {
718     ctrl = controller;
719 }
720 
setLogoUrl(const QString & filename)721 void MpvWidgetInterface::setLogoUrl(const QString &filename)
722 {
723     Q_UNUSED(filename)
724 }
725 
setLogoBackground(const QColor & color)726 void MpvWidgetInterface::setLogoBackground(const QColor &color)
727 {
728     Q_UNUSED(color)
729 }
730 
setDrawLogo(bool yes)731 void MpvWidgetInterface::setDrawLogo(bool yes)
732 {
733     Q_UNUSED(yes)
734 }
735 
736 
737 //----------------------------------------------------------------------------
738 
glMPGetNativeDisplay(const char * name)739 static void* GLAPIENTRY glMPGetNativeDisplay(const char* name)
740 {
741 #if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
742     if (!strcmp(name, "x11")) {
743         return QX11Info::display();
744     }
745 #elif defined(Q_OS_WIN)
746     if (!strcmp(name, "IDirect3DDevice9")) {
747         // Do something here ?
748     }
749 #else
750     Q_UNUSED(name);
751 #endif
752     return nullptr;
753 }
754 
get_proc_address(void * ctx,const char * name)755  void *MpvGlWidget::get_proc_address(void *ctx, const char *name)
756  {
757     (void)ctx;
758     auto glctx = QOpenGLContext::currentContext();
759     if (!strcmp(name, "glMPGetNativeDisplay"))
760         return reinterpret_cast<void*>(glMPGetNativeDisplay);
761     void *res = glctx ? reinterpret_cast<void*>(glctx->getProcAddress(QByteArray(name))) : nullptr;
762 
763 #ifdef Q_OS_WIN32
764     // QOpenGLContext::getProcAddress() in Qt 5.6 and below doesn't resolve all
765     // core OpenGL functions, so fall back to Windows' GetProcAddress().
766     if (!res) {
767         HMODULE module = (HMODULE)QOpenGLContext::openGLModuleHandle();
768         if (!module) {
769             // QOpenGLContext::openGLModuleHandle() returns NULL when Qt isn't
770             // using dynamic OpenGL. In this case, openGLModuleType() can be
771             // used to determine which module to query.
772             switch (QOpenGLContext::openGLModuleType()) {
773             case QOpenGLContext::LibGL:
774                 module = GetModuleHandleW(L"opengl32.dll");
775                 break;
776             case QOpenGLContext::LibGLES:
777                 module = GetModuleHandleW(L"libGLESv2.dll");
778                 break;
779             }
780         }
781         if (module)
782             res = (void*)GetProcAddress(module, name);
783     }
784 #endif
785 
786     return res;
787 }
788 
MpvGlWidget(MpvObject * object,QWidget * parent)789 MpvGlWidget::MpvGlWidget(MpvObject *object, QWidget *parent) :
790     QOpenGLWidget(parent), MpvWidgetInterface(object)
791 {
792     connect(this, &QOpenGLWidget::frameSwapped,
793             this, &MpvGlWidget::self_frameSwapped);
794     connect(mpvObject, &MpvObject::playbackStarted,
795             this, &MpvGlWidget::self_playbackStarted);
796     connect(mpvObject, &MpvObject::playbackFinished,
797             this, &MpvGlWidget::self_playbackFinished);
798     setContextMenuPolicy(Qt::CustomContextMenu);
799 }
800 
~MpvGlWidget()801 MpvGlWidget::~MpvGlWidget()
802 {
803     makeCurrent();
804     if (render) {
805         ctrl->destroyRenderContext(render);
806         render = nullptr;
807     }
808     if (logo) {
809         delete logo;
810         logo = nullptr;
811     }
812     doneCurrent();
813 }
814 
self()815 QWidget *MpvGlWidget::self()
816 {
817     return this;
818 }
819 
initMpv()820 void MpvGlWidget::initMpv()
821 {
822     // this takes place in initializeGL
823 }
824 
825 
setLogoUrl(const QString & filename)826 void MpvGlWidget::setLogoUrl(const QString &filename)
827 {
828     makeCurrent();
829     if (!logo) {
830         logo = new LogoDrawer(this);
831         connect(logo, &LogoDrawer::logoSize,
832                 mpvObject, &MpvObject::logoSizeChanged);
833     }
834     logo->setLogoUrl(filename);
835     logo->resizeGL(width(), height());
836     if (drawLogo)
837         update();
838     doneCurrent();
839 }
840 
setLogoBackground(const QColor & color)841 void MpvGlWidget::setLogoBackground(const QColor &color)
842 {
843     logo->setLogoBackground(color);
844 }
845 
setDrawLogo(bool yes)846 void MpvGlWidget::setDrawLogo(bool yes)
847 {
848     drawLogo = yes;
849     update();
850 }
851 
initializeGL()852 void MpvGlWidget::initializeGL()
853 {
854     if (!logo)
855         logo = new LogoDrawer(this);
856 
857     mpv_opengl_init_params glInit { &get_proc_address, this, nullptr };
858     mpv_render_param params[] {
859         { MPV_RENDER_PARAM_API_TYPE, const_cast<char*>(MPV_RENDER_API_TYPE_OPENGL) },
860         { MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &glInit },
861         { MPV_RENDER_PARAM_INVALID, nullptr },
862         { MPV_RENDER_PARAM_INVALID, nullptr }
863     };
864     QWidget *nativeParent = nativeParentWidget();
865     if (nativeParent == nullptr) {
866         Logger::log("glwidget", "no native parent handle");
867     }
868 #if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
869     else if (QGuiApplication::platformName().contains("xcb")) {
870         Logger::log("glwidget", "assigning x11 display");
871         params[2].type = MPV_RENDER_PARAM_X11_DISPLAY;
872         params[2].data = QX11Info::display();
873     } else if (QGuiApplication::platformName().contains("wayland")) {
874         Logger::log("glwidget", "assigning wayland display");
875         QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface();
876         params[2].type = MPV_RENDER_PARAM_WL_DISPLAY;
877         params[2].data = native->nativeResourceForWindow("display", nullptr);
878     } else
879 #endif
880     {
881         Logger::log("glwidget", "unknown display mode (eglfs et al)");
882     }
883     render = ctrl->createRenderContext(params);
884     mpv_render_context_set_update_callback(render, MpvGlWidget::render_update, this);
885 }
886 
paintGL()887 void MpvGlWidget::paintGL()
888 {
889     if (mpvObject->clientDebuggingMessages())
890         Logger::log("glwidget", "paintGL");
891     if (drawLogo || !render) {
892         if (logo)
893             logo->paintGL(this);
894         return;
895     }
896 
897     bool yes = true;
898     mpv_opengl_fbo fbo { static_cast<int>(defaultFramebufferObject()), glWidth, glHeight, 0 };
899     mpv_render_param params[] {
900         {MPV_RENDER_PARAM_OPENGL_FBO, &fbo },
901         {MPV_RENDER_PARAM_FLIP_Y, &yes}
902     };
903     mpv_render_context_render(render, params);
904 }
905 
resizeGL(int w,int h)906 void MpvGlWidget::resizeGL(int w, int h)
907 {
908     qreal r = devicePixelRatio();
909     glWidth = int(w * r);
910     glHeight = int(h * r);
911     logo->resizeGL(width(),height());
912 }
913 
mouseMoveEvent(QMouseEvent * event)914 void MpvGlWidget::mouseMoveEvent(QMouseEvent *event)
915 {
916     emit mpvObject->mouseMoved(event->x(), event->y());
917     QOpenGLWidget::mouseMoveEvent(event);
918 }
919 
mousePressEvent(QMouseEvent * event)920 void MpvGlWidget::mousePressEvent(QMouseEvent *event)
921 {
922     emit mpvObject->mousePress(event->x(), event->y());
923     QOpenGLWidget::mousePressEvent(event);
924 }
925 
render_update(void * ctx)926 void MpvGlWidget::render_update(void *ctx)
927 {
928     QMetaObject::invokeMethod(reinterpret_cast<MpvGlWidget*>(ctx), "maybeUpdate");
929 }
930 
maybeUpdate()931 void MpvGlWidget::maybeUpdate()
932 {
933     if (window()->isMinimized()) {
934         makeCurrent();
935         paintGL();
936         context()->swapBuffers(context()->surface());
937         self_frameSwapped();
938         doneCurrent();
939     } else {
940         update();
941     }
942 }
943 
self_frameSwapped()944 void MpvGlWidget::self_frameSwapped()
945 {
946     if (render && !drawLogo)
947         mpv_render_context_report_swap(render);
948 }
949 
self_playbackStarted()950 void MpvGlWidget::self_playbackStarted()
951 {
952     drawLogo = false;
953 }
954 
self_playbackFinished()955 void MpvGlWidget::self_playbackFinished()
956 {
957     drawLogo = true;
958     update();
959 }
960 
961 
962 
MpvCallback(const Callback & callback,QObject * owner)963 MpvCallback::MpvCallback(const Callback &callback,
964                          QObject *owner)
965     : QObject(owner)
966 {
967     this->callback = callback;
968 }
969 
reply(QVariant value)970 void MpvCallback::reply(QVariant value)
971 {
972     callback(value);
973     deleteLater();
974 }
975 
976 
977 
MpvController(QObject * parent)978 MpvController::MpvController(QObject *parent) : QObject(parent),
979     lastVideoSize(0,0)
980 {
981     throttler = new QTimer(this);
982     connect(throttler, &QTimer::timeout,
983             this, &MpvController::flushProperties);
984     throttler->setInterval(1000/12);
985     throttler->start();
986 }
987 
~MpvController()988 MpvController::~MpvController()
989 {
990     stop();
991     throttler->deleteLater();
992 }
993 
create(const OptionList & earlyOptions)994 void MpvController::create(const OptionList &earlyOptions)
995 {
996     mpv = mpv::qt::Handle::FromRawHandle(mpv_create());
997     if (!mpv)
998         throw std::runtime_error("could not create mpv context");
999 
1000     // Certain things like encoding options and input server need to be
1001     // set _before_ mpv initialize.
1002     for (const MpvOption &option : earlyOptions)
1003         setOptionVariant(option.name, option.value);
1004 
1005     if (mpv_initialize(mpv) < 0)
1006         throw std::runtime_error("could not initialize mpv context");
1007 
1008     mpv_set_wakeup_callback(mpv, MpvController::mpvWakeup, this);
1009     protocolList_ = getPropertyVariant("protocol-list").toStringList();
1010 }
1011 
stop()1012 void MpvController::stop()
1013 {
1014     mpv_set_wakeup_callback(mpv, nullptr, nullptr);
1015 }
1016 
createRenderContext(mpv_render_param * params)1017 mpv_render_context *MpvController::createRenderContext(mpv_render_param *params)
1018 {
1019     mpv_render_context *render = nullptr;
1020     if (mpv_render_context_create(&render, mpv, params) < 0)
1021         throw std::runtime_error("Could not create render context");
1022     return render;
1023 }
1024 
destroyRenderContext(mpv_render_context * render)1025 void MpvController::destroyRenderContext(mpv_render_context *render)
1026 {
1027     mpv_render_context_set_update_callback(render, nullptr, nullptr);
1028     mpv_render_context_free(render);
1029 }
1030 
addHook(const QString & name,uint64_t selfId)1031 void MpvController::addHook(const QString &name, uint64_t selfId)
1032 {
1033     // The caller of this function MUST listen to the hookEvent signal,
1034     // and when it hears its selfId, it MUST invoke continueHook.
1035     mpv_hook_add(mpv, selfId, name.toUtf8().data(), 0);
1036 }
1037 
continueHook(uint64_t mpvId)1038 void MpvController::continueHook(uint64_t mpvId)
1039 {
1040     mpv_hook_continue(mpv, mpvId);
1041 }
1042 
observeProperties(const MpvController::PropertyList & properties,const QSet<QString> & throttled)1043 int MpvController::observeProperties(const MpvController::PropertyList &properties,
1044                                       const QSet<QString> &throttled)
1045 {
1046     int rval = 0;
1047     foreach (const MpvProperty &item, properties)
1048         rval  = std::min(rval, mpv_observe_property(mpv, item.userData, item.name.toUtf8().data(), item.format));
1049     throttledProperties.unite(throttled);
1050     return rval;
1051 }
1052 
unobservePropertiesById(const QSet<uint64_t> & ids)1053 int MpvController::unobservePropertiesById(const QSet<uint64_t> &ids)
1054 {
1055     int rval = 0;
1056     foreach (uint64_t id, ids)
1057         rval = std::min(rval, mpv_unobserve_property(mpv, id));
1058     return rval;
1059 }
1060 
setThrottleTime(int msec)1061 void MpvController::setThrottleTime(int msec)
1062 {
1063     throttler->setInterval(msec);
1064 }
1065 
clientName()1066 QString MpvController::clientName()
1067 {
1068     return QString::fromUtf8(mpv_client_name(mpv));
1069 }
1070 
protocolList()1071 QStringList MpvController::protocolList()
1072 {
1073     return protocolList_;
1074 }
1075 
timeMicroseconds()1076 int64_t MpvController::timeMicroseconds()
1077 {
1078     return mpv_get_time_us(mpv);
1079 }
1080 
apiVersion()1081 unsigned long MpvController::apiVersion()
1082 {
1083     return mpv_client_api_version();
1084 }
1085 
setLogLevel(QString logLevel)1086 void MpvController::setLogLevel(QString logLevel)
1087 {
1088     mpv_request_log_messages(mpv, logLevel.toUtf8().data());
1089 }
1090 
showStatsPage(int page)1091 void MpvController::showStatsPage(int page)
1092 {
1093     bool statsVisible = (shownStatsPage > 0 && shownStatsPage < 3);
1094     bool wantVisible = (page > 0 && page < 3);
1095     if (wantVisible ^ statsVisible) {
1096         Logger::log("mpvctrl", "toggling stats page");
1097         command(QStringList({"script-binding",
1098                              "stats/display-stats-toggle"}));
1099     }
1100     if (wantVisible) {
1101         LogStream("mpvctrl") << "setting page to " << page;
1102         QString pageCommand("stats/display-page-%1");
1103         command(QStringList({"script-binding",
1104                              pageCommand.arg(QString::number(page))}));
1105     }
1106     shownStatsPage = page;
1107 }
1108 
setOptionVariant(QString name,const QVariant & value)1109 int MpvController::setOptionVariant(QString name, const QVariant &value)
1110 {
1111     return mpv::qt::set_option_variant(mpv, name, value);
1112 }
1113 
command(const QVariant & params)1114 QVariant MpvController::command(const QVariant &params)
1115 {
1116     if (params.canConvert<QString>()) {
1117         int value = mpv_command_string(mpv, params.toString().toUtf8().data());
1118         if (value < 0)
1119             return QVariant::fromValue(MpvErrorCode(value));
1120         return QVariant();
1121     }
1122 
1123     mpv::qt::node_builder node(params);
1124     mpv_node res;
1125     int value = mpv_command_node(mpv, node.node(), &res);
1126     if (value < 0)
1127         return QVariant::fromValue(MpvErrorCode(value));
1128     mpv::qt::node_autofree f(&res);
1129     QVariant v = mpv::qt::node_to_variant(&res);
1130     return v;
1131 }
1132 
setPropertyVariant(const QString & name,const QVariant & value)1133 int MpvController::setPropertyVariant(const QString &name, const QVariant &value)
1134 {
1135     return mpv::qt::set_property_variant(mpv, name, value);
1136 }
1137 
getPropertyVariant(const QString & name)1138 QVariant MpvController::getPropertyVariant(const QString &name)
1139 {
1140     mpv_node node;
1141     int r = mpv_get_property(mpv, name.toUtf8().data(), MPV_FORMAT_NODE, &node);
1142     if (r < 0)
1143         return QVariant::fromValue<MpvErrorCode>(MpvErrorCode(r));
1144     QVariant v = mpv::qt::node_to_variant(&node);
1145     mpv_free_node_contents(&node);
1146     return v;
1147 }
1148 
setPropertyString(const QString & name,const QString & value)1149 int MpvController::setPropertyString(const QString &name, const QString &value)
1150 {
1151     return mpv_set_property_string(mpv, name.toUtf8().data(), value.toUtf8().data());
1152 }
1153 
getPropertyString(const QString & name)1154 QString MpvController::getPropertyString(const QString &name)
1155 {
1156     char *c = mpv_get_property_string(mpv, name.toUtf8().data());
1157     if (!c)
1158         return QString();
1159     QByteArray b(c);
1160     mpv_free(c);
1161     return QString::fromUtf8(b);
1162 }
1163 
commandAsync(const QVariant & params,MpvCallback * callback)1164 void MpvController::commandAsync(const QVariant &params, MpvCallback *callback)
1165 {
1166     mpv::qt::node_builder node(params);
1167     mpv_command_node_async(mpv, reinterpret_cast<uint64_t>(callback),
1168                            node.node());
1169 }
1170 
setPropertyVariantAsync(const QString & name,const QVariant & value,MpvCallback * callback)1171 void MpvController::setPropertyVariantAsync(const QString &name,
1172                                             const QVariant &value,
1173                                             MpvCallback *callback)
1174 {
1175     mpv::qt::node_builder node(value);
1176     mpv_set_property_async(mpv, reinterpret_cast<uint64_t>(callback),
1177                            name.toUtf8().data(), MPV_FORMAT_NODE, node.node());
1178 }
1179 
getPropertyVariantAsync(const QString & name,MpvCallback * callback)1180 void MpvController::getPropertyVariantAsync(const QString &name,
1181                                             MpvCallback *callback)
1182 {
1183     mpv_get_property_async(mpv, reinterpret_cast<uint64_t>(callback),
1184                            name.toUtf8().data(), MPV_FORMAT_NODE);
1185 }
1186 
parseMpvEvents()1187 void MpvController::parseMpvEvents()
1188 {
1189     // Process all events, until the event queue is empty.
1190     while (mpv) {
1191         mpv_event *event = mpv_wait_event(mpv, 0);
1192         if (event->event_id == MPV_EVENT_NONE) {
1193             break;
1194         }
1195         handleMpvEvent(event);
1196     }
1197 }
1198 
setThrottledProperty(const QString & name,const QVariant & v,uint64_t userData)1199 void MpvController::setThrottledProperty(const QString &name, const QVariant &v, uint64_t userData)
1200 {
1201     throttledValues.insert(name, QPair<QVariant,uint64_t>(v,userData));
1202 }
1203 
flushProperties()1204 void MpvController::flushProperties()
1205 {
1206     for (auto it = throttledValues.begin(); it != throttledValues.end(); it++)
1207         emit mpvPropertyChanged(it.key(), it.value().first, it.value().second);
1208     throttledValues.clear();
1209 }
1210 
handleMpvEvent(mpv_event * event)1211 void MpvController::handleMpvEvent(mpv_event *event)
1212 {
1213     auto propertyToVariant = [event](mpv_event_property *prop) -> QVariant {
1214         auto asBool = [&](bool dflt = false) {
1215             return (prop->format != MPV_FORMAT_FLAG || prop->data == nullptr) ?
1216                         dflt : *reinterpret_cast<bool*>(prop->data);
1217         };
1218         auto asDouble = [&](double dflt = nan("")) {
1219             return (prop->format != MPV_FORMAT_DOUBLE || prop->data == nullptr) ?
1220                         dflt : *reinterpret_cast<double*>(prop->data);
1221         };
1222         auto asInt64 = [&](int64_t dflt = -1) {
1223             return (prop->format != MPV_FORMAT_INT64 || prop->data == nullptr) ?
1224                         dflt : *reinterpret_cast<int64_t*>(prop->data);
1225         };
1226         auto asString = [&](QString dflt = QString()) {
1227             return (!(prop->format == MPV_FORMAT_STRING ||
1228                       prop->format == MPV_FORMAT_OSD_STRING) ||
1229                     prop->data == nullptr) ?
1230                         dflt : QString(*reinterpret_cast<char**>(prop->data));
1231         };
1232         auto asNode = [&](QVariant dflt = QVariant()) {
1233             return (prop->format != MPV_FORMAT_NODE || prop->data == nullptr) ?
1234                         dflt : mpv::qt::node_to_variant(
1235                             reinterpret_cast<mpv_node*>(prop->data));
1236         };
1237         if (prop->data == nullptr) {
1238             return QVariant::fromValue<MpvErrorCode>(MpvErrorCode(event->error));
1239         } else if (prop->format == MPV_FORMAT_NODE) {
1240             return asNode();
1241         } else if (prop->format == MPV_FORMAT_INT64) {
1242             return qlonglong(asInt64());
1243         } else if (prop->format == MPV_FORMAT_DOUBLE) {
1244             return asDouble();
1245         } else if (prop->format == MPV_FORMAT_STRING ||
1246                    prop->format == MPV_FORMAT_OSD_STRING) {
1247             return asString();
1248         } else if (prop->format == MPV_FORMAT_FLAG) {
1249             return asBool();
1250         }
1251         return QVariant();
1252     };
1253 
1254     switch (event->event_id) {
1255     case MPV_EVENT_GET_PROPERTY_REPLY: {
1256         QVariant v = propertyToVariant(reinterpret_cast<mpv_event_property*>(event->data));
1257         if (!event->reply_userdata)
1258             return;
1259         QMetaObject::invokeMethod(reinterpret_cast<MpvCallback*>(event->reply_userdata),
1260                                   "reply", Qt::QueuedConnection,
1261                                   Q_ARG(QVariant, v));
1262         break;
1263     }
1264     case MPV_EVENT_COMMAND_REPLY:
1265     case MPV_EVENT_SET_PROPERTY_REPLY: {
1266         QVariant v = QVariant::fromValue<MpvErrorCode>(MpvErrorCode(event->error));
1267         if (!event->reply_userdata)
1268             return;
1269         QMetaObject::invokeMethod(reinterpret_cast<MpvCallback*>(event->reply_userdata),
1270                                   "reply", Qt::QueuedConnection,
1271                                   Q_ARG(QVariant, v));
1272         break;
1273     }
1274     case MPV_EVENT_PROPERTY_CHANGE: {
1275         QVariant v = propertyToVariant(reinterpret_cast<mpv_event_property*>(event->data));
1276         QString propname = QString::fromUtf8(reinterpret_cast<mpv_event_property*>(event->data)->name);
1277         if (throttledProperties.contains(propname))
1278             setThrottledProperty(propname, v, event->reply_userdata);
1279         else
1280             emit mpvPropertyChanged(propname, v, event->reply_userdata);
1281         break;
1282     }
1283     case MPV_EVENT_LOG_MESSAGE: {
1284         mpv_event_log_message *msg =
1285                 reinterpret_cast<mpv_event_log_message*>(event->data);
1286         emit logMessageByParts(msg->prefix, msg->level, msg->text);
1287         break;
1288     }
1289     case MPV_EVENT_CLIENT_MESSAGE: {
1290         mpv_event_client_message *msg =
1291                 reinterpret_cast<mpv_event_client_message*>(event->data);
1292         QStringList list;
1293         for (int i = 0; i < msg->num_args; i++)
1294             list.append(msg->args[i]);
1295         emit clientMessage(event->reply_userdata, list);
1296         break;
1297     }
1298     case MPV_EVENT_VIDEO_RECONFIG: {
1299         // Retrieve the new video size.
1300         QVariant vw, vh;
1301         vw = getPropertyVariant("dwidth");
1302         vh = getPropertyVariant("dheight");
1303         int w, h;
1304         if (!vw.canConvert<MpvErrorCode>() && !vh.canConvert<MpvErrorCode>()
1305                 && (w = vw.toInt()) > 0 && (h = vh.toInt()) > 0) {
1306             QSize videoSize(w, h);
1307             if (lastVideoSize != videoSize) {
1308                 emit videoSizeChanged(videoSize);
1309                 lastVideoSize = videoSize;
1310             }
1311         } else if (!lastVideoSize.isEmpty()) {
1312             lastVideoSize = QSize();
1313             emit videoSizeChanged(QSize());
1314         }
1315         break;
1316     }
1317     case MPV_EVENT_HOOK: {
1318         mpv_event_hook *msg = reinterpret_cast<mpv_event_hook*>(event->data);
1319         emit hookEvent(msg->name, event->reply_userdata, msg->id);
1320         break;
1321     }
1322     default:
1323         emit unhandledMpvEvent(event->event_id);
1324     }
1325 }
1326 
mpvWakeup(void * ctx)1327 void MpvController::mpvWakeup(void *ctx)
1328 {
1329     QMetaObject::invokeMethod(static_cast<MpvController*>(ctx), "parseMpvEvents",
1330                               Qt::QueuedConnection);
1331 }
1332