#include "mpvwidget.hh" #include <stdexcept> #include <QtGui/QOpenGLContext> #include <QtCore/QMetaObject> #include "qthelper.hh" #include "../mpv.h" static void wakeup(void *ctx) { QMetaObject::invokeMethod(static_cast<MpvWidget *>(ctx), "on_mpv_events", Qt::QueuedConnection); } static void * get_proc_address(void *ctx, const char *name) { Q_UNUSED(ctx); QOpenGLContext *glctx = QOpenGLContext::currentContext(); if (!glctx) return nullptr; return reinterpret_cast<void *>(glctx->getProcAddress(QByteArray(name))); } MpvWidget::MpvWidget(struct queue *queue, lkt_db *db, QWidget *parent) : QOpenGLWidget(parent) , m_queue(queue) , m_db(db) { setFocusPolicy(Qt::StrongFocus); mpv = mpv_create(); if (!mpv) throw std::runtime_error("could not create mpv context"); mpv_set_option_string(mpv, "input-default-bindings", "yes"); mpv_set_option_string(mpv, "input-vo-keyboard", "yes"); int val = 1; mpv_set_option(mpv, "osc", MPV_FORMAT_FLAG, &val); mpv_set_option_string(mpv, "terminal", "yes"); mpv_set_option_string(mpv, "msg-level", "all=v"); if (mpv_initialize(mpv) < 0) throw std::runtime_error("could not initialize mpv context"); // Request hw decoding, just for testing. mpv::qt::set_option_variant(mpv, "hwdec", "auto"); mpv_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{ get_proc_address, nullptr, nullptr }; mpv_render_param params[]{ { MPV_RENDER_PARAM_API_TYPE, const_cast<char *>(MPV_RENDER_API_TYPE_OPENGL) }, { MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params }, { MPV_RENDER_PARAM_INVALID, nullptr } }; if (mpv_render_context_create(&mpv_gl, mpv, params) < 0) throw std::runtime_error("failed to initialize mpv GL context"); mpv_render_context_set_update_callback(mpv_gl, MpvWidget::on_update, reinterpret_cast<void *>(this)); } void MpvWidget::paintGL() { 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::on_mpv_events() { // Process all events, until the event queue is empty. while (mpv) { mpv_event *event = mpv_wait_event(mpv, 0); if (event->event_id == MPV_EVENT_NONE) { break; } handle_mpv_event(event); } } void MpvWidget::handle_mpv_event(mpv_event *event) { switch (event->event_id) { case MPV_EVENT_PROPERTY_CHANGE: { mpv_event_property *prop = static_cast<mpv_event_property *>(event->data); if (strcmp(prop->name, "time-pos") == 0) { if (prop->format == MPV_FORMAT_DOUBLE) { double time = *static_cast<double *>(prop->data); Q_EMIT positionChanged(static_cast<int>(time)); } } else if (strcmp(prop->name, "duration") == 0) { if (prop->format == MPV_FORMAT_DOUBLE) { double time = *static_cast<double *>(prop->data); Q_EMIT durationChanged(static_cast<int>(time)); } } break; } case MPV_EVENT_SHUTDOWN: 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_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: 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; } // Ignore uninteresting or unknown events. } } // Make Qt invoke mpv_render_context_render() to draw a new/updated video frame. void MpvWidget::maybeUpdate() { // If the Qt window is not visible, Qt's update() will just skip rendering. // This confuses mpv's render API, and may lead to small occasional // freezes due to video rendering timing out. // Handle this by manually redrawing. // Note: Qt doesn't seem to provide a way to query whether update() will // be skipped, and the following code still fails when e.g. switching // to a different workspace with a reparenting window manager. if (window()->isMinimized()) { makeCurrent(); paintGL(); context()->swapBuffers(context()->surface()); doneCurrent(); } else { update(); } } void MpvWidget::on_update(void *ctx) { QMetaObject::invokeMethod(static_cast<MpvWidget *>(ctx), "maybeUpdate"); } bool MpvWidget::get_elapsed(int *elapsed_sec) { (void)elapsed_sec; return true; } bool MpvWidget::get_duration(int *dur_sec) { (void)dur_sec; return true; } bool MpvWidget::set_paussed(int paused) { (void)paused; return true; } bool MpvWidget::set_volume(int vol) { (void)vol; return true; } bool MpvWidget::set_position(int sec) { (void)sec; return true; } bool MpvWidget::load_file(const char *filepath) { const bool ret = !lmpv_load_file(mpv, filepath); if (ret) { LOG_DEBUG("WINDOW", "Loaded file: %s", filepath); //set_window_title(win); } else { LOG_ERROR("WINDOW", "Failed to load kara with path: %s", filepath); } //LOG_DEBUG("WINDOW", "Hinib flag at %d", win->hinib); return ret; } bool MpvWidget::toggle_pause() { return true; }