Reformat code.
This commit is contained in:
parent
e1dd828968
commit
9cf2aafd73
5
.clang-format
Normal file
5
.clang-format
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
Language: Cpp
|
||||||
|
BasedOnStyle: WebKit
|
||||||
|
...
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
@ -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");
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
13
src/logger.h
13
src/logger.h
|
@ -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
|
||||||
|
|
131
src/main.cpp
131
src/main.cpp
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1062
src/qml/MainMenu.qml
1062
src/qml/MainMenu.qml
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
SmoothButton {
|
SmoothButton {
|
||||||
iconSource: "icons/" + appearance.themeName + "/fullscreen.svg"
|
iconSource: "icons/" + appearance.themeName + "/fullscreen.svg"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
toggleFullscreen()
|
toggleFullscreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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++]
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
894
src/qml/main.qml
894
src/qml/main.qml
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
549
src/qthelper.hpp
549
src/qthelper.hpp
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
173
src/utils.cpp
173
src/utils.cpp
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue