Sélectionner une révision Git
mpvwidget.cc 12,91 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(queue *queue, lkt_db *db, module_reg *reg, bool *launched, QWidget *parent)
: QOpenGLWidget(parent)
, m_queue(queue)
, m_db(db)
, m_reg(reg)
, m_launched(launched)
{
mpv = mpv_create();
if (!mpv)
throw std::runtime_error("could not create mpv context");
setFocusPolicy(Qt::StrongFocus);
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_observe_property(mpv, 0, "pause", MPV_FORMAT_FLAG);
mpv_observe_property(mpv, 0, "unpause", MPV_FORMAT_FLAG);
mpv_observe_property(mpv, 0, "idle-active", MPV_FORMAT_FLAG);
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)
{
size_t ao_volume;
mpv_event_property *prop;
(void)ao_volume;
(void)prop;
switch (event->event_id) {
case MPV_EVENT_SHUTDOWN:
lkt_queue_send(m_queue, LKT_EVENT_PLAY_TOGGLE, LKT_PLAY_STOP);
*m_launched = false;
reg_call(m_reg, "close", 1);
break;
case MPV_EVENT_START_FILE:
m_inhib = false;
LOG_DEBUG("WINDOW", "Start of file!");
break;
case MPV_EVENT_END_FILE:
LOG_DEBUG("WINDOW", "End of file!");
if (!m_inhib && m_state != STOP)
lkt_queue_send(m_queue, LKT_EVENT_PLAY_NEXT, nullptr);
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));
}
} else if (strcmp(prop->name, "pause") == 0) {
LOG_DEBUG("WINDOW", "Detected pause");
if (prop->format == MPV_FORMAT_FLAG) {
if (*static_cast<int*>(prop->data))
goto apply_pause;
else
goto apply_unpause;
}
} else if (strcmp(prop->name, "unpause") == 0) {
LOG_DEBUG("WINDOW", "Detected unpause");
if (prop->format == MPV_FORMAT_FLAG) {
if (*static_cast<int*>(prop->data))
goto apply_unpause;
else
goto apply_pause;
}
} else if (strcmp(prop->name, "idle-active") == 0) {
LOG_DEBUG("WINDOW", "Detected idle");
if (prop->format == MPV_FORMAT_FLAG && *static_cast<int*>(prop->data)) {
LOG_DEBUG("WINDOW", "Applying 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));
*m_launched = true;
emit titleChanged("[Lektord] Stopped");
}
}
break;
apply_pause:
LOG_DEBUG("WINDOW", "Applying pause");
lkt_queue_send(m_queue, LKT_EVENT_PLAY_TOGGLE, LKT_PLAY_PAUSE);
break;
apply_unpause:
LOG_DEBUG("WINDOW", "Applying unpause");
lkt_queue_send(m_queue, LKT_EVENT_PLAY_TOGGLE, LKT_PLAY_PLAY);
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_CLIENT_MESSAGE:
case MPV_EVENT_VIDEO_RECONFIG:
case MPV_EVENT_AUDIO_RECONFIG:
case MPV_EVENT_SEEK:
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);
m_inhib = true;
if (ret) {
LOG_DEBUG("WINDOW", "Loaded file: %s", filepath);
m_state = NONSTOPPED;
update_window_title();
} else {
LOG_ERROR("WINDOW", "Failed to load kara with path: %s", filepath);
}
return ret;
}
void
MpvWidget::update_window_title()
{
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;
}
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:
lmpv_toggle_pause(mpv);
//lkt_queue_send(m_queue, LKT_EVENT_PLAY_TOGGLE, LKT_PLAY_TOGGLE);
break;
case Qt::Key_Return:
case Qt::Key_Greater: lkt_queue_send(m_queue, LKT_EVENT_PLAY_NEXT, nullptr); break;
case Qt::Key_Less: 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", 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);
}
}