1
0
Fork 0

Reformat code.

This commit is contained in:
namedkitten 2020-05-06 12:43:01 +01:00
parent e1dd828968
commit 9cf2aafd73
57 changed files with 3906 additions and 3995 deletions

5
.clang-format Normal file
View file

@ -0,0 +1,5 @@
---
Language: Cpp
BasedOnStyle: WebKit
...

View file

@ -1,6 +1,8 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -x
SOURCE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" SOURCE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
pushd $SOURCE_DIR pushd $SOURCE_DIR
find . -name "*.qml" -exec qmlfmt -w {} \; find . -name "*.qml" -exec qmlfmt -i 2 -w {} \;
find . -name "*.cpp" -o -name "*.hpp" -o -name "*.c" -o -name "*.h" -exec clang-format90 -style mozilla -i {} \; clang-format -style=file -i $(find src -name "*.cpp" -o -name "*.hpp" -o -name "*.c" -o -name "*.h")
#find . -name "*.cpp" -o -name "*.hpp" -o -name "*.c" -o -name "*.h" -exec clang-format -style=file -i {} \;
popd popd

View file

@ -1,24 +1,24 @@
#include "src/Backends/MPV/MPVBackend.hpp" #include "src/Backends/MPV/MPVBackend.hpp"
#include <QtCore/qglobal.h> #include "src/Backends/MPVCommon/MPVCommon.hpp"
#include <mpv/render_gl.h> #include "src/qthelper.hpp"
#include "src/utils.hpp"
#include <QByteArray> #include <QByteArray>
#include <QCoreApplication> #include <QCoreApplication>
#include <QGuiApplication>
#include <QEvent>
#include <QDebug> #include <QDebug>
#include <QEvent>
#include <QGuiApplication>
#include <QIcon> #include <QIcon>
#include <QMetaObject> #include <QMetaObject>
#include <QObject> #include <QObject>
#include <QOpenGLContext> #include <QOpenGLContext>
#include <QOpenGLFramebufferObject> #include <QOpenGLFramebufferObject>
#include <QQuickWindow> #include <QQuickWindow>
#include <stdio.h> #include <QtCore/qglobal.h>
#include <clocale> #include <clocale>
#include <iostream> #include <iostream>
#include "src/qthelper.hpp" #include <mpv/render_gl.h>
#include <stdexcept> #include <stdexcept>
#include "src/Backends/MPVCommon/MPVCommon.hpp" #include <stdio.h>
#include "src/utils.hpp"
class QQuickItem; class QQuickItem;
class QSize; class QSize;
@ -26,328 +26,308 @@ class QSize;
#ifdef ENABLE_X11 #ifdef ENABLE_X11
#include <QX11Info> // IWYU pragma: keep #include <QX11Info> // IWYU pragma: keep
#include <QtX11Extras/QX11Info> // IWYU pragma: keep #include <QtX11Extras/QX11Info> // IWYU pragma: keep
#include <qx11info_x11.h> // IWYU pragma: keep
#include <X11/Xlib.h> // IWYU pragma: keep #include <X11/Xlib.h> // IWYU pragma: keep
#include <X11/Xutil.h> // IWYU pragma: keep #include <X11/Xutil.h> // IWYU pragma: keep
#include <qx11info_x11.h> // IWYU pragma: keep
#endif #endif
#include <qpa/qplatformnativeinterface.h> // IWYU pragma: keep #include <qpa/qplatformnativeinterface.h> // IWYU pragma: keep
#endif #endif
bool usedirect = false; bool usedirect = false;
namespace { namespace {
void void wakeup(void* ctx)
wakeup(void* ctx)
{ {
QCoreApplication::postEvent((MPVBackend*)ctx, new QEvent(QEvent::User)); QCoreApplication::postEvent((MPVBackend*)ctx, new QEvent(QEvent::User));
} }
void void on_mpv_redraw(void* ctx)
on_mpv_redraw(void* ctx)
{ {
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
reinterpret_cast<MPVBackend*>(ctx), "update", Qt::QueuedConnection); reinterpret_cast<MPVBackend*>(ctx), "update", Qt::QueuedConnection);
} }
static void* static void*
get_proc_address_mpv(void* ctx, const char* name) get_proc_address_mpv(void* ctx, const char* name)
{ {
return reinterpret_cast<void*>( return reinterpret_cast<void*>(
reinterpret_cast<QOpenGLContext*>(ctx)->getProcAddress(QByteArray(name))); reinterpret_cast<QOpenGLContext*>(ctx)->getProcAddress(QByteArray(name)));
} }
} // namespace } // namespace
class MpvRenderer : public QQuickFramebufferObject::Renderer class MpvRenderer : public QQuickFramebufferObject::Renderer {
{ MPVBackend* obj;
MPVBackend* obj;
public: public:
MpvRenderer(MPVBackend* new_obj) MpvRenderer(MPVBackend* new_obj)
: obj{ new_obj } : obj{ new_obj }
{ {
if (usedirect) { if (usedirect) {
int r = int r = mpv_opengl_cb_init_gl(obj->mpv_gl_cb, NULL, get_proc_address_mpv, QOpenGLContext::currentContext());
mpv_opengl_cb_init_gl(obj->mpv_gl_cb, NULL, get_proc_address_mpv, QOpenGLContext::currentContext()); if (r < 0) {
if (r < 0) { std::cout << "No." << std::endl;
std::cout << "No." << std::endl; throw std::runtime_error("failed to initialize mpv GL context");
throw std::runtime_error("failed to initialize mpv GL context"); }
} }
} }
} virtual ~MpvRenderer() {}
virtual ~MpvRenderer() {} // This function is called when a new FBO is needed.
// This happens on the initial frame.
// This function is called when a new FBO is needed. QOpenGLFramebufferObject* createFramebufferObject(const QSize& size)
// This happens on the initial frame. {
QOpenGLFramebufferObject* createFramebufferObject(const QSize& size) // init mpv_gl:
{ if (!obj->mpv_gl && !usedirect) {
// init mpv_gl: mpv_opengl_init_params gl_init_params{ get_proc_address_mpv,
if (!obj->mpv_gl && !usedirect) { QOpenGLContext::currentContext(),
mpv_opengl_init_params gl_init_params{ get_proc_address_mpv, nullptr };
QOpenGLContext::currentContext(), mpv_render_param params[]{
nullptr }; { MPV_RENDER_PARAM_API_TYPE,
mpv_render_param params[]{ const_cast<char*>(MPV_RENDER_API_TYPE_OPENGL) },
{ MPV_RENDER_PARAM_API_TYPE, { MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params },
const_cast<char*>(MPV_RENDER_API_TYPE_OPENGL) }, { MPV_RENDER_PARAM_INVALID, nullptr },
{ MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params }, { MPV_RENDER_PARAM_INVALID, nullptr }
{ MPV_RENDER_PARAM_INVALID, nullptr }, };
{ MPV_RENDER_PARAM_INVALID, nullptr }
};
#if defined(__linux__) || defined(__FreeBSD__) #if defined(__linux__) || defined(__FreeBSD__)
#ifdef ENABLE_X11 #ifdef ENABLE_X11
if (QGuiApplication::platformName().contains("xcb")) { if (QGuiApplication::platformName().contains("xcb")) {
params[2].type = MPV_RENDER_PARAM_X11_DISPLAY; params[2].type = MPV_RENDER_PARAM_X11_DISPLAY;
params[2].data = QX11Info::display(); params[2].data = QX11Info::display();
} }
#endif #endif
if (QGuiApplication::platformName().contains("wayland")) { if (QGuiApplication::platformName().contains("wayland")) {
params[2].type = MPV_RENDER_PARAM_WL_DISPLAY; params[2].type = MPV_RENDER_PARAM_WL_DISPLAY;
auto *native = QGuiApplication::platformNativeInterface(); auto* native = QGuiApplication::platformNativeInterface();
params[2].data = native->nativeResourceForWindow("display", NULL); params[2].data = native->nativeResourceForWindow("display", NULL);
} }
#endif #endif
if (mpv_render_context_create(&obj->mpv_gl, obj->mpv, params) < 0) { if (mpv_render_context_create(&obj->mpv_gl, obj->mpv, params) < 0) {
std::cout << "Failed to use render API, try setting Backend/direct to true in settings." << std::endl; std::cout << "Failed to use render API, try setting Backend/direct to true in settings." << std::endl;
throw std::runtime_error("failed to initialize mpv GL context"); throw std::runtime_error("failed to initialize mpv GL context");
} }
mpv_render_context_set_update_callback(obj->mpv_gl, on_mpv_redraw, obj); mpv_render_context_set_update_callback(obj->mpv_gl, on_mpv_redraw, obj);
} }
QMetaObject::invokeMethod(obj, "startPlayer"); QMetaObject::invokeMethod(obj, "startPlayer");
return QQuickFramebufferObject::Renderer::createFramebufferObject(size);
return QQuickFramebufferObject::Renderer::createFramebufferObject(size);
}
void render()
{
obj->window()->resetOpenGLState();
QOpenGLFramebufferObject* fbo = framebufferObject();
if (usedirect) {
mpv_opengl_cb_draw(obj->mpv_gl_cb, fbo->handle(), fbo->width(), fbo->height());
} else {
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[] = { { MPV_RENDER_PARAM_OPENGL_FBO, &mpfbo },
{ MPV_RENDER_PARAM_FLIP_Y, &flip_y },
{ MPV_RENDER_PARAM_INVALID, nullptr } };
mpv_render_context_render(obj->mpv_gl, params);
} }
obj->window()->resetOpenGLState(); void render()
} {
obj->window()->resetOpenGLState();
QOpenGLFramebufferObject* fbo = framebufferObject();
if (usedirect) {
mpv_opengl_cb_draw(obj->mpv_gl_cb, fbo->handle(), fbo->width(), fbo->height());
} else {
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[] = { { MPV_RENDER_PARAM_OPENGL_FBO, &mpfbo },
{ MPV_RENDER_PARAM_FLIP_Y, &flip_y },
{ MPV_RENDER_PARAM_INVALID, nullptr } };
mpv_render_context_render(obj->mpv_gl, params);
}
obj->window()->resetOpenGLState();
}
}; };
MPVBackend::MPVBackend(QQuickItem* parent) MPVBackend::MPVBackend(QQuickItem* parent)
: QQuickFramebufferObject(parent) : QQuickFramebufferObject(parent)
, mpv{ mpv_create() } , mpv{ mpv_create() }
, mpv_gl(nullptr) , mpv_gl(nullptr)
, mpv_gl_cb(nullptr) , mpv_gl_cb(nullptr)
{ {
if (!mpv) if (!mpv)
throw std::runtime_error("could not create mpv context"); throw std::runtime_error("could not create mpv context");
QSettings settings; QSettings settings;
usedirect = settings.value("Backend/direct", false).toBool(); usedirect = settings.value("Backend/direct", false).toBool();
mpv_set_option_string(mpv, "terminal", "false"); mpv_set_option_string(mpv, "terminal", "false");
mpv_set_option_string(mpv, "msg-level", "all=v"); mpv_set_option_string(mpv, "msg-level", "all=v");
// Fix? // Fix?
mpv_set_option_string(mpv, "ytdl", "yes"); mpv_set_option_string(mpv, "ytdl", "yes");
mpv_set_option_string(mpv, "slang", "en"); mpv_set_option_string(mpv, "slang", "en");
mpv_set_option_string(mpv, "config", "yes"); mpv_set_option_string(mpv, "config", "yes");
mpv_observe_property(mpv, 0, "tracks-menu", MPV_FORMAT_NONE); mpv_observe_property(mpv, 0, "tracks-menu", MPV_FORMAT_NONE);
mpv_observe_property(mpv, 0, "chapter-list", MPV_FORMAT_NODE); mpv_observe_property(mpv, 0, "chapter-list", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "playback-abort", MPV_FORMAT_NONE); 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, "chapter-list", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "track-list", MPV_FORMAT_NODE); mpv_observe_property(mpv, 0, "track-list", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "audio-device-list", MPV_FORMAT_NODE); mpv_observe_property(mpv, 0, "audio-device-list", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "playlist-pos", MPV_FORMAT_DOUBLE); mpv_observe_property(mpv, 0, "playlist-pos", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "volume", MPV_FORMAT_NONE); mpv_observe_property(mpv, 0, "volume", MPV_FORMAT_NONE);
mpv_observe_property(mpv, 0, "mute", MPV_FORMAT_NONE); mpv_observe_property(mpv, 0, "mute", MPV_FORMAT_NONE);
mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE); mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "media-title", MPV_FORMAT_STRING); mpv_observe_property(mpv, 0, "media-title", MPV_FORMAT_STRING);
mpv_observe_property(mpv, 0, "sub-text", MPV_FORMAT_STRING); mpv_observe_property(mpv, 0, "sub-text", MPV_FORMAT_STRING);
mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE); mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "demuxer-cache-duration", MPV_FORMAT_DOUBLE); mpv_observe_property(mpv, 0, "demuxer-cache-duration", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_NODE); mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "playlist", MPV_FORMAT_NODE); mpv_observe_property(mpv, 0, "playlist", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "speed", MPV_FORMAT_DOUBLE); mpv_observe_property(mpv, 0, "speed", MPV_FORMAT_DOUBLE);
mpv_request_log_messages(mpv, "v"); mpv_request_log_messages(mpv, "v");
mpv_set_wakeup_callback(mpv, wakeup, this); mpv_set_wakeup_callback(mpv, wakeup, this);
if (mpv_initialize(mpv) < 0) if (mpv_initialize(mpv) < 0)
throw std::runtime_error("could not initialize mpv context"); throw std::runtime_error("could not initialize mpv context");
if (usedirect) { if (usedirect) {
mpv_set_option_string(mpv, "vo", "libmpv"); mpv_set_option_string(mpv, "vo", "libmpv");
mpv_gl_cb = (mpv_opengl_cb_context*)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB); mpv_gl_cb = (mpv_opengl_cb_context*)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
if (!mpv_gl_cb) if (!mpv_gl_cb)
throw std::runtime_error("OpenGL not compiled in"); throw std::runtime_error("OpenGL not compiled in");
mpv_opengl_cb_set_update_callback(mpv_gl_cb, on_mpv_redraw, (void*)this); mpv_opengl_cb_set_update_callback(mpv_gl_cb, on_mpv_redraw, (void*)this);
} else { } else {
mpv_set_option_string(mpv, "vo", "libmpv"); mpv_set_option_string(mpv, "vo", "libmpv");
} }
connect(this, connect(this,
&MPVBackend::onUpdate, &MPVBackend::onUpdate,
this, this,
&MPVBackend::doUpdate, &MPVBackend::doUpdate,
Qt::QueuedConnection); Qt::QueuedConnection);
connect(this, connect(this,
&MPVBackend::positionChanged, &MPVBackend::positionChanged,
this, this,
&MPVBackend::updateDurationString, &MPVBackend::updateDurationString,
Qt::QueuedConnection); Qt::QueuedConnection);
connect(this, connect(this,
&MPVBackend::durationChanged, &MPVBackend::durationChanged,
this, this,
&MPVBackend::updateDurationString, &MPVBackend::updateDurationString,
Qt::QueuedConnection); Qt::QueuedConnection);
} }
MPVBackend::~MPVBackend() MPVBackend::~MPVBackend()
{ {
printf("Shutting down...\n"); printf("Shutting down...\n");
Utils::SetDPMS(true); Utils::SetDPMS(true);
command("write-watch-later-config"); command("write-watch-later-config");
if (usedirect && mpv_gl_cb) { if (usedirect && mpv_gl_cb) {
mpv_opengl_cb_uninit_gl(mpv_gl_cb); mpv_opengl_cb_uninit_gl(mpv_gl_cb);
} else if (mpv_gl){ } else if (mpv_gl) {
mpv_render_context_free(mpv_gl); mpv_render_context_free(mpv_gl);
} }
mpv_terminate_destroy(mpv); mpv_terminate_destroy(mpv);
printf("MPV terminated.\n"); printf("MPV terminated.\n");
} }
void void MPVBackend::on_update(void* ctx)
MPVBackend::on_update(void* ctx)
{ {
MPVBackend* self = (MPVBackend*)ctx; MPVBackend* self = (MPVBackend*)ctx;
emit self->onUpdate(); emit self->onUpdate();
} }
void void MPVBackend::doUpdate()
MPVBackend::doUpdate()
{ {
update(); update();
} }
QVariant QVariant
MPVBackend::getProperty(const QString& name) const MPVBackend::getProperty(const QString& name) const
{ {
return mpv::qt::get_property_variant(mpv, name); return mpv::qt::get_property_variant(mpv, name);
} }
void void MPVBackend::command(const QVariant& params)
MPVBackend::command(const QVariant& params)
{ {
mpv::qt::node_builder node(params); mpv::qt::node_builder node(params);
mpv_command_node(mpv, node.node(), nullptr); mpv_command_node(mpv, node.node(), nullptr);
} }
void void MPVBackend::setProperty(const QString& name, const QVariant& value)
MPVBackend::setProperty(const QString& name, const QVariant& value)
{ {
mpv::qt::node_builder node(value); mpv::qt::node_builder node(value);
qDebug() << "Setting property" << name << "to" << value; qDebug() << "Setting property" << name << "to" << value;
mpv_set_property(mpv, name.toUtf8().data(), MPV_FORMAT_NODE, node.node()); mpv_set_property(mpv, name.toUtf8().data(), MPV_FORMAT_NODE, node.node());
} }
void void MPVBackend::setOption(const QString& name, const QVariant& value)
MPVBackend::setOption(const QString& name, const QVariant& value)
{ {
mpv::qt::set_option_variant(mpv, name, value); mpv::qt::set_option_variant(mpv, name, value);
} }
QVariant QVariant
MPVBackend::playerCommand(const Enums::Commands& cmd) MPVBackend::playerCommand(const Enums::Commands& cmd)
{ {
return playerCommand(cmd, QVariant("NoArgProvided")); return playerCommand(cmd, QVariant("NoArgProvided"));
} }
QVariant QVariant
MPVBackend::playerCommand(const Enums::Commands& cmd, const QVariant& args) MPVBackend::playerCommand(const Enums::Commands& cmd, const QVariant& args)
{ {
return MPVCommon::playerCommand(this, cmd, args); return MPVCommon::playerCommand(this, cmd, args);
} }
QString QString
MPVBackend::getStats() MPVBackend::getStats()
{ {
return MPVCommon::getStats(this); return MPVCommon::getStats(this);
} }
void MPVBackend::updateDurationString(int numTime)
void
MPVBackend::updateDurationString(int numTime)
{ {
QMetaMethod metaMethod = sender()->metaObject()->method(senderSignalIndex()); QMetaMethod metaMethod = sender()->metaObject()->method(senderSignalIndex());
MPVCommon::updateDurationString(this, numTime, metaMethod); MPVCommon::updateDurationString(this, numTime, metaMethod);
} }
void MPVBackend::toggleOnTop()
void
MPVBackend::toggleOnTop()
{ {
onTop = !onTop; onTop = !onTop;
Utils::AlwaysOnTop(window()->winId(), onTop); Utils::AlwaysOnTop(window()->winId(), onTop);
} }
bool bool MPVBackend::event(QEvent* event)
MPVBackend::event(QEvent* event)
{ {
if (event->type() == QEvent::User) { if (event->type() == QEvent::User) {
on_mpv_events(); on_mpv_events();
} }
return QObject::event(event); return QObject::event(event);
} }
void void MPVBackend::on_mpv_events()
MPVBackend::on_mpv_events() {
{ while (mpv) {
while (mpv) { mpv_event* event = mpv_wait_event(mpv, 0);
mpv_event* event = mpv_wait_event(mpv, 0); if (event->event_id == MPV_EVENT_NONE) {
if (event->event_id == MPV_EVENT_NONE) { break;
break; }
handle_mpv_event(event);
} }
handle_mpv_event(event);
}
} }
QVariantMap QVariantMap
MPVBackend::getAudioDevices(const QVariant& drivers) const MPVBackend::getAudioDevices(const QVariant& drivers) const
{ {
return MPVCommon::getAudioDevices(drivers); return MPVCommon::getAudioDevices(drivers);
} }
void void MPVBackend::handle_mpv_event(mpv_event* event)
MPVBackend::handle_mpv_event(mpv_event* event)
{ {
MPVCommon::handle_mpv_event(this, event); MPVCommon::handle_mpv_event(this, event);
} }
QQuickFramebufferObject::Renderer* QQuickFramebufferObject::Renderer*
MPVBackend::createRenderer() const MPVBackend::createRenderer() const
{ {
window()->setIcon(QIcon(":/icon.png")); window()->setIcon(QIcon(":/icon.png"));
window()->setPersistentOpenGLContext(true); window()->setPersistentOpenGLContext(true);
window()->setPersistentSceneGraph(true); window()->setPersistentSceneGraph(true);
return new MpvRenderer(const_cast<MPVBackend*>(this)); return new MpvRenderer(const_cast<MPVBackend*>(this));
} }

View file

@ -1,101 +1,100 @@
#ifndef MPVBackend_H #ifndef MPVBackend_H
#define MPVBackend_H #define MPVBackend_H
#include <mpv/client.h> #include "src/backendinterface.hpp"
#include <mpv/opengl_cb.h> #include "src/enums.hpp"
#include <mpv/render.h> #include <QEvent>
#include <QMetaType> #include <QMetaType>
#include <QObject> #include <QObject>
#include <QEvent>
#include <QQuickItem>
#include <QQuickFramebufferObject> #include <QQuickFramebufferObject>
#include <QQuickItem>
#include <QSettings> #include <QSettings>
#include <QString> #include <QString>
#include <QVariant> #include <QVariant>
#include "src/backendinterface.hpp" #include <mpv/client.h>
#include "src/enums.hpp" #include <mpv/opengl_cb.h>
#include <mpv/render.h>
class MPVBackend class MPVBackend
: public QQuickFramebufferObject : public QQuickFramebufferObject,
, public BackendInterface public BackendInterface {
{ Q_INTERFACES(BackendInterface)
Q_INTERFACES(BackendInterface)
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool logging READ logging WRITE setLogging) Q_PROPERTY(bool logging READ logging WRITE setLogging)
mpv_handle* mpv; mpv_handle* mpv;
mpv_render_context* mpv_gl; mpv_render_context* mpv_gl;
mpv_opengl_cb_context* mpv_gl_cb; mpv_opengl_cb_context* mpv_gl_cb;
QSettings settings; QSettings settings;
bool onTop = false; bool onTop = false;
bool m_logging = true; bool m_logging = true;
friend class MpvRenderer; friend class MpvRenderer;
public: public:
static void on_update(void* ctx); static void on_update(void* ctx);
MPVBackend(QQuickItem* parent = 0); MPVBackend(QQuickItem* parent = 0);
virtual ~MPVBackend(); virtual ~MPVBackend();
virtual Renderer* createRenderer() const; virtual Renderer* createRenderer() const;
void setLogging(bool a) void setLogging(bool a)
{ {
if (a != m_logging) { if (a != m_logging) {
m_logging = a; m_logging = a;
}
} }
} bool logging() const { return m_logging; }
bool logging() const { return m_logging; }
int lastTime = 0; int lastTime = 0;
double lastSpeed = 0; double lastSpeed = 0;
QString totalDurationString; QString totalDurationString;
QString lastPositionString; QString lastPositionString;
public slots: public slots:
QVariant playerCommand(const Enums::Commands& command, const QVariant& args); QVariant playerCommand(const Enums::Commands& command, const QVariant& args);
QVariant playerCommand(const Enums::Commands& command); QVariant playerCommand(const Enums::Commands& command);
void toggleOnTop(); void toggleOnTop();
QString getStats(); QString getStats();
// Optional but handy for MPV or custom backend settings. // Optional but handy for MPV or custom backend settings.
void command(const QVariant& params); void command(const QVariant& params);
void setProperty(const QString& name, const QVariant& value); void setProperty(const QString& name, const QVariant& value);
void setOption(const QString& name, const QVariant& value); void setOption(const QString& name, const QVariant& value);
QVariant getProperty(const QString& name) const; QVariant getProperty(const QString& name) const;
// Just used for adding missing audio devices to list. // Just used for adding missing audio devices to list.
QVariantMap getAudioDevices(const QVariant& drivers) const; QVariantMap getAudioDevices(const QVariant& drivers) const;
bool event(QEvent* event); bool event(QEvent* event);
signals: signals:
void onUpdate(); void onUpdate();
void mpv_events(); void mpv_events();
void onMpvEvent(mpv_event* event); void onMpvEvent(mpv_event* event);
// All below required for Player API // All below required for Player API
void playStatusChanged(const Enums::PlayStatus& status); void playStatusChanged(const Enums::PlayStatus& status);
void volumeStatusChanged(const Enums::VolumeStatus& status); void volumeStatusChanged(const Enums::VolumeStatus& status);
void volumeChanged(const int& volume); void volumeChanged(const int& volume);
void durationChanged(const double& duration); void durationChanged(const double& duration);
void positionChanged(const double& position); void positionChanged(const double& position);
void cachedDurationChanged(const double& duration); void cachedDurationChanged(const double& duration);
void playlistPositionChanged(const double& position); void playlistPositionChanged(const double& position);
void titleChanged(const QString& title); void titleChanged(const QString& title);
void subtitlesChanged(const QString& subtitles); void subtitlesChanged(const QString& subtitles);
void durationStringChanged(const QString& string); void durationStringChanged(const QString& string);
void tracksChanged(const QVariantList& tracks); void tracksChanged(const QVariantList& tracks);
void audioDevicesChanged(const QVariantMap& devices); void audioDevicesChanged(const QVariantMap& devices);
void playlistChanged(const QVariantList& devices); void playlistChanged(const QVariantList& devices);
void chaptersChanged(const QVariantList& devices); void chaptersChanged(const QVariantList& devices);
void speedChanged(const double& speed); void speedChanged(const double& speed);
private slots: private slots:
void doUpdate(); void doUpdate();
void on_mpv_events(); void on_mpv_events();
void updateDurationString(int numTime); void updateDurationString(int numTime);
private: private:
void handle_mpv_event(mpv_event* event); void handle_mpv_event(mpv_event* event);
}; };
#endif #endif

View file

@ -1,47 +1,47 @@
#include "src/Backends/MPVCommon/MPVCommon.hpp" #include "src/Backends/MPVCommon/MPVCommon.hpp"
#include "spdlog/logger.h"
#include "src/backendinterface.hpp"
#include "src/logger.h"
#include "src/utils.hpp"
#include <QByteArray> #include <QByteArray>
#include <QCoreApplication> #include <QCoreApplication>
#include <QJsonObject> #include <QJsonObject>
#include <QList> #include <QList>
#include <QLocale> #include <QLocale>
#include <QMap> #include <QMap>
#include <QMetaObject>
#include <QMetaMethod> #include <QMetaMethod>
#include <QMetaObject>
#include <QObject> #include <QObject>
#include <QSettings> #include <QSettings>
#include <spdlog/fmt/fmt.h>
#include <string.h>
#include <exception> #include <exception>
#include <memory> #include <memory>
#include "spdlog/logger.h" #include <spdlog/fmt/fmt.h>
#include "src/backendinterface.hpp" #include <string.h>
#include "src/logger.h"
#include "src/utils.hpp"
auto mpvLogger = initLogger("mpv"); auto mpvLogger = initLogger("mpv");
QString humanSize(uint64_t bytes) QString humanSize(uint64_t bytes)
{ {
const char *suffix[5] = {"B", "KB", "MB", "GB", "TB"}; const char* suffix[5] = { "B", "KB", "MB", "GB", "TB" };
char length = sizeof(suffix) / sizeof(suffix[0]); char length = sizeof(suffix) / sizeof(suffix[0]);
int i = 0; int i = 0;
double dblBytes = bytes; double dblBytes = bytes;
if (bytes > 1024) { if (bytes > 1024) {
for (i = 0; (bytes / 1024) > 0 && i<length-1; i++, bytes /= 1024) for (i = 0; (bytes / 1024) > 0 && i < length - 1; i++, bytes /= 1024)
dblBytes = bytes / 1024.0; dblBytes = bytes / 1024.0;
} }
static char output[200]; static char output[200];
sprintf(output, "%.02lf %s", dblBytes, suffix[i]); sprintf(output, "%.02lf %s", dblBytes, suffix[i]);
return QString(output); return QString(output);
} }
static inline QVariant mpvnode_to_variant(const mpv_node *node) static inline QVariant mpvnode_to_variant(const mpv_node* node)
{ {
if (!node) { if (!node) {
return QVariant(); return QVariant();
} }
switch (node->format) { switch (node->format) {
@ -54,18 +54,18 @@ static inline QVariant mpvnode_to_variant(const mpv_node *node)
case MPV_FORMAT_DOUBLE: case MPV_FORMAT_DOUBLE:
return QVariant(node->u.double_); return QVariant(node->u.double_);
case MPV_FORMAT_NODE_ARRAY: { case MPV_FORMAT_NODE_ARRAY: {
mpv_node_list *list = node->u.list; mpv_node_list* list = node->u.list;
QVariantList qlist; QVariantList qlist;
for (int n = 0; n < list->num; n++) for (int n = 0; n < list->num; n++)
qlist.append(mpvnode_to_variant(&list->values[n])); qlist.append(mpvnode_to_variant(&list->values[n]));
return QVariant(qlist); return QVariant(qlist);
} }
case MPV_FORMAT_NODE_MAP: { case MPV_FORMAT_NODE_MAP: {
mpv_node_list *list = node->u.list; mpv_node_list* list = node->u.list;
QVariantMap qmap; QVariantMap qmap;
for (int n = 0; n < list->num; n++) { for (int n = 0; n < list->num; n++) {
qmap.insert(QString::fromUtf8(list->keys[n]), qmap.insert(QString::fromUtf8(list->keys[n]),
mpvnode_to_variant(&list->values[n])); mpvnode_to_variant(&list->values[n]));
} }
return QVariant(qmap); return QVariant(qmap);
} }
@ -74,498 +74,487 @@ static inline QVariant mpvnode_to_variant(const mpv_node *node)
} }
} }
namespace MPVCommon { namespace MPVCommon {
QString getStats(BackendInterface *b) { QString getStats(BackendInterface* b)
QString stats; {
stats = QString stats;
"<style> blockquote { text-indent: 0px; margin-left:40px; margin-top: 0px; " stats = "<style> blockquote { text-indent: 0px; margin-left:40px; margin-top: 0px; "
"margin-bottom: 0px; padding-bottom: 0px; padding-top: 0px; padding-left: " "margin-bottom: 0px; padding-bottom: 0px; padding-top: 0px; padding-left: "
"0px; } b span p br { margin-bottom: 0px; margin-top: 0px; padding-top: " "0px; } b span p br { margin-bottom: 0px; margin-top: 0px; padding-top: "
"0px; padding-botom: 0px; text-indent: 0px; } </style>"; "0px; padding-botom: 0px; text-indent: 0px; } </style>";
QString filename = b->getProperty("filename").toString(); QString filename = b->getProperty("filename").toString();
// File Info // File Info
stats += "<b>File:</b> " + filename; stats += "<b>File:</b> " + filename;
stats += "<blockquote>";
QString title = b->getProperty("media-title").toString();
if (title != filename) {
stats += "<b>Title:</b> " + title + "<br>";
}
QString fileFormat = b->getProperty("file-format").toString();
stats += "<b>Format/Protocol:</b> " + fileFormat + "<br>";
double cacheUsed = b->getProperty("cache-used").toDouble();
int demuxerSecs = b->getProperty("demuxer-cache-duration").toInt();
QVariantMap demuxerState = b->getProperty("demuxer-cache-state").toMap();
int demuxerCache = demuxerState.value("fw-bytes", QVariant(0)).toInt();
if (demuxerSecs + demuxerCache + cacheUsed > 0) {
QString cacheStats;
cacheStats += "<b>Total Cache:</b> ";
cacheStats += humanSize(demuxerCache + cacheUsed);
cacheStats += " (<b>Demuxer:</b> ";
cacheStats += humanSize(demuxerCache);
cacheStats += ", ";
cacheStats += QString::number(demuxerSecs) + "s) ";
double cacheSpeed = b->getProperty("cache-speed").toDouble();
if (cacheSpeed > 0) {
cacheStats += "<b>Speed:</b> ";
cacheStats += humanSize(demuxerSecs);
cacheStats += "/s";
}
cacheStats += "<br>";
stats += cacheStats;
}
QString fileSize =
humanSize(b->getProperty("file-size").toInt()).remove("-");
stats += "<b>Size:</b> " + fileSize + "<br>";
stats += "</blockquote>";
// Video Info
QVariant videoParams = b->getProperty("video-params");
if (videoParams.isNull()) {
videoParams = b->getProperty("video-out-params");
}
if (!videoParams.isNull()) {
stats += "<b>Video:</b> " + b->getProperty("video-codec").toString();
stats += "<blockquote>"; stats += "<blockquote>";
QString avsync = QString::number(b->getProperty("avsync").toDouble(), 'f', 3); QString title = b->getProperty("media-title").toString();
stats += "<b>A-V:</b> " + QString(avsync) + "<br>"; if (title != filename) {
stats += "<b>Title:</b> " + title + "<br>";
stats += "<b>Dropped Frames:</b> ";
int dFDC = b->getProperty("decoder-frame-drop-count").toInt();
if (dFDC > 0) {
stats += QString::number(dFDC) + " (decoder) ";
} }
int fDC = b->getProperty("frame-drop-count").toInt(); QString fileFormat = b->getProperty("file-format").toString();
if (fDC > 0) { stats += "<b>Format/Protocol:</b> " + fileFormat + "<br>";
stats += QString::number(fDC) + " (output)"; double cacheUsed = b->getProperty("cache-used").toDouble();
} int demuxerSecs = b->getProperty("demuxer-cache-duration").toInt();
stats += "<br>"; QVariantMap demuxerState = b->getProperty("demuxer-cache-state").toMap();
int demuxerCache = demuxerState.value("fw-bytes", QVariant(0)).toInt();
int dFPS = b->getProperty("display-fps").toInt();
int eDFPS = b->getProperty("estimated-display-fps").toInt(); if (demuxerSecs + demuxerCache + cacheUsed > 0) {
if ((dFPS + eDFPS) > 0) { QString cacheStats;
stats += "<b>Display FPS:</b> "; cacheStats += "<b>Total Cache:</b> ";
cacheStats += humanSize(demuxerCache + cacheUsed);
if (dFPS > 0) { cacheStats += " (<b>Demuxer:</b> ";
stats += QString::number(dFPS);
stats += " (specified) "; cacheStats += humanSize(demuxerCache);
} cacheStats += ", ";
if (eDFPS > 0) { cacheStats += QString::number(demuxerSecs) + "s) ";
stats += QString::number(eDFPS); double cacheSpeed = b->getProperty("cache-speed").toDouble();
stats += " (estimated)"; if (cacheSpeed > 0) {
} cacheStats += "<b>Speed:</b> ";
stats += "<br>"; cacheStats += humanSize(demuxerSecs);
} cacheStats += "/s";
}
int cFPS = b->getProperty("container-fps").toInt(); cacheStats += "<br>";
int eVFPS = b->getProperty("estimated-vf-fps").toInt(); stats += cacheStats;
if ((cFPS + eVFPS) > 0) {
stats += "<b>FPS:</b> ";
if (cFPS > 0) {
stats += QString::number(cFPS);
stats += " (specified) ";
}
if (eVFPS > 0) {
stats += QString::number(eVFPS);
stats += " (estimated)";
}
stats += "<br>";
}
QVariantMap vPM = videoParams.toMap();
stats += "<b>Native Resolution:</b> ";
stats += vPM["w"].toString() + " x " + vPM["h"].toString();
stats += "<br>";
stats += "<b>Window Scale:</b> ";
stats += vPM["window-scale"].toString();
stats += "<br>";
stats += "<b>Aspect Ratio:</b> ";
stats += vPM["aspect"].toString();
stats += "<br>";
stats += "<b>Pixel Format:</b> ";
stats += vPM["pixelformat"].toString();
stats += "<br>";
stats += "<b>Primaries:</b> ";
stats += vPM["primaries"].toString();
stats += " <b>Colormatrix:</b> ";
stats += vPM["colormatrix"].toString();
stats += "<br>";
stats += "<b>Levels:</b> ";
stats += vPM["colorlevels"].toString();
double sigPeak = vPM.value("sig-peak", QVariant(0.0)).toInt();
if (sigPeak > 0) {
stats += " (HDR Peak: " + QString::number(sigPeak) + ")";
}
stats += "<br>";
stats += "<b>Gamma:</b> ";
stats += vPM["gamma"].toString();
stats += "<br>";
int pVB = b->getProperty("packet-video-bitrate").toInt();
if (pVB > 0) {
stats += "<b>Bitrate:</b> ";
stats += humanSize(pVB) + "/s";
stats += "<br>";
} }
QString fileSize = humanSize(b->getProperty("file-size").toInt()).remove("-");
stats += "<b>Size:</b> " + fileSize + "<br>";
stats += "</blockquote>"; stats += "</blockquote>";
} // Video Info
QVariant audioParams = b->getProperty("audio-params"); QVariant videoParams = b->getProperty("video-params");
if (audioParams.isNull()) { if (videoParams.isNull()) {
audioParams = b->getProperty("audio-out-params"); videoParams = b->getProperty("video-out-params");
} }
if (!audioParams.isNull()) { if (!videoParams.isNull()) {
stats += "<b>Audio:</b> " + b->getProperty("audio-codec").toString(); stats += "<b>Video:</b> " + b->getProperty("video-codec").toString();
stats += "<blockquote>"; stats += "<blockquote>";
QVariantMap aPM = audioParams.toMap(); QString avsync = QString::number(b->getProperty("avsync").toDouble(), 'f', 3);
stats += "<b>A-V:</b> " + QString(avsync) + "<br>";
stats += "<b>Format:</b> "; stats += "<b>Dropped Frames:</b> ";
stats += aPM["format"].toString(); int dFDC = b->getProperty("decoder-frame-drop-count").toInt();
stats += "<br>"; if (dFDC > 0) {
stats += QString::number(dFDC) + " (decoder) ";
}
int fDC = b->getProperty("frame-drop-count").toInt();
if (fDC > 0) {
stats += QString::number(fDC) + " (output)";
}
stats += "<br>";
stats += "<b>Sample Rate:</b> "; int dFPS = b->getProperty("display-fps").toInt();
stats += aPM["samplerate"].toString() + " Hz"; int eDFPS = b->getProperty("estimated-display-fps").toInt();
stats += "<br>"; if ((dFPS + eDFPS) > 0) {
stats += "<b>Display FPS:</b> ";
stats += "<b>Channels:</b> "; if (dFPS > 0) {
stats += aPM["chanel-count"].toString(); stats += QString::number(dFPS);
stats += "<br>"; stats += " (specified) ";
}
if (eDFPS > 0) {
stats += QString::number(eDFPS);
stats += " (estimated)";
}
stats += "<br>";
}
int pAB = b->getProperty("packet-audio-bitrate").toInt(); int cFPS = b->getProperty("container-fps").toInt();
if (pAB > 0) { int eVFPS = b->getProperty("estimated-vf-fps").toInt();
stats += "<b>Bitrate:</b> "; if ((cFPS + eVFPS) > 0) {
stats += humanSize(pAB) + "/s"; stats += "<b>FPS:</b> ";
stats += "<br>";
if (cFPS > 0) {
stats += QString::number(cFPS);
stats += " (specified) ";
}
if (eVFPS > 0) {
stats += QString::number(eVFPS);
stats += " (estimated)";
}
stats += "<br>";
}
QVariantMap vPM = videoParams.toMap();
stats += "<b>Native Resolution:</b> ";
stats += vPM["w"].toString() + " x " + vPM["h"].toString();
stats += "<br>";
stats += "<b>Window Scale:</b> ";
stats += vPM["window-scale"].toString();
stats += "<br>";
stats += "<b>Aspect Ratio:</b> ";
stats += vPM["aspect"].toString();
stats += "<br>";
stats += "<b>Pixel Format:</b> ";
stats += vPM["pixelformat"].toString();
stats += "<br>";
stats += "<b>Primaries:</b> ";
stats += vPM["primaries"].toString();
stats += " <b>Colormatrix:</b> ";
stats += vPM["colormatrix"].toString();
stats += "<br>";
stats += "<b>Levels:</b> ";
stats += vPM["colorlevels"].toString();
double sigPeak = vPM.value("sig-peak", QVariant(0.0)).toInt();
if (sigPeak > 0) {
stats += " (HDR Peak: " + QString::number(sigPeak) + ")";
}
stats += "<br>";
stats += "<b>Gamma:</b> ";
stats += vPM["gamma"].toString();
stats += "<br>";
int pVB = b->getProperty("packet-video-bitrate").toInt();
if (pVB > 0) {
stats += "<b>Bitrate:</b> ";
stats += humanSize(pVB) + "/s";
stats += "<br>";
}
stats += "</blockquote>";
}
QVariant audioParams = b->getProperty("audio-params");
if (audioParams.isNull()) {
audioParams = b->getProperty("audio-out-params");
}
if (!audioParams.isNull()) {
stats += "<b>Audio:</b> " + b->getProperty("audio-codec").toString();
stats += "<blockquote>";
QVariantMap aPM = audioParams.toMap();
stats += "<b>Format:</b> ";
stats += aPM["format"].toString();
stats += "<br>";
stats += "<b>Sample Rate:</b> ";
stats += aPM["samplerate"].toString() + " Hz";
stats += "<br>";
stats += "<b>Channels:</b> ";
stats += aPM["chanel-count"].toString();
stats += "<br>";
int pAB = b->getProperty("packet-audio-bitrate").toInt();
if (pAB > 0) {
stats += "<b>Bitrate:</b> ";
stats += humanSize(pAB) + "/s";
stats += "<br>";
}
stats += "</blockquote>";
} }
stats += "</blockquote>"; return stats;
}
return stats;
} }
QVariant playerCommand(BackendInterface *b, const Enums::Commands& cmd, const QVariant& args) QVariant playerCommand(BackendInterface* b, const Enums::Commands& cmd, const QVariant& args)
{ {
switch (cmd) { switch (cmd) {
case Enums::Commands::TogglePlayPause: { case Enums::Commands::TogglePlayPause: {
b->command(QVariantList() << "cycle" b->command(QVariantList() << "cycle"
<< "pause"); << "pause");
break; break;
} }
case Enums::Commands::ToggleMute: { case Enums::Commands::ToggleMute: {
b->command(QVariantList() << "cycle" b->command(QVariantList() << "cycle"
<< "mute"); << "mute");
break; break;
} }
case Enums::Commands::SetAudioDevice: { case Enums::Commands::SetAudioDevice: {
b->setProperty("audio-device", args.toString()); b->setProperty("audio-device", args.toString());
break; break;
} }
case Enums::Commands::SetVolume: { case Enums::Commands::SetVolume: {
b->command(QVariantList() << "set" b->command(QVariantList() << "set"
<< "volume" << args); << "volume" << args);
break; break;
} }
case Enums::Commands::AddVolume: { case Enums::Commands::AddVolume: {
b->command(QVariantList() << "add" b->command(QVariantList() << "add"
<< "volume" << args); << "volume" << args);
break; break;
} }
case Enums::Commands::AddSpeed: { case Enums::Commands::AddSpeed: {
QString speedString = QString speedString = QString::number(b->getProperty("speed").toDouble() + args.toDouble());
QString::number(b->getProperty("speed").toDouble() + args.toDouble()); QVariant newSpeed = QVariant(speedString.left(speedString.lastIndexOf('.') + 2));
QVariant newSpeed =
QVariant(speedString.left(speedString.lastIndexOf('.') + 2));
b->playerCommand(Enums::Commands::SetSpeed, newSpeed); b->playerCommand(Enums::Commands::SetSpeed, newSpeed);
break; break;
} }
case Enums::Commands::SubtractSpeed: { case Enums::Commands::SubtractSpeed: {
QString speedString = QString speedString = QString::number(b->getProperty("speed").toDouble() - args.toDouble());
QString::number(b->getProperty("speed").toDouble() - args.toDouble()); QVariant newSpeed = QVariant(speedString.left(speedString.lastIndexOf('.') + 2));
QVariant newSpeed = b->playerCommand(Enums::Commands::SetSpeed, newSpeed);
QVariant(speedString.left(speedString.lastIndexOf('.') + 2)); break;
b->playerCommand(Enums::Commands::SetSpeed, newSpeed);
break;
} }
case Enums::Commands::ChangeSpeed: { case Enums::Commands::ChangeSpeed: {
b->playerCommand( b->playerCommand(
Enums::Commands::SetSpeed, Enums::Commands::SetSpeed,
QVariant(b->getProperty("speed").toDouble() * args.toDouble())); QVariant(b->getProperty("speed").toDouble() * args.toDouble()));
break; break;
} }
case Enums::Commands::SetSpeed: { case Enums::Commands::SetSpeed: {
b->command(QVariantList() << "set" b->command(QVariantList() << "set"
<< "speed" << args.toString()); << "speed" << args.toString());
break; break;
} }
case Enums::Commands::ToggleStats: { case Enums::Commands::ToggleStats: {
b->command(QVariantList() << "script-binding" b->command(QVariantList() << "script-binding"
<< "stats/display-stats-toggle"); << "stats/display-stats-toggle");
break; break;
} }
case Enums::Commands::NextAudioTrack: { case Enums::Commands::NextAudioTrack: {
b->command(QVariantList() << "cycle" b->command(QVariantList() << "cycle"
<< "audio"); << "audio");
break; break;
} }
case Enums::Commands::NextSubtitleTrack: { case Enums::Commands::NextSubtitleTrack: {
b->command(QVariantList() << "cycle" b->command(QVariantList() << "cycle"
<< "sub"); << "sub");
break; break;
} }
case Enums::Commands::NextVideoTrack: { case Enums::Commands::NextVideoTrack: {
b->command(QVariantList() << "cycle" b->command(QVariantList() << "cycle"
<< "video"); << "video");
break; break;
} }
case Enums::Commands::PreviousPlaylistItem: { case Enums::Commands::PreviousPlaylistItem: {
b->command(QVariantList() << "playlist-prev"); b->command(QVariantList() << "playlist-prev");
break; break;
} }
case Enums::Commands::NextPlaylistItem: { case Enums::Commands::NextPlaylistItem: {
b->command(QVariantList() << "playlist-next" b->command(QVariantList() << "playlist-next"
<< "force"); << "force");
break; break;
} }
case Enums::Commands::LoadFile: { case Enums::Commands::LoadFile: {
b->command(QVariantList() << "loadfile" << args); b->command(QVariantList() << "loadfile" << args);
break; break;
} }
case Enums::Commands::AppendFile: { case Enums::Commands::AppendFile: {
b->command(QVariantList() << "loadfile" << args << "append-play"); b->command(QVariantList() << "loadfile" << args << "append-play");
break; break;
} }
case Enums::Commands::Seek: { case Enums::Commands::Seek: {
b->command(QVariantList() << "seek" << args); b->command(QVariantList() << "seek" << args);
break; break;
} }
case Enums::Commands::SeekAbsolute: { case Enums::Commands::SeekAbsolute: {
b->command(QVariantList() << "seek" << args << "absolute"); b->command(QVariantList() << "seek" << args << "absolute");
break; break;
} }
case Enums::Commands::ForwardFrame: { case Enums::Commands::ForwardFrame: {
b->command(QVariantList() << "frame-step"); b->command(QVariantList() << "frame-step");
break; break;
} }
case Enums::Commands::BackwardFrame: { case Enums::Commands::BackwardFrame: {
b->command(QVariantList() << "frame-back-step"); b->command(QVariantList() << "frame-back-step");
break; break;
} }
case Enums::Commands::SetTrack: { case Enums::Commands::SetTrack: {
b->command(QVariantList() << "set" << args.toList()[0] << args.toList()[1]); b->command(QVariantList() << "set" << args.toList()[0] << args.toList()[1]);
break; break;
} }
case Enums::Commands::SetPlaylistPos: { case Enums::Commands::SetPlaylistPos: {
b->command(QVariantList() << "set" b->command(QVariantList() << "set"
<< "playlist-pos" << args); << "playlist-pos" << args);
break; break;
} }
case Enums::Commands::ForcePause: { case Enums::Commands::ForcePause: {
b->command(QVariantList() << "set" b->command(QVariantList() << "set"
<< "pause" << "pause"
<< "yes"); << "yes");
break; break;
} }
default: { default: {
//qDebug() << "Command not found: " << cmd; //qDebug() << "Command not found: " << cmd;
break; break;
} }
} }
return QVariant("NoOutput"); return QVariant("NoOutput");
} }
void updateDurationString(BackendInterface *b, int numTime, QMetaMethod metaMethod) void updateDurationString(BackendInterface* b, int numTime, QMetaMethod metaMethod)
{ {
QVariant speed = b->getProperty("speed"); QVariant speed = b->getProperty("speed");
QSettings settings; QSettings settings;
if (metaMethod.name() == "positionChanged") { if (metaMethod.name() == "positionChanged") {
if (speed != b->lastSpeed) { if (speed != b->lastSpeed) {
b->lastSpeed = speed.toDouble(); b->lastSpeed = speed.toDouble();
} else { } else {
if (numTime == b->lastTime) { if (numTime == b->lastTime) {
return; return;
} }
}
b->lastTime = numTime;
b->lastPositionString = Utils::createTimestamp(b->lastTime);
} else if (metaMethod.name() == "durationChanged") {
b->totalDurationString = Utils::createTimestamp(numTime);
} }
b->lastTime = numTime; QString durationString;
b->lastPositionString = Utils::createTimestamp(b->lastTime); durationString += b->lastPositionString;
} else if (metaMethod.name() == "durationChanged") { durationString += " / ";
b->totalDurationString = Utils::createTimestamp(numTime); durationString += b->totalDurationString;
} if (b->lastSpeed != 1) {
QString durationString; if (settings.value("Appearance/themeName", "").toString() != "RoosterTeeth") {
durationString += b->lastPositionString; durationString += " (" + speed.toString() + "x)";
durationString += " / "; }
durationString += b->totalDurationString;
if (b->lastSpeed != 1) {
if (settings.value("Appearance/themeName", "").toString() !=
"RoosterTeeth") {
durationString += " (" + speed.toString() + "x)";
} }
} emit b->durationStringChanged(durationString);
emit b->durationStringChanged(durationString);
} }
void void handle_mpv_event(BackendInterface* b, mpv_event* event)
handle_mpv_event(BackendInterface *b, mpv_event* event)
{ {
switch (event->event_id) { switch (event->event_id) {
case MPV_EVENT_PROPERTY_CHANGE: { case MPV_EVENT_PROPERTY_CHANGE: {
mpv_event_property* prop = (mpv_event_property*)event->data; mpv_event_property* prop = (mpv_event_property*)event->data;
if (strcmp(prop->name, "time-pos") == 0) { if (strcmp(prop->name, "time-pos") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) { if (prop->format == MPV_FORMAT_DOUBLE) {
double time = *(double*)prop->data; double time = *(double*)prop->data;
emit b->positionChanged(time); emit b->positionChanged(time);
}
} else if (strcmp(prop->name, "duration") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
double time = *(double*)prop->data;
emit b->durationChanged(time);
}
} else if (strcmp(prop->name, "mute") == 0 || strcmp(prop->name, "volume") == 0) {
double volume = b->getProperty("volume").toDouble();
bool mute = b->getProperty("mute").toBool();
if (mute || volume == 0) {
emit b->volumeStatusChanged(Enums::VolumeStatus::Muted);
} else {
if (volume < 25) {
emit b->volumeStatusChanged(Enums::VolumeStatus::Low);
} else {
emit b->volumeStatusChanged(Enums::VolumeStatus::Normal);
}
}
// emit volumeChanged(volume);
} else if (strcmp(prop->name, "media-title") == 0) {
if (prop->format == MPV_FORMAT_STRING) {
char* title = *(char**)prop->data;
emit b->titleChanged(QString(title));
}
} else if (strcmp(prop->name, "sub-text") == 0) {
if (prop->format == MPV_FORMAT_STRING) {
char* subs = *(char**)prop->data;
emit b->subtitlesChanged(QString(subs));
}
} else if (strcmp(prop->name, "demuxer-cache-duration") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
double duration = *(double*)prop->data;
emit b->cachedDurationChanged(duration);
}
} else if (strcmp(prop->name, "playlist-pos") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
double pos = *(double*)prop->data;
emit b->playlistPositionChanged(pos);
}
} else if (strcmp(prop->name, "pause") == 0) {
mpv_node* nod = (mpv_node*)prop->data;
if (mpvnode_to_variant(nod).toBool()) {
emit b->playStatusChanged(Enums::PlayStatus::Paused);
// Utils::SetScreensaver(window()->winId(), true);
} else {
emit b->playStatusChanged(Enums::PlayStatus::Playing);
// Utils::SetScreensaver(window()->winId(), false);
}
} else if (strcmp(prop->name, "track-list") == 0) {
mpv_node* nod = (mpv_node*)prop->data;
emit b->tracksChanged(mpvnode_to_variant(nod).toList());
} else if (strcmp(prop->name, "audio-device-list") == 0) {
mpv_node* nod = (mpv_node*)prop->data;
emit b->audioDevicesChanged(b->getAudioDevices(mpvnode_to_variant(nod)));
} else if (strcmp(prop->name, "playlist") == 0) {
mpv_node* nod = (mpv_node*)prop->data;
emit b->playlistChanged(mpvnode_to_variant(nod).toList());
} else if (strcmp(prop->name, "chapter-list") == 0) {
mpv_node* nod = (mpv_node*)prop->data;
emit b->chaptersChanged(mpvnode_to_variant(nod).toList());
} else if (strcmp(prop->name, "speed") == 0) {
double speed = *(double*)prop->data;
emit b->speedChanged(speed);
} }
} else if (strcmp(prop->name, "duration") == 0) { break;
if (prop->format == MPV_FORMAT_DOUBLE) {
double time = *(double*)prop->data;
emit b->durationChanged(time);
}
} else if (strcmp(prop->name, "mute") == 0 ||
strcmp(prop->name, "volume") == 0) {
double volume = b->getProperty("volume").toDouble();
bool mute = b->getProperty("mute").toBool();
if (mute || volume == 0) {
emit b->volumeStatusChanged(Enums::VolumeStatus::Muted);
} else {
if (volume < 25) {
emit b->volumeStatusChanged(Enums::VolumeStatus::Low);
} else {
emit b->volumeStatusChanged(Enums::VolumeStatus::Normal);
}
}
// emit volumeChanged(volume);
} else if (strcmp(prop->name, "media-title") == 0) {
if (prop->format == MPV_FORMAT_STRING) {
char* title = *(char**)prop->data;
emit b->titleChanged(QString(title));
}
} else if (strcmp(prop->name, "sub-text") == 0) {
if (prop->format == MPV_FORMAT_STRING) {
char* subs = *(char**)prop->data;
emit b->subtitlesChanged(QString(subs));
}
} else if (strcmp(prop->name, "demuxer-cache-duration") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
double duration = *(double*)prop->data;
emit b->cachedDurationChanged(duration);
}
} else if (strcmp(prop->name, "playlist-pos") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
double pos = *(double*)prop->data;
emit b->playlistPositionChanged(pos);
}
} else if (strcmp(prop->name, "pause") == 0) {
mpv_node* nod = (mpv_node*)prop->data;
if (mpvnode_to_variant(nod).toBool()) {
emit b->playStatusChanged(Enums::PlayStatus::Paused);
// Utils::SetScreensaver(window()->winId(), true);
} else {
emit b->playStatusChanged(Enums::PlayStatus::Playing);
// Utils::SetScreensaver(window()->winId(), false);
}
} else if (strcmp(prop->name, "track-list") == 0) {
mpv_node* nod = (mpv_node*)prop->data;
emit b->tracksChanged(mpvnode_to_variant(nod).toList());
} else if (strcmp(prop->name, "audio-device-list") == 0) {
mpv_node* nod = (mpv_node*)prop->data;
emit b->audioDevicesChanged(b->getAudioDevices(mpvnode_to_variant(nod)));
} else if (strcmp(prop->name, "playlist") == 0) {
mpv_node* nod = (mpv_node*)prop->data;
emit b->playlistChanged(mpvnode_to_variant(nod).toList());
} else if (strcmp(prop->name, "chapter-list") == 0) {
mpv_node* nod = (mpv_node*)prop->data;
emit b->chaptersChanged(mpvnode_to_variant(nod).toList());
} else if (strcmp(prop->name, "speed") == 0) {
double speed = *(double*)prop->data;
emit b->speedChanged(speed);
}
break;
} }
case MPV_EVENT_LOG_MESSAGE: { case MPV_EVENT_LOG_MESSAGE: {
struct mpv_event_log_message* msg = struct mpv_event_log_message* msg = (struct mpv_event_log_message*)event->data;
(struct mpv_event_log_message*)event->data;
QString logMsg = "[" + QString(msg->prefix) + "] " + QString(msg->text); QString logMsg = "[" + QString(msg->prefix) + "] " + QString(msg->text);
QString msgLevel = QString(msg->level); QString msgLevel = QString(msg->level);
if (msgLevel.startsWith("d") || msgLevel.startsWith("t")) { if (msgLevel.startsWith("d") || msgLevel.startsWith("t")) {
mpvLogger->info("{}", logMsg.toStdString()); mpvLogger->info("{}", logMsg.toStdString());
} else if (msgLevel.startsWith("v") || msgLevel.startsWith("i")) { } else if (msgLevel.startsWith("v") || msgLevel.startsWith("i")) {
mpvLogger->info("{}", logMsg.toStdString()); mpvLogger->info("{}", logMsg.toStdString());
} else { } else {
mpvLogger->debug("{}", logMsg.toStdString()); mpvLogger->debug("{}", logMsg.toStdString());
} }
break; break;
} }
case MPV_EVENT_SHUTDOWN: { case MPV_EVENT_SHUTDOWN: {
qApp->exit(); qApp->exit();
break; break;
} }
default: { default: {
break; break;
}
} }
}
} }
QVariantMap getAudioDevices(const QVariant& drivers) QVariantMap getAudioDevices(const QVariant& drivers)
{ {
QVariantMap newDrivers; QVariantMap newDrivers;
if (drivers.isNull()) { if (drivers.isNull()) {
return newDrivers;
}
QSequentialIterable iterable = drivers.value<QSequentialIterable>();
foreach (const QVariant& v, iterable) {
QVariantMap item = v.toMap();
newDrivers[item["description"].toString()] = item;
}
return newDrivers; return newDrivers;
}
QSequentialIterable iterable = drivers.value<QSequentialIterable>();
foreach (const QVariant& v, iterable) {
QVariantMap item = v.toMap();
newDrivers[item["description"].toString()] = item;
}
return newDrivers;
} }
} }

View file

@ -1,24 +1,21 @@
#ifndef MPVCommon_H #ifndef MPVCommon_H
#define MPVCommon_H #define MPVCommon_H
#include <mpv/client.h> #include "src/enums.hpp"
#include <QMetaType> #include <QMetaType>
#include <QString> #include <QString>
#include <QVariant> #include <QVariant>
#include "src/enums.hpp" #include <mpv/client.h>
class BackendInterface; class BackendInterface;
class QMetaMethod; class QMetaMethod;
namespace MPVCommon { namespace MPVCommon {
QString getStats(BackendInterface *b); QString getStats(BackendInterface* b);
QVariant playerCommand(BackendInterface *b, const Enums::Commands& cmd, const QVariant& args); QVariant playerCommand(BackendInterface* b, const Enums::Commands& cmd, const QVariant& args);
void updateDurationString(BackendInterface *b, int numTime, QMetaMethod metaMethod); void updateDurationString(BackendInterface* b, int numTime, QMetaMethod metaMethod);
void handle_mpv_event(BackendInterface *b, mpv_event* event); void handle_mpv_event(BackendInterface* b, mpv_event* event);
QVariantMap getAudioDevices(const QVariant& drivers); QVariantMap getAudioDevices(const QVariant& drivers);
} }
#endif #endif

View file

@ -1,316 +1,295 @@
#include "src/Backends/MPVNoFBO/MPVNoFBOBackend.hpp" #include "src/Backends/MPVNoFBO/MPVNoFBOBackend.hpp"
#include "src/Backends/MPVCommon/MPVCommon.hpp"
#include "src/qthelper.hpp"
#include "src/utils.hpp"
#include <QApplication> #include <QApplication>
#include <QByteArray> #include <QByteArray>
#include <QCoreApplication> #include <QCoreApplication>
#include <QEvent>
#include <QDebug> #include <QDebug>
#include <QEvent>
#include <QIcon> #include <QIcon>
#include <QMetaObject> #include <QMetaObject>
#include <QOpenGLContext> #include <QOpenGLContext>
#include <QQuickWindow> #include <QQuickWindow>
#include <clocale>
#include <stdexcept>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <clocale>
#include "src/qthelper.hpp"
#include <stdexcept>
#include "src/Backends/MPVCommon/MPVCommon.hpp"
#include "src/utils.hpp"
void nofbowakeup(void* ctx)
void
nofbowakeup(void* ctx)
{ {
QCoreApplication::postEvent((MPVNoFBOBackend*)ctx, new QEvent(QEvent::User)); QCoreApplication::postEvent((MPVNoFBOBackend*)ctx, new QEvent(QEvent::User));
} }
static void* static void*
get_proc_address(void* ctx, const char* name) get_proc_address(void* ctx, const char* name)
{ {
(void)ctx; (void)ctx;
QOpenGLContext* glctx = QOpenGLContext::currentContext(); QOpenGLContext* glctx = QOpenGLContext::currentContext();
if (!glctx) if (!glctx)
return NULL; return NULL;
return (void*)glctx->getProcAddress(QByteArray(name)); return (void*)glctx->getProcAddress(QByteArray(name));
} }
MPVNoFBORenderer::MPVNoFBORenderer(mpv_handle* a_mpv, mpv_opengl_cb_context* a_mpv_gl) MPVNoFBORenderer::MPVNoFBORenderer(mpv_handle* a_mpv, mpv_opengl_cb_context* a_mpv_gl)
: mpv(a_mpv) : mpv(a_mpv)
, mpv_gl(a_mpv_gl) , mpv_gl(a_mpv_gl)
, window(0) , window(0)
, size() , size()
{ {
int r = mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address, NULL); int r = mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address, NULL);
if (r < 0) if (r < 0)
qDebug() << "could not initialize OpenGL"; qDebug() << "could not initialize OpenGL";
} }
MPVNoFBORenderer::~MPVNoFBORenderer() MPVNoFBORenderer::~MPVNoFBORenderer()
{ {
// Until this call is done, we need to make sure the player remains // Until this call is done, we need to make sure the player remains
// alive. This is done implicitly with the mpv::qt::Handle instance // alive. This is done implicitly with the mpv::qt::Handle instance
// in this class. // in this class.
exit(0); exit(0);
} }
void void MPVNoFBORenderer::paint()
MPVNoFBORenderer::paint()
{ {
window->resetOpenGLState(); window->resetOpenGLState();
// This uses 0 as framebuffer, which indicates that mpv will render directly // This uses 0 as framebuffer, which indicates that mpv will render directly
// to the frontbuffer. Note that mpv will always switch framebuffers // to the frontbuffer. Note that mpv will always switch framebuffers
// explicitly. Some QWindow setups (such as using QQuickWidget) actually // explicitly. Some QWindow setups (such as using QQuickWidget) actually
// want you to render into a FBO in the beforeRendering() signal, and this // want you to render into a FBO in the beforeRendering() signal, and this
// code won't work there. // code won't work there.
// The negation is used for rendering with OpenGL's flipped coordinates. // The negation is used for rendering with OpenGL's flipped coordinates.
mpv_opengl_cb_draw(mpv_gl, 0, size.width(), -size.height()); mpv_opengl_cb_draw(mpv_gl, 0, size.width(), -size.height());
window->resetOpenGLState(); window->resetOpenGLState();
} }
MPVNoFBOBackend::MPVNoFBOBackend(QQuickItem* parent) MPVNoFBOBackend::MPVNoFBOBackend(QQuickItem* parent)
: QQuickItem(parent) : QQuickItem(parent)
, mpv_gl(0) , mpv_gl(0)
, renderer(0) , renderer(0)
{ {
mpv = mpv_create(); mpv = mpv_create();
if (!mpv) if (!mpv)
throw std::runtime_error("could not create mpv context"); throw std::runtime_error("could not create mpv context");
mpv_set_option_string(mpv, "terminal", "no"); mpv_set_option_string(mpv, "terminal", "no");
mpv_set_option_string(mpv, "msg-level", "all=v"); mpv_set_option_string(mpv, "msg-level", "all=v");
// Fix? // Fix?
mpv_set_option_string(mpv, "ytdl", "yes"); mpv_set_option_string(mpv, "ytdl", "yes");
mpv_set_option_string(mpv, "slang", "en"); mpv_set_option_string(mpv, "slang", "en");
mpv_set_option_string(mpv, "config", "yes"); mpv_set_option_string(mpv, "config", "yes");
mpv_observe_property(mpv, 0, "tracks-menu", MPV_FORMAT_NONE); mpv_observe_property(mpv, 0, "tracks-menu", MPV_FORMAT_NONE);
mpv_observe_property(mpv, 0, "chapter-list", MPV_FORMAT_NODE); mpv_observe_property(mpv, 0, "chapter-list", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "playback-abort", MPV_FORMAT_NONE); 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, "chapter-list", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "track-list", MPV_FORMAT_NODE); mpv_observe_property(mpv, 0, "track-list", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "audio-device-list", MPV_FORMAT_NODE); mpv_observe_property(mpv, 0, "audio-device-list", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "playlist-pos", MPV_FORMAT_DOUBLE); mpv_observe_property(mpv, 0, "playlist-pos", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "volume", MPV_FORMAT_NONE); mpv_observe_property(mpv, 0, "volume", MPV_FORMAT_NONE);
mpv_observe_property(mpv, 0, "mute", MPV_FORMAT_NONE); mpv_observe_property(mpv, 0, "mute", MPV_FORMAT_NONE);
mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE); mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "media-title", MPV_FORMAT_STRING); mpv_observe_property(mpv, 0, "media-title", MPV_FORMAT_STRING);
mpv_observe_property(mpv, 0, "sub-text", MPV_FORMAT_STRING); mpv_observe_property(mpv, 0, "sub-text", MPV_FORMAT_STRING);
mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE); mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "demuxer-cache-duration", MPV_FORMAT_DOUBLE); mpv_observe_property(mpv, 0, "demuxer-cache-duration", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_NODE); mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "playlist", MPV_FORMAT_NODE); mpv_observe_property(mpv, 0, "playlist", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "speed", MPV_FORMAT_DOUBLE); mpv_observe_property(mpv, 0, "speed", MPV_FORMAT_DOUBLE);
mpv_set_wakeup_callback(mpv, nofbowakeup, this); mpv_set_wakeup_callback(mpv, nofbowakeup, this);
if (mpv_initialize(mpv) < 0) if (mpv_initialize(mpv) < 0)
throw std::runtime_error("could not initialize mpv context"); throw std::runtime_error("could not initialize mpv context");
// Make use of the MPV_SUB_API_OPENGL_CB API. // Make use of the MPV_SUB_API_OPENGL_CB API.
mpv::qt::set_option_variant(mpv, "vo", "opengl-cb"); mpv::qt::set_option_variant(mpv, "vo", "opengl-cb");
// Setup the callback that will make QtQuick update and redraw if there // Setup the callback that will make QtQuick update and redraw if there
// is a new video frame. Use a queued connection: this makes sure the // is a new video frame. Use a queued connection: this makes sure the
// doUpdate() function is run on the GUI thread. // doUpdate() function is run on the GUI thread.
mpv_gl = (mpv_opengl_cb_context*)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB); mpv_gl = (mpv_opengl_cb_context*)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
if (!mpv_gl) if (!mpv_gl)
throw std::runtime_error("OpenGL not compiled in"); throw std::runtime_error("OpenGL not compiled in");
mpv_opengl_cb_set_update_callback( mpv_opengl_cb_set_update_callback(
mpv_gl, MPVNoFBOBackend::on_update, (void*)this); mpv_gl, MPVNoFBOBackend::on_update, (void*)this);
connect(this, connect(this,
&MPVNoFBOBackend::onUpdate, &MPVNoFBOBackend::onUpdate,
this, this,
&MPVNoFBOBackend::doUpdate, &MPVNoFBOBackend::doUpdate,
Qt::QueuedConnection); Qt::QueuedConnection);
connect(this, connect(this,
&MPVNoFBOBackend::positionChanged, &MPVNoFBOBackend::positionChanged,
&MPVNoFBOBackend::updateDurationString); &MPVNoFBOBackend::updateDurationString);
connect(this, connect(this,
&MPVNoFBOBackend::durationChanged, &MPVNoFBOBackend::durationChanged,
&MPVNoFBOBackend::updateDurationString); &MPVNoFBOBackend::updateDurationString);
connect(this, connect(this,
&QQuickItem::windowChanged, &QQuickItem::windowChanged,
this, this,
&MPVNoFBOBackend::handleWindowChanged); &MPVNoFBOBackend::handleWindowChanged);
} }
MPVNoFBOBackend::~MPVNoFBOBackend() MPVNoFBOBackend::~MPVNoFBOBackend()
{ {
printf("Shutting down...\n"); printf("Shutting down...\n");
qApp->quit(); qApp->quit();
printf("MPV terminated.\n"); printf("MPV terminated.\n");
} }
void void MPVNoFBOBackend::sync()
MPVNoFBOBackend::sync()
{ {
if (!renderer) { if (!renderer) {
window()->setIcon(QIcon(":/icon.png")); window()->setIcon(QIcon(":/icon.png"));
renderer = new MPVNoFBORenderer(mpv, mpv_gl); renderer = new MPVNoFBORenderer(mpv, mpv_gl);
connect(window(), connect(window(),
&QQuickWindow::beforeRendering, &QQuickWindow::beforeRendering,
renderer, renderer,
&MPVNoFBORenderer::paint, &MPVNoFBORenderer::paint,
Qt::DirectConnection); Qt::DirectConnection);
QMetaObject::invokeMethod(this, "startPlayer"); QMetaObject::invokeMethod(this, "startPlayer");
} }
renderer->window = window(); renderer->window = window();
renderer->size = window()->size() * window()->devicePixelRatio(); renderer->size = window()->size() * window()->devicePixelRatio();
} }
void void MPVNoFBOBackend::swapped()
MPVNoFBOBackend::swapped()
{ {
mpv_opengl_cb_report_flip(mpv_gl, 0); mpv_opengl_cb_report_flip(mpv_gl, 0);
} }
void void MPVNoFBOBackend::cleanup()
MPVNoFBOBackend::cleanup()
{ {
if (renderer) { if (renderer) {
delete renderer; delete renderer;
renderer = 0; renderer = 0;
} }
} }
void void MPVNoFBOBackend::on_update(void* ctx)
MPVNoFBOBackend::on_update(void* ctx)
{ {
MPVNoFBOBackend* self = (MPVNoFBOBackend*)ctx; MPVNoFBOBackend* self = (MPVNoFBOBackend*)ctx;
emit self->onUpdate(); emit self->onUpdate();
} }
void void MPVNoFBOBackend::doUpdate()
MPVNoFBOBackend::doUpdate()
{ {
window()->update(); window()->update();
update(); update();
} }
QVariant QVariant
MPVNoFBOBackend::getProperty(const QString& name) const MPVNoFBOBackend::getProperty(const QString& name) const
{ {
return mpv::qt::get_property_variant(mpv, name); return mpv::qt::get_property_variant(mpv, name);
} }
void void MPVNoFBOBackend::command(const QVariant& params)
MPVNoFBOBackend::command(const QVariant& params)
{ {
mpv::qt::command_variant(mpv, params); mpv::qt::command_variant(mpv, params);
} }
void void MPVNoFBOBackend::setProperty(const QString& name, const QVariant& value)
MPVNoFBOBackend::setProperty(const QString& name, const QVariant& value)
{ {
mpv::qt::set_property_variant(mpv, name, value); mpv::qt::set_property_variant(mpv, name, value);
} }
void void MPVNoFBOBackend::setOption(const QString& name, const QVariant& value)
MPVNoFBOBackend::setOption(const QString& name, const QVariant& value)
{ {
mpv::qt::set_option_variant(mpv, name, value); mpv::qt::set_option_variant(mpv, name, value);
} }
void void MPVNoFBOBackend::launchAboutQt()
MPVNoFBOBackend::launchAboutQt()
{ {
QApplication* qapp = QApplication* qapp = qobject_cast<QApplication*>(QCoreApplication::instance());
qobject_cast<QApplication*>(QCoreApplication::instance()); qapp->aboutQt();
qapp->aboutQt();
} }
QVariant QVariant
MPVNoFBOBackend::playerCommand(const Enums::Commands& cmd) MPVNoFBOBackend::playerCommand(const Enums::Commands& cmd)
{ {
return playerCommand(cmd, QVariant("NoArgProvided")); return playerCommand(cmd, QVariant("NoArgProvided"));
} }
QVariant QVariant
MPVNoFBOBackend::playerCommand(const Enums::Commands& cmd, MPVNoFBOBackend::playerCommand(const Enums::Commands& cmd,
const QVariant& args) const QVariant& args)
{ {
return MPVCommon::playerCommand(this, cmd, args); return MPVCommon::playerCommand(this, cmd, args);
} }
void void MPVNoFBOBackend::handleWindowChanged(QQuickWindow* win)
MPVNoFBOBackend::handleWindowChanged(QQuickWindow* win)
{ {
if (!win) if (!win)
return; return;
connect(win, connect(win,
&QQuickWindow::beforeSynchronizing, &QQuickWindow::beforeSynchronizing,
this, this,
&MPVNoFBOBackend::sync, &MPVNoFBOBackend::sync,
Qt::DirectConnection); Qt::DirectConnection);
connect(win, connect(win,
&QQuickWindow::sceneGraphInvalidated, &QQuickWindow::sceneGraphInvalidated,
this, this,
&MPVNoFBOBackend::cleanup, &MPVNoFBOBackend::cleanup,
Qt::DirectConnection); Qt::DirectConnection);
connect(win, connect(win,
&QQuickWindow::frameSwapped, &QQuickWindow::frameSwapped,
this, this,
&MPVNoFBOBackend::swapped, &MPVNoFBOBackend::swapped,
Qt::DirectConnection); Qt::DirectConnection);
win->setClearBeforeRendering(false); win->setClearBeforeRendering(false);
} }
void void MPVNoFBOBackend::toggleOnTop()
MPVNoFBOBackend::toggleOnTop()
{ {
onTop = !onTop; onTop = !onTop;
Utils::AlwaysOnTop(window()->winId(), onTop); Utils::AlwaysOnTop(window()->winId(), onTop);
} }
bool bool MPVNoFBOBackend::event(QEvent* event)
MPVNoFBOBackend::event(QEvent* event)
{ {
if (event->type() == QEvent::User) { if (event->type() == QEvent::User) {
on_mpv_events(); on_mpv_events();
}
return QObject::event(event);
}
void
MPVNoFBOBackend::on_mpv_events()
{
while (mpv) {
mpv_event* event = mpv_wait_event(mpv, 0);
if (event->event_id == MPV_EVENT_NONE) {
break;
} }
handle_mpv_event(event); return QObject::event(event);
}
} }
void void MPVNoFBOBackend::on_mpv_events()
MPVNoFBOBackend::updateDurationString(int numTime)
{ {
QMetaMethod metaMethod = sender()->metaObject()->method(senderSignalIndex()); while (mpv) {
MPVCommon::updateDurationString(this, numTime, metaMethod); mpv_event* event = mpv_wait_event(mpv, 0);
if (event->event_id == MPV_EVENT_NONE) {
break;
}
handle_mpv_event(event);
}
}
void MPVNoFBOBackend::updateDurationString(int numTime)
{
QMetaMethod metaMethod = sender()->metaObject()->method(senderSignalIndex());
MPVCommon::updateDurationString(this, numTime, metaMethod);
} }
QVariantMap QVariantMap
MPVNoFBOBackend::getAudioDevices(const QVariant& drivers) const MPVNoFBOBackend::getAudioDevices(const QVariant& drivers) const
{ {
return MPVCommon::getAudioDevices(drivers); return MPVCommon::getAudioDevices(drivers);
} }
void void MPVNoFBOBackend::handle_mpv_event(mpv_event* event)
MPVNoFBOBackend::handle_mpv_event(mpv_event* event)
{ {
MPVCommon::handle_mpv_event(this, event); MPVCommon::handle_mpv_event(this, event);
} }
QString QString
MPVNoFBOBackend::getStats() MPVNoFBOBackend::getStats()
{ {
return MPVCommon::getStats(this); return MPVCommon::getStats(this);
} }

View file

@ -1,8 +1,8 @@
#ifndef MPVNoFBOBackend_H #ifndef MPVNoFBOBackend_H
#define MPVNoFBOBackend_H #define MPVNoFBOBackend_H
#include <mpv/client.h> #include "src/backendinterface.hpp"
#include <mpv/opengl_cb.h> #include "src/enums.hpp"
#include <QEvent> #include <QEvent>
#include <QMetaType> #include <QMetaType>
#include <QObject> #include <QObject>
@ -12,110 +12,108 @@
#include <QSize> #include <QSize>
#include <QString> #include <QString>
#include <QVariant> #include <QVariant>
#include "src/backendinterface.hpp" #include <mpv/client.h>
#include "src/enums.hpp" #include <mpv/opengl_cb.h>
class MPVNoFBORenderer : public QObject class MPVNoFBORenderer : public QObject {
{ Q_OBJECT
Q_OBJECT mpv_handle* mpv;
mpv_handle* mpv; mpv_opengl_cb_context* mpv_gl;
mpv_opengl_cb_context* mpv_gl;
public: public:
QQuickWindow* window; QQuickWindow* window;
QSize size; QSize size;
friend class MpvObject; friend class MpvObject;
MPVNoFBORenderer(mpv_handle* a_mpv, mpv_opengl_cb_context* a_mpv_gl); MPVNoFBORenderer(mpv_handle* a_mpv, mpv_opengl_cb_context* a_mpv_gl);
virtual ~MPVNoFBORenderer(); virtual ~MPVNoFBORenderer();
public slots: public slots:
void paint(); void paint();
}; };
class MPVNoFBOBackend class MPVNoFBOBackend
: public QQuickItem : public QQuickItem,
, public BackendInterface public BackendInterface {
{ Q_INTERFACES(BackendInterface)
Q_INTERFACES(BackendInterface)
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool logging READ logging WRITE setLogging) Q_PROPERTY(bool logging READ logging WRITE setLogging)
mpv_handle* mpv; mpv_handle* mpv;
mpv_opengl_cb_context* mpv_gl; mpv_opengl_cb_context* mpv_gl;
MPVNoFBORenderer* renderer; MPVNoFBORenderer* renderer;
bool onTop = false; bool onTop = false;
bool m_logging = true; bool m_logging = true;
QSettings settings; QSettings settings;
public: public:
static void on_update(void* ctx); static void on_update(void* ctx);
void setLogging(bool a) void setLogging(bool a)
{ {
if (a != m_logging) { if (a != m_logging) {
m_logging = a; m_logging = a;
}
} }
} bool logging() const { return m_logging; }
bool logging() const { return m_logging; }
MPVNoFBOBackend(QQuickItem* parent = 0); MPVNoFBOBackend(QQuickItem* parent = 0);
virtual ~MPVNoFBOBackend(); virtual ~MPVNoFBOBackend();
int lastTime = 0; int lastTime = 0;
double lastSpeed = 0; double lastSpeed = 0;
QString totalDurationString; QString totalDurationString;
QString lastPositionString; QString lastPositionString;
public slots: public slots:
QVariant playerCommand(const Enums::Commands& command, const QVariant& args); QVariant playerCommand(const Enums::Commands& command, const QVariant& args);
QVariant playerCommand(const Enums::Commands& command); QVariant playerCommand(const Enums::Commands& command);
void launchAboutQt(); void launchAboutQt();
void toggleOnTop(); void toggleOnTop();
QString getStats(); QString getStats();
// Optional but handy for MPV or custom backend settings. // Optional but handy for MPV or custom backend settings.
void command(const QVariant& params); void command(const QVariant& params);
void setProperty(const QString& name, const QVariant& value); void setProperty(const QString& name, const QVariant& value);
void setOption(const QString& name, const QVariant& value); void setOption(const QString& name, const QVariant& value);
QVariant getProperty(const QString& name) const; QVariant getProperty(const QString& name) const;
void sync(); void sync();
void swapped(); void swapped();
void cleanup(); void cleanup();
// Just used for adding missing audio devices to list. // Just used for adding missing audio devices to list.
QVariantMap getAudioDevices(const QVariant& drivers) const; QVariantMap getAudioDevices(const QVariant& drivers) const;
bool event(QEvent* event); bool event(QEvent* event);
signals: signals:
void onUpdate(); void onUpdate();
void mpv_events(); void mpv_events();
// All below required for Player API // All below required for Player API
void playStatusChanged(const Enums::PlayStatus& status); void playStatusChanged(const Enums::PlayStatus& status);
void volumeStatusChanged(const Enums::VolumeStatus& status); void volumeStatusChanged(const Enums::VolumeStatus& status);
void volumeChanged(const int& volume); void volumeChanged(const int& volume);
void durationChanged(const double& duration); void durationChanged(const double& duration);
void positionChanged(const double& position); void positionChanged(const double& position);
void cachedDurationChanged(const double& duration); void cachedDurationChanged(const double& duration);
void playlistPositionChanged(const double& position); void playlistPositionChanged(const double& position);
void titleChanged(const QString& title); void titleChanged(const QString& title);
void subtitlesChanged(const QString& subtitles); void subtitlesChanged(const QString& subtitles);
void durationStringChanged(const QString& string); void durationStringChanged(const QString& string);
void tracksChanged(const QVariantList& tracks); void tracksChanged(const QVariantList& tracks);
void audioDevicesChanged(const QVariantMap& devices); void audioDevicesChanged(const QVariantMap& devices);
void playlistChanged(const QVariantList& devices); void playlistChanged(const QVariantList& devices);
void chaptersChanged(const QVariantList& devices); void chaptersChanged(const QVariantList& devices);
void speedChanged(const double& speed); void speedChanged(const double& speed);
private slots: private slots:
void doUpdate(); void doUpdate();
void on_mpv_events(); void on_mpv_events();
void updateDurationString(int numTime); void updateDurationString(int numTime);
void handleWindowChanged(QQuickWindow* win); void handleWindowChanged(QQuickWindow* win);
private: private:
void handle_mpv_event(mpv_event* event); void handle_mpv_event(mpv_event* event);
}; };
#endif #endif

View file

@ -4,22 +4,22 @@
class QObject; class QObject;
Process::Process(QObject* parent) Process::Process(QObject* parent)
: QProcess(parent) : QProcess(parent)
{}
void
Process::start(const QString& program, const QVariantList& arguments)
{ {
QStringList args; }
for (int i = 0; i < arguments.length(); i++) void Process::start(const QString& program, const QVariantList& arguments)
args << arguments[i].toString(); {
QStringList args;
QProcess::start(program, args); for (int i = 0; i < arguments.length(); i++)
args << arguments[i].toString();
QProcess::start(program, args);
} }
QString QString
Process::getOutput() Process::getOutput()
{ {
return QProcess::readAllStandardOutput(); return QProcess::readAllStandardOutput();
} }

View file

@ -6,15 +6,14 @@
#include <QProcess> #include <QProcess>
#include <QString> #include <QString>
class Process : public QProcess class Process : public QProcess {
{ Q_OBJECT
Q_OBJECT
public: public:
explicit Process(QObject* parent = 0); explicit Process(QObject* parent = 0);
Q_INVOKABLE void start(const QString& program, const QVariantList& arguments); Q_INVOKABLE void start(const QString& program, const QVariantList& arguments);
Q_INVOKABLE QString getOutput(); Q_INVOKABLE QString getOutput();
}; };
#endif #endif

View file

@ -3,9 +3,9 @@
#include <QByteArray> #include <QByteArray>
#include <QCryptographicHash> #include <QCryptographicHash>
#include <QFileInfo> #include <QFileInfo>
#include <QIODevice>
#include <QImage> #include <QImage>
#include <QImageReader> #include <QImageReader>
#include <QIODevice>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QNetworkReply> #include <QNetworkReply>
#include <QNetworkRequest> #include <QNetworkRequest>
@ -13,57 +13,54 @@
#include <QUrl> #include <QUrl>
ThumbnailCache::ThumbnailCache(QObject* parent) ThumbnailCache::ThumbnailCache(QObject* parent)
: QObject(parent) : QObject(parent)
, manager(new QNetworkAccessManager(this)) , manager(new QNetworkAccessManager(this))
{ {
cacheFolder = cacheFolder = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/thumbs");
QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + if (!cacheFolder.exists()) {
"/thumbs"); cacheFolder.mkpath(".");
if (!cacheFolder.exists()) { }
cacheFolder.mkpath(".");
}
} }
void void ThumbnailCache::addURL(const QString& name, const QString& mediaURL)
ThumbnailCache::addURL(const QString& name, const QString& mediaURL)
{ {
QString hashedURL = QString( QString hashedURL = QString(
QCryptographicHash::hash(name.toUtf8(), QCryptographicHash::Md5).toHex()); QCryptographicHash::hash(name.toUtf8(), QCryptographicHash::Md5).toHex());
QString cacheFilename = hashedURL + ".jpg"; QString cacheFilename = hashedURL + ".jpg";
QString cachedFilePath = cacheFolder.absoluteFilePath(cacheFilename); QString cachedFilePath = cacheFolder.absoluteFilePath(cacheFilename);
if (cacheFolder.exists(cacheFilename)) { if (cacheFolder.exists(cacheFilename)) {
emit thumbnailReady(name, mediaURL, "file://" + cachedFilePath); emit thumbnailReady(name, mediaURL, "file://" + cachedFilePath);
return; return;
} }
QString url(mediaURL); QString url(mediaURL);
QFileInfo isFile = QFileInfo(url); QFileInfo isFile = QFileInfo(url);
if (isFile.exists()) { if (isFile.exists()) {
QImageReader reader(url); QImageReader reader(url);
QImage image = reader.read(); QImage image = reader.read();
image.save(cachedFilePath, "JPG"); image.save(cachedFilePath, "JPG");
emit thumbnailReady(name, mediaURL, "file://" + cachedFilePath); emit thumbnailReady(name, mediaURL, "file://" + cachedFilePath);
return; return;
} }
QNetworkRequest request(url); QNetworkRequest request(url);
QNetworkReply* reply = manager->get(request); QNetworkReply* reply = manager->get(request);
connect(reply, &QNetworkReply::finished, [=] { connect(reply, &QNetworkReply::finished, [=] {
QByteArray response_data = reply->readAll(); QByteArray response_data = reply->readAll();
QBuffer buffer(&response_data); QBuffer buffer(&response_data);
buffer.open(QIODevice::ReadOnly); buffer.open(QIODevice::ReadOnly);
QImageReader reader(&buffer); QImageReader reader(&buffer);
QImage image = reader.read(); QImage image = reader.read();
image.save(cachedFilePath, "JPG"); image.save(cachedFilePath, "JPG");
emit thumbnailReady(name, mediaURL, "file://" + cachedFilePath); emit thumbnailReady(name, mediaURL, "file://" + cachedFilePath);
}); });
} }

View file

@ -1,27 +1,25 @@
#ifndef ThumbnailCache_H #ifndef ThumbnailCache_H
#define ThumbnailCache_H #define ThumbnailCache_H
#include <QDir> #include <QDir>
#include <QNetworkAccessManager>
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include <QNetworkAccessManager>
class ThumbnailCache : public QObject class ThumbnailCache : public QObject {
{ Q_OBJECT
Q_OBJECT
public: public:
explicit ThumbnailCache(QObject* parent = nullptr); explicit ThumbnailCache(QObject* parent = nullptr);
public slots: public slots:
Q_INVOKABLE void addURL(const QString& name, const QString& url); Q_INVOKABLE void addURL(const QString& name, const QString& url);
signals: signals:
void thumbnailReady(const QString& name, void thumbnailReady(const QString& name, const QString& url,
const QString& url, const QString& filePath);
const QString& filePath);
private: private:
QNetworkAccessManager* manager; QNetworkAccessManager* manager;
QDir cacheFolder; QDir cacheFolder;
}; };
#endif #endif

View file

@ -2,45 +2,45 @@
#define BackendInterface_H #define BackendInterface_H
#include "enums.hpp" #include "enums.hpp"
#include <QObject> #include <QObject>
class BackendInterface class BackendInterface {
{
public: public:
virtual ~BackendInterface(){}; virtual ~BackendInterface(){};
int lastTime = 0; int lastTime = 0;
double lastSpeed = 0; double lastSpeed = 0;
QString totalDurationString; QString totalDurationString;
QString lastPositionString; QString lastPositionString;
public slots: public slots:
// All 5 required for Player API // All 5 required for Player API
virtual QVariant playerCommand(const Enums::Commands& command, virtual QVariant playerCommand(const Enums::Commands& command,
const QVariant& args) = 0; const QVariant& args)
virtual QVariant playerCommand(const Enums::Commands& command) = 0; = 0;
virtual void toggleOnTop() = 0; virtual QVariant playerCommand(const Enums::Commands& command) = 0;
// Optional but handy for MPV or custom backend settings. virtual void toggleOnTop() = 0;
virtual void command(const QVariant& params) = 0; // Optional but handy for MPV or custom backend settings.
virtual void setProperty(const QString& name, const QVariant& value) = 0; virtual void command(const QVariant& params) = 0;
virtual void setOption(const QString& name, const QVariant& value) = 0; virtual void setProperty(const QString& name, const QVariant& value) = 0;
virtual QVariant getProperty(const QString& name) const = 0; virtual void setOption(const QString& name, const QVariant& value) = 0;
virtual QVariantMap getAudioDevices(const QVariant& drivers) const = 0; virtual QVariant getProperty(const QString& name) const = 0;
virtual QVariantMap getAudioDevices(const QVariant& drivers) const = 0;
signals: signals:
// All below required for Player API // All below required for Player API
virtual void playStatusChanged(const Enums::PlayStatus& status) = 0; virtual void playStatusChanged(const Enums::PlayStatus& status) = 0;
virtual void volumeStatusChanged(const Enums::VolumeStatus& status) = 0; virtual void volumeStatusChanged(const Enums::VolumeStatus& status) = 0;
virtual void volumeChanged(const int& volume) = 0; virtual void volumeChanged(const int& volume) = 0;
virtual void durationChanged(const double& duration) = 0; virtual void durationChanged(const double& duration) = 0;
virtual void positionChanged(const double& position) = 0; virtual void positionChanged(const double& position) = 0;
virtual void cachedDurationChanged(const double& duration) = 0; virtual void cachedDurationChanged(const double& duration) = 0;
virtual void playlistPositionChanged(const double& position) = 0; virtual void playlistPositionChanged(const double& position) = 0;
virtual void titleChanged(const QString& title) = 0; virtual void titleChanged(const QString& title) = 0;
virtual void subtitlesChanged(const QString& subtitles) = 0; virtual void subtitlesChanged(const QString& subtitles) = 0;
virtual void durationStringChanged(const QString& string) = 0; virtual void durationStringChanged(const QString& string) = 0;
virtual void tracksChanged(const QVariantList& tracks) = 0; virtual void tracksChanged(const QVariantList& tracks) = 0;
virtual void audioDevicesChanged(const QVariantMap& devices) = 0; virtual void audioDevicesChanged(const QVariantMap& devices) = 0;
virtual void playlistChanged(const QVariantList& devices) = 0; virtual void playlistChanged(const QVariantList& devices) = 0;
virtual void chaptersChanged(const QVariantList& devices) = 0; virtual void chaptersChanged(const QVariantList& devices) = 0;
virtual void speedChanged(const double& speed) = 0; virtual void speedChanged(const double& speed) = 0;
}; };
Q_DECLARE_INTERFACE(BackendInterface, "NamedKitten.BackendInterface"); Q_DECLARE_INTERFACE(BackendInterface, "NamedKitten.BackendInterface");

View file

@ -6,61 +6,55 @@
namespace Enums { namespace Enums {
Q_NAMESPACE Q_NAMESPACE
enum class PlayStatus : int enum class PlayStatus : int {
{ Playing = 0,
Playing = 0, Paused = 1
Paused = 1
}; };
Q_ENUM_NS(PlayStatus) Q_ENUM_NS(PlayStatus)
enum class VolumeStatus : int enum class VolumeStatus : int {
{ Muted = 0,
Muted = 0, Low = 1,
Low = 1, Normal = 2
Normal = 2
}; };
Q_ENUM_NS(VolumeStatus) Q_ENUM_NS(VolumeStatus)
enum class Commands : int enum class Commands : int {
{ TogglePlayPause = 0,
TogglePlayPause = 0, ToggleMute = 1,
ToggleMute = 1, SetAudioDevice = 2,
SetAudioDevice = 2, AddVolume = 3,
AddVolume = 3, SetVolume = 4,
SetVolume = 4, AddSpeed = 5,
AddSpeed = 5, SubtractSpeed = 6,
SubtractSpeed = 6, ChangeSpeed = 7,
ChangeSpeed = 7, SetSpeed = 8,
SetSpeed = 8, ToggleStats = 9,
ToggleStats = 9, NextAudioTrack = 10,
NextAudioTrack = 10, NextVideoTrack = 11,
NextVideoTrack = 11, NextSubtitleTrack = 12,
NextSubtitleTrack = 12, PreviousPlaylistItem = 13,
PreviousPlaylistItem = 13, NextPlaylistItem = 14,
NextPlaylistItem = 14, LoadFile = 15,
LoadFile = 15, AppendFile = 16,
AppendFile = 16, Seek = 17,
Seek = 17, SeekAbsolute = 18,
SeekAbsolute = 18, ForwardFrame = 19,
ForwardFrame = 19, BackwardFrame = 20,
BackwardFrame = 20, SetTrack = 21,
SetTrack = 21, SetPlaylistPos = 22,
SetPlaylistPos = 22, ForcePause = 23,
ForcePause = 23,
}; };
Q_ENUM_NS(Commands) Q_ENUM_NS(Commands)
enum class Backends : int enum class Backends : int {
{ MPVBackend = 0,
MPVBackend = 0, DirectMPVBackend = 1
DirectMPVBackend = 1
}; };
Q_ENUM_NS(Backends) Q_ENUM_NS(Backends)
} }
// Forces meta generation. // Forces meta generation.
class Dummy : public QObject class Dummy : public QObject {
{ Q_OBJECT
Q_OBJECT
}; };
#endif #endif

View file

@ -1,39 +1,37 @@
#include <QByteArray>
#include <QSettings>
#include <QString>
#include <QVariant>
#include <spdlog/logger.h>
#include <iterator>
#include <vector>
#include <iosfwd> // IWYU pragma: keep
#include <memory> // IWYU pragma: keep
#include <spdlog/spdlog.h> // IWYU pragma: export
#include <spdlog/sinks/basic_file_sink.h> // IWYU pragma: export
#include <spdlog/sinks/daily_file_sink.h> // IWYU pragma: export
#include <spdlog/sinks/stdout_color_sinks.h> // IWYU pragma: export
#include "spdlog/common.h" #include "spdlog/common.h"
#include "spdlog/details/file_helper-inl.h" #include "spdlog/details/file_helper-inl.h"
#include "spdlog/sinks/ansicolor_sink-inl.h" #include "spdlog/sinks/ansicolor_sink-inl.h"
#include "spdlog/sinks/base_sink-inl.h" #include "spdlog/sinks/base_sink-inl.h"
#include "spdlog/sinks/basic_file_sink-inl.h" #include "spdlog/sinks/basic_file_sink-inl.h"
#include "spdlog/spdlog-inl.h" #include "spdlog/spdlog-inl.h"
#include <QByteArray>
#include <QSettings>
#include <QString>
#include <QVariant>
#include <iosfwd> // IWYU pragma: keep
#include <iterator>
#include <memory> // IWYU pragma: keep
#include <spdlog/logger.h>
#include <spdlog/sinks/basic_file_sink.h> // IWYU pragma: export
#include <spdlog/sinks/daily_file_sink.h> // IWYU pragma: export
#include <spdlog/sinks/stdout_color_sinks.h> // IWYU pragma: export
#include <spdlog/spdlog.h> // IWYU pragma: export
#include <vector>
std::shared_ptr<spdlog::logger> std::shared_ptr<spdlog::logger>
initLogger(std::string name) initLogger(std::string name)
{ {
QSettings settings("KittehPlayer", "KittehPlayer"); QSettings settings("KittehPlayer", "KittehPlayer");
QString logFile = QString logFile = settings.value("Logging/logFile", "/tmp/KittehPlayer.log").toString();
settings.value("Logging/logFile", "/tmp/KittehPlayer.log").toString();
std::vector<spdlog::sink_ptr> sinks; std::vector<spdlog::sink_ptr> sinks;
sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>()); sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>());
sinks.push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>( sinks.push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>(
logFile.toUtf8().constData())); logFile.toUtf8().constData()));
auto console = auto console = std::make_shared<spdlog::logger>(name, begin(sinks), end(sinks));
std::make_shared<spdlog::logger>(name, begin(sinks), end(sinks)); console->set_pattern("[%l][%n] %v%$");
console->set_pattern("[%l][%n] %v%$"); spdlog::register_logger(console);
spdlog::register_logger(console);
return spdlog::get(name); return spdlog::get(name);
} }

View file

@ -1,13 +1,12 @@
#ifndef LOGGER_HPP #ifndef LOGGER_HPP
#define LOGGER_HPP #define LOGGER_HPP
#include <iosfwd> // IWYU pragma: keep #include <iosfwd> // IWYU pragma: keep
#include <memory> // IWYU pragma: keep #include <memory> // IWYU pragma: keep
#include <spdlog/sinks/basic_file_sink.h> // IWYU pragma: keep #include <spdlog/sinks/basic_file_sink.h> // IWYU pragma: keep
#include <spdlog/sinks/daily_file_sink.h> // IWYU pragma: keep #include <spdlog/sinks/daily_file_sink.h> // IWYU pragma: keep
#include <spdlog/sinks/stdout_color_sinks.h> // IWYU pragma: keep #include <spdlog/sinks/stdout_color_sinks.h> // IWYU pragma: keep
#include <spdlog/spdlog.h> // IWYU pragma: keep #include <spdlog/spdlog.h> // IWYU pragma: keep
std::shared_ptr<spdlog::logger> std::shared_ptr<spdlog::logger> initLogger(std::string name);
initLogger(std::string name);
#endif #endif

View file

@ -1,24 +1,24 @@
#include <QtGlobal>
#include <locale.h>
#include <QApplication> #include <QApplication>
#include <QProcess> #include <QProcess>
#include <QtQml>
#include <QQmlApplicationEngine> #include <QQmlApplicationEngine>
#include <QtGlobal>
#include <QtQml>
#include <locale.h>
#ifdef QT_QML_DEBUG #ifdef QT_QML_DEBUG
#warning "QML Debugging Enabled!!!" #warning "QML Debugging Enabled!!!"
#include <QQmlDebug> #include <QQmlDebug>
#endif #endif
#include "logger.h"
#include "spdlog/logger.h"
#include <QSettings> #include <QSettings>
#include <QString> #include <QString>
#include <QUrl> #include <QUrl>
#include <QVariant> #include <QVariant>
#include <spdlog/fmt/fmt.h>
#include <cstdlib> #include <cstdlib>
#include <exception> #include <exception>
#include <iosfwd> #include <iosfwd>
#include <memory> #include <memory>
#include "logger.h" #include <spdlog/fmt/fmt.h>
#include "spdlog/logger.h"
extern void registerTypes(); extern void registerTypes();
@ -29,94 +29,89 @@ extern void registerTypes();
auto qmlLogger = initLogger("qml"); auto qmlLogger = initLogger("qml");
auto miscLogger = initLogger("misc"); auto miscLogger = initLogger("misc");
void void spdLogger(QtMsgType type, const QMessageLogContext& context, const QString& msg)
spdLogger(QtMsgType type, const QMessageLogContext& context, const QString& msg)
{ {
std::string localMsg = msg.toUtf8().constData(); std::string localMsg = msg.toUtf8().constData();
std::shared_ptr<spdlog::logger> logger; std::shared_ptr<spdlog::logger> logger;
if (QString(context.category).startsWith(QString("qml"))) { if (QString(context.category).startsWith(QString("qml"))) {
logger = qmlLogger; logger = qmlLogger;
} else { } else {
logger = miscLogger; logger = miscLogger;
} }
switch (type) { switch (type) {
case QtDebugMsg: case QtDebugMsg:
logger->debug("{}", localMsg); logger->debug("{}", localMsg);
break; break;
case QtInfoMsg: case QtInfoMsg:
logger->info("{}", localMsg); logger->info("{}", localMsg);
break; break;
case QtWarningMsg: case QtWarningMsg:
logger->warn("{}", localMsg); logger->warn("{}", localMsg);
break; break;
case QtCriticalMsg: case QtCriticalMsg:
logger->critical("{}", localMsg); logger->critical("{}", localMsg);
break; break;
case QtFatalMsg: case QtFatalMsg:
logger->critical("{}", localMsg); logger->critical("{}", localMsg);
abort(); abort();
} }
} }
int int main(int argc, char* argv[])
main(int argc, char* argv[])
{ {
qInstallMessageHandler(spdLogger); qInstallMessageHandler(spdLogger);
auto launcherLogger = initLogger("launcher"); auto launcherLogger = initLogger("launcher");
launcherLogger->info("Starting up!"); launcherLogger->info("Starting up!");
setenv("QT_QUICK_CONTROLS_STYLE", "Desktop", 1); setenv("QT_QUICK_CONTROLS_STYLE", "Desktop", 1);
QApplication app(argc, argv); QApplication app(argc, argv);
app.setOrganizationName("KittehPlayer"); app.setOrganizationName("KittehPlayer");
app.setOrganizationDomain("kitteh.pw"); app.setOrganizationDomain("kitteh.pw");
app.setApplicationName("KittehPlayer"); app.setApplicationName("KittehPlayer");
#ifdef QT_QML_DEBUG #ifdef QT_QML_DEBUG
// Allows debug. // Allows debug.
QQmlDebuggingEnabler enabler; QQmlDebuggingEnabler enabler;
#endif #endif
QSettings settings; QSettings settings;
bool ranFirstTimeSetup = settings.value("Setup/ranSetup", false).toBool(); bool ranFirstTimeSetup = settings.value("Setup/ranSetup", false).toBool();
#ifdef __linux__ #ifdef __linux__
// WARNING, THIS IS A BIG HACK // WARNING, THIS IS A BIG HACK
// this is only to make it so KittehPlayer works first try on pinephone. // this is only to make it so KittehPlayer works first try on pinephone.
// TODO: launch a opengl window or use offscreen to see if GL_ARB_framebuffer_object // TODO: launch a opengl window or use offscreen to see if GL_ARB_framebuffer_object
// can be found // can be found
if (! (settings.value("Backend/disableSunxiCheck", false).toBool() || ranFirstTimeSetup)) { if (!(settings.value("Backend/disableSunxiCheck", false).toBool() || ranFirstTimeSetup)) {
FILE *fd = popen("grep sun[x8]i /proc/modules", "r"); FILE* fd = popen("grep sun[x8]i /proc/modules", "r");
char buf[16]; char buf[16];
if (fread(buf, 1, sizeof (buf), fd) > 0) { if (fread(buf, 1, sizeof(buf), fd) > 0) {
launcherLogger->info("Running on sunxi, switching to NoFBO."); launcherLogger->info("Running on sunxi, switching to NoFBO.");
settings.setValue("Appearance/clickToPause", false); settings.setValue("Appearance/clickToPause", false);
settings.setValue("Appearance/doubleTapToSeek", true); settings.setValue("Appearance/doubleTapToSeek", true);
settings.setValue("Appearance/scaleFactor", 2.2); settings.setValue("Appearance/scaleFactor", 2.2);
settings.setValue("Appearance/subtitlesFontSize", 38); settings.setValue("Appearance/subtitlesFontSize", 38);
settings.setValue("Appearance/uiFadeTimer", 0); settings.setValue("Appearance/uiFadeTimer", 0);
}
} }
}
#endif #endif
settings.setValue("Setup/ranSetup", true);
settings.setValue("Setup/ranSetup", true); QString newpath = QProcessEnvironment::systemEnvironment().value("APPDIR", "") + "/usr/bin:" + QProcessEnvironment::systemEnvironment().value("PATH", "");
setenv("PATH", newpath.toUtf8().constData(), 1);
QString newpath = registerTypes();
QProcessEnvironment::systemEnvironment().value("APPDIR", "") +
"/usr/bin:" + QProcessEnvironment::systemEnvironment().value("PATH", "");
setenv("PATH", newpath.toUtf8().constData(), 1);
registerTypes(); setlocale(LC_NUMERIC, "C");
launcherLogger->info("Loading player...");
setlocale(LC_NUMERIC, "C"); QQmlApplicationEngine engine;
launcherLogger->info("Loading player..."); engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
QQmlApplicationEngine engine; return app.exec();
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
return app.exec();
} }

View file

@ -4,99 +4,99 @@ import QtQuick.Layouts 1.2
import player 1.0 import player 1.0
Item { Item {
id: controlsBarItem id: controlsBarItem
property var combinedHeight: progressBar.height + controlsBackground.height property var combinedHeight: progressBar.height + controlsBackground.height
property bool controlsShowing: true property bool controlsShowing: true
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
Connections {
target: globalConnections
onHideUI: function (force) {
controlsBarItem.controlsShowing = false
}
onShowUI: {
controlsBarItem.controlsShowing = true
}
}
Connections {
target: appearance
onThemeNameChanged: setControlsTheme(appearance.themeName)
}
function setControlsTheme(themeName) {
for (var i = 0; i < controlsBar.children.length; ++i) {
if (controlsBar.children[i].objectName == "buttonLayout") {
controlsBar.children[i].destroy()
}
}
var component = Qt.createComponent(themeName + "ButtonLayout.qml")
if (component.status == Component.Error) {
console.error("Error loading component: " + component.errorString())
}
component.createObject(controlsBar, {})
}
SubtitlesBar {
anchors.bottom: controlsBackground.top
}
VideoProgress {
id: progressBar
visible: controlsBarItem.controlsShowing
&& appearance.themeName != "RoosterTeeth"
bottomPadding: 0
rightPadding: 0
leftPadding: 0
z: 20
anchors { anchors {
bottom: parent.bottom bottom: controlsBackground.top
left: parent.left left: controlsBackground.left
right: parent.right right: controlsBackground.right
leftMargin: parent.width / 128
rightMargin: parent.width / 128
bottomMargin: 0
} }
}
Connections { Rectangle {
target: globalConnections id: controlsBackground
onHideUI: function (force) { height: controlsBar.visible ? controlsBar.height
controlsBarItem.controlsShowing = false + (appearance.themeName
} == "RoosterTeeth" ? 0 : progressBar.topPadding) : 0
onShowUI: { Layout.fillWidth: true
controlsBarItem.controlsShowing = true Layout.fillHeight: true
} color: getAppearanceValueForTheme(appearance.themeName, "mainBackground")
visible: controlsBarItem.controlsShowing
z: 10
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
} }
}
Connections { Item {
target: appearance id: controlsBar
onThemeNameChanged: setControlsTheme(appearance.themeName) height: controlsBar.visible ? mainWindow.virtualHeight / 20 : 0
visible: controlsBarItem.controlsShowing
z: 30
anchors {
right: parent.right
rightMargin: parent.width / 128
left: parent.left
leftMargin: parent.width / 128
bottom: parent.bottom
bottomMargin: 0
} }
}
function setControlsTheme(themeName) { Component.onCompleted: {
for (var i = 0; i < controlsBar.children.length; ++i) { setControlsTheme(appearance.themeName)
if (controlsBar.children[i].objectName == "buttonLayout") { }
controlsBar.children[i].destroy()
}
}
var component = Qt.createComponent(themeName + "ButtonLayout.qml")
if (component.status == Component.Error) {
console.error("Error loading component: " + component.errorString())
}
component.createObject(controlsBar, {})
}
SubtitlesBar {
anchors.bottom: controlsBackground.top
}
VideoProgress {
id: progressBar
visible: controlsBarItem.controlsShowing && appearance.themeName != "RoosterTeeth"
bottomPadding: 0
rightPadding: 0
leftPadding: 0
z: 20
anchors {
bottom: controlsBackground.top
left: controlsBackground.left
right: controlsBackground.right
leftMargin: parent.width / 128
rightMargin: parent.width / 128
bottomMargin: 0
}
}
Rectangle {
id: controlsBackground
height: controlsBar.visible ? controlsBar.height
+ (appearance.themeName
== "RoosterTeeth" ? 0 : progressBar.topPadding) : 0
Layout.fillWidth: true
Layout.fillHeight: true
color: getAppearanceValueForTheme(appearance.themeName,
"mainBackground")
visible: controlsBarItem.controlsShowing
z: 10
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
}
Item {
id: controlsBar
height: controlsBar.visible ? mainWindow.virtualHeight / 20 : 0
visible: controlsBarItem.controlsShowing
z: 30
anchors {
right: parent.right
rightMargin: parent.width / 128
left: parent.left
leftMargin: parent.width / 128
bottom: parent.bottom
bottomMargin: 0
}
}
Component.onCompleted: {
setControlsTheme(appearance.themeName)
}
} }

View file

@ -2,12 +2,11 @@ import QtQuick 2.0
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
Menu { Menu {
width: 300 width: 300
background: Rectangle { background: Rectangle {
implicitWidth: parent.width implicitWidth: parent.width
implicitHeight: 10 implicitHeight: 10
color: getAppearanceValueForTheme(appearance.themeName, color: getAppearanceValueForTheme(appearance.themeName, "mainBackground")
"mainBackground") }
} delegate: CustomMenuItem {}
delegate: CustomMenuItem {}
} }

View file

@ -5,218 +5,214 @@ import QtQuick.Window 2.2
import player 1.0 import player 1.0
Dialog { Dialog {
id: playlistDialog id: playlistDialog
title: "Playlist" title: "Playlist"
height: Math.max(480, childrenRect.height * playlistListView.count) height: Math.max(480, childrenRect.height * playlistListView.count)
width: 720 width: 720
modality: Qt.NonModal modality: Qt.NonModal
property int thumbnailJobsRunning: 0 property int thumbnailJobsRunning: 0
property variant thumbnailJobs: [] property variant thumbnailJobs: []
property int titleJobsRunning: 0 property int titleJobsRunning: 0
property variant titleJobs: [] property variant titleJobs: []
function addThumbnailToCache(name, output) { function addThumbnailToCache(name, output) {
output = output.replace("maxresdefault", "sddefault").split('\n')[0] output = output.replace("maxresdefault", "sddefault").split('\n')[0]
thumbnailCache.addURL(name, output) thumbnailCache.addURL(name, output)
thumbnailJobs.shift() thumbnailJobs.shift()
thumbnailJobsRunning -= 1 thumbnailJobsRunning -= 1
} }
ThumbnailCache { ThumbnailCache {
id: thumbnailCache id: thumbnailCache
} }
Rectangle { Rectangle {
visible: false visible: false
id: titleGetter id: titleGetter
signal titleFound(string name, string title) signal titleFound(string name, string title)
} }
Timer { Timer {
interval: 100 interval: 100
repeat: true repeat: true
triggeredOnStart: true triggeredOnStart: true
running: true running: true
onTriggered: { onTriggered: {
if (thumbnailJobsRunning < 2) { if (thumbnailJobsRunning < 2) {
if (thumbnailJobs.length > 0) { if (thumbnailJobs.length > 0) {
if (thumbnailJobs[0].startsWith( if (thumbnailJobs[0].startsWith(
"https://www.youtube.com/playlist?list=")) { "https://www.youtube.com/playlist?list=")) {
thumbnailJobs.shift() thumbnailJobs.shift()
return return
} }
var component = Qt.createComponent("ThumbnailProcess.qml") var component = Qt.createComponent("ThumbnailProcess.qml")
var thumbnailerProcess = component.createObject( var thumbnailerProcess = component.createObject(playlistDialog, {
playlistDialog, { "name": thumbnailJobs[0]
"name": thumbnailJobs[0] })
}) if (String(titleJobs[0]).indexOf("://") !== -1) {
if (String(titleJobs[0]).indexOf("://") !== -1) {
thumbnailerProcess.start( thumbnailerProcess.start("youtube-dl",
"youtube-dl", ["--get-thumbnail", thumbnailJobs[0]])
["--get-thumbnail", thumbnailJobs[0]]) } else {
} else { thumbnailerProcess.start(
thumbnailerProcess.start( "ffmpegthumbnailer",
"ffmpegthumbnailer", ["-i", thumbnailJobs[0], "-o", "/tmp/" + Qt.md5(
["-i", thumbnailJobs[0], "-o", "/tmp/" + Qt.md5( thumbnailJobs[0]) + ".png"])
thumbnailJobs[0]) + ".png"]) }
}
thumbnailJobsRunning += 1 thumbnailJobsRunning += 1
}
}
} }
}
} }
}
Timer { Timer {
interval: 100 interval: 100
repeat: true repeat: true
triggeredOnStart: true triggeredOnStart: true
running: true running: true
onTriggered: { onTriggered: {
if (titleJobsRunning < 5) { if (titleJobsRunning < 5) {
if (titleJobs.length > 0) { if (titleJobs.length > 0) {
if (titleJobs[0].startsWith( if (titleJobs[0].startsWith(
"https://www.youtube.com/playlist?list=")) { "https://www.youtube.com/playlist?list=")) {
titleJobs.shift() titleJobs.shift()
return return
} }
var component = Qt.createComponent("TitleProcess.qml") var component = Qt.createComponent("TitleProcess.qml")
var titleProcess = component.createObject(playlistDialog, { var titleProcess = component.createObject(playlistDialog, {
"name": titleJobs[0] "name": titleJobs[0]
}) })
titleProcess.start("youtube-dl", titleProcess.start("youtube-dl", ["--get-title", titleJobs[0]])
["--get-title", titleJobs[0]]) titleJobs.shift()
titleJobs.shift() titleJobsRunning += 1
titleJobsRunning += 1
}
}
} }
}
} }
}
Connections { Connections {
target: player target: player
onPlaylistChanged: function (playlist) { onPlaylistChanged: function (playlist) {
playlistModel.clear() playlistModel.clear()
thumbnailJobs = [] thumbnailJobs = []
titleJobs = [] titleJobs = []
titleJobsRunning = 0 titleJobsRunning = 0
thumbnailJobsRunning = 0 thumbnailJobsRunning = 0
for (var thing in playlist) { for (var thing in playlist) {
var item = playlist[thing] var item = playlist[thing]
playlistModel.append({ playlistModel.append({
"playlistItemTitle": item["title"], "playlistItemTitle": item["title"],
"playlistItemFilename": item["filename"], "playlistItemFilename": item["filename"],
"current": item["current"], "current": item["current"],
"playlistPos": thing "playlistPos": thing
}) })
} }
}
} }
}
Component { Component {
id: playlistDelegate id: playlistDelegate
Item { Item {
id: playlistItem id: playlistItem
property string itemURL: "" property string itemURL: ""
property string itemTitle: "" property string itemTitle: ""
width: playlistDialog.width width: playlistDialog.width
height: childrenRect.height height: childrenRect.height
function getText(title, filename) { function getText(title, filename) {
var itemText = "" var itemText = ""
if (title.length > 0) { if (title.length > 0) {
itemText += '<b>Title:</b> ' + title + "<br>" itemText += '<b>Title:</b> ' + title + "<br>"
}
if (filename.length > 0) {
itemText += '<b>Filename:</b> ' + filename
}
return itemText
}
Connections {
target: thumbnailCache
onThumbnailReady: function (name, url, path) {
if (name == playlistItem.itemURL) {
thumbnail.source = path
}
}
}
Connections {
target: titleGetter
onTitleFound: function (name, title) {
if (name == playlistItem.itemURL) {
titleJobsRunning -= 1
playlistItem.itemTitle = title
}
}
}
Image {
id: thumbnail
source: ""
height: source.toString().length > 1 ? 144 : 0
width: source.toString().length > 1 ? 256 : 0
}
Button {
width: parent.width - 20
id: playlistItemButton
font.pixelSize: 12
padding: 0
anchors.left: thumbnail.right
bottomPadding: 0
contentItem: Text {
id: playlistItemText
font: parent.font
color: "white"
text: playlistItem.getText(itemTitle, itemURL)
height: parent.height
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
wrapMode: Text.Wrap
}
onClicked: {
player.playerCommand(Enums.Commands.SetPlaylistPos,
playlistPos)
}
background: Rectangle {
color: current ? "orange" : "transparent"
}
}
Component.onCompleted: {
if (typeof playlistItemTitle !== "undefined") {
playlistItem.itemTitle = playlistItemTitle
} else {
playlistDialog.titleJobs.push(playlistItemFilename)
}
if (typeof playlistItemFilename !== "undefined") {
playlistItem.itemURL = playlistItemFilename
} else {
playlistItem.itemURL = ""
}
playlistDialog.thumbnailJobs.push(playlistItemFilename)
}
} }
if (filename.length > 0) {
itemText += '<b>Filename:</b> ' + filename
}
return itemText
}
Connections {
target: thumbnailCache
onThumbnailReady: function (name, url, path) {
if (name == playlistItem.itemURL) {
thumbnail.source = path
}
}
}
Connections {
target: titleGetter
onTitleFound: function (name, title) {
if (name == playlistItem.itemURL) {
titleJobsRunning -= 1
playlistItem.itemTitle = title
}
}
}
Image {
id: thumbnail
source: ""
height: source.toString().length > 1 ? 144 : 0
width: source.toString().length > 1 ? 256 : 0
}
Button {
width: parent.width - 20
id: playlistItemButton
font.pixelSize: 12
padding: 0
anchors.left: thumbnail.right
bottomPadding: 0
contentItem: Text {
id: playlistItemText
font: parent.font
color: "white"
text: playlistItem.getText(itemTitle, itemURL)
height: parent.height
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
wrapMode: Text.Wrap
}
onClicked: {
player.playerCommand(Enums.Commands.SetPlaylistPos, playlistPos)
}
background: Rectangle {
color: current ? "orange" : "transparent"
}
}
Component.onCompleted: {
if (typeof playlistItemTitle !== "undefined") {
playlistItem.itemTitle = playlistItemTitle
} else {
playlistDialog.titleJobs.push(playlistItemFilename)
}
if (typeof playlistItemFilename !== "undefined") {
playlistItem.itemURL = playlistItemFilename
} else {
playlistItem.itemURL = ""
}
playlistDialog.thumbnailJobs.push(playlistItemFilename)
}
} }
}
ListView { ListView {
id: playlistListView id: playlistListView
anchors.fill: parent anchors.fill: parent
model: ListModel { model: ListModel {
id: playlistModel id: playlistModel
}
delegate: playlistDelegate
highlight: Item {}
snapMode: ListView.SnapToItem
flickableDirection: Flickable.VerticalFlick
boundsBehavior: Flickable.StopAtBounds
ScrollBar.vertical: ScrollBar {
active: playlistListView.count > 1 ? true : true
}
focus: true
} }
delegate: playlistDelegate
highlight: Item {}
snapMode: ListView.SnapToItem
flickableDirection: Flickable.VerticalFlick
boundsBehavior: Flickable.StopAtBounds
ScrollBar.vertical: ScrollBar {
active: playlistListView.count > 1 ? true : true
}
focus: true
}
} }

View file

@ -6,154 +6,160 @@ import QtQuick.Window 2.2
import player 1.0 import player 1.0
Dialog { Dialog {
id: settingsDialog id: settingsDialog
title: translate.getTranslation("SETTINGS", i18n.language) title: translate.getTranslation("SETTINGS", i18n.language)
height: 100 height: 100
width: 720 width: 720
modality: Qt.NonModal modality: Qt.NonModal
signal done; signal done
ScrollView { ScrollView {
id: content id: content
height: parent.height height: parent.height
width: parent.width width: parent.width
clip: true clip: true
ScrollBar.vertical.policy: ScrollBar.AlwaysOn ScrollBar.vertical.policy: ScrollBar.AlwaysOn
Item {
id: settingsContent
implicitHeight: childrenRect.height
implicitWidth: childrenRect.width
ColumnLayout {
Text {
height: 30
text: translate.getTranslation("LANGUAGE", i18n.language)
verticalAlignment: Text.AlignVCenter
}
LanguageSettings {
Layout.leftMargin: 30
}
Text {
height: 30
text: translate.getTranslation("APPEARANCE", i18n.language)
verticalAlignment: Text.AlignVCenter
}
CheckBox {
checked: appearance.titleOnlyOnFullscreen
onClicked: appearance.titleOnlyOnFullscreen = !appearance.titleOnlyOnFullscreen
text: translate.getTranslation("TITLE_ONLY_ON_FULLSCREEN",
i18n.language)
Layout.leftMargin: 30
}
CheckBox {
checked: appearance.doubleTapToSeek
onClicked: appearance.doubleTapToSeek = !appearance.doubleTapToSeek
text: translate.getTranslation("DOUBLE_TAP_TO_SEEK", i18n.language)
Layout.leftMargin: 30
}
Item { Item {
id: settingsContent Layout.leftMargin: 30
implicitHeight: childrenRect.height Layout.bottomMargin: 10
implicitWidth: childrenRect.width height: 30
ColumnLayout { Text {
Text { id: seekByLabel
height: 30 height: 30
text: translate.getTranslation("LANGUAGE", i18n.language) text: translate.getTranslation("DOUBLE_TAP_TO_SEEK_BY",
verticalAlignment: Text.AlignVCenter i18n.language)
} verticalAlignment: Text.AlignVCenter
LanguageSettings { Layout.leftMargin: 30 } }
Text { TextField {
height: 30 id: seekBy
text: translate.getTranslation("APPEARANCE", i18n.language) anchors.left: seekByLabel.right
verticalAlignment: Text.AlignVCenter anchors.leftMargin: 10
} validator: IntValidator {}
CheckBox { inputMethodHints: Qt.ImhFormattedNumbersOnly
checked: appearance.titleOnlyOnFullscreen text: appearance.doubleTapToSeekBy
onClicked: appearance.titleOnlyOnFullscreen = !appearance.titleOnlyOnFullscreen function setSeekBy() {
text: translate.getTranslation("TITLE_ONLY_ON_FULLSCREEN", i18n.language) appearance.doubleTapToSeekBy = parseInt(seekBy.text)
Layout.leftMargin: 30
}
CheckBox {
checked: appearance.doubleTapToSeek
onClicked: appearance.doubleTapToSeek = !appearance.doubleTapToSeek
text: translate.getTranslation("DOUBLE_TAP_TO_SEEK", i18n.language)
Layout.leftMargin: 30
}
Item {
Layout.leftMargin: 30
Layout.bottomMargin: 10
height: 30
Text {
id: seekByLabel
height: 30
text: translate.getTranslation("DOUBLE_TAP_TO_SEEK_BY", i18n.language)
verticalAlignment: Text.AlignVCenter
}
TextField {
id: seekBy
anchors.left: seekByLabel.right
anchors.leftMargin: 10
validator: IntValidator {}
inputMethodHints: Qt.ImhFormattedNumbersOnly
text: appearance.doubleTapToSeekBy
function setSeekBy() {
appearance.doubleTapToSeekBy = parseInt(seekBy.text)
}
onEditingFinished: setSeekBy()
}
}
Item {
height: 30
Layout.bottomMargin: 10
Layout.leftMargin: 30
Text {
id: fontLabel
height: 30
text: translate.getTranslation("FONT", i18n.language)
verticalAlignment: Text.AlignVCenter
}
TextField {
id: fontInput
anchors.left: fontLabel.right
anchors.leftMargin: 10
text: appearance.fontName
function setFont() {
appearance.fontName = fontInput.text
}
onEditingFinished: setFont()
}
}
Item {
Layout.leftMargin: 30
Layout.bottomMargin: 10
height: 30
Text {
id: subtitlesFontSizeLabel
height: 30
text: translate.getTranslation("SUBTITLES_FONT_SIZE", i18n.language)
verticalAlignment: Text.AlignVCenter
}
TextField {
id: subtitlesFontSizeInput
anchors.left: subtitlesFontSizeLabel.right
anchors.leftMargin: 10
validator: IntValidator {}
inputMethodHints: Qt.ImhFormattedNumbersOnly
text: appearance.subtitlesFontSize
function setSubtitlesFontSize() {
appearance.subtitlesFontSize = parseInt(subtitlesFontSizeInput.text)
}
onEditingFinished: setSubtitlesFontSize()
}
}
Item {
Layout.leftMargin: 30
Layout.bottomMargin: 10
height: 30
Text {
id: uiFadeTimeLabel
height: 30
text: translate.getTranslation("UI_FADE_TIME", i18n.language)
verticalAlignment: Text.AlignVCenter
}
TextField {
id: uiFadeTimeInput
anchors.left: uiFadeTimeLabel.right
anchors.leftMargin: 10
validator: IntValidator { bottom: 0 }
inputMethodHints: Qt.ImhFormattedNumbersOnly
text: appearance.uiFadeTimer
function setUIFadeTime() {
appearance.uiFadeTimer = parseInt(uiFadeTimeInput.text)
}
onEditingFinished: setUIFadeTime()
}
}
} }
onEditingFinished: setSeekBy()
}
} }
Item {
} height: 30
Layout.bottomMargin: 10
Connections { Layout.leftMargin: 30
target: settingsDialog Text {
onAccepted: { id: fontLabel
seekBy.setSeekBy() height: 30
fontInput.setFont() text: translate.getTranslation("FONT", i18n.language)
subtitlesFontSizeInput.setSubtitlesFontSize() verticalAlignment: Text.AlignVCenter
uiFadeTimeInput.setUIFadeTime() }
settingsDialog.done() TextField {
id: fontInput
anchors.left: fontLabel.right
anchors.leftMargin: 10
text: appearance.fontName
function setFont() {
appearance.fontName = fontInput.text
}
onEditingFinished: setFont()
}
} }
Item {
Layout.leftMargin: 30
Layout.bottomMargin: 10
height: 30
Text {
id: subtitlesFontSizeLabel
height: 30
text: translate.getTranslation("SUBTITLES_FONT_SIZE", i18n.language)
verticalAlignment: Text.AlignVCenter
}
TextField {
id: subtitlesFontSizeInput
anchors.left: subtitlesFontSizeLabel.right
anchors.leftMargin: 10
validator: IntValidator {}
inputMethodHints: Qt.ImhFormattedNumbersOnly
text: appearance.subtitlesFontSize
function setSubtitlesFontSize() {
appearance.subtitlesFontSize = parseInt(
subtitlesFontSizeInput.text)
}
onEditingFinished: setSubtitlesFontSize()
}
}
Item {
Layout.leftMargin: 30
Layout.bottomMargin: 10
height: 30
Text {
id: uiFadeTimeLabel
height: 30
text: translate.getTranslation("UI_FADE_TIME", i18n.language)
verticalAlignment: Text.AlignVCenter
}
TextField {
id: uiFadeTimeInput
anchors.left: uiFadeTimeLabel.right
anchors.leftMargin: 10
validator: IntValidator {
bottom: 0
}
inputMethodHints: Qt.ImhFormattedNumbersOnly
text: appearance.uiFadeTimer
function setUIFadeTime() {
appearance.uiFadeTimer = parseInt(uiFadeTimeInput.text)
}
onEditingFinished: setUIFadeTime()
}
}
}
} }
Component.onCompleted: { }
settingsDialog.open()
Connections {
target: settingsDialog
onAccepted: {
seekBy.setSeekBy()
fontInput.setFont()
subtitlesFontSizeInput.setSubtitlesFontSize()
uiFadeTimeInput.setUIFadeTime()
settingsDialog.done()
} }
}
Component.onCompleted: {
settingsDialog.open()
}
} }

View file

@ -3,28 +3,32 @@ import QtQuick.Controls 2.3
import "translations.js" as Translations import "translations.js" as Translations
ComboBox { ComboBox {
id: languageSelector id: languageSelector
height: 30 height: 30
editable: false editable: false
pressed: true pressed: true
model: Object.keys(Translations.languages).map(function(key) {return Translations.languages[key];}) model: Object.keys(Translations.languages).map(function (key) {
delegate: ItemDelegate { return Translations.languages[key]
height: 25 })
width: languageSelector.width delegate: ItemDelegate {
contentItem: Text { height: 25
text: modelData width: languageSelector.width
color: "#21be2b" contentItem: Text {
font: languageSelector.font text: modelData
elide: Text.ElideRight color: "#21be2b"
verticalAlignment: Text.AlignVCenter font: languageSelector.font
} elide: Text.ElideRight
highlighted: languageSelector.highlightedIndex === index verticalAlignment: Text.AlignVCenter
}
onActivated: {
console.warn(currentText)
i18n.language = Object.keys(Translations.languages).filter(function(key) {return Translations.languages[key] === currentText})[0];
}
Component.onCompleted: {
currentIndex = languageSelector.find(Translations.languages[i18n.language])
} }
highlighted: languageSelector.highlightedIndex === index
}
onActivated: {
console.warn(currentText)
i18n.language = Object.keys(Translations.languages).filter(function (key) {
return Translations.languages[key] === currentText
})[0]
}
Component.onCompleted: {
currentIndex = languageSelector.find(Translations.languages[i18n.language])
}
} }

View file

@ -2,12 +2,12 @@ import QtQuick.Controls 2.3
import player 1.0 import player 1.0
Action { Action {
id: audioDeviceItem id: audioDeviceItem
property string deviceID: "none" property string deviceID: "none"
checkable: false checkable: false
checked: false checked: false
onTriggered: { onTriggered: {
player.playerCommand(Enums.Commands.SetAudioDevice, deviceID) player.playerCommand(Enums.Commands.SetAudioDevice, deviceID)
} }
} }

View file

@ -1,24 +1,23 @@
import QtQuick 2.0 import QtQuick 2.0
Rectangle { Rectangle {
id: chapterMarker id: chapterMarker
property int time: 0 property int time: 0
color: getAppearanceValueForTheme(appearance.themeName, color: getAppearanceValueForTheme(appearance.themeName, "chapterMarkerColor")
"chapterMarkerColor") width: 4
width: 4 height: parent.height
height: parent.height x: progressBar.background.width / progressBar.to * time
x: progressBar.background.width / progressBar.to * time z: 9000
z: 9000 anchors {
anchors { top: parent.top
top: parent.top bottom: parent.bottom
bottom: parent.bottom }
}
Connections { Connections {
target: player target: player
enabled: true enabled: true
onChaptersChanged: { onChaptersChanged: {
chapterMarker.destroy() chapterMarker.destroy()
}
} }
}
} }

View file

@ -2,25 +2,25 @@ import QtQuick 2.0
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
MenuItem { MenuItem {
id: menuItem id: menuItem
implicitHeight: 20 implicitHeight: 20
contentItem: Text { contentItem: Text {
text: menuItem.text text: menuItem.text
opacity: 1 opacity: 1
color: menuItem.highlighted ? "#5a50da" : "white" color: menuItem.highlighted ? "#5a50da" : "white"
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight elide: Text.ElideRight
font { font {
family: appearance.fontName family: appearance.fontName
bold: menuItem.highlighted bold: menuItem.highlighted
}
} }
}
background: Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
opacity: 1 opacity: 1
color: menuItem.highlighted ? "#c0c0f0" : "transparent" color: menuItem.highlighted ? "#c0c0f0" : "transparent"
} }
} }

View file

@ -1,14 +1,13 @@
import player 1.0 import player 1.0
Process { Process {
id: thumbnailerProcess id: thumbnailerProcess
property string name: "" property string name: ""
onFinished: function () { onFinished: function () {
if (String(name).indexOf("://") !== -1) { if (String(name).indexOf("://") !== -1) {
playlistDialog.addThumbnailToCache(name, getOutput()) playlistDialog.addThumbnailToCache(name, getOutput())
} else { } else {
playlistDialog.addThumbnailToCache(name, playlistDialog.addThumbnailToCache(name, "/tmp/" + Qt.md5(name) + ".png")
"/tmp/" + Qt.md5(name) + ".png")
}
} }
}
} }

View file

@ -1,9 +1,9 @@
import player 1.0 import player 1.0
Process { Process {
id: titleProcess id: titleProcess
property string name: "" property string name: ""
onReadyRead: function () { onReadyRead: function () {
titleGetter.titleFound(name, getOutput()) titleGetter.titleFound(name, getOutput())
} }
} }

View file

@ -2,13 +2,13 @@ import QtQuick.Controls 2.3
import player 1.0 import player 1.0
Action { Action {
id: trackItem id: trackItem
property string trackType: "none" property string trackType: "none"
property string trackID: "none" property string trackID: "none"
checkable: true checkable: true
checked: false checked: false
onTriggered: { onTriggered: {
player.playerCommand(Enums.Commands.SetTrack, [trackType, trackID]) player.playerCommand(Enums.Commands.SetTrack, [trackType, trackID])
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -2,89 +2,89 @@ import QtQuick 2.0
import player 1.0 import player 1.0
Item { Item {
objectName: "buttonLayout" objectName: "buttonLayout"
id: layout id: layout
anchors.fill: controlsBar anchors.fill: controlsBar
PlayPauseButton { PlayPauseButton {
id: playPauseButton id: playPauseButton
anchors { anchors {
left: parent.left left: parent.left
top: parent.top top: parent.top
bottom: parent.bottom bottom: parent.bottom
}
} }
VolumeButton { }
id: volumeButton VolumeButton {
anchors { id: volumeButton
left: playPauseButton.right anchors {
top: parent.top left: playPauseButton.right
bottom: parent.bottom top: parent.top
} bottom: parent.bottom
} }
VolumeSlider { }
anchors { VolumeSlider {
left: volumeButton.right anchors {
top: parent.top left: volumeButton.right
bottom: parent.bottom top: parent.top
} bottom: parent.bottom
} }
}
PlaylistPrevButton { PlaylistPrevButton {
id: playlistPrevButton id: playlistPrevButton
anchors { anchors {
right: backwardButton.left right: backwardButton.left
top: parent.top top: parent.top
bottom: parent.bottom bottom: parent.bottom
}
} }
BackwardButton { }
id: backwardButton BackwardButton {
anchors { id: backwardButton
right: timeLabel.left anchors {
top: parent.top right: timeLabel.left
bottom: parent.bottom top: parent.top
} bottom: parent.bottom
} }
TimeLabel { }
id: timeLabel TimeLabel {
anchors { id: timeLabel
centerIn: parent anchors {
top: parent.top centerIn: parent
bottom: parent.bottom top: parent.top
} bottom: parent.bottom
} }
ForwardButton { }
id: forwardButton ForwardButton {
anchors { id: forwardButton
left: timeLabel.right anchors {
top: parent.top left: timeLabel.right
bottom: parent.bottom top: parent.top
} bottom: parent.bottom
} }
PlaylistNextButton { }
id: playlistNextButton PlaylistNextButton {
anchors { id: playlistNextButton
left: forwardButton.right anchors {
top: parent.top left: forwardButton.right
bottom: parent.bottom top: parent.top
} bottom: parent.bottom
} }
}
FullscreenButton { FullscreenButton {
id: fullscreenButton id: fullscreenButton
anchors { anchors {
right: settingsButton.left right: settingsButton.left
top: parent.top top: parent.top
bottom: parent.bottom bottom: parent.bottom
}
} }
SettingsButton { }
id: settingsButton SettingsButton {
anchors { id: settingsButton
right: parent.right anchors {
top: parent.top right: parent.right
bottom: parent.bottom top: parent.top
} bottom: parent.bottom
} }
}
} }

View file

@ -2,110 +2,110 @@ import QtQuick 2.0
import player 1.0 import player 1.0
Item { Item {
objectName: "buttonLayout" objectName: "buttonLayout"
id: layout id: layout
anchors.fill: controlsBar anchors.fill: controlsBar
PlayPauseButton { PlayPauseButton {
id: playPauseButton id: playPauseButton
anchors { anchors {
top: parent.top top: parent.top
bottom: parent.bottom bottom: parent.bottom
left: parent.left left: parent.left
}
} }
}
MouseArea { MouseArea {
id: mouseAreaVolumeArea id: mouseAreaVolumeArea
anchors { anchors {
right: volumeSliderArea.right right: volumeSliderArea.right
bottom: volumeButton.bottom bottom: volumeButton.bottom
left: volumeButton.left left: volumeButton.left
}
height: parent.height + (volumeSliderArea.visible ? volumeSliderArea.height : 0)
hoverEnabled: true
z: 500
propagateComposedEvents: true
acceptedButtons: Qt.NoButton
onEntered: {
mouseAreaPlayerTimer.stop()
}
onExited: {
mouseAreaPlayerTimer.restart()
}
} }
height: parent.height + (volumeSliderArea.visible ? volumeSliderArea.height : 0)
hoverEnabled: true
z: 500
propagateComposedEvents: true
acceptedButtons: Qt.NoButton
onEntered: {
mouseAreaPlayerTimer.stop()
}
onExited: {
mouseAreaPlayerTimer.restart()
}
}
VolumeButton { VolumeButton {
id: volumeButton id: volumeButton
anchors { anchors {
left: playPauseButton.right left: playPauseButton.right
top: parent.top top: parent.top
bottom: parent.bottom bottom: parent.bottom
}
hoverEnabled: true
} }
hoverEnabled: true
}
VerticalVolume { VerticalVolume {
id: volumeSliderArea id: volumeSliderArea
anchors { anchors {
bottom: volumeButton.top bottom: volumeButton.top
left: volumeButton.left left: volumeButton.left
right: volumeButton.right right: volumeButton.right
}
width: volumeButton.width
visible: mouseAreaVolumeArea.containsMouse || volumeButton.hovered
} }
width: volumeButton.width
visible: mouseAreaVolumeArea.containsMouse || volumeButton.hovered
}
TimeLabel { TimeLabel {
id: timeLabel id: timeLabel
anchors { anchors {
left: volumeButton.right left: volumeButton.right
top: parent.top top: parent.top
bottom: parent.bottom bottom: parent.bottom
}
} }
}
VideoProgress { VideoProgress {
id: videoProgressRoosterTeeth id: videoProgressRoosterTeeth
anchors { anchors {
top: parent.top top: parent.top
bottom: parent.bottom bottom: parent.bottom
left: timeLabel.right left: timeLabel.right
leftMargin: parent.width / 128 leftMargin: parent.width / 128
right: speedText.left right: speedText.left
rightMargin: parent.width / 128 rightMargin: parent.width / 128
}
rightPadding: 0
leftPadding: 0
height: parent.height
to: progressBar.to
value: progressBar.value
center: true
} }
rightPadding: 0
leftPadding: 0
height: parent.height
to: progressBar.to
value: progressBar.value
center: true
}
SpeedText { SpeedText {
id: speedText id: speedText
anchors { anchors {
top: parent.top top: parent.top
bottom: parent.bottom bottom: parent.bottom
right: fullscreenButton.left right: fullscreenButton.left
}
} }
}
FullscreenButton { FullscreenButton {
id: fullscreenButton id: fullscreenButton
anchors { anchors {
right: settingsButton.left right: settingsButton.left
top: parent.top top: parent.top
bottom: parent.bottom bottom: parent.bottom
}
} }
SettingsButton { }
id: settingsButton SettingsButton {
anchors { id: settingsButton
right: parent.right anchors {
top: parent.top right: parent.right
bottom: parent.bottom top: parent.top
} bottom: parent.bottom
} }
}
} }

View file

@ -4,64 +4,64 @@ import QtQuick.Layouts 1.2
import player 1.0 import player 1.0
Item { Item {
id: subtitlesBar id: subtitlesBar
visible: !appearance.useMpvSubs visible: !appearance.useMpvSubs
height: player.height / 8 height: player.height / 8
anchors {
bottomMargin: 5
right: parent.right
left: parent.left
}
RowLayout {
id: nativeSubtitles
height: childrenRect.height
visible: true
anchors { anchors {
bottomMargin: 5 left: subtitlesBar.left
right: parent.right right: subtitlesBar.right
left: parent.left bottom: parent.bottom
bottomMargin: 10
} }
RowLayout { Item {
id: nativeSubtitles id: subsContainer
height: childrenRect.height height: childrenRect.height
visible: true Layout.fillWidth: true
anchors { Layout.fillHeight: true
left: subtitlesBar.left Layout.rightMargin: 0
right: subtitlesBar.right Layout.leftMargin: 0
bottom: parent.bottom Layout.maximumWidth: nativeSubtitles.width
bottomMargin: 10 Label {
id: nativeSubs
objectName: "nativeSubs"
anchors.horizontalCenter: parent.horizontalCenter
color: "white"
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
horizontalAlignment: Text.AlignHCenter
opacity: 1
font {
pixelSize: appearance.subtitlesFontSize
family: appearance.fontName
} }
Item { background: Rectangle {
id: subsContainer id: subsBackground
height: childrenRect.height color: getAppearanceValueForTheme(appearance.themeName,
Layout.fillWidth: true "mainBackground")
Layout.fillHeight: true width: subsContainer.childrenRect.width
Layout.rightMargin: 0 height: subsContainer.childrenRect.height
Layout.leftMargin: 0
Layout.maximumWidth: nativeSubtitles.width
Label {
id: nativeSubs
objectName: "nativeSubs"
anchors.horizontalCenter: parent.horizontalCenter
color: "white"
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
horizontalAlignment: Text.AlignHCenter
opacity: 1
font {
pixelSize: appearance.subtitlesFontSize
family: appearance.fontName
}
background: Rectangle {
id: subsBackground
color: getAppearanceValueForTheme(appearance.themeName,
"mainBackground")
width: subsContainer.childrenRect.width
height: subsContainer.childrenRect.height
}
onWidthChanged: {
if (width > parent.width - 10)
width = parent.width - 10
}
onTextChanged: if (width <= parent.width - 10)
width = undefined
Connections {
target: player
onSubtitlesChanged: function (subtitles) {
nativeSubs.text = subtitles
}
}
}
} }
onWidthChanged: {
if (width > parent.width - 10)
width = parent.width - 10
}
onTextChanged: if (width <= parent.width - 10)
width = undefined
Connections {
target: player
onSubtitlesChanged: function (subtitles) {
nativeSubs.text = subtitles
}
}
}
} }
}
} }

View file

@ -1,8 +1,8 @@
import player 1.0 import player 1.0
SmoothButton { SmoothButton {
iconSource: "icons/" + appearance.themeName + "/backward.svg" iconSource: "icons/" + appearance.themeName + "/backward.svg"
onClicked: { onClicked: {
player.playerCommand(Enums.Commands.Seek, "-10") player.playerCommand(Enums.Commands.Seek, "-10")
} }
} }

View file

@ -1,8 +1,8 @@
import player 1.0 import player 1.0
SmoothButton { SmoothButton {
iconSource: "icons/" + appearance.themeName + "/forward.svg" iconSource: "icons/" + appearance.themeName + "/forward.svg"
onClicked: { onClicked: {
player.playerCommand(Enums.Commands.Seek, "10") player.playerCommand(Enums.Commands.Seek, "10")
} }
} }

View file

@ -1,6 +1,6 @@
SmoothButton { SmoothButton {
iconSource: "icons/" + appearance.themeName + "/fullscreen.svg" iconSource: "icons/" + appearance.themeName + "/fullscreen.svg"
onClicked: { onClicked: {
toggleFullscreen() toggleFullscreen()
} }
} }

View file

@ -3,75 +3,73 @@ import QtQuick.Controls 2.3
import QtQuick.Window 2.2 import QtQuick.Window 2.2
Item { Item {
id: menuTitleBar id: menuTitleBar
height: menuBar.height
visible: true
anchors {
left: parent.left
right: parent.right
top: parent.top
}
Connections {
target: globalConnections
onHideUI: function () {
if (!menuBar.anythingOpen()) {
menuTitleBar.visible = false
}
}
onShowUI: {
menuTitleBar.visible = true
}
}
MainMenu {
id: menuBar
}
Rectangle {
height: menuBar.height height: menuBar.height
visible: true color: getAppearanceValueForTheme(appearance.themeName, "mainBackground")
anchors { anchors {
right: parent.right
left: menuBar.right
top: parent.top
}
Text {
id: titleLabel
objectName: "titleLabel"
text: translate.getTranslation("TITLE", i18n.language)
color: "white"
width: parent.width
height: parent.height
fontSizeMode: Text.VerticalFit
opacity: 1
visible: menuTitleBar.visible
&& ((!appearance.titleOnlyOnFullscreen)
|| (mainWindow.visibility == Window.FullScreen
|| mainWindow.visibility == Window.Maximized))
font {
family: appearance.fontName
bold: true
pixelSize: appearance.scaleFactor * (height - anchors.topMargin - anchors.bottomMargin - 2)
}
anchors {
left: parent.left left: parent.left
right: parent.right leftMargin: 4
bottom: parent.bottom
bottomMargin: 4
top: parent.top top: parent.top
} }
Connections { Connections {
target: globalConnections target: player
onHideUI: function () { onTitleChanged: function (title) {
if (!menuBar.anythingOpen()) { titleLabel.text = title
menuTitleBar.visible = false mainWindow.title = "KittehPlayer - " + title
}
}
onShowUI: {
menuTitleBar.visible = true
}
}
MainMenu {
id: menuBar
}
Rectangle {
height: menuBar.height
color: getAppearanceValueForTheme(appearance.themeName,
"mainBackground")
anchors {
right: parent.right
left: menuBar.right
top: parent.top
}
Text {
id: titleLabel
objectName: "titleLabel"
text: translate.getTranslation("TITLE", i18n.language)
color: "white"
width: parent.width
height: parent.height
fontSizeMode: Text.VerticalFit
opacity: 1
visible: menuTitleBar.visible
&& ((!appearance.titleOnlyOnFullscreen)
|| (mainWindow.visibility == Window.FullScreen
|| mainWindow.visibility == Window.Maximized))
font {
family: appearance.fontName
bold: true
pixelSize: appearance.scaleFactor * (height - anchors.topMargin
- anchors.bottomMargin - 2)
}
anchors {
left: parent.left
leftMargin: 4
bottom: parent.bottom
bottomMargin: 4
top: parent.top
}
Connections {
target: player
onTitleChanged: function (title) {
titleLabel.text = title
mainWindow.title = "KittehPlayer - " + title
}
}
} }
}
} }
}
} }

View file

@ -2,15 +2,16 @@ import QtQuick 2.0
import player 1.0 import player 1.0
SmoothButton { SmoothButton {
property var playing: Enums.PlayStatus.Playing property var playing: Enums.PlayStatus.Playing
iconSource: "icons/" + appearance.themeName + (playing == Enums.PlayStatus.Playing ? "/pause.svg" : "/play.svg") iconSource: "icons/" + appearance.themeName
onClicked: { + (playing == Enums.PlayStatus.Playing ? "/pause.svg" : "/play.svg")
player.playerCommand(Enums.Commands.TogglePlayPause) onClicked: {
} player.playerCommand(Enums.Commands.TogglePlayPause)
Connections { }
target: player Connections {
onPlayStatusChanged: function (status) { target: player
playing = status onPlayStatusChanged: function (status) {
} playing = status
} }
}
} }

View file

@ -1,8 +1,8 @@
import player 1.0 import player 1.0
SmoothButton { SmoothButton {
iconSource: "icons/" + appearance.themeName + "/next.svg" iconSource: "icons/" + appearance.themeName + "/next.svg"
onClicked: { onClicked: {
player.playerCommand(Enums.Commands.NextPlaylistItem) player.playerCommand(Enums.Commands.NextPlaylistItem)
} }
} }

View file

@ -2,22 +2,22 @@ import QtQuick 2.0
import player 1.0 import player 1.0
SmoothButton { SmoothButton {
id: playlistPrevButton id: playlistPrevButton
iconSource: "icons/" + appearance.themeName + "/prev.svg" iconSource: "icons/" + appearance.themeName + "/prev.svg"
visible: appearance.themeName == "Youtube" ? false : true visible: appearance.themeName == "Youtube" ? false : true
onClicked: { onClicked: {
player.playerCommand(Enums.Commands.PreviousPlaylistItem) player.playerCommand(Enums.Commands.PreviousPlaylistItem)
} }
Connections { Connections {
target: player target: player
onPlaylistPositionChanged: function (position) { onPlaylistPositionChanged: function (position) {
if (appearance.themeName == "YouTube") { if (appearance.themeName == "YouTube") {
if (position != 0) { if (position != 0) {
visible = true visible = true
} else { } else {
visible = false visible = false
}
}
} }
}
} }
}
} }

View file

@ -1,21 +1,21 @@
import player 1.0 import player 1.0
SmoothButton { SmoothButton {
id: settingsButton id: settingsButton
iconSource: "icons/" + appearance.themeName + "/settings.svg" iconSource: "icons/" + appearance.themeName + "/settings.svg"
onClicked: { onClicked: {
switch(appearance.themeName) { switch (appearance.themeName) {
case "YouTube": case "YouTube":
appearance.themeName = "RoosterTeeth" appearance.themeName = "RoosterTeeth"
break break
case "RoosterTeeth": case "RoosterTeeth":
appearance.themeName = "Niconico" appearance.themeName = "Niconico"
break break
case "Niconico": case "Niconico":
appearance.themeName = "YouTube" appearance.themeName = "YouTube"
break break
default: default:
appearance.themeName = "YouTube" appearance.themeName = "YouTube"
}
} }
}
} }

View file

@ -3,47 +3,48 @@ import QtQuick.Controls 2.3
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
Control { Control {
id: root id: root
hoverEnabled: true
property alias iconSource: buttonImage.source
property alias containsMouse: mouseArea.containsMouse
background: null
focusPolicy: Qt.NoFocus
signal clicked
leftPadding: root.height / (appearance.themeName == "Niconico" ? 2.5 : 12)
rightPadding: root.leftPadding
contentItem: Image {
id: buttonImage
smooth: false
fillMode: Image.PreserveAspectFit
sourceSize.height: Math.floor(
root.parent.height / (appearance.themeName == "Niconico" ? 1.8 : 1.25))
sourceSize.width: Math.floor(
root.parent.height / (appearance.themeName == "Niconico" ? 1.8 : 1.25))
}
ColorOverlay {
id: colorOverlay
anchors.fill: buttonImage
source: buttonImage
color: getAppearanceValueForTheme(appearance.themeName, "buttonColor")
cached: true
Binding on color {
when: root.hovered
value: root.hovered ? getAppearanceValueForTheme(
appearance.themeName,
"buttonHoverColor") : getAppearanceValueForTheme(
appearance.themeName, "buttonColor")
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true hoverEnabled: true
property alias iconSource: buttonImage.source propagateComposedEvents: true
property alias containsMouse: mouseArea.containsMouse onClicked: root.clicked()
}
background: null
focusPolicy: Qt.NoFocus
signal clicked
leftPadding: root.height / (appearance.themeName == "Niconico" ? 2.5 : 12)
rightPadding: root.leftPadding
contentItem: Image {
id: buttonImage
smooth: false
fillMode: Image.PreserveAspectFit
sourceSize.height: Math.floor(root.parent.height / (appearance.themeName == "Niconico" ? 1.8 : 1.25))
sourceSize.width: Math.floor(root.parent.height / (appearance.themeName == "Niconico" ? 1.8 : 1.25))
}
ColorOverlay {
id: colorOverlay
anchors.fill: buttonImage
source: buttonImage
color: getAppearanceValueForTheme(appearance.themeName, "buttonColor")
cached: true
Binding on color {
when: root.hovered
value: root.hovered ? getAppearanceValueForTheme(
appearance.themeName,
"buttonHoverColor") : getAppearanceValueForTheme(
appearance.themeName, "buttonColor")
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
onClicked: root.clicked()
}
} }

View file

@ -3,30 +3,30 @@ import QtQuick.Controls 2.3
import player 1.0 import player 1.0
Text { Text {
id: speedText id: speedText
text: "1x" text: "1x"
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
color: speedStatusMouseArea.containsMouse ? getAppearanceValueForTheme( color: speedStatusMouseArea.containsMouse ? getAppearanceValueForTheme(
appearance.themeName, appearance.themeName,
"buttonHoverColor") : getAppearanceValueForTheme( "buttonHoverColor") : getAppearanceValueForTheme(
appearance.themeName, appearance.themeName,
"buttonColor") "buttonColor")
font { font {
family: appearance.fontName family: appearance.fontName
pixelSize: layout.height / 2.5 pixelSize: layout.height / 2.5
} }
Connections { Connections {
target: player target: player
onSpeedChanged: function (speed) { onSpeedChanged: function (speed) {
text = String(speed) + "x" text = String(speed) + "x"
}
}
MouseArea {
id: speedStatusMouseArea
anchors.fill: parent
height: parent.height
hoverEnabled: true
propagateComposedEvents: true
acceptedButtons: Qt.NoButton
} }
}
MouseArea {
id: speedStatusMouseArea
anchors.fill: parent
height: parent.height
hoverEnabled: true
propagateComposedEvents: true
acceptedButtons: Qt.NoButton
}
} }

View file

@ -2,17 +2,17 @@ import QtQuick 2.0
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
Text { Text {
id: timeLabel id: timeLabel
text: "0:00 / 0:00" text: "0:00 / 0:00"
color: "white" color: "white"
font.family: appearance.fontName font.family: appearance.fontName
font.pixelSize: layout.height / 2.5 font.pixelSize: layout.height / 2.5
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering renderType: Text.NativeRendering
Connections { Connections {
target: player target: player
onDurationStringChanged: function (durationString) { onDurationStringChanged: function (durationString) {
timeLabel.text = durationString timeLabel.text = durationString
}
} }
}
} }

View file

@ -3,70 +3,69 @@ import QtQuick.Controls 2.3
import player 1.0 import player 1.0
Rectangle { Rectangle {
id: volumeSliderArea id: volumeSliderArea
height: visible ? 70 : 0 height: visible ? 70 : 0
color: getAppearanceValueForTheme(appearance.themeName, "mainBackground") color: getAppearanceValueForTheme(appearance.themeName, "mainBackground")
visible: false visible: false
Slider { Slider {
id: volumeSlider id: volumeSlider
anchors.fill: parent anchors.fill: parent
to: 100 to: 100
value: 100 value: 100
orientation: Qt.Vertical orientation: Qt.Vertical
implicitWidth: Math.max( implicitWidth: Math.max(
background ? background.implicitWidth : 0, background ? background.implicitWidth : 0,
(handle ? handle.implicitWidth : 0) + leftPadding + rightPadding) (handle ? handle.implicitWidth : 0) + leftPadding + rightPadding)
implicitHeight: Math.max( implicitHeight: Math.max(background.implicitHeight,
background.implicitHeight, handle.implicitHeight + topPadding + bottomPadding)
handle.implicitHeight + topPadding + bottomPadding)
padding: 6 padding: 6
Connections { Connections {
target: player target: player
onVolumeChanged: function (volume) { onVolumeChanged: function (volume) {
volumeSlider.value = volume volumeSlider.value = volume
} }
}
onMoved: {
player.playerCommand(Enums.Commands.SetVolume,
Math.round(volumeSlider.value).toString())
}
handle: Rectangle {
x: volumeSlider.leftPadding + ((volumeSlider.availableWidth - width) / 2)
y: volumeSlider.topPadding + (volumeSlider.visualPosition
* (volumeSlider.availableHeight - height))
implicitWidth: 10
implicitHeight: 10
radius: width / 2
color: "white"
border.width: 0
}
background: Rectangle {
x: volumeSlider.leftPadding + ((volumeSlider.availableWidth - width) / 2)
y: volumeSlider.topPadding
implicitWidth: 4
implicitHeight: 70
width: implicitWidth
height: volumeSlider.availableHeight
radius: 3
color: getAppearanceValueForTheme(appearance.themeName,
"progressBackgroundColor")
Rectangle {
y: volumeSlider.visualPosition * parent.height
width: 4
height: volumeSlider.position * parent.height
radius: 3
color: getAppearanceValueForTheme(appearance.themeName,
"volumeSliderBackground")
}
}
} }
onMoved: {
player.playerCommand(Enums.Commands.SetVolume,
Math.round(volumeSlider.value).toString())
}
handle: Rectangle {
x: volumeSlider.leftPadding + ((volumeSlider.availableWidth - width) / 2)
y: volumeSlider.topPadding + (volumeSlider.visualPosition
* (volumeSlider.availableHeight - height))
implicitWidth: 10
implicitHeight: 10
radius: width / 2
color: "white"
border.width: 0
}
background: Rectangle {
x: volumeSlider.leftPadding + ((volumeSlider.availableWidth - width) / 2)
y: volumeSlider.topPadding
implicitWidth: 4
implicitHeight: 70
width: implicitWidth
height: volumeSlider.availableHeight
radius: 3
color: getAppearanceValueForTheme(appearance.themeName,
"progressBackgroundColor")
Rectangle {
y: volumeSlider.visualPosition * parent.height
width: 4
height: volumeSlider.position * parent.height
radius: 3
color: getAppearanceValueForTheme(appearance.themeName,
"volumeSliderBackground")
}
}
}
} }

View file

@ -3,200 +3,200 @@ import QtQuick.Controls 2.3
import player 1.0 import player 1.0
Slider { Slider {
id: progressBar id: progressBar
objectName: "progressBar" objectName: "progressBar"
property string currentMediaURL: "" property string currentMediaURL: ""
property bool playing: false property bool playing: false
property bool center: false property bool center: false
to: 1 to: 1
value: 0.0 value: 0.0
Rectangle {
id: timestampBox
visible: false
width: hoverProgressLabel.width
height: hoverProgressLabel.height
z: 100
color: getAppearanceValueForTheme(appearance.themeName, "mainBackground")
Text {
id: hoverProgressLabel
text: "0:00"
color: "white"
font.family: appearance.fontName
font.pixelSize: mainWindow.virtualHeight / 50
horizontalAlignment: Text.AlignHCenter
renderType: Text.NativeRendering
}
}
Connections {
target: player
onPlayStatusChanged: function (status) {
if (status == Enums.PlayStatus.Playing) {
progressBar.playing = true
} else if (status == Enums.PlayStatus.Paused) {
progressBar.playing = false
}
}
onPositionChanged: function (position) {
if (!pressed) {
progressBar.value = position
}
}
onDurationChanged: function (duration) {
progressBar.to = duration
}
onCachedDurationChanged: function (duration) {
cachedLength.duration = duration
}
}
onMoved: {
player.playerCommand(Enums.Commands.SeekAbsolute, value)
}
function getProgressBarHeight(nyan, isMouse) {
var x = fun.nyanCat ? mainWindow.virtualHeight / 64 : mainWindow.virtualHeight / 380
if (appearance.themeName == "Niconico" && !fun.nyanCat) {
return x * 2
} else if (isMouse & !fun.nyanCat) {
return x * 2
} else {
return x
}
}
function getHandleVisibility(themeName, isMouse) {
if (fun.nyanCat) {
return true
}
if (appearance.themeName == "Niconico") {
return isMouse
} else {
return true
}
}
MouseArea {
id: mouseAreaProgressBar
width: progressBar.width
height: parent.height
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
acceptedButtons: Qt.NoButton
z: 100
property string currentTime: ""
onEntered: timestampBox.visible = true
onExited: timestampBox.visible = false
onPositionChanged: {
// code taken from https://github.com/qt/qtquickcontrols2/blob/39892547145ba4e73bebee86352bd384732b5d19/src/quicktemplates2/qquickslider.cpp#L138
var a = ((mouseAreaProgressBar.mouseX - (handleRect.width / 2))
/ (progressBar.availableWidth - handleRect.width)) * progressBar.to
hoverProgressLabel.text = utils.createTimestamp(a)
timestampBox.x = mouseAreaProgressBar.mouseX - (timestampBox.width / 2)
timestampBox.y = progressBackground.y - timestampBox.height * 2
}
}
background: Rectangle {
anchors.bottom: parent.bottom
anchors.bottomMargin: progressBar.center ? (progressBar.height / 2) - (height / 2) : 0
id: progressBackground
z: 30
width: progressBar.availableWidth
height: progressBar.getProgressBarHeight(fun.nyanCat,
mouseAreaProgressBar.containsMouse)
color: getAppearanceValueForTheme(appearance.themeName,
"progressBackgroundColor")
ProgressBar {
id: cachedLength
background: null
contentItem: Item {
Rectangle {
width: cachedLength.visualPosition * parent.width
height: parent.height
color: getAppearanceValueForTheme(appearance.themeName,
"progressCachedColor")
AnimatedImage {
visible: fun.nyanCat
height: parent.height
id: nyancacheimation
smooth: false
anchors.fill: parent
source: "qrc:/icons/nyancache.gif"
fillMode: Image.TileHorizontally
}
}
}
z: 40
to: progressBar.to
property int duration
value: progressBar.value + duration
anchors.fill: parent
}
Item {
anchors.fill: parent
id: chapterMarkers
Connections {
target: player
onChaptersChanged: function (chapters) {
for (var i = 0, len = chapters.length; i < len; i++) {
var component = Qt.createComponent("ChapterMarker.qml")
var marker = component.createObject(chapterMarkers, {
"time": chapters[i]["time"]
})
}
}
}
}
Rectangle { Rectangle {
id: timestampBox id: progressLength
visible: false z: 50
width: hoverProgressLabel.width anchors.left: progressBackground.left
height: hoverProgressLabel.height width: progressBar.visualPosition * parent.width
z: 100 height: parent.height
color: getAppearanceValueForTheme(appearance.themeName, color: getAppearanceValueForTheme(appearance.themeName,
"mainBackground") "progressSliderColor")
Text { Image {
id: hoverProgressLabel visible: fun.nyanCat
text: "0:00" id: rainbow
color: "white"
font.family: appearance.fontName
font.pixelSize: mainWindow.virtualHeight / 50
horizontalAlignment: Text.AlignHCenter
renderType: Text.NativeRendering
}
}
Connections {
target: player
onPlayStatusChanged: function (status) {
if (status == Enums.PlayStatus.Playing) {
progressBar.playing = true
} else if (status == Enums.PlayStatus.Paused) {
progressBar.playing = false
}
}
onPositionChanged: function (position) {
if (!pressed) {
progressBar.value = position
}
}
onDurationChanged: function (duration) {
progressBar.to = duration
}
onCachedDurationChanged: function (duration) {
cachedLength.duration = duration
}
}
onMoved: {
player.playerCommand(Enums.Commands.SeekAbsolute, value)
}
function getProgressBarHeight(nyan, isMouse) {
var x = fun.nyanCat ? mainWindow.virtualHeight / 64 : mainWindow.virtualHeight / 380
if (appearance.themeName == "Niconico" && !fun.nyanCat) {
return x * 2
} else if (isMouse & !fun.nyanCat) {
return x * 2
} else {
return x
}
}
function getHandleVisibility(themeName, isMouse) {
if (fun.nyanCat) {
return true
}
if (appearance.themeName == "Niconico") {
return isMouse
} else {
return true
}
}
MouseArea {
id: mouseAreaProgressBar
width: progressBar.width
height: parent.height
anchors.fill: parent anchors.fill: parent
height: parent.height
hoverEnabled: true width: parent.width
propagateComposedEvents: true source: "qrc:/icons/rainbow.png"
acceptedButtons: Qt.NoButton fillMode: Image.TileHorizontally
z: 100 }
property string currentTime: ""
onEntered: timestampBox.visible = true
onExited: timestampBox.visible = false
onPositionChanged: {
// code taken from https://github.com/qt/qtquickcontrols2/blob/39892547145ba4e73bebee86352bd384732b5d19/src/quicktemplates2/qquickslider.cpp#L138
var a = ((mouseAreaProgressBar.mouseX - (handleRect.width / 2)) / (progressBar.availableWidth - handleRect.width)) * progressBar.to
hoverProgressLabel.text = utils.createTimestamp(a)
timestampBox.x = mouseAreaProgressBar.mouseX - (timestampBox.width / 2)
timestampBox.y = progressBackground.y - timestampBox.height * 2
}
} }
}
background: Rectangle { handle: Rectangle {
anchors.bottom: parent.bottom z: 70
anchors.bottomMargin: progressBar.center ? (progressBar.height / 2) - (height / 2) : 0 id: handleRect
id: progressBackground x: progressBar.visualPosition * (progressBar.availableWidth - width)
z: 30 anchors.verticalCenter: parent.background.verticalCenter
width: progressBar.availableWidth implicitHeight: radius
height: progressBar.getProgressBarHeight( implicitWidth: radius
fun.nyanCat, mouseAreaProgressBar.containsMouse) radius: mainWindow.virtualHeight / 59
color: getAppearanceValueForTheme(appearance.themeName, color: appearance.themeName
"progressBackgroundColor") == "RoosterTeeth" ? "white" : fun.nyanCat ? "transparent" : getAppearanceValueForTheme(
appearance.themeName,
ProgressBar { "progressSliderColor")
id: cachedLength visible: getHandleVisibility(appearance.themeName,
background: null mouseAreaProgressBar.containsMouse)
contentItem: Item { AnimatedImage {
Rectangle { z: 80
width: cachedLength.visualPosition * parent.width visible: fun.nyanCat
height: parent.height paused: progressBar.pressed
color: getAppearanceValueForTheme(appearance.themeName, height: mainWindow.virtualHeight / 28
"progressCachedColor") id: nyanimation
AnimatedImage { smooth: false
visible: fun.nyanCat anchors.centerIn: parent
height: parent.height source: "qrc:/icons/nyancat.gif"
id: nyancacheimation fillMode: Image.PreserveAspectFit
smooth: false
anchors.fill: parent
source: "qrc:/icons/nyancache.gif"
fillMode: Image.TileHorizontally
}
}
}
z: 40
to: progressBar.to
property int duration
value: progressBar.value + duration
anchors.fill: parent
}
Item {
anchors.fill: parent
id: chapterMarkers
Connections {
target: player
onChaptersChanged: function (chapters) {
for (var i = 0, len = chapters.length; i < len; i++) {
var component = Qt.createComponent("ChapterMarker.qml")
var marker = component.createObject(chapterMarkers, {
"time": chapters[i]["time"]
})
}
}
}
}
Rectangle {
id: progressLength
z: 50
anchors.left: progressBackground.left
width: progressBar.visualPosition * parent.width
height: parent.height
color: getAppearanceValueForTheme(appearance.themeName,
"progressSliderColor")
Image {
visible: fun.nyanCat
id: rainbow
anchors.fill: parent
height: parent.height
width: parent.width
source: "qrc:/icons/rainbow.png"
fillMode: Image.TileHorizontally
}
}
}
handle: Rectangle {
z: 70
id: handleRect
x: progressBar.visualPosition * (progressBar.availableWidth - width)
anchors.verticalCenter: parent.background.verticalCenter
implicitHeight: radius
implicitWidth: radius
radius: mainWindow.virtualHeight / 59
color: appearance.themeName
== "RoosterTeeth" ? "white" : fun.nyanCat ? "transparent" : getAppearanceValueForTheme(
appearance.themeName,
"progressSliderColor")
visible: getHandleVisibility(appearance.themeName,
mouseAreaProgressBar.containsMouse)
AnimatedImage {
z: 80
visible: fun.nyanCat
paused: progressBar.pressed
height: mainWindow.virtualHeight / 28
id: nyanimation
smooth: false
anchors.centerIn: parent
source: "qrc:/icons/nyancat.gif"
fillMode: Image.PreserveAspectFit
}
} }
}
} }

View file

@ -2,21 +2,21 @@ import QtQuick 2.0
import player 1.0 import player 1.0
SmoothButton { SmoothButton {
id: volumeButton id: volumeButton
iconSource: "icons/" + appearance.themeName + "/volume-up.svg" iconSource: "icons/" + appearance.themeName + "/volume-up.svg"
onClicked: { onClicked: {
player.playerCommand(Enums.Commands.ToggleMute) player.playerCommand(Enums.Commands.ToggleMute)
} }
Connections { Connections {
target: player target: player
onVolumeStatusChanged: function(status) { onVolumeStatusChanged: function (status) {
if (status == Enums.VolumeStatus.Muted) { if (status == Enums.VolumeStatus.Muted) {
volumeButton.iconSource = "qrc:/icons/" + appearance.themeName + "/volume-mute.svg" volumeButton.iconSource = "qrc:/icons/" + appearance.themeName + "/volume-mute.svg"
} else if (status == Enums.VolumeStatus.Low) { } else if (status == Enums.VolumeStatus.Low) {
volumeButton.iconSource = "qrc:/icons/" + appearance.themeName + "/volume-down.svg" volumeButton.iconSource = "qrc:/icons/" + appearance.themeName + "/volume-down.svg"
} else if (status == Enums.VolumeStatus.Normal) { } else if (status == Enums.VolumeStatus.Normal) {
volumeButton.iconSource = "qrc:/icons/" + appearance.themeName + "/volume-up.svg" volumeButton.iconSource = "qrc:/icons/" + appearance.themeName + "/volume-up.svg"
} }
}
} }
}
} }

View file

@ -3,69 +3,67 @@ import QtQuick.Controls 2.3
import player 1.0 import player 1.0
Slider { Slider {
id: volumeBar id: volumeBar
to: 100 to: 100
value: 100 value: 100
palette.dark: "#f00" palette.dark: "#f00"
hoverEnabled: true hoverEnabled: true
implicitWidth: Math.max( implicitWidth: Math.max(
background ? background.implicitWidth : 0, background ? background.implicitWidth : 0,
(handle ? handle.implicitWidth : 0) + leftPadding + rightPadding) (handle ? handle.implicitWidth : 0) + leftPadding + rightPadding)
implicitHeight: Math.max( implicitHeight: Math.max(
background ? background.implicitHeight : 0, background ? background.implicitHeight : 0,
(handle ? handle.implicitHeight : 0) + topPadding + bottomPadding) (handle ? handle.implicitHeight : 0) + topPadding + bottomPadding)
onMoved: { onMoved: {
player.playerCommand(Enums.Commands.SetVolume, player.playerCommand(Enums.Commands.SetVolume,
Math.round(volumeBar.value).toString()) Math.round(volumeBar.value).toString())
} }
Connections { Connections {
target: player target: player
onVolumeChanged: function (volume) { onVolumeChanged: function (volume) {
volumeBar.value = volume volumeBar.value = volume
}
}
handle: Rectangle {
x: volumeBar.leftPadding + volumeBar.visualPosition * (volumeBar.availableWidth - width)
y: volumeBar.topPadding + volumeBar.availableHeight / 2 - height / 2
implicitWidth: height
implicitHeight: layout.height / 2.6
radius: height
visible: appearance.themeName == "Niconico" ? false : true
color: "#f6f6f6"
border.color: "#f6f6f6"
} }
}
handle: Rectangle {
x: volumeBar.leftPadding + volumeBar.visualPosition * (volumeBar.availableWidth - width)
y: volumeBar.topPadding + volumeBar.availableHeight / 2 - height / 2
implicitWidth: height
implicitHeight: layout.height / 2.6
radius: height
visible: appearance.themeName == "Niconico" ? false : true
color: "#f6f6f6"
border.color: "#f6f6f6"
}
background: Rectangle { background: Rectangle {
x: volumeBar.leftPadding x: volumeBar.leftPadding
y: volumeBar.topPadding + volumeBar.availableHeight / 2 - height / 2 y: volumeBar.topPadding + volumeBar.availableHeight / 2 - height / 2
implicitWidth: layout.width / 11 implicitWidth: layout.width / 11
implicitHeight: appearance.themeName == "Niconico" ? layout.height / 6 : layout.height / 10 implicitHeight: appearance.themeName == "Niconico" ? layout.height / 6 : layout.height / 10
width: volumeBar.availableWidth width: volumeBar.availableWidth
height: implicitHeight height: implicitHeight
color: getAppearanceValueForTheme(appearance.themeName, color: getAppearanceValueForTheme(appearance.themeName,
"progressBackgroundColor") "progressBackgroundColor")
Rectangle { Rectangle {
width: volumeBar.visualPosition * parent.width width: volumeBar.visualPosition * parent.width
height: parent.height height: parent.height
color: getAppearanceValueForTheme(appearance.themeName, color: getAppearanceValueForTheme(appearance.themeName,
"volumeSliderBackground") "volumeSliderBackground")
} }
MouseArea { MouseArea {
acceptedButtons: Qt.NoButton acceptedButtons: Qt.NoButton
z: 10 z: 10
anchors.fill: parent anchors.fill: parent
propagateComposedEvents: true propagateComposedEvents: true
onWheel: { onWheel: {
if (wheel.angleDelta.y<0){ if (wheel.angleDelta.y < 0) {
volumeBar.value -= 5 volumeBar.value -= 5
} } else {
else { volumeBar.value += 5
volumeBar.value += 5
}
} }
}
} }
} }
} }

View file

@ -2,20 +2,19 @@ import QtQuick 2.0
import "translations.js" as Translations import "translations.js" as Translations
Item { Item {
function getTranslation(code, language) {
function getTranslation(code, language) { var lang = Translations.translations[language]
var lang = Translations.translations[language] if (lang == undefined || lang == "undefined") {
if (lang == undefined || lang == "undefined") { return "TranslationNotFound"
return "TranslationNotFound"
}
var text = String(Translations.translations[i18n.language][code])
if (text == "undefined"){
console.warn(code, "missing for language", language)
}
var args = Array.prototype.slice.call(arguments, 1)
var i = 0
return text.replace(/%s/g, function () {
return args[i++]
})
} }
var text = String(Translations.translations[i18n.language][code])
if (text == "undefined") {
console.warn(code, "missing for language", language)
}
var args = Array.prototype.slice.call(arguments, 1)
var i = 0
return text.replace(/%s/g, function () {
return args[i++]
})
}
} }

View file

@ -2,91 +2,91 @@ import QtQuick 2.0
import player 1.0 import player 1.0
Item { Item {
objectName: "buttonLayout" objectName: "buttonLayout"
id: layout id: layout
anchors.fill: controlsBar anchors.fill: controlsBar
height: parent.height
PlaylistPrevButton {
id: playlistPrevButton
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
}
width: visible ? playlistNextButton.width : 0
}
PlayPauseButton {
id: playPauseButton
anchors {
left: playlistPrevButton.right
top: parent.top
bottom: parent.bottom
}
leftPadding: 14
}
PlaylistNextButton {
id: playlistNextButton
anchors {
left: playPauseButton.right
top: parent.top
bottom: parent.bottom
}
}
MouseArea {
id: mouseAreaVolumeArea
anchors.fill: volumeSlider
width: volumeSlider.width
hoverEnabled: true
propagateComposedEvents: true
acceptedButtons: Qt.NoButton
z: 100
}
VolumeButton {
id: volumeButton
anchors {
left: playlistNextButton.right
top: parent.top
bottom: parent.bottom
}
z: 50
}
VolumeSlider {
id: volumeSlider
anchors {
left: volumeButton.right
top: parent.top
bottom: parent.bottom
}
height: parent.height height: parent.height
visible: mouseAreaVolumeArea.containsMouse || volumeButton.hovered
width: visible ? implicitWidth : 0
}
TimeLabel {
anchors {
left: volumeSlider.right
top: parent.top
bottom: parent.bottom
leftMargin: parent.width / 128
}
}
PlaylistPrevButton { SettingsButton {
id: playlistPrevButton id: settingsButton
anchors { anchors {
left: parent.left right: fullscreenButton.left
top: parent.top top: parent.top
bottom: parent.bottom bottom: parent.bottom
}
width: visible ? playlistNextButton.width : 0
} }
PlayPauseButton { }
id: playPauseButton FullscreenButton {
anchors { id: fullscreenButton
left: playlistPrevButton.right anchors {
top: parent.top right: parent.right
bottom: parent.bottom top: parent.top
} bottom: parent.bottom
leftPadding: 14
}
PlaylistNextButton {
id: playlistNextButton
anchors {
left: playPauseButton.right
top: parent.top
bottom: parent.bottom
}
}
MouseArea {
id: mouseAreaVolumeArea
anchors.fill: volumeSlider
width: volumeSlider.width
hoverEnabled: true
propagateComposedEvents: true
acceptedButtons: Qt.NoButton
z: 100
}
VolumeButton {
id: volumeButton
anchors {
left: playlistNextButton.right
top: parent.top
bottom: parent.bottom
}
z: 50
}
VolumeSlider {
id: volumeSlider
anchors {
left: volumeButton.right
top: parent.top
bottom: parent.bottom
}
height: parent.height
visible: mouseAreaVolumeArea.containsMouse || volumeButton.hovered
width: visible ? implicitWidth : 0
}
TimeLabel {
anchors {
left: volumeSlider.right
top: parent.top
bottom: parent.bottom
leftMargin: parent.width / 128
}
}
SettingsButton {
id: settingsButton
anchors {
right: fullscreenButton.left
top: parent.top
bottom: parent.bottom
}
}
FullscreenButton {
id: fullscreenButton
anchors {
right: parent.right
top: parent.top
bottom: parent.bottom
}
} }
}
} }

View file

@ -5,475 +5,471 @@ import Qt.labs.settings 1.0
import player 1.0 import player 1.0
Window { Window {
id: mainWindow id: mainWindow
title: "KittehPlayer" title: "KittehPlayer"
visible: true visible: true
width: Math.min(720, Screen.width) width: Math.min(720, Screen.width)
height: Math.min(480, Screen.height) height: Math.min(480, Screen.height)
property int virtualHeight: Screen.height * appearance.scaleFactor property int virtualHeight: Screen.height * appearance.scaleFactor
property int virtualWidth: Screen.width * appearance.scaleFactor property int virtualWidth: Screen.width * appearance.scaleFactor
property bool onTop: false property bool onTop: false
QMLDebugger { QMLDebugger {
id: qmlDebugger id: qmlDebugger
}
Item {
id: globalConnections
signal showUI
signal hideUI
}
function getAppearanceValueForTheme(themeName, name) {
switch (themeName) {
case "YouTube":
return youTubeAppearance[name]
case "Niconico":
return nicoNicoAppearance[name]
case "RoosterTeeth":
return roosterTeethAppearance[name]
default:
appearance.themeName = "YouTube"
return youTubeAppearance[name]
}
}
Translator {
id: translate
}
Settings {
id: loggingSettings
category: "Logging"
property string logFile: "/tmp/KittehPlayer.log"
property bool logBackend: true
}
Settings {
id: backendSettings
category: "Backend"
property string backend: "mpv"
property bool fbo: true
property bool direct: false
}
Settings {
id: appearance
category: "Appearance"
property bool titleOnlyOnFullscreen: true
property bool useMpvSubs: false
property string themeName: "YouTube"
property string fontName: "Roboto"
property double scaleFactor: 1.0
property int subtitlesFontSize: 24
property int uiFadeTimer: 1000
property bool doubleTapToSeek: true
property double doubleTapToSeekBy: 5
property bool swipeToResize: true
// Can fix some screen tearing on some devices.
property bool maximizeInsteadOfFullscreen: false
}
Settings {
id: youTubeAppearance
category: "YouTubeAppearance"
property string mainBackground: "#9C000000"
property string progressBackgroundColor: "#33FFFFFF"
property string progressCachedColor: "#66FFFFFF"
property string buttonColor: "white"
property string buttonHoverColor: "white"
property string progressSliderColor: "red"
property string chapterMarkerColor: "#fc0"
property string volumeSliderBackground: "white"
}
Settings {
id: nicoNicoAppearance
category: "NicoNicoAppearance"
property string mainBackground: "#9C000000"
property string progressBackgroundColor: "#444"
property string progressCachedColor: "#66FFFFFF"
property string buttonColor: "white"
property string buttonHoverColor: "white"
property string progressSliderColor: "#007cff"
property string chapterMarkerColor: "#fc0"
property string volumeSliderBackground: "#007cff"
}
Settings {
id: roosterTeethAppearance
category: "RoosterTeethAppearance"
property string mainBackground: "#CC2B333F"
property string progressBackgroundColor: "#444"
property string progressCachedColor: "white"
property string buttonColor: "white"
property string buttonHoverColor: "#c9373f"
property string progressSliderColor: "#c9373f"
property string chapterMarkerColor: "#fc0"
property string volumeSliderBackground: "#c9373f"
}
Settings {
id: i18n
category: "I18N"
property string language: "english"
}
Settings {
id: fun
category: "Fun"
property bool nyanCat: false
}
Settings {
id: keybinds
category: "Keybinds"
property string playPause: "K"
property string forward10: "L"
property string rewind10: "J"
property string forward5: "Right"
property string rewind5: "Left"
property string openFile: "Ctrl+O"
property string openURI: "Ctrl+Shift+O"
property string quit: "Ctrl+Q"
property string fullscreen: "F"
property string tracks: "Ctrl+T"
property string statsForNerds: "I"
property string forwardFrame: "."
property string backwardFrame: ","
property string cycleSub: "Alt+S"
property string cycleSubBackwards: "Alt+Shift+S"
property string cycleAudio: "A"
property string cycleVideo: "V"
property string cycleVideoAspect: "Shift+A"
property string screenshot: "S"
property string screenshotWithoutSubtitles: "Shift+S"
property string fullScreenshot: "Ctrl+S"
property string nyanCat: "Ctrl+N"
property string decreaseSpeedByPointOne: "["
property string increaseSpeedByPointOne: "]"
property string halveSpeed: "{"
property string doubleSpeed: "}"
property string increaseVolume: "*"
property string decreaseVolume: "/"
property string mute: "m"
property string increaseScale: "Ctrl+Shift+="
property string resetScale: "Ctrl+Shift+0"
property string decreaseScale: "Ctrl+Shift+-"
property string customKeybind0: ""
property string customKeybind0Command: ""
property string customKeybind1: ""
property string customKeybind1Command: ""
property string customKeybind2: ""
property string customKeybind2Command: ""
property string customKeybind3: ""
property string customKeybind3Command: ""
property string customKeybind4: ""
property string customKeybind4Command: ""
property string customKeybind5: ""
property string customKeybind5Command: ""
property string customKeybind6: ""
property string customKeybind6Command: ""
property string customKeybind7: ""
property string customKeybind7Command: ""
property string customKeybind8: ""
property string customKeybind8Command: ""
property string customKeybind9: ""
property string customKeybind9Command: ""
}
property int lastScreenVisibility
function toggleFullscreen() {
var fs = Window.FullScreen
if (appearance.maximizeInsteadOfFullscreen) {
fs = Window.Maximized
} }
Item { if (mainWindow.visibility != fs) {
id: globalConnections lastScreenVisibility = mainWindow.visibility
signal showUI mainWindow.visibility = fs
signal hideUI } else {
mainWindow.visibility = lastScreenVisibility
}
}
Utils {
id: utils
}
PlayerBackend {
id: player
anchors.fill: parent
width: parent.width
height: parent.height
logging: loggingSettings.logBackend
z: 1
Action {
onTriggered: {
appearance.scaleFactor += 0.1
}
shortcut: keybinds.increaseScale
}
Action {
onTriggered: {
appearance.scaleFactor = 1
}
shortcut: keybinds.resetScale
}
Action {
onTriggered: {
appearance.scaleFactor -= 0.1
}
shortcut: keybinds.decreaseScale
} }
function getAppearanceValueForTheme(themeName, name) { function startPlayer() {
switch(themeName) { //console.info(qmlDebugger.properties(player))
case"YouTube": console.info("OwO!")
return youTubeAppearance[name]
case "Niconico": var args = Qt.application.arguments
return nicoNicoAppearance[name] var len = Qt.application.arguments.length
case "RoosterTeeth": var argNo = 0
return roosterTeethAppearance[name]
default: if (!appearance.useMpvSubs) {
appearance.themeName = "YouTube" player.setOption("sub-ass-override", "force")
return youTubeAppearance[name] player.setOption("sub-ass", "off")
player.setOption("sub-border-size", "0")
player.setOption("sub-color", "0.0/0.0/0.0/0.0")
player.setOption("sub-border-color", "0.0/0.0/0.0/0.0")
player.setOption("sub-back-color", "0.0/0.0/0.0/0.0")
}
if (len > 1) {
for (argNo = 1; argNo < len; argNo++) {
var argument = args[argNo]
if (argument.indexOf("KittehPlayer") !== -1) {
continue
}
if (argument.startsWith("--")) {
argument = argument.substr(2)
if (argument.length > 0) {
var splitArg = argument.split(/=(.+)/)
if (splitArg[0] == "screen" || splitArg[0] == "fs-screen") {
for (var i = 0, len = Qt.application.screens.length; i < len; i++) {
var screen = Qt.application.screens[i]
console.log(
"Screen Name: " + screen["name"] + " Screen Number: " + String(
i))
if (screen["name"] == splitArg[1] || String(
i) == splitArg[1]) {
console.log("Switching to screen: " + screen["name"])
mainWindow.screen = screen
mainWindow.width = mainWindow.screen.width / 2
mainWindow.height = mainWindow.screen.height / 2
mainWindow.x = mainWindow.screen.virtualX + mainWindow.width / 2
mainWindow.y = mainWindow.screen.virtualY + mainWindow.height / 2
if (splitArg[0] == "fs-screen") {
toggleFullscreen()
}
continue
}
}
continue
}
if (splitArg[0] == "fullscreen") {
toggleFullscreen()
continue
}
if (splitArg[1] == undefined || splitArg[1].length == 0) {
splitArg[1] = "yes"
}
player.setOption(splitArg[0], splitArg[1])
}
} else {
player.playerCommand(Enums.Commands.AppendFile, argument)
}
} }
}
}
}
Item {
id: controlsOverlay
anchors.centerIn: player
height: player.height
width: player.width
property bool controlsShowing: true
z: 2
Connections {
target: globalConnections
onHideUI: function () {
mouseAreaPlayer.cursorShape = Qt.BlankCursor
}
onShowUI: {
mouseAreaPlayer.cursorShape = Qt.ArrowCursor
}
} }
Translator { MouseArea {
id: translate id: mouseAreaBar
width: parent.width
height: controlsBar.combinedHeight * 1.5
hoverEnabled: true
anchors {
bottom: parent.bottom
bottomMargin: 0
}
onEntered: {
mouseAreaPlayerTimer.stop()
}
} }
Settings { MouseArea {
id: loggingSettings id: mouseAreaPlayer
category: "Logging" z: 10
property string logFile: "/tmp/KittehPlayer.log" focus: true
property bool logBackend: true width: parent.width
} hoverEnabled: true
propagateComposedEvents: true
property real velocity: 0.0
property int xStart: 0
property int xPrev: 0
anchors {
bottom: mouseAreaBar.top
bottomMargin: 10
right: parent.right
rightMargin: 0
left: parent.left
leftMargin: 0
top: topBar.bottom
topMargin: 0
}
Settings { Timer {
id: backendSettings id: mouseTapTimer
category: "Backend" interval: 200
property string backend: "mpv" onTriggered: {
property bool fbo: true if (topBar.visible) {
property bool direct: false globalConnections.hideUI()
} } else {
globalConnections.showUI()
Settings { }
id: appearance mouseAreaPlayerTimer.restart()
category: "Appearance"
property bool titleOnlyOnFullscreen: true
property bool useMpvSubs: false
property string themeName: "YouTube"
property string fontName: "Roboto"
property double scaleFactor: 1.0
property int subtitlesFontSize: 24
property int uiFadeTimer: 1000
property bool doubleTapToSeek: true
property double doubleTapToSeekBy: 5
property bool swipeToResize: true
// Can fix some screen tearing on some devices.
property bool maximizeInsteadOfFullscreen: false
}
Settings {
id: youTubeAppearance
category: "YouTubeAppearance"
property string mainBackground: "#9C000000"
property string progressBackgroundColor: "#33FFFFFF"
property string progressCachedColor: "#66FFFFFF"
property string buttonColor: "white"
property string buttonHoverColor: "white"
property string progressSliderColor: "red"
property string chapterMarkerColor: "#fc0"
property string volumeSliderBackground: "white"
}
Settings {
id: nicoNicoAppearance
category: "NicoNicoAppearance"
property string mainBackground: "#9C000000"
property string progressBackgroundColor: "#444"
property string progressCachedColor: "#66FFFFFF"
property string buttonColor: "white"
property string buttonHoverColor: "white"
property string progressSliderColor: "#007cff"
property string chapterMarkerColor: "#fc0"
property string volumeSliderBackground: "#007cff"
}
Settings {
id: roosterTeethAppearance
category: "RoosterTeethAppearance"
property string mainBackground: "#CC2B333F"
property string progressBackgroundColor: "#444"
property string progressCachedColor: "white"
property string buttonColor: "white"
property string buttonHoverColor: "#c9373f"
property string progressSliderColor: "#c9373f"
property string chapterMarkerColor: "#fc0"
property string volumeSliderBackground: "#c9373f"
}
Settings {
id: i18n
category: "I18N"
property string language: "english"
}
Settings {
id: fun
category: "Fun"
property bool nyanCat: false
}
Settings {
id: keybinds
category: "Keybinds"
property string playPause: "K"
property string forward10: "L"
property string rewind10: "J"
property string forward5: "Right"
property string rewind5: "Left"
property string openFile: "Ctrl+O"
property string openURI: "Ctrl+Shift+O"
property string quit: "Ctrl+Q"
property string fullscreen: "F"
property string tracks: "Ctrl+T"
property string statsForNerds: "I"
property string forwardFrame: "."
property string backwardFrame: ","
property string cycleSub: "Alt+S"
property string cycleSubBackwards: "Alt+Shift+S"
property string cycleAudio: "A"
property string cycleVideo: "V"
property string cycleVideoAspect: "Shift+A"
property string screenshot: "S"
property string screenshotWithoutSubtitles: "Shift+S"
property string fullScreenshot: "Ctrl+S"
property string nyanCat: "Ctrl+N"
property string decreaseSpeedByPointOne: "["
property string increaseSpeedByPointOne: "]"
property string halveSpeed: "{"
property string doubleSpeed: "}"
property string increaseVolume: "*"
property string decreaseVolume: "/"
property string mute: "m"
property string increaseScale: "Ctrl+Shift+="
property string resetScale: "Ctrl+Shift+0"
property string decreaseScale: "Ctrl+Shift+-"
property string customKeybind0: ""
property string customKeybind0Command: ""
property string customKeybind1: ""
property string customKeybind1Command: ""
property string customKeybind2: ""
property string customKeybind2Command: ""
property string customKeybind3: ""
property string customKeybind3Command: ""
property string customKeybind4: ""
property string customKeybind4Command: ""
property string customKeybind5: ""
property string customKeybind5Command: ""
property string customKeybind6: ""
property string customKeybind6Command: ""
property string customKeybind7: ""
property string customKeybind7Command: ""
property string customKeybind8: ""
property string customKeybind8Command: ""
property string customKeybind9: ""
property string customKeybind9Command: ""
}
property int lastScreenVisibility
function toggleFullscreen() {
var fs = Window.FullScreen
if (appearance.maximizeInsteadOfFullscreen) {
fs = Window.Maximized
} }
}
if (mainWindow.visibility != fs) { function doubleMouseClick(mouse) {
lastScreenVisibility = mainWindow.visibility if (appearance.doubleTapToSeek) {
mainWindow.visibility = fs if (mouse.x > (mouseAreaPlayer.width / 2)) {
player.playerCommand(Enums.Commands.Seek,
String(appearance.doubleTapToSeekBy))
} else {
player.playerCommand(Enums.Commands.Seek,
"-" + String(appearance.doubleTapToSeekBy))
}
} else { } else {
mainWindow.visibility = lastScreenVisibility toggleFullscreen()
} }
}
onClicked: function (mouse) {
xStart = mouse.x
xPrev = mouse.x
velocity = 0
if (mouseTapTimer.running) {
doubleMouseClick(mouse)
mouseTapTimer.stop()
} else {
mouseTapTimer.restart()
}
}
onReleased: {
var isLongEnough = Math.sqrt(xStart * xStart,
mouse.x * mouse.x) > mainWindow.width * 0.3
if (velocity > 2 && isLongEnough) {
appearance.scaleFactor += 0.2
} else if (velocity < -2 && isLongEnough) {
if (appearance.scaleFactor > 0.8) {
appearance.scaleFactor -= 0.2
}
}
}
onPositionChanged: {
if (mouseAreaPlayer.containsPress) {
var currVel = (mouse.x - xPrev)
velocity = (velocity + currVel) / 2.0
xPrev = mouse.x
}
if (!topBar.visible) {
globalConnections.showUI()
mouseAreaPlayerTimer.restart()
}
}
Action {
onTriggered: {
toggleFullscreen()
}
shortcut: "Esc"
}
Timer {
id: mouseAreaPlayerTimer
interval: appearance.uiFadeTimer
running: (!appearance.uiFadeTimer == 0)
repeat: false
onTriggered: {
if (!(appearance.uiFadeTimer == 0)) {
globalConnections.hideUI()
}
}
}
} }
Utils { Timer {
id: utils id: statsUpdater
interval: 1000
running: statsForNerdsText.visible
repeat: true
onTriggered: {
statsForNerdsText.text = player.getStats()
}
} }
PlayerBackend { Text {
id: player id: statsForNerdsText
anchors.fill: parent text: ""
width: parent.width color: "white"
height: parent.height visible: false
logging: loggingSettings.logBackend height: parent.height
z: 1 width: parent.width
textFormat: Text.RichText
Action { horizontalAlignment: Text.AlignLeft
onTriggered: { verticalAlignment: Text.AlignTop
appearance.scaleFactor += 0.1 renderType: Text.NativeRendering
} lineHeight: 1
shortcut: keybinds.increaseScale font {
} family: appearance.fontName
Action { pixelSize: mainWindow.virtualHeight / 50
onTriggered: { }
appearance.scaleFactor = 1 anchors {
} fill: parent
shortcut: keybinds.resetScale topMargin: mainWindow.virtualHeight / 20
} leftMargin: mainWindow.virtualHeight / 20
Action { }
onTriggered: { Component.onCompleted: {
appearance.scaleFactor -= 0.1 console.error(statsForNerdsText.lineHeight, font.pixelSize)
} }
shortcut: keybinds.decreaseScale
}
function startPlayer() {
//console.info(qmlDebugger.properties(player))
console.info("OwO!")
var args = Qt.application.arguments
var len = Qt.application.arguments.length
var argNo = 0
if (!appearance.useMpvSubs) {
player.setOption("sub-ass-override", "force")
player.setOption("sub-ass", "off")
player.setOption("sub-border-size", "0")
player.setOption("sub-color", "0.0/0.0/0.0/0.0")
player.setOption("sub-border-color", "0.0/0.0/0.0/0.0")
player.setOption("sub-back-color", "0.0/0.0/0.0/0.0")
}
if (len > 1) {
for (argNo = 1; argNo < len; argNo++) {
var argument = args[argNo]
if (argument.indexOf("KittehPlayer") !== -1) {
continue
}
if (argument.startsWith("--")) {
argument = argument.substr(2)
if (argument.length > 0) {
var splitArg = argument.split(/=(.+)/)
if (splitArg[0] == "screen"
|| splitArg[0] == "fs-screen") {
for (var i = 0, len = Qt.application.screens.length; i < len; i++) {
var screen = Qt.application.screens[i]
console.log("Screen Name: " + screen["name"]
+ " Screen Number: " + String(
i))
if (screen["name"] == splitArg[1] || String(
i) == splitArg[1]) {
console.log("Switching to screen: " + screen["name"])
mainWindow.screen = screen
mainWindow.width = mainWindow.screen.width / 2
mainWindow.height = mainWindow.screen.height / 2
mainWindow.x = mainWindow.screen.virtualX
+ mainWindow.width / 2
mainWindow.y = mainWindow.screen.virtualY
+ mainWindow.height / 2
if (splitArg[0] == "fs-screen") {
toggleFullscreen()
}
continue
}
}
continue
}
if (splitArg[0] == "fullscreen") {
toggleFullscreen()
continue
}
if (splitArg[1] == undefined
|| splitArg[1].length == 0) {
splitArg[1] = "yes"
}
player.setOption(splitArg[0], splitArg[1])
}
} else {
player.playerCommand(Enums.Commands.AppendFile,
argument)
}
}
}
}
} }
Item { MenuTitleBar {
id: controlsOverlay id: topBar
anchors.centerIn: player z: 200
height: player.height
width: player.width
property bool controlsShowing: true
z: 2
Connections {
target: globalConnections
onHideUI: function () {
mouseAreaPlayer.cursorShape = Qt.BlankCursor
}
onShowUI: {
mouseAreaPlayer.cursorShape = Qt.ArrowCursor
}
}
MouseArea {
id: mouseAreaBar
width: parent.width
height: controlsBar.combinedHeight * 1.5
hoverEnabled: true
anchors {
bottom: parent.bottom
bottomMargin: 0
}
onEntered: {
mouseAreaPlayerTimer.stop()
}
}
MouseArea {
id: mouseAreaPlayer
z: 10
focus: true
width: parent.width
hoverEnabled: true
propagateComposedEvents: true
property real velocity: 0.0
property int xStart: 0
property int xPrev: 0
anchors {
bottom: mouseAreaBar.top
bottomMargin: 10
right: parent.right
rightMargin: 0
left: parent.left
leftMargin: 0
top: topBar.bottom
topMargin: 0
}
Timer {
id: mouseTapTimer
interval: 200
onTriggered: {
if (topBar.visible) {
globalConnections.hideUI()
} else {
globalConnections.showUI()
}
mouseAreaPlayerTimer.restart()
}
}
function doubleMouseClick(mouse) {
if (appearance.doubleTapToSeek) {
if (mouse.x > (mouseAreaPlayer.width / 2)) {
player.playerCommand(Enums.Commands.Seek, String(
appearance.doubleTapToSeekBy))
} else {
player.playerCommand(Enums.Commands.Seek, "-" + String(
appearance.doubleTapToSeekBy))
}
} else {
toggleFullscreen()
}
}
onClicked: function (mouse) {
xStart = mouse.x
xPrev = mouse.x
velocity = 0
if (mouseTapTimer.running) {
doubleMouseClick(mouse)
mouseTapTimer.stop()
} else {
mouseTapTimer.restart()
}
}
onReleased: {
var isLongEnough = Math.sqrt(xStart*xStart, mouse.x*mouse.x) > mainWindow.width * 0.3
if (velocity > 2 && isLongEnough) {
appearance.scaleFactor += 0.2
} else if (velocity < -2 && isLongEnough) {
if (appearance.scaleFactor > 0.8) {
appearance.scaleFactor -= 0.2
}
}
}
onPositionChanged: {
if (mouseAreaPlayer.containsPress) {
var currVel = (mouse.x - xPrev)
velocity = (velocity + currVel) / 2.0
xPrev = mouse.x
}
if (!topBar.visible) {
globalConnections.showUI()
mouseAreaPlayerTimer.restart()
}
}
Action {
onTriggered: {
toggleFullscreen()
}
shortcut: "Esc"
}
Timer {
id: mouseAreaPlayerTimer
interval: appearance.uiFadeTimer
running: (! appearance.uiFadeTimer == 0)
repeat: false
onTriggered: {
if (! (appearance.uiFadeTimer == 0) ) {
globalConnections.hideUI()
}
}
}
}
Timer {
id: statsUpdater
interval: 1000
running: statsForNerdsText.visible
repeat: true
onTriggered: {
statsForNerdsText.text = player.getStats()
}
}
Text {
id: statsForNerdsText
text: ""
color: "white"
visible: false
height: parent.height
width: parent.width
textFormat: Text.RichText
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignTop
renderType: Text.NativeRendering
lineHeight: 1
font {
family: appearance.fontName
pixelSize: mainWindow.virtualHeight / 50
}
anchors {
fill: parent
topMargin: mainWindow.virtualHeight / 20
leftMargin: mainWindow.virtualHeight / 20
}
Component.onCompleted: {
console.error(statsForNerdsText.lineHeight, font.pixelSize)
}
}
MenuTitleBar {
id: topBar
z: 200
}
ControlsBar {
id: controlsBar
}
} }
ControlsBar {
id: controlsBar
}
}
} }

View file

@ -7,28 +7,28 @@
QString QString
QMLDebugger::properties(QQuickItem* item, bool linebreak) QMLDebugger::properties(QQuickItem* item, bool linebreak)
{ {
const QMetaObject* meta = item->metaObject(); const QMetaObject* meta = item->metaObject();
QHash<QString, QVariant> list; QHash<QString, QVariant> list;
for (int i = 0; i < meta->propertyCount(); i++) { for (int i = 0; i < meta->propertyCount(); i++) {
QMetaProperty property = meta->property(i); QMetaProperty property = meta->property(i);
const char* name = property.name(); const char* name = property.name();
QVariant value = item->property(name); QVariant value = item->property(name);
list[name] = value; list[name] = value;
}
QString out;
QHashIterator<QString, QVariant> i(list);
while (i.hasNext()) {
i.next();
if (!out.isEmpty()) {
out += ", ";
if (linebreak)
out += "\n";
} }
out.append(i.key());
out.append(": "); QString out;
out.append(i.value().toString()); QHashIterator<QString, QVariant> i(list);
} while (i.hasNext()) {
return out; i.next();
if (!out.isEmpty()) {
out += ", ";
if (linebreak)
out += "\n";
}
out.append(i.key());
out.append(": ");
out.append(i.value().toString());
}
return out;
} }

View file

@ -2,15 +2,14 @@
#define QMLDEBUGGER_H #define QMLDEBUGGER_H
#include <QQuickItem> #include <QQuickItem>
#include <QVariant>
#include <QString> #include <QString>
#include <QVariant>
class QMLDebugger : public QObject class QMLDebugger : public QObject {
{ Q_OBJECT
Q_OBJECT
public: public:
Q_INVOKABLE static QString properties(QQuickItem* item, Q_INVOKABLE static QString properties(QQuickItem* item,
bool linebreak = true); bool linebreak = true);
}; };
#endif // QMLDEBUGGER_H #endif // QMLDEBUGGER_H

View file

@ -5,205 +5,216 @@
#include <cstring> #include <cstring>
#include <QVariant>
#include <QString>
#include <QList>
#include <QHash> #include <QHash>
#include <QSharedPointer> #include <QList>
#include <QMetaType> #include <QMetaType>
#include <QSharedPointer>
#include <QString>
#include <QVariant>
namespace mpv { namespace mpv {
namespace qt { namespace qt {
// Wrapper around mpv_handle. Does refcounting under the hood. // Wrapper around mpv_handle. Does refcounting under the hood.
class Handle class Handle {
{ struct container {
struct container { container(mpv_handle* h)
container(mpv_handle *h) : mpv(h) {} : mpv(h)
~container() { mpv_terminate_destroy(mpv); } {
mpv_handle *mpv; }
}; ~container() { mpv_terminate_destroy(mpv); }
QSharedPointer<container> sptr; mpv_handle* mpv;
public: };
// Construct a new Handle from a raw mpv_handle with refcount 1. If the QSharedPointer<container> sptr;
// 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. public:
operator mpv_handle*() const { return sptr ? (*sptr).mpv : 0; } // 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().
static inline QVariant node_to_variant(const mpv_node *node) // Never destroy the mpv_handle manually when using this wrapper. You
{ // will create dangling pointers. Just let the wrapper take care of
switch (node->format) { // destroying the mpv_handle.
case MPV_FORMAT_STRING: // Never create multiple wrappers from the same raw mpv_handle; copy the
return QVariant(QString::fromUtf8(node->u.string)); // wrapper instead (that's what it's for).
case MPV_FORMAT_FLAG: static Handle FromRawHandle(mpv_handle* handle)
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; Handle h;
dst->u.int64 = src.toLongLong(); h.sptr = QSharedPointer<container>(new container(handle));
} else if (test_type(src, QMetaType::Double)) { return h;
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;
}
};
/** // 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. * RAII wrapper that calls mpv_free_node_contents() on the pointer.
*/ */
struct node_autofree { struct node_autofree {
mpv_node *ptr; mpv_node* ptr;
node_autofree(mpv_node *a_ptr) : ptr(a_ptr) {} node_autofree(mpv_node* a_ptr)
~node_autofree() { mpv_free_node_contents(ptr); } : ptr(a_ptr)
}; {
}
~node_autofree() { mpv_free_node_contents(ptr); }
};
/** /**
* Return the given property as mpv_node converted to QVariant, or QVariant() * Return the given property as mpv_node converted to QVariant, or QVariant()
* on error. * on error.
* *
@ -211,140 +222,144 @@ struct node_autofree {
* *
* @param name the property name * @param name the property name
*/ */
static inline QVariant get_property_variant(mpv_handle *ctx, const QString &name) static inline QVariant get_property_variant(mpv_handle* ctx, const QString& name)
{ {
mpv_node node; mpv_node node;
if (mpv_get_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, &node) < 0) if (mpv_get_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, &node) < 0)
return QVariant(); return QVariant();
node_autofree f(&node); node_autofree f(&node);
return node_to_variant(&node); return node_to_variant(&node);
} }
/** /**
* Set the given property as mpv_node converted from the QVariant argument. * Set the given property as mpv_node converted from the QVariant argument.
* @deprecated use set_property() instead * @deprecated use set_property() instead
*/ */
static inline int set_property_variant(mpv_handle *ctx, const QString &name, static inline int set_property_variant(mpv_handle* ctx, const QString& name,
const QVariant &v) const QVariant& v)
{ {
node_builder node(v); node_builder node(v);
return mpv_set_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node()); return mpv_set_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node());
} }
/** /**
* Set the given option as mpv_node converted from the QVariant argument. * Set the given option as mpv_node converted from the QVariant argument.
* *
* @deprecated use set_property() instead * @deprecated use set_property() instead
*/ */
static inline int set_option_variant(mpv_handle *ctx, const QString &name, static inline int set_option_variant(mpv_handle* ctx, const QString& name,
const QVariant &v) const QVariant& v)
{ {
node_builder node(v); node_builder node(v);
return mpv_set_option(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node()); return mpv_set_option(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node());
} }
/** /**
* mpv_command_node() equivalent. Returns QVariant() on error (and * mpv_command_node() equivalent. Returns QVariant() on error (and
* unfortunately, the same on success). * unfortunately, the same on success).
* *
* @deprecated use command() instead * @deprecated use command() instead
*/ */
static inline QVariant command_variant(mpv_handle *ctx, const QVariant &args) static inline QVariant command_variant(mpv_handle* ctx, const QVariant& args)
{ {
node_builder node(args); node_builder node(args);
mpv_node res; mpv_node res;
if (mpv_command_node(ctx, node.node(), &res) < 0) if (mpv_command_node(ctx, node.node(), &res) < 0)
return QVariant(); return QVariant();
node_autofree f(&res); node_autofree f(&res);
return node_to_variant(&res); return node_to_variant(&res);
} }
/** /**
* This is used to return error codes wrapped in QVariant for functions which * This is used to return error codes wrapped in QVariant for functions which
* return QVariant. * return QVariant.
* *
* You can use get_error() or is_error() to extract the error status from a * You can use get_error() or is_error() to extract the error status from a
* QVariant value. * QVariant value.
*/ */
struct ErrorReturn struct ErrorReturn {
{ /**
/**
* enum mpv_error value (or a value outside of it if ABI was extended) * enum mpv_error value (or a value outside of it if ABI was extended)
*/ */
int error; int error;
ErrorReturn() : error(0) {} ErrorReturn()
explicit ErrorReturn(int err) : error(err) {} : error(0)
}; {
}
explicit ErrorReturn(int err)
: error(err)
{
}
};
/** /**
* Return the mpv error code packed into a QVariant, or 0 (success) if it's not * Return the mpv error code packed into a QVariant, or 0 (success) if it's not
* an error value. * an error value.
* *
* @return error code (<0) or success (>=0) * @return error code (<0) or success (>=0)
*/ */
static inline int get_error(const QVariant &v) static inline int get_error(const QVariant& v)
{ {
if (!v.canConvert<ErrorReturn>()) if (!v.canConvert<ErrorReturn>())
return 0; return 0;
return v.value<ErrorReturn>().error; return v.value<ErrorReturn>().error;
} }
/** /**
* Return whether the QVariant carries a mpv error code. * Return whether the QVariant carries a mpv error code.
*/ */
static inline bool is_error(const QVariant &v) static inline bool is_error(const QVariant& v)
{ {
return get_error(v) < 0; return get_error(v) < 0;
} }
/** /**
* Return the given property as mpv_node converted to QVariant, or QVariant() * Return the given property as mpv_node converted to QVariant, or QVariant()
* on error. * on error.
* *
* @param name the property name * @param name the property name
* @return the property value, or an ErrorReturn with the error code * @return the property value, or an ErrorReturn with the error code
*/ */
static inline QVariant get_property(mpv_handle *ctx, const QString &name) static inline QVariant get_property(mpv_handle* ctx, const QString& name)
{ {
mpv_node node; mpv_node node;
int err = mpv_get_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, &node); int err = mpv_get_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, &node);
if (err < 0) if (err < 0)
return QVariant::fromValue(ErrorReturn(err)); return QVariant::fromValue(ErrorReturn(err));
node_autofree f(&node); node_autofree f(&node);
return node_to_variant(&node); return node_to_variant(&node);
} }
/** /**
* Set the given property as mpv_node converted from the QVariant argument. * Set the given property as mpv_node converted from the QVariant argument.
* *
* @return mpv error code (<0 on error, >= 0 on success) * @return mpv error code (<0 on error, >= 0 on success)
*/ */
static inline int set_property(mpv_handle *ctx, const QString &name, static inline int set_property(mpv_handle* ctx, const QString& name,
const QVariant &v) const QVariant& v)
{ {
node_builder node(v); node_builder node(v);
return mpv_set_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node()); return mpv_set_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node());
} }
/** /**
* mpv_command_node() equivalent. * mpv_command_node() equivalent.
* *
* @param args command arguments, with args[0] being the command name as string * @param args command arguments, with args[0] being the command name as string
* @return the property value, or an ErrorReturn with the error code * @return the property value, or an ErrorReturn with the error code
*/ */
static inline QVariant command(mpv_handle *ctx, const QVariant &args) static inline QVariant command(mpv_handle* ctx, const QVariant& args)
{ {
node_builder node(args); node_builder node(args);
mpv_node res; mpv_node res;
int err = mpv_command_node(ctx, node.node(), &res); int err = mpv_command_node(ctx, node.node(), &res);
if (err < 0) if (err < 0)
return QVariant::fromValue(ErrorReturn(err)); return QVariant::fromValue(ErrorReturn(err));
node_autofree f(&res); node_autofree f(&res);
return node_to_variant(&res); return node_to_variant(&res);
} }
} }
} }

View file

@ -9,30 +9,30 @@
#include "utils.hpp" #include "utils.hpp"
#include <QSettings> #include <QSettings>
void registerTypes() { void registerTypes()
QSettings settings; {
QSettings settings;
qmlRegisterUncreatableMetaObject(
Enums::staticMetaObject, "player", 1, 0, "Enums", "Error: only enums");
qRegisterMetaType<Enums::PlayStatus>("Enums.PlayStatus");
qRegisterMetaType<Enums::VolumeStatus>("Enums.VolumeStatus");
qRegisterMetaType<Enums::Backends>("Enums.Backends");
qRegisterMetaType<Enums::Commands>("Enums.Commands");
qmlRegisterType<Process>("player", 1, 0, "Process");
qmlRegisterUncreatableMetaObject( qmlRegisterType<QMLDebugger>("player", 1, 0, "QMLDebugger");
Enums::staticMetaObject, "player", 1, 0, "Enums", "Error: only enums"); qmlRegisterType<ThumbnailCache>("player", 1, 0, "ThumbnailCache");
qRegisterMetaType<Enums::PlayStatus>("Enums.PlayStatus");
qRegisterMetaType<Enums::VolumeStatus>("Enums.VolumeStatus");
qRegisterMetaType<Enums::Backends>("Enums.Backends");
qRegisterMetaType<Enums::Commands>("Enums.Commands");
qmlRegisterType<Process>("player", 1, 0, "Process");
qmlRegisterType<QMLDebugger>("player", 1, 0, "QMLDebugger"); qmlRegisterType<UtilsClass>("player", 1, 0, "Utils");
qmlRegisterType<ThumbnailCache>("player", 1, 0, "ThumbnailCache");
qmlRegisterType<UtilsClass>("player", 1, 0, "Utils");
#ifndef DISABLE_MPV_RENDER_API #ifndef DISABLE_MPV_RENDER_API
if (settings.value("Backend/fbo", true).toBool()) { if (settings.value("Backend/fbo", true).toBool()) {
qmlRegisterType<MPVBackend>("player", 1, 0, "PlayerBackend"); qmlRegisterType<MPVBackend>("player", 1, 0, "PlayerBackend");
} else { } else {
qmlRegisterType<MPVNoFBOBackend>("player", 1, 0, "PlayerBackend"); qmlRegisterType<MPVNoFBOBackend>("player", 1, 0, "PlayerBackend");
} }
#else #else
qmlRegisterType<MPVNoFBOBackend>("player", 1, 0, "PlayerBackend"); qmlRegisterType<MPVNoFBOBackend>("player", 1, 0, "PlayerBackend");
#endif #endif
} }

View file

@ -36,53 +36,52 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
int int setenv(const char* var, const char* value, int overwrite)
setenv(const char* var, const char* value, int overwrite)
{ {
/* Core implementation for both setenv() and unsetenv() functions; /* Core implementation for both setenv() and unsetenv() functions;
* at the outset, assume that the requested operation may fail. * at the outset, assume that the requested operation may fail.
*/ */
int retval = -1; int retval = -1;
/* The specified "var" name MUST be non-NULL, not a zero-length /* The specified "var" name MUST be non-NULL, not a zero-length
* string, and must not include any '=' character. * string, and must not include any '=' character.
*/ */
if (var && *var && (strchr(var, '=') == NULL)) { if (var && *var && (strchr(var, '=') == NULL)) {
/* A properly named variable may be added to, removed from, /* A properly named variable may be added to, removed from,
* or modified within the environment, ONLY if "overwrite" * or modified within the environment, ONLY if "overwrite"
* mode is enabled, OR if the named variable does not yet * mode is enabled, OR if the named variable does not yet
* exist... * exist...
*/ */
if (overwrite || getenv(var) == NULL) { if (overwrite || getenv(var) == NULL) {
/* ... in which cases, we convert the specified name and /* ... in which cases, we convert the specified name and
* value into the appropriate form for use with putenv(), * value into the appropriate form for use with putenv(),
* (noting that we accept a NULL "value" as equivalent to * (noting that we accept a NULL "value" as equivalent to
* a zero-length string, which renders putenv() as the * a zero-length string, which renders putenv() as the
* equivalent of unsetenv()). * equivalent of unsetenv()).
*/ */
const char* fmt = "%s=%s"; const char* fmt = "%s=%s";
const char* val = value ? value : ""; const char* val = value ? value : "";
char buf[1 + snprintf(NULL, 0, fmt, var, val)]; char buf[1 + snprintf(NULL, 0, fmt, var, val)];
snprintf(buf, sizeof(buf), fmt, var, val); snprintf(buf, sizeof(buf), fmt, var, val);
if ((retval = putenv(buf)) != 0) if ((retval = putenv(buf)) != 0)
/* /*
* If putenv() returns non-zero, indicating failure, the * If putenv() returns non-zero, indicating failure, the
* most probable explanation is that there wasn't enough * most probable explanation is that there wasn't enough
* free memory; ensure that errno is set accordingly. * free memory; ensure that errno is set accordingly.
*/ */
errno = ENOMEM; errno = ENOMEM;
} else } else
/* The named variable already exists, and overwrite mode /* The named variable already exists, and overwrite mode
* was not enabled; there is nothing to be done. * was not enabled; there is nothing to be done.
*/ */
retval = 0; retval = 0;
} else } else
/* The specified environment variable name was invalid. /* The specified environment variable name was invalid.
*/ */
errno = EINVAL; errno = EINVAL;
/* Succeed or fail, "retval" has now been set to indicate the /* Succeed or fail, "retval" has now been set to indicate the
* appropriate status for return. * appropriate status for return.
*/ */
return retval; return retval;
} }

View file

@ -1,19 +1,19 @@
#include "utils.hpp" #include "utils.hpp"
#include "logger.h"
#include "spdlog/logger.h"
#include <QApplication> #include <QApplication>
#include <QCoreApplication> #include <QCoreApplication>
#include <QGuiApplication> #include <QGuiApplication>
#include <QProcess> #include <QProcess>
#include <QStringList> #include <QStringList>
#include <memory> #include <memory>
#include "logger.h"
#include "spdlog/logger.h"
#if (defined(__linux__) || defined(__FreeBSD__)) && ENABLE_X11 #if (defined(__linux__) || defined(__FreeBSD__)) && ENABLE_X11
#include <QX11Info> // IWYU pragma: keep
#include <X11/X.h> // IWYU pragma: keep #include <X11/X.h> // IWYU pragma: keep
#include <X11/Xlib.h> // IWYU pragma: keep #include <X11/Xlib.h> // IWYU pragma: keep
#include <X11/Xutil.h> // IWYU pragma: keep #include <X11/Xutil.h> // IWYU pragma: keep
#include <qx11info_x11.h> // IWYU pragma: keep #include <qx11info_x11.h> // IWYU pragma: keep
#include <QX11Info> // IWYU pragma: keep
#undef Bool #undef Bool
#endif #endif
@ -23,130 +23,121 @@ namespace Utils {
QString QString
getPlatformName() getPlatformName()
{ {
QGuiApplication* qapp = QGuiApplication* qapp = qobject_cast<QGuiApplication*>(QCoreApplication::instance());
qobject_cast<QGuiApplication*>(QCoreApplication::instance()); return qapp->platformName();
return qapp->platformName();
} }
void void launchAboutQt()
launchAboutQt()
{ {
QApplication* qapp = QApplication* qapp = qobject_cast<QApplication*>(QCoreApplication::instance());
qobject_cast<QApplication*>(QCoreApplication::instance()); qapp->aboutQt();
qapp->aboutQt();
} }
void updateAppImage() void updateAppImage()
{ {
QString program = QString program = QProcessEnvironment::systemEnvironment().value("APPDIR", "") + "/usr/bin/appimageupdatetool";
QProcessEnvironment::systemEnvironment().value("APPDIR", "") + QProcess updater;
"/usr/bin/appimageupdatetool"; updater.setProcessChannelMode(QProcess::ForwardedChannels);
QProcess updater; updater.start(program,
updater.setProcessChannelMode(QProcess::ForwardedChannels); QStringList() << QProcessEnvironment::systemEnvironment().value(
updater.start(program, "APPIMAGE", ""));
QStringList() << QProcessEnvironment::systemEnvironment().value( updater.waitForFinished();
"APPIMAGE", "")); qApp->exit();
updater.waitForFinished();
qApp->exit();
} }
// https://www.youtube.com/watch?v=nXaxk27zwlk&feature=youtu.be&t=56m34s // https://www.youtube.com/watch?v=nXaxk27zwlk&feature=youtu.be&t=56m34s
inline int inline int
fast_mod(const int input, const int ceil) fast_mod(const int input, const int ceil)
{ {
return input >= ceil ? input % ceil : input; return input >= ceil ? input % ceil : input;
} }
QString QString
createTimestamp(const int seconds) createTimestamp(const int seconds)
{ {
const int s = fast_mod(seconds, 60); const int s = fast_mod(seconds, 60);
const int m = fast_mod(seconds, 3600) / 60; const int m = fast_mod(seconds, 3600) / 60;
const int h = fast_mod(seconds, 86400) / 3600; const int h = fast_mod(seconds, 86400) / 3600;
if (h > 0) { if (h > 0) {
return QString::asprintf("%02d:%02d:%02d", h, m, s); return QString::asprintf("%02d:%02d:%02d", h, m, s);
} else { } else {
return QString::asprintf("%02d:%02d", m, s); return QString::asprintf("%02d:%02d", m, s);
} }
} }
void void SetScreensaver(WId wid, bool on)
SetScreensaver(WId wid, bool on)
{ {
QProcess xdgScreensaver; QProcess xdgScreensaver;
xdgScreensaver.setProcessChannelMode(QProcess::ForwardedChannels); xdgScreensaver.setProcessChannelMode(QProcess::ForwardedChannels);
if (on) { if (on) {
utilsLogger->info("Enabling screensaver."); utilsLogger->info("Enabling screensaver.");
xdgScreensaver.start("xdg-screensaver", xdgScreensaver.start("xdg-screensaver",
QStringList() << "resume" << QString::number(wid)); QStringList() << "resume" << QString::number(wid));
} else { } else {
utilsLogger->info("Disabling screensaver."); utilsLogger->info("Disabling screensaver.");
xdgScreensaver.start("xdg-screensaver", xdgScreensaver.start("xdg-screensaver",
QStringList() << "suspend" << QString::number(wid)); QStringList() << "suspend" << QString::number(wid));
} }
xdgScreensaver.waitForFinished(); xdgScreensaver.waitForFinished();
} }
void void SetDPMS(bool on)
SetDPMS(bool on)
{ {
#if defined(__linux__) || defined(__FreeBSD__) #if defined(__linux__) || defined(__FreeBSD__)
if (getPlatformName() != "xcb") { if (getPlatformName() != "xcb") {
return; return;
} }
QProcess xsetProcess; QProcess xsetProcess;
xsetProcess.setProcessChannelMode(QProcess::ForwardedChannels); xsetProcess.setProcessChannelMode(QProcess::ForwardedChannels);
if (on) { if (on) {
utilsLogger->info("Enabled DPMS."); utilsLogger->info("Enabled DPMS.");
xsetProcess.start("xset", xsetProcess.start("xset",
QStringList() << "s" QStringList() << "s"
<< "on" << "on"
<< "+dpms"); << "+dpms");
} else { } else {
utilsLogger->info("Disabled DPMS."); utilsLogger->info("Disabled DPMS.");
xsetProcess.start("xset", xsetProcess.start("xset",
QStringList() << "s" QStringList() << "s"
<< "off" << "off"
<< "-dpms"); << "-dpms");
} }
xsetProcess.waitForFinished(); xsetProcess.waitForFinished();
#else #else
utilsLogger->error("Can't set DPMS for platform: {}", utilsLogger->error("Can't set DPMS for platform: {}",
getPlatformName().toUtf8().constData()); getPlatformName().toUtf8().constData());
#endif #endif
} }
void void AlwaysOnTop(WId wid, bool on)
AlwaysOnTop(WId wid, bool on)
{ {
#if (defined(__linux__) || defined(__FreeBSD__)) && ENABLE_X11 #if (defined(__linux__) || defined(__FreeBSD__)) && ENABLE_X11
Display* display = QX11Info::display(); Display* display = QX11Info::display();
XEvent event; XEvent event;
event.xclient.type = ClientMessage; event.xclient.type = ClientMessage;
event.xclient.serial = 0; event.xclient.serial = 0;
event.xclient.send_event = true; event.xclient.send_event = true;
event.xclient.display = display; event.xclient.display = display;
event.xclient.window = wid; event.xclient.window = wid;
event.xclient.message_type = XInternAtom(display, "_NET_WM_STATE", False); event.xclient.message_type = XInternAtom(display, "_NET_WM_STATE", False);
event.xclient.format = 32; event.xclient.format = 32;
event.xclient.data.l[0] = on; event.xclient.data.l[0] = on;
event.xclient.data.l[1] = XInternAtom(display, "_NET_WM_STATE_ABOVE", False); event.xclient.data.l[1] = XInternAtom(display, "_NET_WM_STATE_ABOVE", False);
event.xclient.data.l[2] = 0; event.xclient.data.l[2] = 0;
event.xclient.data.l[3] = 0; event.xclient.data.l[3] = 0;
event.xclient.data.l[4] = 0; event.xclient.data.l[4] = 0;
XSendEvent(display, XSendEvent(display,
DefaultRootWindow(display), DefaultRootWindow(display),
False, False,
SubstructureRedirectMask | SubstructureNotifyMask, SubstructureRedirectMask | SubstructureNotifyMask,
&event); &event);
#else #else
utilsLogger->error("Can't set on top for platform: {}", utilsLogger->error("Can't set on top for platform: {}",
getPlatformName().toUtf8().constData()); getPlatformName().toUtf8().constData());
#endif #endif
} }
} }

View file

@ -6,34 +6,27 @@
namespace Utils { namespace Utils {
Q_NAMESPACE Q_NAMESPACE
void void SetDPMS(bool on);
SetDPMS(bool on); void SetScreensaver(WId wid, bool on);
void void AlwaysOnTop(WId wid, bool on);
SetScreensaver(WId wid, bool on);
void
AlwaysOnTop(WId wid, bool on);
QString QString
createTimestamp(int seconds); createTimestamp(int seconds);
void void launchAboutQt();
launchAboutQt(); void updateAppImage();
void
updateAppImage();
} }
class UtilsClass : public QObject class UtilsClass : public QObject {
{ Q_OBJECT
Q_OBJECT
public slots: public slots:
void SetDPMS(bool on) { Utils::SetDPMS(on); }; void SetDPMS(bool on) { Utils::SetDPMS(on); };
void AlwaysOnTop(WId wid, bool on) { Utils::AlwaysOnTop(wid, on); }; void AlwaysOnTop(WId wid, bool on) { Utils::AlwaysOnTop(wid, on); };
void launchAboutQt() { Utils::launchAboutQt(); }; void launchAboutQt() { Utils::launchAboutQt(); };
void updateAppImage() { Utils::updateAppImage(); }; void updateAppImage() { Utils::updateAppImage(); };
QString createTimestamp(int seconds)
QString createTimestamp(int seconds) {
{ return Utils::createTimestamp(seconds);
return Utils::createTimestamp(seconds); };
};
}; };
#endif #endif