2016-03-14 21:37:05 +00:00
|
|
|
/* This file is part of the KDE libraries
|
2022-01-09 04:24:09 +02:00
|
|
|
Copyright (C) 2016 Ivailo Monev <xakepa10@gmail.com>
|
2016-03-14 21:37:05 +00:00
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU Library General Public
|
|
|
|
License version 2, as published by the Free Software Foundation.
|
|
|
|
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
Library General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
|
|
Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
2016-09-21 17:58:44 +00:00
|
|
|
#include "kdebug.h"
|
|
|
|
#include "klocale.h"
|
2019-07-12 14:09:24 +00:00
|
|
|
#include "ksettings.h"
|
2016-09-21 17:58:44 +00:00
|
|
|
#include "kmediaplayer.h"
|
2021-02-26 09:55:47 +02:00
|
|
|
|
2019-05-22 04:44:14 +00:00
|
|
|
#include <QApplication>
|
2016-05-14 10:07:58 +00:00
|
|
|
|
2016-09-19 05:30:36 +00:00
|
|
|
#if defined(HAVE_MPV)
|
2021-01-03 00:09:40 +02:00
|
|
|
#include <locale.h>
|
2016-03-14 21:37:05 +00:00
|
|
|
#include <mpv/client.h>
|
2016-05-14 10:07:58 +00:00
|
|
|
#else
|
|
|
|
static bool s_fullscreen = false;
|
|
|
|
#endif // HAVE_MPV
|
2016-03-26 21:29:11 +00:00
|
|
|
|
2019-05-24 20:29:43 +00:00
|
|
|
/*
|
|
|
|
Since sigals/slots cannot be virtual nor multiple QObject inheritance works (KAbstractPlayer
|
|
|
|
cannot inherit from QObject if it is to be used in a class that inherits QWidget), these
|
|
|
|
pre-processor definitions are used to share the code as much as possible making modification
|
|
|
|
easier.
|
|
|
|
*/
|
2021-02-26 09:55:47 +02:00
|
|
|
#define COMMON_STATE_LOAD \
|
|
|
|
d->m_settings->sync(); \
|
|
|
|
const QString globalaudio = d->m_settings->value("global/audiooutput", "auto").toString(); \
|
|
|
|
const int globalvolume = d->m_settings->value("global/volume", 90).toInt(); \
|
|
|
|
const bool globalmute = d->m_settings->value("global/mute", false).toBool(); \
|
|
|
|
setAudioOutput(d->m_settings->value(d->m_playerid + "/audiooutput", globalaudio).toString()); \
|
|
|
|
setVolume(d->m_settings->value(d->m_playerid + "/volume", globalvolume).toInt()); \
|
|
|
|
setMute(d->m_settings->value(d->m_playerid + "/mute", globalmute).toBool());
|
|
|
|
|
2019-05-24 20:29:43 +00:00
|
|
|
#define COMMON_STATE_SAVE \
|
|
|
|
if (d->m_handle && d->m_settings && d->m_settings->isWritable()) { \
|
2021-02-26 09:55:47 +02:00
|
|
|
d->m_settings->setValue(d->m_playerid + "/audiooutput", audiooutput()); \
|
|
|
|
d->m_settings->setValue(d->m_playerid + "/volume", int(volume())); \
|
|
|
|
d->m_settings->setValue(d->m_playerid + "/mute", mute()); \
|
2019-05-24 20:29:43 +00:00
|
|
|
d->m_settings->sync(); \
|
|
|
|
} else { \
|
|
|
|
kWarning() << i18n("Could not save state"); \
|
|
|
|
}
|
|
|
|
|
2021-07-01 16:46:08 +03:00
|
|
|
#define COMMON_COMMAND_SENDER \
|
2019-05-24 20:29:43 +00:00
|
|
|
kDebug() << i18n("sending command") << command; \
|
|
|
|
if (d->m_handle) { \
|
2022-06-04 00:02:19 +03:00
|
|
|
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(); \
|
2019-05-24 20:29:43 +00:00
|
|
|
} \
|
2022-06-04 00:02:19 +03:00
|
|
|
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;
|
|
|
|
}
|
2019-05-24 20:29:43 +00:00
|
|
|
}
|
2022-06-04 00:02:19 +03:00
|
|
|
return result;
|
|
|
|
}
|
2019-05-24 20:29:43 +00:00
|
|
|
|
2021-03-11 06:40:50 +02:00
|
|
|
// certain properties are not available when not playing for an example thus do not issue warning
|
|
|
|
// in case of error
|
2019-05-24 20:29:43 +00:00
|
|
|
#define COMMON_OPTION_GETTER \
|
|
|
|
kDebug() << i18n("getting option") << name; \
|
|
|
|
if (d->m_handle) { \
|
2022-06-04 00:02:19 +03:00
|
|
|
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); \
|
2019-05-24 20:29:43 +00:00
|
|
|
return QVariant(); \
|
|
|
|
} \
|
2022-06-04 00:02:19 +03:00
|
|
|
QVariant result = mpvNodeToVariant(&mpvnode); \
|
|
|
|
mpv_free_node_contents(&mpvnode); \
|
2019-05-24 20:29:43 +00:00
|
|
|
return result; \
|
|
|
|
}
|
|
|
|
|
2022-06-04 00:02:19 +03:00
|
|
|
// NOTE: flags are integers
|
2019-05-24 20:29:43 +00:00
|
|
|
#define COMMON_OPTION_SETTER \
|
|
|
|
kDebug() << i18n("setting option") << name << value; \
|
|
|
|
if (d->m_handle) { \
|
2022-09-23 08:03:09 +03:00
|
|
|
int mpvresult = 0; \
|
2022-06-04 00:02:19 +03:00
|
|
|
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: { \
|
2022-06-05 13:37:11 +03:00
|
|
|
double doublevalue = value.toDouble(); \
|
2022-06-04 00:02:19 +03:00
|
|
|
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; \
|
|
|
|
} \
|
2022-09-23 08:03:09 +03:00
|
|
|
default: { \
|
|
|
|
kWarning() << i18n("Invalid option type") << value.type(); \
|
|
|
|
break; \
|
|
|
|
} \
|
2022-06-04 00:02:19 +03:00
|
|
|
} \
|
|
|
|
if (mpvresult < 0) { \
|
|
|
|
kWarning() << name << mpv_error_string(mpvresult); \
|
2019-05-24 20:29:43 +00:00
|
|
|
} \
|
|
|
|
}
|
|
|
|
|
2021-06-24 12:38:28 +03:00
|
|
|
#define COMMON_EVENT_HANDLER \
|
2019-05-24 20:29:43 +00:00
|
|
|
while (!d->m_stopprocessing) { \
|
|
|
|
mpv_event *event = mpv_wait_event(d->m_handle, 0); \
|
|
|
|
switch (event->event_id) { \
|
2021-01-03 00:16:00 +02:00
|
|
|
case MPV_EVENT_NONE: { \
|
|
|
|
return; \
|
|
|
|
} \
|
|
|
|
case MPV_EVENT_SHUTDOWN: { \
|
|
|
|
d->m_stopprocessing = true; \
|
|
|
|
break; \
|
|
|
|
} \
|
2019-05-24 20:29:43 +00:00
|
|
|
case MPV_EVENT_FILE_LOADED: { \
|
|
|
|
kDebug() << i18n("playback loaded"); \
|
|
|
|
emit loaded(); \
|
|
|
|
break; \
|
|
|
|
} \
|
|
|
|
case MPV_EVENT_END_FILE: { \
|
|
|
|
mpv_event_end_file *prop = static_cast<mpv_event_end_file *>(event->data); \
|
|
|
|
if (prop->reason == MPV_END_FILE_REASON_ERROR) { \
|
2021-03-13 22:19:51 +02:00
|
|
|
const QString mpverror = QString::fromLatin1(mpv_error_string(prop->error)); \
|
2019-05-24 20:29:43 +00:00
|
|
|
kWarning() << i18n("playback finished with error") << mpverror; \
|
|
|
|
emit finished(); \
|
|
|
|
emit error(mpverror); \
|
|
|
|
} else if (prop->reason == MPV_END_FILE_REASON_EOF \
|
|
|
|
|| prop->reason == MPV_END_FILE_REASON_STOP \
|
|
|
|
|| prop->reason == MPV_END_FILE_REASON_QUIT) { \
|
|
|
|
if (option("path").isNull()) { \
|
|
|
|
kDebug() << i18n("playback finished"); \
|
|
|
|
emit finished(); \
|
|
|
|
} \
|
|
|
|
} \
|
|
|
|
break; \
|
|
|
|
} \
|
|
|
|
case MPV_EVENT_PROPERTY_CHANGE: { \
|
|
|
|
mpv_event_property *prop = static_cast<mpv_event_property *>(event->data); \
|
|
|
|
kDebug() << i18n("property changed") << QString::fromLatin1(prop->name); \
|
2021-01-02 06:29:59 +02:00
|
|
|
if (strcmp(prop->name, "=time-pos") == 0 || strcmp(prop->name, "time-pos") == 0) { \
|
2021-03-13 22:19:51 +02:00
|
|
|
if (prop->format == MPV_FORMAT_NONE) { \
|
|
|
|
kDebug() << i18n("the time-pos property is not valid"); \
|
|
|
|
} else if (prop->format == MPV_FORMAT_DOUBLE) { \
|
|
|
|
const double value = *(double *)prop->data; \
|
|
|
|
emit position(value); \
|
2019-05-24 20:29:43 +00:00
|
|
|
} else { \
|
2021-01-02 06:29:59 +02:00
|
|
|
kWarning() << i18n("the time-pos format has changed") << prop->format; \
|
2019-05-24 20:29:43 +00:00
|
|
|
} \
|
|
|
|
} else if (strcmp(prop->name, "seekable") == 0) { \
|
2021-03-13 22:19:51 +02:00
|
|
|
if (prop->format == MPV_FORMAT_NONE) { \
|
|
|
|
kDebug() << i18n("the seekable property is not valid"); \
|
|
|
|
} else if (prop->format == MPV_FORMAT_FLAG) { \
|
|
|
|
const bool value = *(bool *)prop->data; \
|
|
|
|
emit seekable(value); \
|
2019-05-24 20:29:43 +00:00
|
|
|
} else { \
|
2021-01-02 06:29:59 +02:00
|
|
|
kWarning() << i18n("the seekable format has changed") << prop->format; \
|
2019-05-24 20:29:43 +00:00
|
|
|
} \
|
|
|
|
} else if (strcmp(prop->name, "partially-seekable") == 0) { \
|
|
|
|
if (option("seekable").toBool() == false) { \
|
2021-03-13 22:19:51 +02:00
|
|
|
if (prop->format == MPV_FORMAT_NONE) { \
|
|
|
|
kDebug() << i18n("the partially-seekable property is not valid"); \
|
|
|
|
} else if (prop->format == MPV_FORMAT_FLAG) { \
|
|
|
|
const bool value = *(bool *)prop->data; \
|
|
|
|
emit seekable(value); \
|
2019-05-24 20:29:43 +00:00
|
|
|
} else { \
|
2021-01-02 06:29:59 +02:00
|
|
|
kWarning() << i18n("the partially-seekable format has changed") << prop->format; \
|
2019-05-24 20:29:43 +00:00
|
|
|
} \
|
|
|
|
} \
|
|
|
|
} else if (strcmp(prop->name, "paused-for-cache") == 0) { \
|
2021-03-13 22:19:51 +02:00
|
|
|
if (prop->format == MPV_FORMAT_NONE) { \
|
|
|
|
kDebug() << i18n("the paused-for-cache property is not valid"); \
|
|
|
|
} else if (prop->format == MPV_FORMAT_FLAG) { \
|
|
|
|
const bool value = *(bool *)prop->data; \
|
|
|
|
emit buffering(value); \
|
2019-05-24 20:29:43 +00:00
|
|
|
} else { \
|
2021-01-02 06:29:59 +02:00
|
|
|
kWarning() << i18n("the paused-for-cache format has changed") << prop->format; \
|
2019-05-24 20:29:43 +00:00
|
|
|
} \
|
2022-01-07 23:45:54 +02:00
|
|
|
} else if (strcmp(prop->name, "core-idle") == 0) { \
|
|
|
|
if (prop->format == MPV_FORMAT_NONE) { \
|
|
|
|
kDebug() << i18n("the core-idle property is not valid"); \
|
|
|
|
} else if (prop->format == MPV_FORMAT_FLAG) { \
|
|
|
|
const bool value = *(bool *)prop->data; \
|
|
|
|
if (!option("path").isNull()) { \
|
|
|
|
kDebug() << i18n("playback paused") << value; \
|
|
|
|
emit paused(value); \
|
|
|
|
} \
|
|
|
|
} else { \
|
|
|
|
kWarning() << i18n("the core-idle format has changed") << prop->format; \
|
|
|
|
} \
|
2019-05-24 20:29:43 +00:00
|
|
|
} \
|
|
|
|
break; \
|
|
|
|
} \
|
|
|
|
case MPV_EVENT_LOG_MESSAGE: { \
|
|
|
|
mpv_event_log_message *msg = static_cast<mpv_event_log_message *>(event->data); \
|
|
|
|
kDebug() << msg->prefix << msg->text; \
|
|
|
|
break; \
|
|
|
|
} \
|
|
|
|
case MPV_EVENT_QUEUE_OVERFLOW: { \
|
|
|
|
kWarning() << i18n("event queue overflow"); \
|
|
|
|
break; \
|
|
|
|
} \
|
|
|
|
} \
|
|
|
|
}
|
|
|
|
|
2016-06-22 17:22:29 +00:00
|
|
|
// the video decoder may run into its own thread, make sure that does not cause trouble
|
|
|
|
#if defined(HAVE_MPV) && defined(Q_WS_X11)
|
|
|
|
static int kmp_x11_init_threads() {
|
|
|
|
QApplication::setAttribute(Qt::AA_X11InitThreads, true);
|
|
|
|
return 1;
|
|
|
|
};
|
|
|
|
Q_CONSTRUCTOR_FUNCTION(kmp_x11_init_threads)
|
|
|
|
#endif
|
|
|
|
|
2016-09-19 05:30:36 +00:00
|
|
|
class KAbstractPlayerPrivate
|
|
|
|
{
|
|
|
|
public:
|
2019-05-24 20:29:43 +00:00
|
|
|
KAbstractPlayerPrivate();
|
|
|
|
~KAbstractPlayerPrivate();
|
|
|
|
|
2016-09-19 05:30:36 +00:00
|
|
|
#if defined(HAVE_MPV)
|
|
|
|
mpv_handle *m_handle;
|
|
|
|
#endif
|
2021-02-26 09:55:47 +02:00
|
|
|
QString m_playerid;
|
2019-07-12 14:09:24 +00:00
|
|
|
KSettings *m_settings;
|
2021-03-11 06:40:50 +02:00
|
|
|
// the handle pointer is not NULL-ed once mpv_terminate_destroy() has been called, doing it
|
|
|
|
// manually is a race because _processHandleEvents() is called asynchronous
|
2016-09-19 05:30:36 +00:00
|
|
|
bool m_stopprocessing;
|
|
|
|
};
|
|
|
|
|
2019-05-24 20:29:43 +00:00
|
|
|
KAbstractPlayerPrivate::KAbstractPlayerPrivate()
|
2021-02-26 09:55:47 +02:00
|
|
|
: m_playerid(QApplication::applicationName()),
|
2021-01-03 00:16:00 +02:00
|
|
|
m_settings(new KSettings("kmediaplayer", KSettings::FullConfig)),
|
|
|
|
m_stopprocessing(false)
|
2019-05-24 20:29:43 +00:00
|
|
|
{
|
|
|
|
kDebug() << i18n("initializing player");
|
|
|
|
#if defined(HAVE_MPV)
|
|
|
|
setlocale(LC_NUMERIC, "C");
|
|
|
|
m_handle = mpv_create();
|
|
|
|
if (m_handle) {
|
|
|
|
const int rc = mpv_initialize(m_handle);
|
|
|
|
if (rc < 0) {
|
|
|
|
kWarning() << mpv_error_string(rc);
|
|
|
|
} else {
|
|
|
|
mpv_observe_property(m_handle, 0, "time-pos", MPV_FORMAT_DOUBLE);
|
|
|
|
mpv_observe_property(m_handle, 0, "loadfile", MPV_FORMAT_NONE);
|
|
|
|
mpv_observe_property(m_handle, 0, "paused-for-cache", MPV_FORMAT_FLAG);
|
2022-01-07 23:45:54 +02:00
|
|
|
mpv_observe_property(m_handle, 0, "core-idle", MPV_FORMAT_FLAG);
|
2019-05-24 20:29:43 +00:00
|
|
|
mpv_observe_property(m_handle, 0, "seekable", MPV_FORMAT_FLAG);
|
|
|
|
mpv_observe_property(m_handle, 0, "partially-seekable", MPV_FORMAT_FLAG);
|
|
|
|
mpv_request_log_messages(m_handle, "info");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
kWarning() << i18n("context creation failed");
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
2016-08-09 03:29:12 +00:00
|
|
|
|
2019-05-24 20:29:43 +00:00
|
|
|
KAbstractPlayerPrivate::~KAbstractPlayerPrivate()
|
|
|
|
{
|
|
|
|
kDebug() << i18n("destroying player");
|
|
|
|
m_stopprocessing = true;
|
|
|
|
#if defined(HAVE_MPV)
|
|
|
|
mpv_terminate_destroy(m_handle);
|
|
|
|
#endif
|
|
|
|
if (m_settings) {
|
2022-06-11 22:55:59 +03:00
|
|
|
delete m_settings;
|
2019-05-24 20:29:43 +00:00
|
|
|
}
|
|
|
|
}
|
2019-05-22 04:44:14 +00:00
|
|
|
|
|
|
|
void KAbstractPlayer::load(const QString &path)
|
2016-03-26 21:29:11 +00:00
|
|
|
{
|
2022-06-04 00:02:19 +03:00
|
|
|
command(QList<QByteArray>() << "loadfile" << path.toLocal8Bit());
|
2016-03-26 21:29:11 +00:00
|
|
|
}
|
|
|
|
|
2019-05-22 04:44:14 +00:00
|
|
|
void KAbstractPlayer::load(const QByteArray &data)
|
2016-03-31 17:32:20 +00:00
|
|
|
{
|
2022-06-04 00:02:19 +03:00
|
|
|
// SECURITY: this is dangerous but some applications and libraries (like Okular) require it
|
|
|
|
QByteArray memorydata("memory://");
|
|
|
|
memorydata.append(data);
|
|
|
|
command(QList<QByteArray>() << "loadfile" << memorydata);
|
2016-03-31 17:32:20 +00:00
|
|
|
}
|
|
|
|
|
2016-03-26 18:06:51 +00:00
|
|
|
void KAbstractPlayer::play()
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-23 17:12:53 +00:00
|
|
|
setOption("pause", false);
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void KAbstractPlayer::pause()
|
|
|
|
{
|
2019-05-23 17:12:53 +00:00
|
|
|
setOption("pause", true);
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2016-09-24 18:21:23 +00:00
|
|
|
void KAbstractPlayer::seek(const float position)
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2022-06-04 00:02:19 +03:00
|
|
|
command(QList<QByteArray>() << "seek" << QByteArray::number(position) << "absolute");
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2016-09-24 18:21:23 +00:00
|
|
|
void KAbstractPlayer::seek(const int position)
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2022-06-04 00:02:19 +03:00
|
|
|
command(QList<QByteArray>() << "seek" << QByteArray::number(position) << "absolute");
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void KAbstractPlayer::stop()
|
|
|
|
{
|
2022-06-04 00:02:19 +03:00
|
|
|
command(QList<QByteArray>() << "stop");
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2016-09-21 17:58:44 +00:00
|
|
|
QString KAbstractPlayer::path() const
|
2016-03-23 04:16:54 +00:00
|
|
|
{
|
2019-05-23 17:12:53 +00:00
|
|
|
return option("path").toString();
|
2016-03-23 04:16:54 +00:00
|
|
|
}
|
|
|
|
|
2016-09-21 17:58:44 +00:00
|
|
|
QString KAbstractPlayer::title() const
|
2016-03-23 04:16:54 +00:00
|
|
|
{
|
2019-05-23 17:12:53 +00:00
|
|
|
return option("media-title").toString();
|
2016-03-23 04:16:54 +00:00
|
|
|
}
|
|
|
|
|
2016-09-21 17:58:44 +00:00
|
|
|
float KAbstractPlayer::currentTime() const
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-23 17:12:53 +00:00
|
|
|
return option("time-pos").toFloat();
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2016-09-21 17:58:44 +00:00
|
|
|
float KAbstractPlayer::remainingTime() const
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-23 17:12:53 +00:00
|
|
|
return option("time-remaining").toFloat();
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2016-09-21 17:58:44 +00:00
|
|
|
float KAbstractPlayer::totalTime() const
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-23 17:12:53 +00:00
|
|
|
return option("duration").toFloat();
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2016-09-21 17:58:44 +00:00
|
|
|
float KAbstractPlayer::volume() const
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-23 17:12:53 +00:00
|
|
|
return option("volume").toFloat();
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2016-09-21 17:58:44 +00:00
|
|
|
bool KAbstractPlayer::mute() const
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-23 17:12:53 +00:00
|
|
|
return option("mute").toBool();
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2016-09-21 17:58:44 +00:00
|
|
|
QStringList KAbstractPlayer::protocols() const
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
|
|
|
static QStringList s_protocols;
|
|
|
|
if (s_protocols.isEmpty()) {
|
2019-05-23 17:12:53 +00:00
|
|
|
s_protocols = option("protocol-list").toStringList();
|
2019-06-01 21:59:08 +00:00
|
|
|
s_protocols.removeDuplicates();
|
2019-06-01 23:59:31 +00:00
|
|
|
qSort(s_protocols);
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
return s_protocols;
|
|
|
|
}
|
|
|
|
|
2016-09-21 17:58:44 +00:00
|
|
|
QString KAbstractPlayer::audiooutput() const
|
2016-03-27 23:23:07 +00:00
|
|
|
{
|
2019-05-23 17:12:53 +00:00
|
|
|
return option("audio-device").toString();
|
2016-03-27 23:23:07 +00:00
|
|
|
}
|
|
|
|
|
2016-09-21 17:58:44 +00:00
|
|
|
QStringList KAbstractPlayer::audiooutputs() const
|
2016-03-27 23:23:07 +00:00
|
|
|
{
|
2019-05-23 17:12:53 +00:00
|
|
|
const QVariantList value = option("audio-device-list").toList();
|
2016-03-27 23:23:07 +00:00
|
|
|
QStringList stringlist;
|
2020-02-25 17:40:51 +00:00
|
|
|
foreach (const QVariant &variant, value) {
|
2016-03-27 23:23:07 +00:00
|
|
|
QMapIterator<QString,QVariant> iter(variant.toMap());
|
|
|
|
while (iter.hasNext()) {
|
|
|
|
iter.next();
|
|
|
|
if (iter.key() == "name") {
|
|
|
|
stringlist.append(iter.value().toString());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return stringlist;
|
|
|
|
}
|
|
|
|
|
2016-09-21 17:58:44 +00:00
|
|
|
bool KAbstractPlayer::isPlaying() const
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-23 17:12:53 +00:00
|
|
|
return !option("pause").toBool() && !option("path").isNull();
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2016-09-21 17:58:44 +00:00
|
|
|
bool KAbstractPlayer::isBuffering() const
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-23 17:12:53 +00:00
|
|
|
return option("paused-for-cache").toBool();
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2016-09-21 17:58:44 +00:00
|
|
|
bool KAbstractPlayer::isSeekable() const
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-23 17:12:53 +00:00
|
|
|
return option("seekable").toBool() || option("partially-seekable").toBool();
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2016-09-21 17:58:44 +00:00
|
|
|
bool KAbstractPlayer::isFullscreen() const
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2016-09-19 05:30:36 +00:00
|
|
|
#if defined(HAVE_MPV)
|
2019-05-23 17:12:53 +00:00
|
|
|
return option("fullscreen").toBool();
|
2016-05-14 10:07:58 +00:00
|
|
|
#else
|
|
|
|
return s_fullscreen;
|
|
|
|
#endif // HAVE_MPV
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2019-05-22 04:44:14 +00:00
|
|
|
bool KAbstractPlayer::isProtocolSupported(const QString &protocol) const
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2020-02-25 17:40:51 +00:00
|
|
|
foreach(const QString &proto, protocols()) {
|
2016-03-28 23:59:21 +00:00
|
|
|
if (protocol.startsWith(proto)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2019-05-22 04:44:14 +00:00
|
|
|
bool KAbstractPlayer::isPathSupported(const QString &path) const
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2016-09-21 17:58:44 +00:00
|
|
|
const KMimeType::Ptr mime = KMimeType::findByPath(path);
|
2016-03-23 04:16:54 +00:00
|
|
|
if (mime && isMimeSupported(mime->name())) {
|
2016-03-14 21:37:05 +00:00
|
|
|
return true;
|
|
|
|
}
|
2016-03-23 04:16:54 +00:00
|
|
|
return isProtocolSupported(path);
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2016-09-24 18:21:23 +00:00
|
|
|
void KAbstractPlayer::setVolume(const float volume)
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-23 17:12:53 +00:00
|
|
|
setOption("volume", volume);
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2016-09-24 18:21:23 +00:00
|
|
|
void KAbstractPlayer::setVolume(const int volume)
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-23 17:12:53 +00:00
|
|
|
setOption("volume", volume);
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2016-09-24 18:21:23 +00:00
|
|
|
void KAbstractPlayer::setMute(const bool mute)
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-23 17:12:53 +00:00
|
|
|
setOption("mute", mute);
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2019-05-22 04:44:14 +00:00
|
|
|
void KAbstractPlayer::setAudioOutput(const QString &output)
|
2016-03-27 23:23:07 +00:00
|
|
|
{
|
2019-05-23 17:12:53 +00:00
|
|
|
setOption("audio-device", output);
|
2016-03-27 23:23:07 +00:00
|
|
|
}
|
|
|
|
|
2016-09-24 18:21:23 +00:00
|
|
|
void KAbstractPlayer::setFullscreen(const bool fullscreen)
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2016-09-19 05:30:36 +00:00
|
|
|
#if defined(HAVE_MPV)
|
2019-05-23 17:12:53 +00:00
|
|
|
setOption("fullscreen", fullscreen);
|
2016-05-14 10:07:58 +00:00
|
|
|
#else
|
|
|
|
s_fullscreen = fullscreen;
|
|
|
|
#endif // HAVE_MPV
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void wakeup_audio(void *ctx)
|
|
|
|
{
|
2016-04-08 01:17:17 +00:00
|
|
|
KAudioPlayer *pctx = static_cast<KAudioPlayer*>(ctx);
|
|
|
|
QMetaObject::invokeMethod(pctx, "_processHandleEvents", Qt::QueuedConnection);
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
KAudioPlayer::KAudioPlayer(QObject *parent)
|
2021-02-26 09:55:47 +02:00
|
|
|
: QObject(parent), d(new KAbstractPlayerPrivate())
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-24 20:29:43 +00:00
|
|
|
#if defined(HAVE_MPV)
|
2016-09-19 05:30:36 +00:00
|
|
|
if (d->m_handle) {
|
|
|
|
mpv_set_wakeup_callback(d->m_handle, wakeup_audio, this);
|
2017-07-08 13:08:34 +00:00
|
|
|
|
2019-05-24 20:29:43 +00:00
|
|
|
// newer releases use vid, video is compat! the change is pre-2014 but yeah..
|
2019-05-23 17:12:53 +00:00
|
|
|
setOption("vid", "no");
|
|
|
|
setOption("video", "no");
|
2016-03-14 21:37:05 +00:00
|
|
|
|
2021-02-26 09:55:47 +02:00
|
|
|
COMMON_STATE_LOAD
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
2019-05-24 20:29:43 +00:00
|
|
|
#else
|
|
|
|
kWarning() << i18n("KAudioPlayer is a stub");
|
|
|
|
#endif
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
KAudioPlayer::~KAudioPlayer()
|
|
|
|
{
|
2019-05-24 20:29:43 +00:00
|
|
|
#if defined(HAVE_MPV)
|
|
|
|
COMMON_STATE_SAVE
|
|
|
|
#endif
|
2016-03-14 21:37:05 +00:00
|
|
|
|
2019-05-24 20:29:43 +00:00
|
|
|
delete d;
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2022-06-04 00:02:19 +03:00
|
|
|
void KAudioPlayer::command(const QList<QByteArray> &command) const
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-24 20:29:43 +00:00
|
|
|
#if defined(HAVE_MPV)
|
2021-07-01 16:46:08 +03:00
|
|
|
COMMON_COMMAND_SENDER
|
2019-05-24 20:29:43 +00:00
|
|
|
#else
|
|
|
|
Q_UNUSED(command);
|
|
|
|
#endif
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2022-06-04 00:02:19 +03:00
|
|
|
QVariant KAudioPlayer::option(const QByteArray &name) const
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-24 20:29:43 +00:00
|
|
|
#if defined(HAVE_MPV)
|
2019-05-23 17:12:53 +00:00
|
|
|
COMMON_OPTION_GETTER
|
2019-05-24 20:29:43 +00:00
|
|
|
#else
|
|
|
|
Q_UNUSED(name);
|
|
|
|
#endif
|
|
|
|
return QVariant();
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2022-06-04 00:02:19 +03:00
|
|
|
void KAudioPlayer::setOption(const QByteArray &name, const QVariant &value) const
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-24 20:29:43 +00:00
|
|
|
#if defined(HAVE_MPV)
|
2016-03-14 21:37:05 +00:00
|
|
|
COMMON_OPTION_SETTER
|
2019-05-24 20:29:43 +00:00
|
|
|
#else
|
|
|
|
Q_UNUSED(name);
|
|
|
|
Q_UNUSED(value);
|
|
|
|
#endif
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void KAudioPlayer::_processHandleEvents()
|
|
|
|
{
|
2019-05-24 20:29:43 +00:00
|
|
|
#if defined(HAVE_MPV)
|
2021-06-24 12:38:28 +03:00
|
|
|
COMMON_EVENT_HANDLER
|
2019-05-24 20:29:43 +00:00
|
|
|
#endif
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2021-02-26 09:55:47 +02:00
|
|
|
void KAudioPlayer::setPlayerID(const QString &id)
|
|
|
|
{
|
|
|
|
d->m_playerid = id;
|
|
|
|
#if defined(HAVE_MPV)
|
|
|
|
COMMON_STATE_LOAD
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2019-05-22 04:44:14 +00:00
|
|
|
bool KAudioPlayer::isMimeSupported(const QString &mime) const
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-24 20:29:43 +00:00
|
|
|
#if defined(HAVE_MPV)
|
2016-03-14 21:37:05 +00:00
|
|
|
return mime.startsWith("audio/") || mime == QLatin1String("application/octet-stream");
|
2019-05-24 20:29:43 +00:00
|
|
|
#else
|
|
|
|
Q_UNUSED(mime);
|
|
|
|
return false;
|
|
|
|
#endif
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/////
|
|
|
|
static void wakeup_media(void *ctx)
|
|
|
|
{
|
2016-04-08 01:17:17 +00:00
|
|
|
KMediaPlayer *pctx = static_cast<KMediaPlayer*>(ctx);
|
|
|
|
QMetaObject::invokeMethod(pctx, "_processHandleEvents", Qt::QueuedConnection);
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
KMediaPlayer::KMediaPlayer(QWidget *parent)
|
2016-09-19 05:30:36 +00:00
|
|
|
: QWidget(parent), d(new KAbstractPlayerPrivate)
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-24 20:29:43 +00:00
|
|
|
#if defined(HAVE_MPV)
|
2016-09-19 05:30:36 +00:00
|
|
|
if (d->m_handle) {
|
|
|
|
mpv_set_wakeup_callback(d->m_handle, wakeup_media, this);
|
2017-07-08 13:08:34 +00:00
|
|
|
|
2020-01-25 00:59:00 +00:00
|
|
|
// QVariant cannot be constructed from WId type and the actual WId type is ulong
|
2016-03-14 21:37:05 +00:00
|
|
|
QVariant wid;
|
|
|
|
if (parent) {
|
2020-01-25 00:59:00 +00:00
|
|
|
wid = QVariant::fromValue(qlonglong(quintptr(parent->winId())));
|
2016-03-14 21:37:05 +00:00
|
|
|
} else {
|
2020-01-25 00:59:00 +00:00
|
|
|
wid = QVariant::fromValue(qlonglong(quintptr(winId())));
|
2016-06-03 22:15:57 +00:00
|
|
|
}
|
|
|
|
if (wid.isValid()) {
|
|
|
|
setOption("wid", wid);
|
|
|
|
} else {
|
|
|
|
kWarning() << i18n("Could not get widget ID");
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2021-02-26 09:55:47 +02:00
|
|
|
COMMON_STATE_LOAD
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
2019-05-24 20:29:43 +00:00
|
|
|
#else
|
|
|
|
kWarning() << i18n("KMediaPlayer is a stub");
|
|
|
|
#endif
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
KMediaPlayer::~KMediaPlayer()
|
|
|
|
{
|
2019-05-24 20:29:43 +00:00
|
|
|
#if defined(HAVE_MPV)
|
|
|
|
COMMON_STATE_SAVE
|
|
|
|
#endif
|
2016-03-14 21:37:05 +00:00
|
|
|
|
2019-05-24 20:29:43 +00:00
|
|
|
delete d;
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2022-06-04 00:02:19 +03:00
|
|
|
void KMediaPlayer::command(const QList<QByteArray> &command) const
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-24 20:29:43 +00:00
|
|
|
#if defined(HAVE_MPV)
|
2021-07-01 16:46:08 +03:00
|
|
|
COMMON_COMMAND_SENDER
|
2019-05-24 20:29:43 +00:00
|
|
|
#else
|
|
|
|
Q_UNUSED(command);
|
|
|
|
#endif
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2022-06-04 00:02:19 +03:00
|
|
|
QVariant KMediaPlayer::option(const QByteArray &name) const
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-24 20:29:43 +00:00
|
|
|
#if defined(HAVE_MPV)
|
2019-05-23 17:12:53 +00:00
|
|
|
COMMON_OPTION_GETTER
|
2019-05-24 20:29:43 +00:00
|
|
|
#else
|
|
|
|
Q_UNUSED(name);
|
|
|
|
#endif
|
|
|
|
return QVariant();
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2022-06-04 00:02:19 +03:00
|
|
|
void KMediaPlayer::setOption(const QByteArray &name, const QVariant &value) const
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-24 20:29:43 +00:00
|
|
|
#if defined(HAVE_MPV)
|
2016-03-14 21:37:05 +00:00
|
|
|
COMMON_OPTION_SETTER
|
2019-05-24 20:29:43 +00:00
|
|
|
#else
|
|
|
|
Q_UNUSED(name);
|
|
|
|
Q_UNUSED(value);
|
|
|
|
#endif
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void KMediaPlayer::_processHandleEvents()
|
|
|
|
{
|
2019-05-24 20:29:43 +00:00
|
|
|
#if defined(HAVE_MPV)
|
2021-06-24 12:38:28 +03:00
|
|
|
COMMON_EVENT_HANDLER
|
2019-05-24 20:29:43 +00:00
|
|
|
#endif
|
2016-03-14 21:37:05 +00:00
|
|
|
}
|
|
|
|
|
2021-02-26 09:55:47 +02:00
|
|
|
void KMediaPlayer::setPlayerID(const QString &id)
|
|
|
|
{
|
|
|
|
d->m_playerid = id;
|
|
|
|
#if defined(HAVE_MPV)
|
|
|
|
COMMON_STATE_LOAD
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2019-05-22 04:44:14 +00:00
|
|
|
bool KMediaPlayer::isMimeSupported(const QString &mime) const
|
2016-03-14 21:37:05 +00:00
|
|
|
{
|
2019-05-24 20:29:43 +00:00
|
|
|
#if defined(HAVE_MPV)
|
2016-03-14 21:37:05 +00:00
|
|
|
return mime.startsWith("audio/") || mime.startsWith("video/")
|
|
|
|
|| mime == QLatin1String("application/octet-stream");
|
2019-05-24 20:29:43 +00:00
|
|
|
#else
|
2016-05-14 10:07:58 +00:00
|
|
|
Q_UNUSED(mime);
|
|
|
|
return false;
|
2019-05-24 20:29:43 +00:00
|
|
|
#endif
|
2016-05-14 10:07:58 +00:00
|
|
|
}
|
|
|
|
|
2016-03-14 21:37:05 +00:00
|
|
|
#include "moc_kmediaplayer.cpp"
|