kutils: reimplement media classes command sender, option getter and option setter

less bits, less CPU cycles wasted

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
This commit is contained in:
Ivailo Monev 2022-06-04 00:02:19 +03:00
parent fc968ddd58
commit 30bb40bf9f
3 changed files with 120 additions and 412 deletions

View file

@ -26,7 +26,6 @@
#if defined(HAVE_MPV)
#include <locale.h>
#include <mpv/client.h>
#include "qthelper.hpp"
#else
static bool s_fullscreen = false;
#endif // HAVE_MPV
@ -59,31 +58,119 @@ static bool s_fullscreen = false;
#define COMMON_COMMAND_SENDER \
kDebug() << i18n("sending command") << command; \
if (d->m_handle) { \
const QVariant result = mpv::qt::command(d->m_handle, command); \
if (mpv::qt::is_error(result)) { \
kWarning() << command << mpv_error_string(mpv::qt::get_error(result)); \
const char* commanddata[command.size() + 1]; \
::memset(commanddata, 0, command.size() * sizeof(const char*)); \
for (int i = 0; i < command.size(); i++) { \
commanddata[i] = command.at(i).constData(); \
} \
commanddata[command.size()] = NULL; \
const int mpvresult = mpv_command(d->m_handle, commanddata); \
if (mpvresult < 0) { \
kWarning() << command << mpv_error_string(mpvresult); \
} \
}
static QVariant mpvNodeToVariant(const mpv_node *mpvnode)
{
QVariant result;
switch (mpvnode->format) {
case MPV_FORMAT_NONE: {
break;
}
case MPV_FORMAT_FLAG: {
result = QVariant(bool(mpvnode->u.flag));
break;
}
case MPV_FORMAT_INT64: {
result = QVariant(qlonglong(mpvnode->u.int64));
break;
}
case MPV_FORMAT_DOUBLE: {
result = QVariant(double(mpvnode->u.double_));
break;
}
case MPV_FORMAT_STRING: {
result = QVariant(QString::fromUtf8(mpvnode->u.string));
break;
}
case MPV_FORMAT_NODE_ARRAY: {
QVariantList resultlist;
for (int i = 0; i < mpvnode->u.list->num; i++) {
resultlist.append(mpvNodeToVariant(&mpvnode->u.list->values[i]));
}
result = resultlist;
break;
}
case MPV_FORMAT_NODE_MAP: {
QVariantMap resultmap;
for (int i = 0; i < mpvnode->u.list->num; i++) {
resultmap.insert(
QString::fromUtf8(mpvnode->u.list->keys[i]),
mpvNodeToVariant(&mpvnode->u.list->values[i])
);
}
result = resultmap;
break;
}
default: {
kWarning() << i18n("Unknown node format") << mpvnode->format;
break;
}
}
return result;
}
// certain properties are not available when not playing for an example thus do not issue warning
// in case of error
#define COMMON_OPTION_GETTER \
kDebug() << i18n("getting option") << name; \
if (d->m_handle) { \
const QVariant result = mpv::qt::get_property(d->m_handle, name); \
if (mpv::qt::is_error(result)) { \
kDebug() << name << mpv_error_string(mpv::qt::get_error(result)); \
mpv_node mpvnode; \
const int mpvresult = mpv_get_property(d->m_handle, name.constData(), MPV_FORMAT_NODE, &mpvnode); \
if (mpvresult < 0) { \
kDebug() << name << mpv_error_string(mpvresult); \
return QVariant(); \
} \
QVariant result = mpvNodeToVariant(&mpvnode); \
mpv_free_node_contents(&mpvnode); \
return result; \
}
// NOTE: flags are integers
#define COMMON_OPTION_SETTER \
kDebug() << i18n("setting option") << name << value; \
if (d->m_handle) { \
const QVariant result = mpv::qt::set_property(d->m_handle, name, value); \
if (mpv::qt::is_error(result)) { \
kWarning() << name << mpv_error_string(mpv::qt::get_error(result)); \
int mpvresult = MPV_ERROR_PROPERTY_FORMAT; \
switch (value.type()) { \
case QVariant::Bool: { \
int boolvalue = value.toBool(); \
mpvresult = mpv_set_property(d->m_handle, name.constData(), MPV_FORMAT_FLAG, &boolvalue); \
break; \
} \
case QVariant::Int: \
case QVariant::UInt: \
case QVariant::LongLong: \
case QVariant::ULongLong: { \
qint64 int64value = value.toLongLong(); \
mpvresult = mpv_set_property(d->m_handle, name.constData(), MPV_FORMAT_INT64, &int64value); \
break; \
} \
case QVariant::Float: \
case QVariant::Double: { \
qint64 doublevalue = value.toDouble(); \
mpvresult = mpv_set_property(d->m_handle, name.constData(), MPV_FORMAT_DOUBLE, &doublevalue); \
break; \
} \
case QVariant::ByteArray: \
case QVariant::String: { \
QByteArray bytevalue = value.toByteArray(); \
char* bytevaluedata = bytevalue.data(); \
mpvresult = mpv_set_property(d->m_handle, name.constData(), MPV_FORMAT_STRING, &bytevaluedata); \
break; \
} \
} \
if (mpvresult < 0) { \
kWarning() << name << mpv_error_string(mpvresult); \
} \
}
@ -256,13 +343,15 @@ KAbstractPlayerPrivate::~KAbstractPlayerPrivate()
void KAbstractPlayer::load(const QString &path)
{
command(QVariantList() << "loadfile" << path);
command(QList<QByteArray>() << "loadfile" << path.toLocal8Bit());
}
void KAbstractPlayer::load(const QByteArray &data)
{
// SECURITY: this is dangerous but some applications/libraries (like KHTML) require it
command(QVariantList() << "loadfile" << QString("memory://%1").arg(data.data()));
// SECURITY: this is dangerous but some applications and libraries (like Okular) require it
QByteArray memorydata("memory://");
memorydata.append(data);
command(QList<QByteArray>() << "loadfile" << memorydata);
}
void KAbstractPlayer::play()
@ -277,17 +366,17 @@ void KAbstractPlayer::pause()
void KAbstractPlayer::seek(const float position)
{
command(QVariantList() << "seek" << position << "absolute");
command(QList<QByteArray>() << "seek" << QByteArray::number(position) << "absolute");
}
void KAbstractPlayer::seek(const int position)
{
command(QVariantList() << "seek" << position << "absolute");
command(QList<QByteArray>() << "seek" << QByteArray::number(position) << "absolute");
}
void KAbstractPlayer::stop()
{
command(QVariantList() << "stop");
command(QList<QByteArray>() << "stop");
}
QString KAbstractPlayer::path() const
@ -462,7 +551,7 @@ KAudioPlayer::~KAudioPlayer()
delete d;
}
void KAudioPlayer::command(const QVariant &command) const
void KAudioPlayer::command(const QList<QByteArray> &command) const
{
#if defined(HAVE_MPV)
COMMON_COMMAND_SENDER
@ -471,7 +560,7 @@ void KAudioPlayer::command(const QVariant &command) const
#endif
}
QVariant KAudioPlayer::option(const QString &name) const
QVariant KAudioPlayer::option(const QByteArray &name) const
{
#if defined(HAVE_MPV)
COMMON_OPTION_GETTER
@ -481,7 +570,7 @@ QVariant KAudioPlayer::option(const QString &name) const
return QVariant();
}
void KAudioPlayer::setOption(const QString &name, const QVariant &value) const
void KAudioPlayer::setOption(const QByteArray &name, const QVariant &value) const
{
#if defined(HAVE_MPV)
COMMON_OPTION_SETTER
@ -559,7 +648,7 @@ KMediaPlayer::~KMediaPlayer()
delete d;
}
void KMediaPlayer::command(const QVariant &command) const
void KMediaPlayer::command(const QList<QByteArray> &command) const
{
#if defined(HAVE_MPV)
COMMON_COMMAND_SENDER
@ -568,7 +657,7 @@ void KMediaPlayer::command(const QVariant &command) const
#endif
}
QVariant KMediaPlayer::option(const QString &name) const
QVariant KMediaPlayer::option(const QByteArray &name) const
{
#if defined(HAVE_MPV)
COMMON_OPTION_GETTER
@ -578,7 +667,7 @@ QVariant KMediaPlayer::option(const QString &name) const
return QVariant();
}
void KMediaPlayer::setOption(const QString &name, const QVariant &value) const
void KMediaPlayer::setOption(const QByteArray &name, const QVariant &value) const
{
#if defined(HAVE_MPV)
COMMON_OPTION_SETTER

View file

@ -52,11 +52,11 @@ public:
you rely on them.
**/
//! @brief A low-level player command sender
virtual void command(const QVariant &params) const = 0;
virtual void command(const QList<QByteArray> &params) const = 0;
//! @brief A low-level player property getter
virtual QVariant option(const QString &name) const = 0;
virtual QVariant option(const QByteArray &name) const = 0;
//! @brief A low-level player property setter
virtual void setOption(const QString &name, const QVariant &value) const = 0;
virtual void setOption(const QByteArray &name, const QVariant &value) const = 0;
//@}
/*!
@ -244,9 +244,9 @@ public:
KAudioPlayer(QObject *parent = 0);
~KAudioPlayer();
void command(const QVariant &command) const;
QVariant option(const QString &name) const;
void setOption(const QString &name, const QVariant& value) const;
void command(const QList<QByteArray> &command) const;
QVariant option(const QByteArray &name) const;
void setOption(const QByteArray &name, const QVariant& value) const;
void setPlayerID(const QString &id);
bool isMimeSupported(const QString &mime) const;
@ -308,9 +308,9 @@ public:
KMediaPlayer(QWidget *parent = 0);
~KMediaPlayer();
void command(const QVariant &command) const;
QVariant option(const QString &name) const;
void setOption(const QString &name, const QVariant &value) const;
void command(const QList<QByteArray> &command) const;
QVariant option(const QByteArray &name) const;
void setOption(const QByteArray &name, const QVariant &value) const;
void setPlayerID(const QString &id);
bool isMimeSupported(const QString &mime) const;

View file

@ -1,381 +0,0 @@
/* Copyright (C) 2017 the mpv developers
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef MPV_CLIENT_API_QTHELPER_H_
#define MPV_CLIENT_API_QTHELPER_H_
#include <mpv/client.h>
/**
* Note: these helpers are provided for convenience for C++/Qt applications.
* This is based on the public API in client.h, and it does not encode any
* knowledge that is not known or guaranteed outside of the C client API. You
* can even copy and modify this code as you like, or implement similar things
* for other languages.
*/
#include <cstring>
#include <QVariant>
#include <QString>
#include <QList>
#include <QHash>
#include <QSharedPointer>
#include <QMetaType>
namespace mpv {
namespace qt {
// Wrapper around mpv_handle. Does refcounting under the hood.
class Handle
{
struct container {
container(mpv_handle *h) : mpv(h) {}
~container() { mpv_terminate_destroy(mpv); }
mpv_handle *mpv;
};
QSharedPointer<container> sptr;
public:
// Construct a new Handle from a raw mpv_handle with refcount 1. If the
// last Handle goes out of scope, the mpv_handle will be destroyed with
// mpv_terminate_destroy().
// Never destroy the mpv_handle manually when using this wrapper. You
// will create dangling pointers. Just let the wrapper take care of
// destroying the mpv_handle.
// Never create multiple wrappers from the same raw mpv_handle; copy the
// wrapper instead (that's what it's for).
static Handle FromRawHandle(mpv_handle *handle) {
Handle h;
h.sptr = QSharedPointer<container>(new container(handle));
return h;
}
// Return the raw handle; for use with the libmpv C API.
operator mpv_handle*() const { return sptr ? (*sptr).mpv : 0; }
};
static inline QVariant node_to_variant(const mpv_node *node)
{
switch (node->format) {
case MPV_FORMAT_STRING:
return QVariant(QString::fromUtf8(node->u.string));
case MPV_FORMAT_FLAG:
return QVariant(static_cast<bool>(node->u.flag));
case MPV_FORMAT_INT64:
return QVariant(static_cast<qlonglong>(node->u.int64));
case MPV_FORMAT_DOUBLE:
return QVariant(node->u.double_);
case MPV_FORMAT_NODE_ARRAY: {
mpv_node_list *list = node->u.list;
QVariantList qlist;
for (int n = 0; n < list->num; n++)
qlist.append(node_to_variant(&list->values[n]));
return QVariant(qlist);
}
case MPV_FORMAT_NODE_MAP: {
mpv_node_list *list = node->u.list;
QVariantMap qmap;
for (int n = 0; n < list->num; n++) {
qmap.insert(QString::fromUtf8(list->keys[n]),
node_to_variant(&list->values[n]));
}
return QVariant(qmap);
}
default: // MPV_FORMAT_NONE, unknown values (e.g. future extensions)
return QVariant();
}
}
struct node_builder {
node_builder(const QVariant& v) {
set(&node_, v);
}
~node_builder() {
free_node(&node_);
}
mpv_node *node() { return &node_; }
private:
Q_DISABLE_COPY(node_builder)
mpv_node node_;
mpv_node_list *create_list(mpv_node *dst, bool is_map, int num) {
dst->format = is_map ? MPV_FORMAT_NODE_MAP : MPV_FORMAT_NODE_ARRAY;
mpv_node_list *list = new mpv_node_list();
dst->u.list = list;
if (!list)
goto err;
list->values = new mpv_node[num]();
if (!list->values)
goto err;
if (is_map) {
list->keys = new char*[num]();
if (!list->keys)
goto err;
}
return list;
err:
free_node(dst);
return NULL;
}
char *dup_qstring(const QString &s) {
QByteArray b = s.toUtf8();
char *r = new char[b.size() + 1];
if (r)
std::memcpy(r, b.data(), b.size() + 1);
return r;
}
bool test_type(const QVariant &v, QMetaType::Type t) {
// The Qt docs say: "Although this function is declared as returning
// "QVariant::Type(obsolete), the return value should be interpreted
// as QMetaType::Type."
// So a cast really seems to be needed to avoid warnings (urgh).
return static_cast<int>(v.type()) == static_cast<int>(t);
}
void set(mpv_node *dst, const QVariant &src) {
if (test_type(src, QMetaType::QString)) {
dst->format = MPV_FORMAT_STRING;
dst->u.string = dup_qstring(src.toString());
if (!dst->u.string)
goto fail;
} else if (test_type(src, QMetaType::Bool)) {
dst->format = MPV_FORMAT_FLAG;
dst->u.flag = src.toBool() ? 1 : 0;
} else if (test_type(src, QMetaType::Int) ||
test_type(src, QMetaType::LongLong) ||
test_type(src, QMetaType::UInt) ||
test_type(src, QMetaType::ULongLong))
{
dst->format = MPV_FORMAT_INT64;
dst->u.int64 = src.toLongLong();
} else if (test_type(src, QMetaType::Double)) {
dst->format = MPV_FORMAT_DOUBLE;
dst->u.double_ = src.toDouble();
} else if (src.canConvert<QVariantList>()) {
QVariantList qlist = src.toList();
mpv_node_list *list = create_list(dst, false, qlist.size());
if (!list)
goto fail;
list->num = qlist.size();
for (int n = 0; n < qlist.size(); n++)
set(&list->values[n], qlist[n]);
} else if (src.canConvert<QVariantMap>()) {
QVariantMap qmap = src.toMap();
mpv_node_list *list = create_list(dst, true, qmap.size());
if (!list)
goto fail;
list->num = qmap.size();
for (int n = 0; n < qmap.size(); n++) {
list->keys[n] = dup_qstring(qmap.keys()[n]);
if (!list->keys[n]) {
free_node(dst);
goto fail;
}
set(&list->values[n], qmap.values()[n]);
}
} else {
goto fail;
}
return;
fail:
dst->format = MPV_FORMAT_NONE;
}
void free_node(mpv_node *dst) {
switch (dst->format) {
case MPV_FORMAT_STRING:
delete[] dst->u.string;
break;
case MPV_FORMAT_NODE_ARRAY:
case MPV_FORMAT_NODE_MAP: {
mpv_node_list *list = dst->u.list;
if (list) {
for (int n = 0; n < list->num; n++) {
if (list->keys)
delete[] list->keys[n];
if (list->values)
free_node(&list->values[n]);
}
delete[] list->keys;
delete[] list->values;
}
delete list;
break;
}
default: ;
}
dst->format = MPV_FORMAT_NONE;
}
};
/**
* RAII wrapper that calls mpv_free_node_contents() on the pointer.
*/
struct node_autofree {
mpv_node *ptr;
node_autofree(mpv_node *a_ptr) : ptr(a_ptr) {}
~node_autofree() { mpv_free_node_contents(ptr); }
};
/**
* Return the given property as mpv_node converted to QVariant, or QVariant()
* on error.
*
* @deprecated use get_property() instead
*
* @param name the property name
*/
static inline QVariant get_property_variant(mpv_handle *ctx, const QString &name)
{
mpv_node node;
QByteArray nameref = name.toUtf8();
if (mpv_get_property(ctx, nameref.constData(), MPV_FORMAT_NODE, &node) < 0)
return QVariant();
node_autofree f(&node);
return node_to_variant(&node);
}
/**
* Set the given property as mpv_node converted from the QVariant argument.
* @deprecated use set_property() instead
*/
static inline int set_property_variant(mpv_handle *ctx, const QString &name,
const QVariant &v)
{
node_builder node(v);
QByteArray nameref = name.toUtf8();
return mpv_set_property(ctx, nameref.constData(), MPV_FORMAT_NODE, node.node());
}
/**
* Set the given option as mpv_node converted from the QVariant argument.
*
* @deprecated use set_property() instead
*/
static inline int set_option_variant(mpv_handle *ctx, const QString &name,
const QVariant &v)
{
node_builder node(v);
QByteArray nameref = name.toUtf8();
return mpv_set_option(ctx, nameref.constData(), MPV_FORMAT_NODE, node.node());
}
/**
* mpv_command_node() equivalent. Returns QVariant() on error (and
* unfortunately, the same on success).
*
* @deprecated use command() instead
*/
static inline QVariant command_variant(mpv_handle *ctx, const QVariant &args)
{
node_builder node(args);
mpv_node res;
if (mpv_command_node(ctx, node.node(), &res) < 0)
return QVariant();
node_autofree f(&res);
return node_to_variant(&res);
}
/**
* This is used to return error codes wrapped in QVariant for functions which
* return QVariant.
*
* You can use get_error() or is_error() to extract the error status from a
* QVariant value.
*/
struct ErrorReturn
{
/**
* enum mpv_error value (or a value outside of it if ABI was extended)
*/
int error;
ErrorReturn() : error(0) {}
explicit ErrorReturn(int err) : error(err) {}
};
/**
* Return the mpv error code packed into a QVariant, or 0 (success) if it's not
* an error value.
*
* @return error code (<0) or success (>=0)
*/
static inline int get_error(const QVariant &v)
{
if (!v.canConvert<ErrorReturn>())
return 0;
return v.value<ErrorReturn>().error;
}
/**
* Return whether the QVariant carries a mpv error code.
*/
static inline bool is_error(const QVariant &v)
{
return get_error(v) < 0;
}
/**
* Return the given property as mpv_node converted to QVariant, or QVariant()
* on error.
*
* @param name the property name
* @return the property value, or an ErrorReturn with the error code
*/
static inline QVariant get_property(mpv_handle *ctx, const QString &name)
{
mpv_node node;
QByteArray nameref = name.toUtf8();
int err = mpv_get_property(ctx, nameref.constData(), MPV_FORMAT_NODE, &node);
if (err < 0)
return QVariant::fromValue(ErrorReturn(err));
node_autofree f(&node);
return node_to_variant(&node);
}
/**
* Set the given property as mpv_node converted from the QVariant argument.
*
* @return mpv error code (<0 on error, >= 0 on success)
*/
static inline int set_property(mpv_handle *ctx, const QString &name,
const QVariant &v)
{
node_builder node(v);
QByteArray nameref = name.toUtf8();
return mpv_set_property(ctx, nameref.constData(), MPV_FORMAT_NODE, node.node());
}
/**
* mpv_command_node() equivalent.
*
* @param args command arguments, with args[0] being the command name as string
* @return the property value, or an ErrorReturn with the error code
*/
static inline QVariant command(mpv_handle *ctx, const QVariant &args)
{
node_builder node(args);
mpv_node res;
int err = mpv_command_node(ctx, node.node(), &res);
if (err < 0)
return QVariant::fromValue(ErrorReturn(err));
node_autofree f(&res);
return node_to_variant(&res);
}
}
}
Q_DECLARE_METATYPE(mpv::qt::ErrorReturn)
#endif