diff --git a/src/UI/DocumentViews/MpvWidget.cc b/src/UI/DocumentViews/MpvWidget.cc new file mode 100644 index 0000000000000000000000000000000000000000..04e88150f739a35a892f171a905fc0d43917f4c8 --- /dev/null +++ b/src/UI/DocumentViews/MpvWidget.cc @@ -0,0 +1,341 @@ +#include "MpvWidget.hh" + +#include <stdexcept> +#include <QtGui/QOpenGLContext> +#include <QtCore/QMetaObject> +#include <QApplication> +#include <QKeyEvent> +#include <QString> + +#include "QtHelper.hh" + +namespace Vivy +{ + +static void +wakeup(void *ctx) +{ + QMetaObject::invokeMethod(static_cast<MpvWidget *>(ctx), "onMpvEvents", Qt::QueuedConnection); +} + +static void * +getProcAddress(void *ctx, const char *name) +{ + Q_UNUSED(ctx); + QOpenGLContext *glctx = QOpenGLContext::currentContext(); + if (!glctx) + return nullptr; + return reinterpret_cast<void *>(glctx->getProcAddress(QByteArray(name))); +} + +MpvWidget::MpvWidget(QWidget *parent) + : QOpenGLWidget(parent) +{ + mpv = mpv_create(); + if (!mpv) + throw std::runtime_error("could not create mpv context"); + + setFocusPolicy(Qt::StrongFocus); + + mpv_set_option_string(mpv, "terminal", "yes"); + mpv_set_option_string(mpv, "msg-level", "all=v"); + mpv_set_option_string(mpv, "osc", "yes"); + if (mpv_initialize(mpv) < 0) + throw std::runtime_error("could not initialize mpv context"); + + // Request hw decoding, just for testing. + mpv_set_option_string(mpv, "hwdec", "auto"); + + mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE); + mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE); + mpv_set_wakeup_callback(mpv, wakeup, this); +} + +MpvWidget::~MpvWidget() +{ + makeCurrent(); + if (mpv_gl) + mpv_render_context_free(mpv_gl); + mpv_terminate_destroy(mpv); +} + +void +MpvWidget::command(const QVariant ¶ms) +{ + mpv::qt::command(mpv, params); +} + +void +MpvWidget::setProperty(const QString &name, const QVariant &value) +{ + mpv::qt::set_property(mpv, name, value); +} + +QVariant +MpvWidget::getProperty(const QString &name) const +{ + return mpv::qt::get_property(mpv, name); +} + +void +MpvWidget::initializeGL() +{ + mpv_opengl_init_params gl_init_params{ getProcAddress, nullptr, nullptr }; + mpv_render_param params[]{ { MPV_RENDER_PARAM_API_TYPE, + const_cast<char *>(MPV_RENDER_API_TYPE_OPENGL) }, + { MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params }, + { MPV_RENDER_PARAM_INVALID, nullptr } }; + + if (mpv_render_context_create(&mpv_gl, mpv, params) < 0) + throw std::runtime_error("failed to initialize mpv GL context"); + mpv_render_context_set_update_callback(mpv_gl, MpvWidget::onUpdate, + reinterpret_cast<void *>(this)); +} + +void +MpvWidget::paintGL() +{ + mpv_opengl_fbo mpfbo{ static_cast<int>(defaultFramebufferObject()), width(), height(), 0 }; + int flip_y{ 1 }; + + mpv_render_param params[] = { { MPV_RENDER_PARAM_OPENGL_FBO, &mpfbo }, + { MPV_RENDER_PARAM_FLIP_Y, &flip_y }, + { MPV_RENDER_PARAM_INVALID, nullptr } }; + // See render_gl.h on what OpenGL environment mpv expects, and + // other API details. + mpv_render_context_render(mpv_gl, params); +} + +void +MpvWidget::onMpvEvents() +{ + // Process all events, until the event queue is empty. + while (mpv) { + mpv_event *event = mpv_wait_event(mpv, 0); + if (event->event_id == MPV_EVENT_NONE) { + break; + } + handleMpvEvent(event); + } +} + +void +MpvWidget::handleMpvEvent(mpv_event *event) +{ + size_t ao_volume; + mpv_event_property *prop; + (void)ao_volume; + (void)prop; + + switch (event->event_id) { + case MPV_EVENT_PAUSE: m_state = PAUSE; break; + case MPV_EVENT_UNPAUSE: m_state = PLAY; break; + + case MPV_EVENT_SHUTDOWN: m_state = STOP; break; + + case MPV_EVENT_START_FILE: break; + + case MPV_EVENT_END_FILE: break; + + case MPV_EVENT_PROPERTY_CHANGE: { + prop = static_cast<mpv_event_property *>(event->data); + if (strcmp(prop->name, "time-pos") == 0) { + if (prop->format == MPV_FORMAT_DOUBLE) { + m_position = static_cast<int>(*reinterpret_cast<double *>(prop->data)); + } + } else if (strcmp(prop->name, "duration") == 0) { + if (prop->format == MPV_FORMAT_DOUBLE) { + m_duration = static_cast<int>(*reinterpret_cast<double *>(prop->data)); + } + } + break; + } + + case MPV_EVENT_IDLE: emit titleChanged("[Lektord] Stopped"); break; + + case MPV_EVENT_LOG_MESSAGE: + case MPV_EVENT_GET_PROPERTY_REPLY: + case MPV_EVENT_SET_PROPERTY_REPLY: + case MPV_EVENT_NONE: + case MPV_EVENT_COMMAND_REPLY: + case MPV_EVENT_FILE_LOADED: + case MPV_EVENT_TRACKS_CHANGED: + case MPV_EVENT_TRACK_SWITCHED: + case MPV_EVENT_TICK: + case MPV_EVENT_SCRIPT_INPUT_DISPATCH: + case MPV_EVENT_CLIENT_MESSAGE: + case MPV_EVENT_VIDEO_RECONFIG: + case MPV_EVENT_AUDIO_RECONFIG: + case MPV_EVENT_METADATA_UPDATE: + case MPV_EVENT_SEEK: + case MPV_EVENT_CHAPTER_CHANGE: + case MPV_EVENT_PLAYBACK_RESTART: + case MPV_EVENT_QUEUE_OVERFLOW: + case MPV_EVENT_HOOK: break; + } +} + +// Make Qt invoke mpv_render_context_render() to draw a new/updated video frame. +void +MpvWidget::maybeUpdate() +{ + // If the Qt window is not visible, Qt's update() will just skip rendering. + // This confuses mpv's render API, and may lead to small occasional + // freezes due to video rendering timing out. + // Handle this by manually redrawing. + // Note: Qt doesn't seem to provide a way to query whether update() will + // be skipped, and the following code still fails when e.g. switching + // to a different workspace with a reparenting window manager. + if (window()->isMinimized()) { + makeCurrent(); + paintGL(); + context()->swapBuffers(context()->surface()); + doneCurrent(); + } else { + update(); + } +} + +void +MpvWidget::onUpdate(void *ctx) +{ + QMetaObject::invokeMethod(static_cast<MpvWidget *>(ctx), "maybeUpdate"); +} + +bool +MpvWidget::getElapsed(int *elapsed_sec) +{ + *elapsed_sec = m_position; + return true; +} + +bool +MpvWidget::getDuration(int *dur_sec) +{ + *dur_sec = m_duration; + return true; +} + +bool +MpvWidget::setPaused(int paused) +{ + const char *cmd[] = { "set", "pause", paused == 1 ? "yes" : "no", nullptr }; + mpv_command_async(mpv, 0, cmd); + return true; +} + +bool +MpvWidget::setVolume([[maybe_unused]] int vol) +{ + return true; +} + +bool +MpvWidget::setPosition([[maybe_unused]] int sec) +{ + throw std::logic_error("lmpv_set_position"); +} + +bool +MpvWidget::loadFile(const QString &filepath) +{ + const bool ret = false; + throw std::runtime_error("lmpv_load_file"); + // const bool ret = !lmpv_load_file(mpv, filepath); + + if (ret) { + m_state = NONSTOPPED; + } + return ret; +} + +bool +MpvWidget::togglePause() +{ + const char *cmd[] = { "cycle", "pause", nullptr }; + mpv_command_async(mpv, 0, cmd); + return true; +} + +bool +MpvWidget::stop() +{ + m_state = STOP; + const char *cmd[] = { "stop", nullptr }; + mpv_command_async(mpv, 0, cmd); + emit titleChanged("[Lektord] Stopped"); + return true; +} + +#define MPV_SEND_COMMAND_ASYNC(...) \ + { \ + const char *cmd[] = { __VA_ARGS__ }; \ + mpv_command_async(mpv, 0, cmd); \ + break; \ + } +void +MpvWidget::keyPressEvent(QKeyEvent *event) +{ + if (m_state == STOP) + return QOpenGLWidget::keyPressEvent(event); + + switch (event->modifiers()) { + /* SHIFTED */ + case Qt::ShiftModifier: + switch (event->key()) { + case Qt::Key_J: MPV_SEND_COMMAND_ASYNC("osd-msg", "cycle", "sub", nullptr); + case Qt::Key_Period: MPV_SEND_COMMAND_ASYNC("osd-msg", "frame-step", nullptr); + case Qt::Key_Z: MPV_SEND_COMMAND_ASYNC("osd-msg", "add", "sub-delay", "+0.1", nullptr); + case Qt::Key_G: MPV_SEND_COMMAND_ASYNC("osd-msg", "add", "sub-scale", "+0.1", nullptr); + case Qt::Key_F: MPV_SEND_COMMAND_ASYNC("osd-msg", "add", "sub-scale", "-0.1", nullptr); + } + break; + + /* UN-SHIFTED */ + default: + switch (event->key()) { + /* Playback */ + case Qt::Key_Space: + throw std::logic_error("lmpv_toggle_pause"); + // lmpv_toggle_pause(mpv); + break; + case Qt::Key_Return: + case Qt::Key_Left: MPV_SEND_COMMAND_ASYNC("osd-msg-bar", "seek", "-5", "relative", nullptr); + case Qt::Key_Right: + MPV_SEND_COMMAND_ASYNC("osd-msg-bar", "seek", "+5", "relative", nullptr); + case Qt::Key_Down: + MPV_SEND_COMMAND_ASYNC("osd-msg-bar", "seek", "-60", "relative", nullptr); + case Qt::Key_Up: MPV_SEND_COMMAND_ASYNC("osd-msg-bar", "seek", "+60", "relative", nullptr); + case Qt::Key_L: MPV_SEND_COMMAND_ASYNC("osd-msg", "ab-loop", nullptr); + case Qt::Key_O: MPV_SEND_COMMAND_ASYNC("osd-msg-bar", "show-progress", nullptr); + case Qt::Key_BracketLeft: + MPV_SEND_COMMAND_ASYNC("osd-msg", "multiply", "speed", "1/1.1", nullptr); + case Qt::Key_BracketRight: + MPV_SEND_COMMAND_ASYNC("osd-msg", "multiply", "speed", "1.1", nullptr); + case Qt::Key_BraceLeft: + MPV_SEND_COMMAND_ASYNC("osd-msg", "multiply", "speed", "0.5", nullptr); + case Qt::Key_BraceRight: + MPV_SEND_COMMAND_ASYNC("osd-msg", "multiply", "speed", "2", nullptr); + case Qt::Key_Backspace: MPV_SEND_COMMAND_ASYNC("osd-msg", "set", "speed", "1.0", nullptr); + case Qt::Key_Semicolon: MPV_SEND_COMMAND_ASYNC("osd-msg", "frame-step", nullptr); + case Qt::Key_Comma: MPV_SEND_COMMAND_ASYNC("osd-msg", "frame-back-step", nullptr); + + /* Track management */ + case Qt::Key_NumberSign: MPV_SEND_COMMAND_ASYNC("osd-msg", "cycle", "audio", nullptr); + case Qt::Key_J: MPV_SEND_COMMAND_ASYNC("osd-msg", "cycle", "sub", "down", nullptr); + case Qt::Key_Underscore: MPV_SEND_COMMAND_ASYNC("osd-msg", "cycle", "video", nullptr); + + /* Misc */ + case Qt::Key_I: MPV_SEND_COMMAND_ASYNC("script-binding", "stats/display-stats", nullptr); + case Qt::Key_Delete: + MPV_SEND_COMMAND_ASYNC("script-message", "osc-visibility", + (m_oscVisible = !m_oscVisible) ? "always" : "never", nullptr); + case Qt::Key_Z: MPV_SEND_COMMAND_ASYNC("osd-msg", "add", "sub-delay", "-0.1", nullptr); + case Qt::Key_M: MPV_SEND_COMMAND_ASYNC("osd-msg", "cycle", "mute", nullptr); + + default: break; + } + return QOpenGLWidget::keyPressEvent(event); + } +} +} diff --git a/src/UI/DocumentViews/MpvWidget.hh b/src/UI/DocumentViews/MpvWidget.hh new file mode 100644 index 0000000000000000000000000000000000000000..d392e8560b7e6f6e9cefb75732f05e8f0ac70762 --- /dev/null +++ b/src/UI/DocumentViews/MpvWidget.hh @@ -0,0 +1,63 @@ +#pragma once + +#include <QtWidgets/QOpenGLWidget> +#include <QString> +#include <QtGui> + +#include <mpv/client.h> +#include <mpv/render_gl.h> + +namespace Vivy +{ +class MpvWidget final : public QOpenGLWidget { + Q_OBJECT + +public: + MpvWidget(QWidget *parent); + ~MpvWidget() override; + + void command(const QVariant ¶ms); + void setProperty(const QString &name, const QVariant &value); + QVariant getProperty(const QString &name) const; + QSize sizeHint() const override { return QSize(480, 270); } + +protected: + void initializeGL() override; + void paintGL() override; + +private slots: + void onMpvEvents(); + void maybeUpdate(); + +public: + void handleMpvEvent(mpv_event *event); + static void onUpdate(void *ctx); + +private: + enum { PLAY, PAUSE, STOP, NONSTOPPED } m_state = STOP; + + mpv_handle *mpv; + mpv_render_context *mpv_gl; + + int m_position; + int m_duration; + bool m_oscVisible = false; + bool m_inhib = false; + +protected: + void keyPressEvent(QKeyEvent *event) override; + +public: + bool getElapsed(int *); + bool getDuration(int *); + bool setPaused(int); + bool setVolume(int); + bool setPosition(int); + bool loadFile(const QString &); + bool togglePause(); + bool stop(); + +signals: + void titleChanged(QString str); +}; +} diff --git a/src/UI/DocumentViews/QtHelper.hh b/src/UI/DocumentViews/QtHelper.hh new file mode 100644 index 0000000000000000000000000000000000000000..4ef7a109fe8a208ba3d59c32d4124d3c91918af2 --- /dev/null +++ b/src/UI/DocumentViews/QtHelper.hh @@ -0,0 +1,318 @@ +#pragma once + +#include <cstring> + +#include <QVariant> +#include <QString> +#include <QList> +#include <QHash> +#include <QSharedPointer> +#include <QMetaType> + +namespace mpv::qt +{ + +// Wrapper around mpv_handle. Does refcounting under the hood. +class Handle { + struct container { + container(mpv_handle *h) + : mpv(h) + { + } + ~container() { mpv_terminate_destroy(mpv); } + mpv_handle *mpv; + }; + QSharedPointer<container> sptr; + +public: + // Construct a new Handle from a raw mpv_handle with refcount 1. If the + // last Handle goes out of scope, the mpv_handle will be destroyed with + // mpv_terminate_destroy(). + // Never destroy the mpv_handle manually when using this wrapper. You + // will create dangling pointers. Just let the wrapper take care of + // destroying the mpv_handle. + // Never create multiple wrappers from the same raw mpv_handle; copy the + // wrapper instead (that's what it's for). + static Handle FromRawHandle(mpv_handle *handle) + { + Handle h; + h.sptr = QSharedPointer<container>(new container(handle)); + return h; + } + + // Return the raw handle; for use with the libmpv C API. + operator mpv_handle *() const { return sptr ? (*sptr).mpv : nullptr; } +}; + +static inline QVariant +node_to_variant(const mpv_node *node) +{ + switch (node->format) { + case MPV_FORMAT_STRING: return QVariant(QString::fromUtf8(node->u.string)); + case MPV_FORMAT_FLAG: return QVariant(static_cast<bool>(node->u.flag)); + case MPV_FORMAT_INT64: return QVariant(static_cast<qlonglong>(node->u.int64)); + case MPV_FORMAT_DOUBLE: return QVariant(node->u.double_); + + case MPV_FORMAT_NODE_ARRAY: { + mpv_node_list *list = node->u.list; + QVariantList qlist; + for (int n = 0; n < list->num; n++) + qlist.append(node_to_variant(&list->values[n])); + return QVariant(qlist); + } + + case MPV_FORMAT_NODE_MAP: { + mpv_node_list *list = node->u.list; + QVariantMap qmap; + for (int n = 0; n < list->num; n++) { + qmap.insert(QString::fromUtf8(list->keys[n]), node_to_variant(&list->values[n])); + } + return QVariant(qmap); + } + + case MPV_FORMAT_OSD_STRING: + case MPV_FORMAT_NONE: + case MPV_FORMAT_NODE: + case MPV_FORMAT_BYTE_ARRAY: return QVariant(); + } +} + +struct node_builder { + node_builder(const QVariant &v) { set(&node_, v); } + ~node_builder() { free_node(&node_); } + mpv_node *node() { return &node_; } + +private: + Q_DISABLE_COPY(node_builder) + mpv_node node_; + + mpv_node_list *create_list(mpv_node *dst, bool is_map, int num) + { + dst->format = is_map ? MPV_FORMAT_NODE_MAP : MPV_FORMAT_NODE_ARRAY; + mpv_node_list *list = new mpv_node_list(); + dst->u.list = list; + if (!list) + goto err; + list->values = new mpv_node[static_cast<size_t>(num)](); + if (!list->values) + goto err; + if (is_map) { + list->keys = new char *[static_cast<size_t>(num)](); + if (!list->keys) + goto err; + } + return list; + err: + free_node(dst); + return nullptr; + } + + char *dup_qstring(const QString &s) + { + QByteArray b = s.toUtf8(); + char *r = new char[static_cast<size_t>(b.size() + 1)]; + if (r) + std::memcpy(r, b.data(), static_cast<size_t>(b.size() + 1)); + return r; + } + + bool test_type(const QVariant &v, QMetaType::Type t) + { + // The Qt docs say: "Although this function is declared as returning + // "QVariant::Type(obsolete), the return value should be interpreted + // as QMetaType::Type." + // So a cast really seems to be needed to avoid warnings (urgh). + return static_cast<int>(v.type()) == static_cast<int>(t); + } + + void set(mpv_node *dst, const QVariant &src) + { + if (test_type(src, QMetaType::QString)) { + dst->format = MPV_FORMAT_STRING; + dst->u.string = dup_qstring(src.toString()); + if (!dst->u.string) + goto fail; + } else if (test_type(src, QMetaType::Bool)) { + dst->format = MPV_FORMAT_FLAG; + dst->u.flag = src.toBool() ? 1 : 0; + } else if (test_type(src, QMetaType::Int) || test_type(src, QMetaType::LongLong) || + test_type(src, QMetaType::UInt) || test_type(src, QMetaType::ULongLong)) { + dst->format = MPV_FORMAT_INT64; + dst->u.int64 = src.toLongLong(); + } else if (test_type(src, QMetaType::Double)) { + dst->format = MPV_FORMAT_DOUBLE; + dst->u.double_ = src.toDouble(); + } else if (src.canConvert<QVariantList>()) { + QVariantList qlist = src.toList(); + mpv_node_list *list = create_list(dst, false, qlist.size()); + if (!list) + goto fail; + list->num = qlist.size(); + for (int n = 0; n < qlist.size(); n++) + set(&list->values[n], qlist[n]); + } else if (src.canConvert<QVariantMap>()) { + QVariantMap qmap = src.toMap(); + mpv_node_list *list = create_list(dst, true, qmap.size()); + if (!list) + goto fail; + list->num = qmap.size(); + for (int n = 0; n < qmap.size(); n++) { + list->keys[n] = dup_qstring(qmap.keys()[n]); + if (!list->keys[n]) { + free_node(dst); + goto fail; + } + set(&list->values[n], qmap.values()[n]); + } + } else { + goto fail; + } + return; + fail: + dst->format = MPV_FORMAT_NONE; + } + + void free_node(mpv_node *dst) + { + mpv_node_list *list = nullptr; + switch (dst->format) { + case MPV_FORMAT_STRING: delete[] dst->u.string; break; + + case MPV_FORMAT_NODE_ARRAY: + case MPV_FORMAT_NODE_MAP: + list = dst->u.list; + if (list) { + for (int n = 0; n < list->num; n++) { + if (list->keys) + delete[] list->keys[n]; + if (list->values) + free_node(&list->values[n]); + } + delete[] list->keys; + delete[] list->values; + } + delete list; + break; + + case MPV_FORMAT_OSD_STRING: + case MPV_FORMAT_NONE: + case MPV_FORMAT_NODE: + case MPV_FORMAT_FLAG: + case MPV_FORMAT_INT64: + case MPV_FORMAT_DOUBLE: + case MPV_FORMAT_BYTE_ARRAY: break; + } + + dst->format = MPV_FORMAT_NONE; + } +}; + +/** + * RAII wrapper that calls mpv_free_node_contents() on the pointer. + */ +struct node_autofree { + mpv_node *ptr; + node_autofree(mpv_node *a_ptr) + : ptr(a_ptr) + { + } + ~node_autofree() { mpv_free_node_contents(ptr); } +}; + +/** + * This is used to return error codes wrapped in QVariant for functions which + * return QVariant. + * + * You can use get_error() or is_error() to extract the error status from a + * QVariant value. + */ +struct ErrorReturn { + /** + * enum mpv_error value (or a value outside of it if ABI was extended) + */ + int error; + + ErrorReturn() + : error(0) + { + } + explicit ErrorReturn(int err) + : error(err) + { + } +}; + +/** + * Return the mpv error code packed into a QVariant, or 0 (success) if it's not + * an error value. + * + * @return error code (<0) or success (>=0) + */ +static inline int +get_error(const QVariant &v) +{ + if (!v.canConvert<ErrorReturn>()) + return 0; + return v.value<ErrorReturn>().error; +} + +/** + * Return whether the QVariant carries a mpv error code. + */ +static inline bool +is_error(const QVariant &v) +{ + return get_error(v) < 0; +} + +/** + * Return the given property as mpv_node converted to QVariant, or QVariant() + * on error. + * + * @param name the property name + * @return the property value, or an ErrorReturn with the error code + */ +static inline QVariant +get_property(mpv_handle *ctx, const QString &name) +{ + mpv_node node; + int err = mpv_get_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, &node); + if (err < 0) + return QVariant::fromValue(ErrorReturn(err)); + node_autofree f(&node); + return node_to_variant(&node); +} + +/** + * Set the given property as mpv_node converted from the QVariant argument. + * + * @return mpv error code (<0 on error, >= 0 on success) + */ +static inline int +set_property(mpv_handle *ctx, const QString &name, const QVariant &v) +{ + node_builder node(v); + return mpv_set_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node()); +} + +/** + * mpv_command_node() equivalent. + * + * @param args command arguments, with args[0] being the command name as string + * @return the property value, or an ErrorReturn with the error code + */ +static inline QVariant +command(mpv_handle *ctx, const QVariant &args) +{ + node_builder node(args); + mpv_node res; + int err = mpv_command_node(ctx, node.node(), &res); + if (err < 0) + return QVariant::fromValue(ErrorReturn(err)); + node_autofree f(&res); + return node_to_variant(&res); +} + +} + +Q_DECLARE_METATYPE(mpv::qt::ErrorReturn) diff --git a/src/UI/DocumentViews/MpvContainer.cc b/src/UI/DocumentViews/old/MpvContainer.cc similarity index 97% rename from src/UI/DocumentViews/MpvContainer.cc rename to src/UI/DocumentViews/old/MpvContainer.cc index ec7c8d477ff85ca4f3981af5b13462a3b6c26163..b8a3fa9789acfd693a55c0eb299d7914915301f6 100644 --- a/src/UI/DocumentViews/MpvContainer.cc +++ b/src/UI/DocumentViews/old/MpvContainer.cc @@ -58,12 +58,14 @@ MpvContainer::initializeMpv() void MpvContainer::reCreateMpvContext() { - closeMpv(); - mpv = mpv_create(); - if (mpv == nullptr) - throw std::runtime_error("Failed to create the MPV context"); - initializeMpv(); - loadFile(previousLoadedFile); + if (!isMpvAlreadyInitialized) { + closeMpv(); + mpv = mpv_create(); + if (mpv == nullptr) + throw std::runtime_error("Failed to create the MPV context"); + initializeMpv(); + loadFile(previousLoadedFile); + } } void diff --git a/src/UI/DocumentViews/MpvContainer.hh b/src/UI/DocumentViews/old/MpvContainer.hh similarity index 100% rename from src/UI/DocumentViews/MpvContainer.hh rename to src/UI/DocumentViews/old/MpvContainer.hh diff --git a/src/UI/DocumentViews/MpvControls.cc b/src/UI/DocumentViews/old/MpvControls.cc similarity index 100% rename from src/UI/DocumentViews/MpvControls.cc rename to src/UI/DocumentViews/old/MpvControls.cc diff --git a/src/UI/DocumentViews/MpvControls.hh b/src/UI/DocumentViews/old/MpvControls.hh similarity index 100% rename from src/UI/DocumentViews/MpvControls.hh rename to src/UI/DocumentViews/old/MpvControls.hh diff --git a/src/UI/VivyDocumentView.cc b/src/UI/VivyDocumentView.cc index 6cc127fa6bdcde6dac57a37aa233bf3b8d97f49a..2b12fc262174b9ef7f1af45ad925f769ddb898a1 100644 --- a/src/UI/VivyDocumentView.cc +++ b/src/UI/VivyDocumentView.cc @@ -92,8 +92,8 @@ VivyDocumentView::loadVideoView() noexcept } Utils::deleteInternalWidget(videoView); - videoView->setWidget(new VideoView(videoView)); - qobject_cast<VideoView *>(videoView->widget()) + videoView->setWidget(new MpvWidget(videoView)); + qobject_cast<MpvWidget *>(videoView->widget()) ->loadFile(document->getVideoSubDocument()->getFilePath()); } diff --git a/src/UI/VivyDocumentView.hh b/src/UI/VivyDocumentView.hh index 47ce593ab458a42d1d7e79c9282a683f1c704645..2a6edf5d02b89bedcfafee5b31211d2d1151f9ed 100644 --- a/src/UI/VivyDocumentView.hh +++ b/src/UI/VivyDocumentView.hh @@ -11,7 +11,7 @@ #include "UI/PropertyModel.hh" #include "UI/UnclosableDockWidget.hh" #include "UI/AbstractDocumentView.hh" -#include "UI/DocumentViews/VideoView.hh" +#include "UI/DocumentViews/MpvWidget.hh" #include "UI/DocumentViews/AssLinesView.hh" #include "UI/DocumentViews/AssLinesModel.hh"