From 4f95b65f72f76aca3a0766645659de7fcc894a68 Mon Sep 17 00:00:00 2001
From: Kubat <mael.martin31@gmail.com>
Date: Tue, 3 Aug 2021 14:50:33 +0200
Subject: [PATCH] UI: Handle MPV events + set locale correctly for MPV

---
 src/UI/DocumentViews/MpvContainer.cc | 110 +++++++++++++++++++++++----
 src/UI/DocumentViews/MpvContainer.hh |  14 +++-
 src/VivyApplication.cc               |   4 +
 3 files changed, 111 insertions(+), 17 deletions(-)

diff --git a/src/UI/DocumentViews/MpvContainer.cc b/src/UI/DocumentViews/MpvContainer.cc
index 9693d75e..2ac832af 100644
--- a/src/UI/DocumentViews/MpvContainer.cc
+++ b/src/UI/DocumentViews/MpvContainer.cc
@@ -26,22 +26,35 @@ MpvContainer::MpvContainer()
     mpv_set_option_string(mpv, "input-default-bindings", "no");
     mpv_set_option_string(mpv, "input-vo-keyboard", "no");
     mpv_request_log_messages(mpv, "info");
+    mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_FLAG);
+    mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
     mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
 
     connect(this, &MpvContainer::mpvEvent, this, &MpvContainer::onMpvEvent, Qt::QueuedConnection);
     mpv_set_wakeup_callback(mpv, &MpvContainer::mpvEventWakeUpCB, this);
 
-    if (mpv_initialize(mpv) < 0)
+    if (int rc = mpv_initialize(mpv); rc < 0) {
+        printMpvError(rc);
         throw std::runtime_error("Failed to initialize the mpv context");
+    }
 }
 
 void
 MpvContainer::registerMpvTimeCallback(void (*callback)(double)) noexcept
 {
-    if (mpv_time_callback)
-        qWarning() << "Override a previous mpv callback, was" << mpv_time_callback
+    if (mpvTimeCallback)
+        qWarning() << "Override a previous mpv callback, was" << mpvTimeCallback
                    << "and now will be" << callback;
-    mpv_time_callback = callback;
+    mpvTimeCallback = callback;
+}
+
+void
+MpvContainer::registerMpvDurationCallback(void (*callback)(double)) noexcept
+{
+    if (mpvDurationCallback)
+        qWarning() << "Override a previous mpv callback, was" << mpvDurationCallback
+                   << "and now will be" << callback;
+    mpvDurationCallback = callback;
 }
 
 void
@@ -49,6 +62,7 @@ MpvContainer::closeMpv() noexcept
 {
     if (mpv) {
         registerMpvTimeCallback(nullptr);
+        registerMpvDurationCallback(nullptr);
         mpv_handle *tmp_mpv = mpv;
         mpv                 = nullptr; // Stop all other callbacks here
         mpv_destroy(tmp_mpv);
@@ -65,17 +79,24 @@ MpvContainer::handleMpvEvent(mpv_event *event) noexcept
 {
     // Declare here variables that can be used in the switch-case statements
     qint64 w, h;
+    double time;
     union {
         mpv_event_log_message *msg;
         mpv_event_property *prop;
     };
 
+    auto checkProp = [](mpv_event_property *prop, const std::string &str,
+                        int format) noexcept -> bool {
+        return (prop->name == str) && (prop->format == format);
+    };
+
     switch (event->event_id) {
     case MPV_EVENT_SHUTDOWN:
         closeMpv();
         break;
 
     case MPV_EVENT_VIDEO_RECONFIG:
+        // TODO: Those are sync calls, prefer async calls
         if (mpv_get_property(mpv, "dwidth", MPV_FORMAT_INT64, &w) >= 0 &&
             mpv_get_property(mpv, "dheight", MPV_FORMAT_INT64, &h) >= 0 && (w > 0 && h > 0)) {
             qDebug() << "Reconfigure video to" << w << "x" << h;
@@ -89,14 +110,37 @@ MpvContainer::handleMpvEvent(mpv_event *event) noexcept
 
     case MPV_EVENT_PROPERTY_CHANGE:
         prop = reinterpret_cast<mpv_event_property *>(event->data);
-        if (prop->name == "time-pos"s) {
-            if (prop->format == MPV_FORMAT_DOUBLE) {
-                double time = *reinterpret_cast<double *>(prop->data);
-                qDebug() << "Playback time:" << time;
-            } else {
-                qCritical() << "Got playback time but not a double!";
-            }
+        if (checkProp(prop, "time-pos"s, MPV_FORMAT_DOUBLE) && mpvTimeCallback) {
+            time = *reinterpret_cast<double *>(prop->data);
+            mpvTimeCallback(time);
+        }
+
+        else if (checkProp(prop, "duration"s, MPV_FORMAT_DOUBLE) && mpvDurationCallback) {
+            time = *reinterpret_cast<double *>(prop->data);
+            mpvDurationCallback(time);
         }
+
+        else if (checkProp(prop, "pause"s, MPV_FORMAT_FLAG)) {
+            isPlaybackPaused = *reinterpret_cast<bool *>(prop->data);
+            emit mpvPlaybackToggled(!isPlaybackPaused);
+        }
+
+        break;
+
+    case MPV_EVENT_PAUSE:
+        isPlaybackPaused = true;
+        break;
+
+    case MPV_EVENT_UNPAUSE:
+        isPlaybackPaused = false;
+        break;
+
+    case MPV_EVENT_START_FILE:
+        qDebug() << "MPV: Begin of file";
+        break;
+
+    case MPV_EVENT_END_FILE:
+        qDebug() << "MPV: Reached end of file!";
         break;
 
     // Explicitly ignored
@@ -104,14 +148,10 @@ MpvContainer::handleMpvEvent(mpv_event *event) noexcept
     case MPV_EVENT_GET_PROPERTY_REPLY:
     case MPV_EVENT_SET_PROPERTY_REPLY:
     case MPV_EVENT_COMMAND_REPLY:
-    case MPV_EVENT_START_FILE:
-    case MPV_EVENT_END_FILE:
     case MPV_EVENT_FILE_LOADED:
     case MPV_EVENT_TRACKS_CHANGED:
     case MPV_EVENT_TRACK_SWITCHED:
     case MPV_EVENT_IDLE:
-    case MPV_EVENT_PAUSE:
-    case MPV_EVENT_UNPAUSE:
     case MPV_EVENT_TICK:
     case MPV_EVENT_SCRIPT_INPUT_DISPATCH:
     case MPV_EVENT_CLIENT_MESSAGE:
@@ -131,8 +171,46 @@ MpvContainer::onMpvEvent() noexcept
 {
     while (mpv) {
         mpv_event *event = mpv_wait_event(mpv, 0);
-        if (event->event_id == MPV_EVENT_NONE)
+        if (event == nullptr || event->event_id == MPV_EVENT_NONE)
             break;
         handleMpvEvent(event);
     }
 }
+
+void
+MpvContainer::loadFile(const QString &filename) noexcept
+{
+    const QByteArray c_filename = filename.toUtf8();
+    const char *args[]          = { "loadfile", c_filename.data(), nullptr };
+    printMpvError(mpv_command_async(mpv, 0, args));
+}
+
+void
+MpvContainer::printMpvError(int rc) const noexcept
+{
+    if (rc == MPV_ERROR_SUCCESS)
+        return;
+
+    qCritical() << "MPV error:" << mpv_error_string(rc);
+}
+
+void
+MpvContainer::mpvPlay() noexcept
+{
+    if (isPlaybackPaused)
+        mpvTogglePlayback();
+}
+
+void
+MpvContainer::mpvPause() noexcept
+{
+    if (!isPlaybackPaused)
+        mpvTogglePlayback();
+}
+
+void
+MpvContainer::mpvTogglePlayback() noexcept
+{
+    const char *cmd[] = { "cycle", "pause", "up", nullptr };
+    printMpvError(mpv_command_async(mpv, 0, cmd));
+}
diff --git a/src/UI/DocumentViews/MpvContainer.hh b/src/UI/DocumentViews/MpvContainer.hh
index f56fdc2d..df1fe150 100644
--- a/src/UI/DocumentViews/MpvContainer.hh
+++ b/src/UI/DocumentViews/MpvContainer.hh
@@ -18,27 +18,39 @@ class MpvContainer : public QWidget {
     VIVY_UNMOVABLE_OBJECT(MpvContainer)
 
 private:
+    bool isPlaybackPaused{ true };
     mpv_handle *mpv{ nullptr };
-    void (*mpv_time_callback)(double){ nullptr };
+    void (*mpvTimeCallback)(double){ nullptr };
+    void (*mpvDurationCallback)(double){ nullptr };
 
 public:
     explicit MpvContainer();
     ~MpvContainer() noexcept override;
 
+    void loadFile(const QString &) noexcept;
+
     // Register a callback for time change, don't use Qt's signals for that.
     void registerMpvTimeCallback(void (*)(double)) noexcept;
+    void registerMpvDurationCallback(void (*)(double)) noexcept;
 
 private:
     void handleMpvEvent(mpv_event *) noexcept;
     void closeMpv() noexcept;
+    void printMpvError(int) const noexcept;
 
     // Must be static to be passed as a function ptr
     static void mpvEventWakeUpCB(void *) noexcept;
 
+public slots:
+    void mpvPlay() noexcept;
+    void mpvPause() noexcept;
+    void mpvTogglePlayback() noexcept;
+
 private slots:
     void onMpvEvent() noexcept;
 
 signals:
     void mpvEvent();
+    void mpvPlaybackToggled(bool isPlaying);
 };
 }
diff --git a/src/VivyApplication.cc b/src/VivyApplication.cc
index 86ef22ab..70c84396 100644
--- a/src/VivyApplication.cc
+++ b/src/VivyApplication.cc
@@ -4,6 +4,7 @@
 #include <QtGlobal>
 #include <QIcon>
 #include <QFontDatabase>
+#include <locale>
 
 using namespace Vivy;
 
@@ -34,6 +35,9 @@ VivyApplication::setTheme(Theme theme) noexcept
 int
 VivyApplication::exec() noexcept
 {
+    // For MPV
+    std::setlocale(LC_NUMERIC, "C");
+
     // Add fonts
     fontIdMonospace     = QFontDatabase::addApplicationFont(":/fonts/FiraCode-Regular.ttf");
     fontIdMonospaceBold = QFontDatabase::addApplicationFont(":/fonts/FiraCode-Bold.ttf");
-- 
GitLab