Skip to content
Extraits de code Groupes Projets
Sélectionner une révision Git
  • ebea1f6b8a2cd622b0915446c6a18727de9f88f4
  • master par défaut protégée
  • devel
  • v5.2.6
  • v5.2.5
5 résultats

Action.pm

Blame
  • mpvwidget.cc 11,23 Kio
    #include "mpvwidget.hh"
    
    #include <stdexcept>
    #include <QtGui/QOpenGLContext>
    #include <QtCore/QMetaObject>
    #include <QApplication>
    #include <QKeyEvent>
    #include <QString>
    
    #include "qthelper.hh"
    #include "../mpv.h"
    
    #include <lektor/mkv.h>
    
    PRIVATE_FUNCTION void
    wakeup(void *ctx)
    {
        QMetaObject::invokeMethod(static_cast<MpvWidget *>(ctx), "on_mpv_events", Qt::QueuedConnection);
    }
    
    PRIVATE_FUNCTION 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, struct module_reg *reg, QWidget *parent)
        : QOpenGLWidget(parent)
        , m_queue(queue)
        , m_db(db)
        , m_reg(reg)
    {
        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 &params)
    {
        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)
    {
        size_t ao_volume;
        mpv_event_property *prop;
        (void)ao_volume;
        (void)prop;
    
        switch (event->event_id) {
        case MPV_EVENT_PAUSE:
            lkt_queue_send(m_queue, LKT_EVENT_PLAY_TOGGLE, LKT_PLAY_PAUSE);
            break;
    
        case MPV_EVENT_UNPAUSE:
            lkt_queue_send(m_queue, LKT_EVENT_PLAY_TOGGLE, LKT_PLAY_PLAY);
            break;
    
        case MPV_EVENT_SHUTDOWN:
            lkt_queue_send(m_queue, LKT_EVENT_PLAY_TOGGLE, LKT_PLAY_STOP);
            reg_call(m_reg, "close", 1);
            break;
    
        case MPV_EVENT_START_FILE:
            //SET_STATE_FLAG(*(arg->state), PLAY);
            //*(arg->hinib) = false;
            LOG_DEBUG("WINDOW", "Start of file!");
            break;
    
        case MPV_EVENT_END_FILE:
            //LOG_DEBUG("WINDOW", "End of file!");
            //if (((*(arg->state)) != STATE_STOP) && !(*(arg->hinib))) {
            //    LOG_DEBUG("WINDOW", "Send play_next event");
            //    lkt_queue_send(queue, LKT_EVENT_PLAY_NEXT, NULL);
            //} else
            //    LOG_DEBUG("WINDOW", "Don't send play_next event, hinib is %d and state is %s",
            //              *(arg->hinib),
            //              (*(arg->state) == STATE_STOP)    ? "STOP"
            //              : (*(arg->state) == STATE_PLAY)  ? "PLAY"
            //              : (*(arg->state) == STATE_PAUSE) ? "PAUSE"
            //                                               : "UNKNOWN");
            LOG_DEBUG("WINDOW", "End of file!");
            //lkt_queue_send(m_queue, LKT_EVENT_PLAY_NEXT, NULL);
            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:
            lkt_queue_make_available(m_queue, static_cast<LKT_EVENT_TYPE>(LKT_EVENT_PLAY));
            lkt_queue_make_available(m_queue, static_cast<LKT_EVENT_TYPE>(LKT_EVENT_PROP));
            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::on_update(void *ctx)
    {
        QMetaObject::invokeMethod(static_cast<MpvWidget *>(ctx), "maybeUpdate");
    }
    
    bool
    MpvWidget::get_elapsed(int UNUSED *elapsed_sec)
    {
        *elapsed_sec = m_position;
        return true;
    }
    
    bool
    MpvWidget::get_duration(int UNUSED *dur_sec)
    {
        *dur_sec = m_duration;
        return true;
    }
    
    bool
    MpvWidget::set_paused(int paused)
    {
        const char *cmd[] = {"set", "pause", paused == 1 ? "yes" : "no", nullptr};
        mpv_command_async(mpv, 0, cmd);
        return true;
    }
    
    bool
    MpvWidget::set_volume(int UNUSED vol)
    {
        return true;
    }
    
    bool
    MpvWidget::set_position(int sec)
    {
        return lmpv_set_position(mpv, sec);
    }
    
    bool
    MpvWidget::load_file(const char *filepath)
    {
        const bool ret = !lmpv_load_file(mpv, filepath);
    
        if (ret) {
            LOG_DEBUG("WINDOW", "Loaded file: %s", filepath);
            update_window_title();
        } else {
            LOG_ERROR("WINDOW", "Failed to load kara with path: %s", filepath);
        }
    
        //LOG_DEBUG("WINDOW", "Hinib flag at %d", win->hinib);
        return ret;
    }
    
    void
    MpvWidget::update_window_title()
    {
        struct kara_metadata kara_mdt;
        int changed_kara = 0;
        char *kara_title = nullptr;
        char window_title[LKT_LINE_MAX];
    
        if (database_queue_current_kara(m_db, &kara_mdt, &changed_kara)) {
            mdtcat(&kara_mdt, &kara_title);
            safe_snprintf(window_title, LKT_LINE_MAX, "[Lektord] %d: %s", changed_kara, kara_title);
            LOG_DEBUG("WINDOW", "Set window title to: %s", window_title);
            titleChanged(QString::fromLocal8Bit(window_title));
            free(kara_title);
        }
    
        else {
            LOG_ERROR("WINDOW", "Failed to get current kara, can't change window title");
        }
    }
    
    bool
    MpvWidget::toggle_pause()
    {
        const char *cmd[] = {"cycle", "pause", nullptr};
        mpv_command_async(mpv, 0, cmd);
        return true;
    }
    
    #define MPV_SEND_COMMAND_ASYNC( ... )           \
        {                                           \
            const char *cmd[] = { __VA_ARGS__ };    \
            mpv_command_async(mpv, 0, cmd);         \
            break;                                  \
        }
    void
    MpvWidget::keyPressEvent(QKeyEvent* event)
    {
        switch(event->modifiers()){
        /* SHIFTED */
        case Qt::ShiftModifier:
            switch(event->key()){
            case Qt::Key_J:
                MPV_SEND_COMMAND_ASYNC("cycle", "sub", NULL);
    
            }
            break;
    
    
        /* UN-SHIFTED */
        default:
            switch(event->key()){
            /* Playback */
            case Qt::Key_Space:
                lmpv_toggle_pause(mpv);
                //lkt_queue_send(m_queue, LKT_EVENT_PLAY_TOGGLE, LKT_PLAY_TOGGLE);
                break;
            case Qt::Key_Return:
                LOG_DEBUG("WINDOW", "RETURN PRESSED");
                lkt_queue_send(m_queue, LKT_EVENT_PLAY_NEXT, nullptr);
                break;
            case Qt::Key_Less:
                LOG_DEBUG("WINDOW", "LESS PRESSED");
                lkt_queue_send(m_queue, LKT_EVENT_PLAY_PREV, nullptr);
                break;
            case Qt::Key_Left:
                MPV_SEND_COMMAND_ASYNC("osd-msg-bar", "seek", "-5", "relative", NULL);
            case Qt::Key_Right:
                MPV_SEND_COMMAND_ASYNC("osd-msg-bar", "seek", "+5", "relative", NULL);
            case Qt::Key_Down:
                MPV_SEND_COMMAND_ASYNC("osd-msg-bar", "seek", "-60", "relative", NULL);
            case Qt::Key_Up:
                MPV_SEND_COMMAND_ASYNC("osd-msg-bar", "seek", "+60", "relative", NULL);
            case Qt::Key_O:
                MPV_SEND_COMMAND_ASYNC("osd-msg-bar", "show-progress", NULL);
            case Qt::Key_L:
                MPV_SEND_COMMAND_ASYNC("ab-loop", NULL);
    
            /* Track management */
            case Qt::Key_NumberSign:
                MPV_SEND_COMMAND_ASYNC("cycle", "audio", NULL);
            case Qt::Key_J:
                MPV_SEND_COMMAND_ASYNC("cycle", "sub", "down", NULL);
            case Qt::Key_Underscore:
                MPV_SEND_COMMAND_ASYNC("cycle", "video", NULL);
    
            /* Misc */
            case Qt::Key_I:
                MPV_SEND_COMMAND_ASYNC("script-binding", "stats/display-stats", NULL);
            case Qt::Key_Delete:
                MPV_SEND_COMMAND_ASYNC("script-message", "osc-visibility", (m_oscVisible = !m_oscVisible) ? "always" : "never", NULL);
    
            default:
                break;
            }
            return QOpenGLWidget::keyPressEvent(event);
        }
    }