2014-07-04 00:21:14 +00:00
|
|
|
// This example can be built with: qmake && make
|
|
|
|
|
2014-10-07 03:18:08 +00:00
|
|
|
#include <clocale>
|
2014-07-04 00:21:14 +00:00
|
|
|
#include <sstream>
|
|
|
|
|
2014-10-17 20:36:23 +00:00
|
|
|
#include <QtGlobal>
|
2014-07-04 00:21:14 +00:00
|
|
|
#include <QFileDialog>
|
|
|
|
#include <QStatusBar>
|
|
|
|
#include <QMenuBar>
|
|
|
|
#include <QMenu>
|
|
|
|
#include <QGridLayout>
|
|
|
|
#include <QApplication>
|
2014-10-08 11:20:35 +00:00
|
|
|
#include <QTextEdit>
|
2014-10-17 20:36:23 +00:00
|
|
|
|
|
|
|
#if QT_VERSION >= 0x050000
|
2014-10-13 21:55:57 +00:00
|
|
|
#include <QJsonDocument>
|
2014-10-17 20:36:23 +00:00
|
|
|
#endif
|
2014-10-13 21:55:57 +00:00
|
|
|
|
|
|
|
#include <mpv/qthelper.hpp>
|
2014-07-04 00:21:14 +00:00
|
|
|
|
|
|
|
#include "qtexample.h"
|
|
|
|
|
|
|
|
static void wakeup(void *ctx)
|
|
|
|
{
|
|
|
|
// This callback is invoked from any mpv thread (but possibly also
|
|
|
|
// recursively from a thread that is calling the mpv API). Just notify
|
|
|
|
// the Qt GUI thread to wake up (so that it can process events with
|
|
|
|
// mpv_wait_event()), and return as quickly as possible.
|
|
|
|
MainWindow *mainwindow = (MainWindow *)ctx;
|
|
|
|
QCoreApplication::postEvent(mainwindow, new QEvent(QEvent::User));
|
|
|
|
}
|
|
|
|
|
|
|
|
MainWindow::MainWindow(QWidget *parent) :
|
|
|
|
QMainWindow(parent)
|
|
|
|
{
|
2014-10-08 11:20:35 +00:00
|
|
|
setWindowTitle("Qt embedding demo");
|
|
|
|
setMinimumSize(640, 480);
|
|
|
|
|
2014-07-04 00:21:14 +00:00
|
|
|
QMenu *menu = menuBar()->addMenu(tr("&File"));
|
|
|
|
QAction *on_open = new QAction(tr("&Open"), this);
|
|
|
|
on_open->setShortcuts(QKeySequence::Open);
|
|
|
|
on_open->setStatusTip(tr("Open a file"));
|
|
|
|
connect(on_open, SIGNAL(triggered()), this, SLOT(on_file_open()));
|
|
|
|
menu->addAction(on_open);
|
|
|
|
|
|
|
|
statusBar();
|
|
|
|
|
2014-10-08 11:20:35 +00:00
|
|
|
QMainWindow *log_window = new QMainWindow(this);
|
|
|
|
log = new QTextEdit(log_window);
|
|
|
|
log->setReadOnly(true);
|
|
|
|
log_window->setCentralWidget(log);
|
|
|
|
log_window->setWindowTitle("mpv log window");
|
|
|
|
log_window->setMinimumSize(500, 50);
|
|
|
|
log_window->show();
|
|
|
|
|
2014-07-04 00:21:14 +00:00
|
|
|
mpv = mpv_create();
|
|
|
|
if (!mpv)
|
|
|
|
throw "can't create mpv instance";
|
|
|
|
|
|
|
|
// Create a video child window. Force Qt to create a native window, and
|
2014-10-16 19:18:01 +00:00
|
|
|
// pass the window ID to the mpv wid option. Works on: X11, win32, Cocoa
|
2014-07-04 00:21:14 +00:00
|
|
|
mpv_container = new QWidget(this);
|
|
|
|
setCentralWidget(mpv_container);
|
2014-10-16 19:19:30 +00:00
|
|
|
mpv_container->setAttribute(Qt::WA_DontCreateNativeAncestors);
|
2014-07-04 00:21:14 +00:00
|
|
|
mpv_container->setAttribute(Qt::WA_NativeWindow);
|
2014-09-15 16:38:42 +00:00
|
|
|
// If you have a HWND, use: int64_t wid = (intptr_t)hwnd;
|
2014-07-04 00:21:14 +00:00
|
|
|
int64_t wid = mpv_container->winId();
|
|
|
|
mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &wid);
|
|
|
|
|
|
|
|
// Enable default bindings, because we're lazy. Normally, a player using
|
|
|
|
// mpv as backend would implement its own key bindings.
|
|
|
|
mpv_set_option_string(mpv, "input-default-bindings", "yes");
|
|
|
|
|
client API, X11: change default keyboard input handling again
Commit 64b7811c tried to do the "right thing" with respect to whether
keyboard input should be enabled or not. It turns out that X11 does
something stupid by design. All modern toolkits work around this native
X11 behavior, but embedding breaks these workarounds.
The only way to handle this correctly is the XEmbed protocol. It needs
to be supported by the toolkit, and probably also some mpv support. But
Qt has inconsistent support for it. In Qt 4, a X11 specific embedding
widget was needed. Qt 5.0 doesn't support it at all. Qt 5.1 apparently
supports it via QWindow, but if it really does, I couldn't get it to
work.
So add a hack instead. The new --input-x11-keyboard option controls
whether mpv should enable keyboard input on the X11 window or not. In
the command line player, it's enabled by default, but in libmpv it's
disabled.
This hack has the same problem as all previous embedding had: move the
mouse outside of the window, and you don't get keyboard input anymore.
Likewise, mpv will steal all keyboard input from the parent application
as long as the mouse is inside of the mpv window.
Also see issue #1090.
2014-09-28 18:11:00 +00:00
|
|
|
// Enable keyboard input on the X11 window. For the messy details, see
|
2014-10-09 16:28:37 +00:00
|
|
|
// --input-vo-keyboard on the manpage.
|
|
|
|
mpv_set_option_string(mpv, "input-vo-keyboard", "yes");
|
client API, X11: change default keyboard input handling again
Commit 64b7811c tried to do the "right thing" with respect to whether
keyboard input should be enabled or not. It turns out that X11 does
something stupid by design. All modern toolkits work around this native
X11 behavior, but embedding breaks these workarounds.
The only way to handle this correctly is the XEmbed protocol. It needs
to be supported by the toolkit, and probably also some mpv support. But
Qt has inconsistent support for it. In Qt 4, a X11 specific embedding
widget was needed. Qt 5.0 doesn't support it at all. Qt 5.1 apparently
supports it via QWindow, but if it really does, I couldn't get it to
work.
So add a hack instead. The new --input-x11-keyboard option controls
whether mpv should enable keyboard input on the X11 window or not. In
the command line player, it's enabled by default, but in libmpv it's
disabled.
This hack has the same problem as all previous embedding had: move the
mouse outside of the window, and you don't get keyboard input anymore.
Likewise, mpv will steal all keyboard input from the parent application
as long as the mouse is inside of the mpv window.
Also see issue #1090.
2014-09-28 18:11:00 +00:00
|
|
|
|
2014-07-04 00:21:14 +00:00
|
|
|
// Let us receive property change events with MPV_EVENT_PROPERTY_CHANGE if
|
|
|
|
// this property changes.
|
|
|
|
mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
|
|
|
|
|
2014-10-13 21:55:57 +00:00
|
|
|
mpv_observe_property(mpv, 0, "track-list", MPV_FORMAT_NODE);
|
|
|
|
mpv_observe_property(mpv, 0, "chapter-list", MPV_FORMAT_NODE);
|
|
|
|
|
|
|
|
// Request log messages with level "info" or higher.
|
2014-10-08 11:20:35 +00:00
|
|
|
// They are received as MPV_EVENT_LOG_MESSAGE.
|
2014-10-13 21:55:57 +00:00
|
|
|
mpv_request_log_messages(mpv, "info");
|
2014-10-08 11:20:35 +00:00
|
|
|
|
2014-07-04 00:21:14 +00:00
|
|
|
// From this point on, the wakeup function will be called. The callback
|
|
|
|
// can come from any thread, so we use the Qt QEvent mechanism to relay
|
|
|
|
// the wakeup in a thread-safe way.
|
|
|
|
mpv_set_wakeup_callback(mpv, wakeup, this);
|
|
|
|
|
|
|
|
if (mpv_initialize(mpv) < 0)
|
|
|
|
throw "mpv failed to initialize";
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::handle_mpv_event(mpv_event *event)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << "At: " << time;
|
|
|
|
statusBar()->showMessage(QString::fromStdString(ss.str()));
|
|
|
|
} else if (prop->format == MPV_FORMAT_NONE) {
|
|
|
|
// The property is unavailable, which probably means playback
|
|
|
|
// was stopped.
|
|
|
|
statusBar()->showMessage("");
|
|
|
|
}
|
2014-10-13 21:55:57 +00:00
|
|
|
} else if (strcmp(prop->name, "chapter-list") == 0 ||
|
|
|
|
strcmp(prop->name, "track-list") == 0)
|
|
|
|
{
|
2014-10-17 20:36:23 +00:00
|
|
|
// Dump the properties as JSON for demo purposes.
|
|
|
|
#if QT_VERSION >= 0x050000
|
2014-10-13 21:55:57 +00:00
|
|
|
if (prop->format == MPV_FORMAT_NODE) {
|
|
|
|
QVariant v = mpv::qt::node_to_variant((mpv_node *)prop->data);
|
2014-10-14 11:20:24 +00:00
|
|
|
// Abuse JSON support for easily printing the mpv_node contents.
|
2014-10-13 21:55:57 +00:00
|
|
|
QJsonDocument d = QJsonDocument::fromVariant(v);
|
|
|
|
append_log("Change property " + QString(prop->name) + ":\n");
|
|
|
|
append_log(d.toJson().data());
|
|
|
|
}
|
2014-10-17 20:36:23 +00:00
|
|
|
#endif
|
2014-07-04 00:21:14 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2014-09-15 16:38:42 +00:00
|
|
|
case MPV_EVENT_VIDEO_RECONFIG: {
|
|
|
|
// Retrieve the new video size.
|
|
|
|
int64_t w, h;
|
|
|
|
if (mpv_get_property(mpv, "dwidth", MPV_FORMAT_INT64, &w) >= 0 &&
|
|
|
|
mpv_get_property(mpv, "dheight", MPV_FORMAT_INT64, &h) >= 0 &&
|
|
|
|
w > 0 && h > 0)
|
|
|
|
{
|
2014-09-28 18:18:18 +00:00
|
|
|
// Note that the MPV_EVENT_VIDEO_RECONFIG event doesn't necessarily
|
|
|
|
// imply a resize, and you should check yourself if the video
|
|
|
|
// dimensions really changed.
|
2014-09-15 16:38:42 +00:00
|
|
|
// mpv itself will scale/letter box the video to the container size
|
|
|
|
// if the video doesn't fit.
|
2014-09-28 18:18:18 +00:00
|
|
|
std::stringstream ss;
|
|
|
|
ss << "Reconfig: " << w << " " << h;
|
|
|
|
statusBar()->showMessage(QString::fromStdString(ss.str()));
|
2014-09-15 16:38:42 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2014-10-08 11:20:35 +00:00
|
|
|
case MPV_EVENT_LOG_MESSAGE: {
|
|
|
|
struct mpv_event_log_message *msg = (struct mpv_event_log_message *)event->data;
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << "[" << msg->prefix << "] " << msg->level << ": " << msg->text;
|
2014-10-13 21:55:57 +00:00
|
|
|
append_log(QString::fromStdString(ss.str()));
|
2014-10-08 11:20:35 +00:00
|
|
|
break;
|
|
|
|
}
|
2014-07-04 00:21:14 +00:00
|
|
|
case MPV_EVENT_SHUTDOWN: {
|
|
|
|
mpv_terminate_destroy(mpv);
|
|
|
|
mpv = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default: ;
|
|
|
|
// Ignore uninteresting or unknown events.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MainWindow::event(QEvent *event)
|
|
|
|
{
|
|
|
|
// QEvent::User is sent by wakeup().
|
|
|
|
if (event->type() == QEvent::User) {
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return QMainWindow::event(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::on_file_open()
|
|
|
|
{
|
|
|
|
QString filename = QFileDialog::getOpenFileName(this, "Open file");
|
|
|
|
if (mpv) {
|
2014-08-06 22:35:38 +00:00
|
|
|
const QByteArray c_filename = filename.toUtf8();
|
|
|
|
const char *args[] = {"loadfile", c_filename.data(), NULL};
|
2014-07-04 00:21:14 +00:00
|
|
|
mpv_command_async(mpv, 0, args);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-13 21:55:57 +00:00
|
|
|
void MainWindow::append_log(const QString &text)
|
|
|
|
{
|
|
|
|
QTextCursor cursor = log->textCursor();
|
|
|
|
cursor.movePosition(QTextCursor::End);
|
|
|
|
cursor.insertText(text);
|
|
|
|
log->setTextCursor(cursor);
|
|
|
|
}
|
|
|
|
|
2014-07-04 00:21:14 +00:00
|
|
|
MainWindow::~MainWindow()
|
|
|
|
{
|
|
|
|
if (mpv)
|
|
|
|
mpv_terminate_destroy(mpv);
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
|
|
{
|
|
|
|
QApplication a(argc, argv);
|
2014-10-07 03:18:08 +00:00
|
|
|
|
|
|
|
// Qt sets the locale in the QApplication constructor, but libmpv requires
|
|
|
|
// the LC_NUMERIC category to be set to "C", so change it back.
|
|
|
|
std::setlocale(LC_NUMERIC, "C");
|
|
|
|
|
2014-07-04 00:21:14 +00:00
|
|
|
MainWindow w;
|
|
|
|
w.show();
|
|
|
|
|
|
|
|
return a.exec();
|
|
|
|
}
|