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 ¶ms)
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 ¶ms, 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