1
0
Fork 0
VideoPlayer/src/MpvPlayerBackend.cpp

338 lines
9.9 KiB
C++
Raw Normal View History

2018-10-13 15:38:31 +01:00
#include <stdexcept>
#include <clocale>
#include "MpvPlayerBackend.h"
2018-10-13 15:38:31 +01:00
#include <QApplication>
#include <QQuickWindow>
2018-10-13 15:38:31 +01:00
#include <QOpenGLContext>
#include <QOpenGLFramebufferObject>
2018-10-13 15:38:31 +01:00
namespace
{
void wakeup(void *ctx)
{
QMetaObject::invokeMethod((MpvPlayerBackend*)ctx, "on_mpv_events", Qt::QueuedConnection);
2018-10-13 15:38:31 +01:00
}
void on_mpv_redraw(void *ctx)
{
MpvPlayerBackend::on_update(ctx);
2018-10-13 15:38:31 +01:00
}
static void *get_proc_address_mpv(void *ctx, const char *name)
{
Q_UNUSED(ctx)
QOpenGLContext *glctx = QOpenGLContext::currentContext();
if (!glctx) return nullptr;
return reinterpret_cast<void *>(glctx->getProcAddress(QByteArray(name)));
}
}
class MpvRenderer : public QQuickFramebufferObject::Renderer
{
MpvPlayerBackend *obj;
2018-10-13 15:38:31 +01:00
public:
MpvRenderer(MpvPlayerBackend *new_obj)
2018-10-13 15:38:31 +01:00
: obj{new_obj}
{
}
2018-10-21 14:32:39 +01:00
2018-10-13 15:38:31 +01:00
virtual ~MpvRenderer() {}
// This function is called when a new FBO is needed.
// This happens on the initial frame.
QOpenGLFramebufferObject * createFramebufferObject(const QSize &size)
{
// init mpv_gl:
if (!obj->mpv_gl)
{
mpv_opengl_init_params gl_init_params{get_proc_address_mpv, 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(&obj->mpv_gl, obj->mpv, params) < 0)
throw std::runtime_error("failed to initialize mpv GL context");
mpv_render_context_set_update_callback(obj->mpv_gl, on_mpv_redraw, obj);
QMetaObject::invokeMethod(obj,"startPlayer");
2018-10-13 15:38:31 +01:00
}
return QQuickFramebufferObject::Renderer::createFramebufferObject(size);
}
void render()
{
obj->window()->resetOpenGLState();
QOpenGLFramebufferObject *fbo = framebufferObject();
mpv_opengl_fbo mpfbo{.fbo = static_cast<int>(fbo->handle()), .w = fbo->width(), .h = fbo->height(), .internal_format = 0};
int flip_y{0};
mpv_render_param params[] = {
// Specify the default framebuffer (0) as target. This will
// render onto the entire screen. If you want to show the video
// in a smaller rectangle or apply fancy transformations, you'll
// need to render into a separate FBO and draw it manually.
{MPV_RENDER_PARAM_OPENGL_FBO, &mpfbo},
// Flip rendering (needed due to flipped GL coordinate system).
{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(obj->mpv_gl, params);
obj->window()->resetOpenGLState();
}
};
MpvPlayerBackend::MpvPlayerBackend(QQuickItem * parent)
2018-10-13 15:38:31 +01:00
: QQuickFramebufferObject(parent), mpv{mpv_create()}, mpv_gl(nullptr)
{
if (!mpv)
throw std::runtime_error("could not create mpv context");
mpv_set_option_string(mpv, "terminal", "yes");
2018-10-27 16:38:55 +01:00
mpv_set_option_string(mpv, "msg-level", "all=v");
2018-10-13 15:38:31 +01:00
// Fix?
2018-10-23 21:05:38 +01:00
mpv_set_option_string(mpv, "ytdl", "yes");
2018-10-21 14:32:39 +01:00
mpv_set_option_string(mpv, "vo", "libmpv");
2018-10-26 15:13:14 +01:00
//mpp_set_option_string(mpv, "no-sub-ass", "yes)
2018-10-13 15:38:31 +01:00
2018-10-23 21:23:19 +01:00
mpv_set_option_string(mpv, "slang", "en");
mpv_set_option_string(mpv, "config", "yes");
2018-10-26 15:13:14 +01:00
//mpv_set_option_string(mpv, "sub-visibility", "no");
2018-10-29 16:12:19 +00:00
2018-10-23 23:19:26 +01:00
2018-10-13 15:38:31 +01:00
mpv_observe_property(mpv, 0, "playback-abort", MPV_FORMAT_NONE);
mpv_observe_property(mpv, 0, "chapter-list", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "track-list", MPV_FORMAT_NODE);
2018-10-26 15:13:14 +01:00
mpv_observe_property(mpv, 0, "playlist-pos", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "volume", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "muted", MPV_FORMAT_DOUBLE);
2018-10-13 15:38:31 +01:00
mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "media-title", MPV_FORMAT_STRING);
mpv_observe_property(mpv, 0, "sub-text", MPV_FORMAT_STRING);
2018-10-13 15:38:31 +01:00
mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "demuxer-cache-duration", MPV_FORMAT_DOUBLE);
2018-10-26 15:13:14 +01:00
mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_NONE);
2018-10-13 15:38:31 +01:00
mpv_set_wakeup_callback(mpv, wakeup, this);
2018-10-21 14:32:39 +01:00
if (mpv_initialize(mpv) < 0)
throw std::runtime_error("could not initialize mpv context");
2018-10-21 13:00:25 +01:00
connect(this, &MpvPlayerBackend::onUpdate, this, &MpvPlayerBackend::doUpdate,
2018-10-13 15:38:31 +01:00
Qt::QueuedConnection);
2018-10-21 14:32:39 +01:00
2018-10-13 15:38:31 +01:00
}
MpvPlayerBackend::~MpvPlayerBackend()
2018-10-13 15:38:31 +01:00
{
if (mpv_gl)
{
mpv_render_context_free(mpv_gl);
}
mpv_terminate_destroy(mpv);
}
void MpvPlayerBackend::on_update(void *ctx)
2018-10-13 15:38:31 +01:00
{
MpvPlayerBackend *self = (MpvPlayerBackend *)ctx;
2018-10-13 15:38:31 +01:00
emit self->onUpdate();
}
// connected to onUpdate(); signal makes sure it runs on the GUI thread
void MpvPlayerBackend::doUpdate()
2018-10-13 15:38:31 +01:00
{
update();
}
QVariant MpvPlayerBackend::getProperty(const QString &name) const
2018-10-13 15:38:31 +01:00
{
return mpv::qt::get_property_variant(mpv, name);
}
void MpvPlayerBackend::command(const QVariant& params)
2018-10-13 15:38:31 +01:00
{
qDebug() << params;
2018-10-13 15:38:31 +01:00
mpv::qt::command_variant(mpv, params);
}
void MpvPlayerBackend::setProperty(const QString& name, const QVariant& value)
2018-10-13 15:38:31 +01:00
{
mpv::qt::set_property_variant(mpv, name, value);
}
void MpvPlayerBackend::setOption(const QString& name, const QVariant& value)
2018-10-13 15:38:31 +01:00
{
mpv::qt::set_option_variant(mpv, name, value);
}
void MpvPlayerBackend::launchAboutQt()
{
QApplication *qapp = qobject_cast<QApplication *>(QCoreApplication::instance());
qapp->aboutQt();
}
void MpvPlayerBackend::togglePlayPause()
{
command(QVariantList() << "cycle" << "pause");
}
void MpvPlayerBackend::toggleMute()
{
command(QVariantList() << "cycle" << "mute");
}
void MpvPlayerBackend::nextAudioTrack()
{
command(QVariantList() << "cycle" << "audio");
}
void MpvPlayerBackend::nextSubtitleTrack()
{
command(QVariantList() << "cycle" << "sub");
}
void MpvPlayerBackend::nextVideoTrack()
{
command(QVariantList() << "cycle" << "video");
}
void MpvPlayerBackend::prevPlaylistItem()
{
command(QVariantList() << "playlist-prev");
}
void MpvPlayerBackend::nextPlaylistItem()
{
command(QVariantList() << "playlist-next" << "force");
}
void MpvPlayerBackend::loadFile(const QVariant &filename)
{
command(QVariantList() << "loadfile" << filename);
}
void MpvPlayerBackend::setVolume(const QVariant &volume)
{
command(QVariantList() << "set" << "volume" << volume);
}
void MpvPlayerBackend::addVolume(const QVariant &volume)
{
command(QVariantList() << "add" << "volume" << volume);
}
void MpvPlayerBackend::seek(const QVariant &seekTime)
{
command(QVariantList() << "seek" << seekTime);
}
2018-11-04 13:21:50 +00:00
QVariant MpvPlayerBackend::getTracks() const
{
return mpv::qt::get_property_variant(mpv, "track-list");
}
void MpvPlayerBackend::on_mpv_events()
2018-10-13 15:38:31 +01:00
{
while (mpv) {
mpv_event *event = mpv_wait_event(mpv, 0);
if (event->event_id == MPV_EVENT_NONE) {
break;
}
handle_mpv_event(event);
}
}
void MpvPlayerBackend::handle_mpv_event(mpv_event *event)
2018-10-13 15:38:31 +01:00
{
switch (event->event_id) {
case MPV_EVENT_PROPERTY_CHANGE: {
mpv_event_property *prop = (mpv_event_property *)event->data;
if (strcmp(prop->name, "time-pos") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
double time = *(double *)prop->data;
QMetaObject::invokeMethod(this,"setProgressBarValue",Q_ARG(QVariant,time));
}
} else if (strcmp(prop->name, "duration") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
double time = *(double *)prop->data;Q_ARG(QVariant,"txt1"),
QMetaObject::invokeMethod(this,"setProgressBarEnd",Q_ARG(QVariant,time));
}
} else if (strcmp(prop->name, "volume") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
2018-10-26 15:13:14 +01:00
double volume = *(double *)prop->data;
QMetaObject::invokeMethod(this,"updateVolume",Q_ARG(QVariant,volume));
}
} else if (strcmp(prop->name, "muted") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
double muted = *(double *)prop->data;
QMetaObject::invokeMethod(this,"updateMuted",Q_ARG(QVariant,muted));
2018-10-13 15:38:31 +01:00
}
} else if (strcmp(prop->name, "media-title") == 0) {
if (prop->format == MPV_FORMAT_STRING) {
2018-10-26 15:13:14 +01:00
char *title = *(char **)prop->data;
QMetaObject::invokeMethod(this,"setTitle",Q_ARG(QVariant,title));
2018-10-13 15:38:31 +01:00
}
} else if (strcmp(prop->name, "sub-text") == 0) {
2018-10-26 15:13:14 +01:00
if (prop->format == MPV_FORMAT_STRING) {
char *subs = *(char **)prop->data;
QMetaObject::invokeMethod(this,"setSubtitles",Q_ARG(QVariant,subs));
}
} else if (strcmp(prop->name, "demuxer-cache-duration") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
double duration = *(double *)prop->data;
QMetaObject::invokeMethod(this,"setCachedDuration",Q_ARG(QVariant,duration));
}
2018-10-26 15:13:14 +01:00
} else if (strcmp(prop->name, "playlist-pos") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
double pos = *(double *)prop->data;
QMetaObject::invokeMethod(this,"updatePrev",Q_ARG(QVariant,pos));
}
} else if (strcmp(prop->name, "pause") == 0) {
QMetaObject::invokeMethod(this,"updatePlayPause");
2018-10-13 15:38:31 +01:00
}
break;
}
case MPV_EVENT_SHUTDOWN: {
exit(0);
break;
}
default: {
break;
2018-10-13 15:38:31 +01:00
}
}
}
QQuickFramebufferObject::Renderer *MpvPlayerBackend::createRenderer() const
2018-10-13 15:38:31 +01:00
{
window()->setPersistentOpenGLContext(true);
window()->setPersistentSceneGraph(true);
return new MpvRenderer(const_cast<MpvPlayerBackend *>(this));
2018-10-13 15:38:31 +01:00
}