diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ef9604 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ae53055 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,35 @@ +language: cpp +compiler: gcc +sudo: require +dist: xenial + +before_install: + - sudo add-apt-repository ppa:beineri/opt-qt-5.11.1-xenial -y + - sudo add-apt-repository ppa:mc3man/xerus-media -y + - sudo apt-get update -qq + +install: + - sudo apt-get -y install qt511-meta libmpv + - source /opt/qt*/bin/qt*-env.sh + +script: + - qmake CONFIG+=release PREFIX=/usr + - make -j$(nproc) + - make INSTALL_ROOT=appdir -j$(nproc) install ; find appdir/ + - wget -c -nv "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage" + - chmod a+x linuxdeployqt-continuous-x86_64.AppImage + - unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH + - export VERSION=$(git rev-parse --short HEAD) # linuxdeployqt uses this for naming the file + - ./linuxdeployqt-continuous-x86_64.AppImage appdir/usr/share/applications/*.desktop -qmldir=./qml/ -bundle-non-qt-libs + - ./linuxdeployqt-continuous-x86_64.AppImage appdir/usr/share/applications/*.desktop -qmldir=./qml/ -appimage + +after_success: + - find appdir -executable -type f -exec ldd {} \; | grep " => /usr" | cut -d " " -f 2-3 | sort | uniq + - curl --upload-file APPNAME*.AppImage https://transfer.sh/APPNAME-git.$(git rev-parse --short HEAD)-x86_64.AppImage + - wget -c https://github.com/probonopd/uploadtool/raw/master/upload.sh + - bash upload.sh APPNAME*.AppImage* + +branches: + except: + - # Do not build tags that we create when we upload to GitHub Releases + - /^(?i:continuous)/ diff --git a/KittehPlayer.desktop b/KittehPlayer.desktop new file mode 100644 index 0000000..37a90e8 --- /dev/null +++ b/KittehPlayer.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Application +Name=KittehPlayer +Comment=NamedKitten's Video Player +Icon=KittehPlayer +Exec=KittehPlayer %U +Terminal=false +Categories=AudioVideo;Audio;Video;Player;TV; +MimeType=application/ogg;application/x-ogg;application/mxf;application/sdp;application/smil;application/x-smil;application/streamingmedia;application/x-streamingmedia;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;audio/aac;audio/x-aac;audio/vnd.dolby.heaac.1;audio/vnd.dolby.heaac.2;audio/aiff;audio/x-aiff;audio/m4a;audio/x-m4a;application/x-extension-m4a;audio/mp1;audio/x-mp1;audio/mp2;audio/x-mp2;audio/mp3;audio/x-mp3;audio/mpeg;audio/mpeg2;audio/mpeg3;audio/mpegurl;audio/x-mpegurl;audio/mpg;audio/x-mpg;audio/rn-mpeg;audio/musepack;audio/x-musepack;audio/ogg;audio/scpls;audio/x-scpls;audio/vnd.rn-realaudio;audio/wav;audio/x-pn-wav;audio/x-pn-windows-pcm;audio/x-realaudio;audio/x-pn-realaudio;audio/x-ms-wma;audio/x-pls;audio/x-wav;video/mpeg;video/x-mpeg2;video/x-mpeg3;video/mp4v-es;video/x-m4v;video/mp4;application/x-extension-mp4;video/divx;video/vnd.divx;video/msvideo;video/x-msvideo;video/ogg;video/quicktime;video/vnd.rn-realvideo;video/x-ms-afs;video/x-ms-asf;audio/x-ms-asf;application/vnd.ms-asf;video/x-ms-wmv;video/x-ms-wmx;video/x-ms-wvxvideo;video/x-avi;video/avi;video/x-flic;video/fli;video/x-flc;video/flv;video/x-flv;video/x-theora;video/x-theora+ogg;video/x-matroska;video/mkv;audio/x-matroska;application/x-matroska;video/webm;audio/webm;audio/vorbis;audio/x-vorbis;audio/x-vorbis+ogg;video/x-ogm;video/x-ogm+ogg;application/x-ogm;application/x-ogm-audio;application/x-ogm-video;application/x-shorten;audio/x-shorten;audio/x-ape;audio/x-wavpack;audio/x-tta;audio/AMR;audio/ac3;audio/eac3;audio/amr-wb;video/mp2t;audio/flac;audio/mp4;application/x-mpegurl;video/vnd.mpegurl;application/vnd.apple.mpegurl;audio/x-pn-au;video/3gp;video/3gpp;video/3gpp2;audio/3gpp;audio/3gpp2;video/dv;audio/dv;audio/opus;audio/vnd.dts;audio/vnd.dts.hd;audio/x-adpcm;application/x-cue;audio/m3u; +X-KDE-Protocols=ftp,http,https,mms,rtmp,rtsp,sftp,smb,ytdl diff --git a/KittehPlayer.png b/KittehPlayer.png new file mode 100644 index 0000000..4916975 Binary files /dev/null and b/KittehPlayer.png differ diff --git a/KittehPlayer.pro b/KittehPlayer.pro new file mode 100644 index 0000000..8488775 --- /dev/null +++ b/KittehPlayer.pro @@ -0,0 +1,35 @@ +TARGET = KittehPlayer + +TEMPLATE = app +QT += qml quickcontrols2 widgets + +SOURCES += src/main.cpp src/mpvobject.cpp + +QT_CONFIG -= no-pkg-config +CONFIG += link_pkgconfig release +PKGCONFIG += mpv + +RESOURCES += src/qml/qml.qrc + +unix { + isEmpty(PREFIX) { + PREFIX = /usr + } + + target.path = $$PREFIX/bin + + desktop.files = KittehPlayer.desktop + desktop.path = $$PREFIX/share/applications/ + icon.files += KittehPlayer.png + icon.path = $$PREFIX/share/icons/hicolor/256x256/apps/ + + INSTALLS += desktop + INSTALLS += icon +} + +INSTALLS += target + +HEADERS += src/main.h src/mpvobject.h src/config.h + + +DISTFILES += KittehPlayer.desktop KittehPlayer.png README.md LICENSE.txt \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..fa0086a --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. \ No newline at end of file diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..acdec48 --- /dev/null +++ b/src/config.h @@ -0,0 +1,14 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include + +#if MPV_CLIENT_API_VERSION >= MPV_MAKE_VERSION(1, 28) +#define USE_RENDER +#else +#warning "Using deprecated MPV..." +#endif + + + +#endif // CONFIG_H diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..f5b8d05 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,38 @@ +#include +#include + +#include "config.h" +#include "mpvobject.h" + + +#include + +#include +#include + +#include +#include +#include +#include +#include + + +int main( int argc, char *argv[] ) +{ +setenv("QT_QUICK_CONTROLS_STYLE","Desktop",1); + QApplication app(argc, argv); + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + qmlRegisterType("player", 1, 0, "MpvObject"); + + + std::setlocale(LC_NUMERIC, "C"); + +/*QQuickView *view = new QQuickView(); +view->setResizeMode(QQuickView::SizeRootObjectToView); +view->setSource(QUrl("qrc:///player/main.qml")); +view->show();*/ + + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:///player/main.qml"))); + return app.exec(); +} diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..a9870b6 --- /dev/null +++ b/src/main.h @@ -0,0 +1,6 @@ +#ifndef MAIN_H +#define MAIN_H + + + +#endif // MAIN_H diff --git a/src/mpvobject.cpp b/src/mpvobject.cpp new file mode 100644 index 0000000..3f3bab4 --- /dev/null +++ b/src/mpvobject.cpp @@ -0,0 +1,352 @@ + +#include +#include + +#include "mpvobject.h" +#include "config.h" + + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ + +void wakeup(void *ctx) +{ + QMetaObject::invokeMethod((MpvObject*)ctx, "on_mpv_events", Qt::QueuedConnection); +} + +#ifdef USE_RENDER +void on_mpv_redraw(void *ctx) +{ + MpvObject::on_update(ctx); +} + +static void *get_proc_address_mpv(void *ctx, const char *name) +{ + Q_UNUSED(ctx) + + QOpenGLContext *glctx = QOpenGLContext::currentContext(); + if (!glctx) return nullptr; + + return reinterpret_cast(glctx->getProcAddress(QByteArray(name))); +} +#endif + + +} + +class MpvRenderer : public QQuickFramebufferObject::Renderer +{ +#ifdef USE_RENDER + MpvObject *obj; +#else + static void *get_proc_address(void *ctx, const char *name) { + (void)ctx; + QOpenGLContext *glctx = QOpenGLContext::currentContext(); + if (!glctx) + return NULL; + return (void *)glctx->getProcAddress(QByteArray(name)); + } + mpv::qt::Handle mpv; + QQuickWindow *window; + mpv_opengl_cb_context *mpv_gl; +#endif + +public: + +#ifdef USE_RENDER + + MpvRenderer(MpvObject *new_obj) + : obj{new_obj} + { + } + + virtual ~MpvRenderer() {} + + // This function is called when a new FBO is needed. + // This happens on the initial frame. + QOpenGLFramebufferObject * createFramebufferObject(const QSize &size) + { + // init mpv_gl: + if (!obj->mpv_gl) + { + mpv_opengl_init_params gl_init_params{get_proc_address_mpv, nullptr, nullptr}; + mpv_render_param params[]{ + {MPV_RENDER_PARAM_API_TYPE, const_cast(MPV_RENDER_API_TYPE_OPENGL)}, + {MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params}, + {MPV_RENDER_PARAM_INVALID, nullptr} + }; + + if (mpv_render_context_create(&obj->mpv_gl, obj->mpv, params) < 0) + throw std::runtime_error("failed to initialize mpv GL context"); + mpv_render_context_set_update_callback(obj->mpv_gl, on_mpv_redraw, obj); + } + + return QQuickFramebufferObject::Renderer::createFramebufferObject(size); + } + +#else + + MpvRenderer(const MpvObject *obj) + : mpv(obj->mpv), window(obj->window()), mpv_gl(obj->mpv_gl) + { + int r = mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address, NULL); + if (r < 0) + throw std::runtime_error("could not initialize OpenGL"); + } + + virtual ~MpvRenderer() { + mpv_opengl_cb_uninit_gl(mpv_gl); + } + +#endif + + + + void render() + { + +#ifdef USE_RENDER + obj->window()->resetOpenGLState(); + + QOpenGLFramebufferObject *fbo = framebufferObject(); + mpv_opengl_fbo mpfbo{.fbo = static_cast(fbo->handle()), .w = fbo->width(), .h = fbo->height(), .internal_format = 0}; + int flip_y{0}; + + mpv_render_param params[] = { + // Specify the default framebuffer (0) as target. This will + // render onto the entire screen. If you want to show the video + // in a smaller rectangle or apply fancy transformations, you'll + // need to render into a separate FBO and draw it manually. + {MPV_RENDER_PARAM_OPENGL_FBO, &mpfbo}, + // Flip rendering (needed due to flipped GL coordinate system). + {MPV_RENDER_PARAM_FLIP_Y, &flip_y}, + {MPV_RENDER_PARAM_INVALID, nullptr} + }; + // See render_gl.h on what OpenGL environment mpv expects, and + // other API details. + mpv_render_context_render(obj->mpv_gl, params); + + obj->window()->resetOpenGLState(); +#else + QOpenGLFramebufferObject *fbo = framebufferObject(); + window->resetOpenGLState(); + mpv_opengl_cb_draw(mpv_gl, fbo->handle(), fbo->width(), fbo->height()); + window->resetOpenGLState(); +#endif + } +}; + +MpvObject::MpvObject(QQuickItem * parent) +#ifdef USE_RENDER + : QQuickFramebufferObject(parent), mpv{mpv_create()}, mpv_gl(nullptr) +#else + : QQuickFramebufferObject(parent), mpv_gl(0) +#endif +{ + + +#ifndef USE_RENDER + mpv = mpv::qt::Handle::FromRawHandle(mpv_create()); +#endif + + + if (!mpv) + throw std::runtime_error("could not create mpv context"); + + mpv_set_option_string(mpv, "terminal", "yes"); + mpv_set_option_string(mpv, "msg-level", "all=v"); + + if (mpv_initialize(mpv) < 0) + throw std::runtime_error("could not initialize mpv context"); + +#ifndef USE_RENDER + mpv::qt::set_option_variant(mpv, "vo", "opengl-cb"); +#endif + // Fix? + mpv::qt::set_option_variant(mpv, "ytdl", "yes"); + + mpv_set_option_string(mpv, "input-default-bindings", "yes"); + mpv_set_option_string(mpv, "input-vo-keyboard", "yes"); + + + + mpv::qt::set_option_variant(mpv, "hwdec", "off"); + mpv::qt::set_option_variant(mpv, "slang", "en"); + mpv::qt::set_option_variant(mpv, "sub-font", "Noto Sans"); + mpv::qt::set_option_variant(mpv, "sub-ass-override", "force"); + mpv::qt::set_option_variant(mpv, "sub-ass", "off"); + mpv::qt::set_option_variant(mpv, "sub-border-size", "0"); + mpv::qt::set_option_variant(mpv, "sub-bold", "off"); + mpv::qt::set_option_variant(mpv, "sub-scale-by-window", "on"); + mpv::qt::set_option_variant(mpv, "sub-scale-with-window", "on"); + + mpv::qt::set_option_variant(mpv, "sub-back-color", "#C0080808"); + + + + 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, "track-list", MPV_FORMAT_NODE); + + mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE); + mpv_observe_property(mpv, 0, "media-title", MPV_FORMAT_STRING); + + mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE); + + mpv_set_wakeup_callback(mpv, wakeup, this); + +#ifndef USE_RENDER +mpv_gl = (mpv_opengl_cb_context *)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB); + if (!mpv_gl) + throw std::runtime_error("OpenGL not compiled in"); + mpv_opengl_cb_set_update_callback(mpv_gl, MpvObject::on_update, (void *)this); +#endif + + connect(this, &MpvObject::onUpdate, this, &MpvObject::doUpdate, + Qt::QueuedConnection); +} + +MpvObject::~MpvObject() +{ +#ifdef USE_RENDER + if (mpv_gl) + { + mpv_render_context_free(mpv_gl); + } + + mpv_terminate_destroy(mpv); +#else + if (mpv_gl) + mpv_opengl_cb_set_update_callback(mpv_gl, NULL, NULL); +#endif +} + +void MpvObject::on_update(void *ctx) +{ + MpvObject *self = (MpvObject *)ctx; + emit self->onUpdate(); +} + +// connected to onUpdate(); signal makes sure it runs on the GUI thread +void MpvObject::doUpdate() +{ + update(); +} + + +QVariant MpvObject::getThumbnailFile(const QString &name) const +{ + QProcess process; + process.start("youtube-dl --get-thumbnail " + name); + process.waitForFinished(-1); + return process.readAllStandardOutput(); + +} + + + + + +QVariant MpvObject::getProperty(const QString &name) const +{ + return mpv::qt::get_property_variant(mpv, name); +} + + +void MpvObject::command(const QVariant& params) +{ + mpv::qt::command_variant(mpv, params); +} + +void MpvObject::setProperty(const QString& name, const QVariant& value) +{ + mpv::qt::set_property_variant(mpv, name, value); +} + +void MpvObject::setOption(const QString& name, const QVariant& value) +{ + mpv::qt::set_option_variant(mpv, name, value); +} + +void MpvObject::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); + } + +} + +void MpvObject::handle_mpv_event(mpv_event *event) +{ + switch (event->event_id) { + case MPV_EVENT_PROPERTY_CHANGE: { + mpv_event_property *prop = (mpv_event_property *)event->data; + if (strcmp(prop->name, "time-pos") == 0) { + if (prop->format == MPV_FORMAT_DOUBLE) { + double time = *(double *)prop->data; + QMetaObject::invokeMethod(this,"setProgressBarValue",Q_ARG(QVariant,time)); + } + } else if (strcmp(prop->name, "duration") == 0) { + if (prop->format == MPV_FORMAT_DOUBLE) { + double time = *(double *)prop->data;Q_ARG(QVariant,"txt1"), + QMetaObject::invokeMethod(this,"setProgressBarEnd",Q_ARG(QVariant,time)); + } + } else if (strcmp(prop->name, "volume") == 0) { + if (prop->format == MPV_FORMAT_DOUBLE) { + QMetaObject::invokeMethod(this,"updateVolume"); + } + } else if (strcmp(prop->name, "media-title") == 0) { + if (prop->format == MPV_FORMAT_STRING) { + QMetaObject::invokeMethod(this,"setTitle"); + } + } else if (strcmp(prop->name, "chapter-list") == 0 || strcmp(prop->name, "track-list") == 0) { + if (prop->format == MPV_FORMAT_NODE) { + /* + QVariant v = mpv::qt::node_to_variant((mpv_node *)prop->data); + // Abuse JSON support for easily printing the mpv_node contents. + QJsonDocument d = QJsonDocument::fromVariant(v); + printf("Change property %s:\n", QString(prop->name).toStdString().c_str()); + printf(d.toJson().data());*/ + //QMetaObject::invokeMethod(this,"updatePlaylist"); + } + } + break; + } + case MPV_EVENT_SHUTDOWN: { + exit(0); + } + default: ; + // Ignore uninteresting or unknown events. + } +} + +QQuickFramebufferObject::Renderer *MpvObject::createRenderer() const +{ + window()->setPersistentOpenGLContext(true); + window()->setPersistentSceneGraph(true); +#ifdef USE_RENDER + return new MpvRenderer(const_cast(this)); +#else + return new MpvRenderer(this); +#endif +} diff --git a/src/mpvobject.h b/src/mpvobject.h new file mode 100644 index 0000000..3b37be4 --- /dev/null +++ b/src/mpvobject.h @@ -0,0 +1,78 @@ +#ifndef MPVOBJECT_H +#define MPVOBJECT_H + +#include + +#include "config.h" + +#include + +#ifdef USE_RENDER +#include +#else +#include +#endif + +#include + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +class MpvRenderer; + +class MpvObject : public QQuickFramebufferObject +{ + Q_OBJECT +#ifdef USE_RENDER + mpv_handle *mpv; + mpv_render_context *mpv_gl; +#else + mpv::qt::Handle mpv; + mpv_opengl_cb_context *mpv_gl; +#endif + + friend class MpvRenderer; + +public: + static void on_update(void *ctx); + + MpvObject(QQuickItem * parent = 0); + virtual ~MpvObject(); + virtual Renderer *createRenderer() const; + + +public slots: + void command(const QVariant& params); + void setProperty(const QString& name, const QVariant& value); + void setOption(const QString& name, const QVariant& value); + QVariant getProperty(const QString& name) const; + QVariant getThumbnailFile(const QString& name) const; + + +signals: + void onUpdate(); + void positionChanged(int value); + void mpv_events(); + +private slots: + void doUpdate(); + void on_mpv_events(); + +private: + void handle_mpv_event(mpv_event *event); + + +}; + + + +#endif \ No newline at end of file diff --git a/src/qml/codes.js b/src/qml/codes.js new file mode 100644 index 0000000..5d4b2f7 --- /dev/null +++ b/src/qml/codes.js @@ -0,0 +1,13 @@ +function localeCodeToEnglish(loc) { + if (typeof loc !== 'string') throw new TypeError('Input must be string'); + var parts = loc.split('-'), + ISO639_1 = {"ab":"Abkhazian","aa":"Afar","af":"Afrikaans","ak":"Akan","sq":"Albanian","am":"Amharic","ar":"Arabic","an":"Aragonese","hy":"Armenian","as":"Assamese","av":"Avaric","ae":"Avestan","ay":"Aymara","az":"Azerbaijani","bm":"Bambara","ba":"Bashkir","eu":"Basque","be":"Belarusian","bn":"Bengali","bh":"Bihari languages","bi":"Bislama","nb":"Norwegian Bokmål","bs":"Bosnian","br":"Breton","bg":"Bulgarian","my":"Burmese","es":"Spanish","ca":"Valencian","km":"Central Khmer","ch":"Chamorro","ce":"Chechen","ny":"Nyanja","zh":"Chinese","za":"Zhuang","cu":"Old Slavonic","cv":"Chuvash","kw":"Cornish","co":"Corsican","cr":"Cree","hr":"Croatian","cs":"Czech","da":"Danish","dv":"Maldivian","nl":"Flemish","dz":"Dzongkha","en":"English","eo":"Esperanto","et":"Estonian","ee":"Ewe","fo":"Faroese","fj":"Fijian","fi":"Finnish","fr":"French","ff":"Fulah","gd":"Scottish Gaelic","gl":"Galician","lg":"Ganda","ka":"Georgian","de":"German","ki":"Kikuyu","el":"Greek, Modern (1453-)","kl":"Kalaallisut","gn":"Guarani","gu":"Gujarati","ht":"Haitian Creole","ha":"Hausa","he":"Hebrew","hz":"Herero","hi":"Hindi","ho":"Hiri Motu","hu":"Hungarian","is":"Icelandic","io":"Ido","ig":"Igbo","id":"Indonesian","ia":"Interlingua (International Auxiliary Language Association)","ie":"Occidental","iu":"Inuktitut","ik":"Inupiaq","ga":"Irish","it":"Italian","ja":"Japanese","jv":"Javanese","kn":"Kannada","kr":"Kanuri","ks":"Kashmiri","kk":"Kazakh","rw":"Kinyarwanda","ky":"Kyrgyz","kv":"Komi","kg":"Kongo","ko":"Korean","kj":"Kwanyama","ku":"Kurdish","lo":"Lao","la":"Latin","lv":"Latvian","lb":"Luxembourgish","li":"Limburgish","ln":"Lingala","lt":"Lithuanian","lu":"Luba-Katanga","mk":"Macedonian","mg":"Malagasy","ms":"Malay","ml":"Malayalam","mt":"Maltese","gv":"Manx","mi":"Maori","mr":"Marathi","mh":"Marshallese","ro":"Romanian","mn":"Mongolian","na":"Nauru","nv":"Navajo","nd":"North Ndebele","nr":"South Ndebele","ng":"Ndonga","ne":"Nepali","se":"Northern Sami","no":"Norwegian","nn":"Nynorsk, Norwegian","ii":"Sichuan Yi","oc":"Occitan (post 1500)","oj":"Ojibwa","or":"Oriya","om":"Oromo","os":"Ossetic","pi":"Pali","pa":"Punjabi","ps":"Pushto","fa":"Persian","pl":"Polish","pt":"Portuguese","qu":"Quechua","rm":"Romansh","rn":"Rundi","ru":"Russian","sm":"Samoan","sg":"Sango","sa":"Sanskrit","sc":"Sardinian","sr":"Serbian","sn":"Shona","sd":"Sindhi","si":"Sinhalese","sk":"Slovak","sl":"Slovenian","so":"Somali","st":"Sotho, Southern","su":"Sundanese","sw":"Swahili","ss":"Swati","sv":"Swedish","tl":"Tagalog","ty":"Tahitian","tg":"Tajik","ta":"Tamil","tt":"Tatar","te":"Telugu","th":"Thai","bo":"Tibetan","ti":"Tigrinya","to":"Tonga (Tonga Islands)","ts":"Tsonga","tn":"Tswana","tr":"Turkish","tk":"Turkmen","tw":"Twi","ug":"Uyghur","uk":"Ukrainian","ur":"Urdu","uz":"Uzbek","ve":"Venda","vi":"Vietnamese","vo":"Volapük","wa":"Walloon","cy":"Welsh","fy":"Western Frisian","wo":"Wolof","xh":"Xhosa","yi":"Yiddish","yo":"Yoruba","zu":"Zulu"}, + ISO639_2 = {"abk":"Abkhazian","ace":"Achinese","ach":"Acoli","ada":"Adangme","ady":"Adyghe","aar":"Afar","afh":"Afrihili","afr":"Afrikaans","afa":"Afro-Asiatic languages","ain":"Ainu","aka":"Akan","akk":"Akkadian","alb":"Albanian","sqi":"Albanian","gsw":"Swiss German","ale":"Aleut","alg":"Algonquian languages","tut":"Altaic languages","amh":"Amharic","anp":"Angika","apa":"Apache languages","ara":"Arabic","arg":"Aragonese","arp":"Arapaho","arw":"Arawak","arm":"Armenian","hye":"Armenian","rup":"Macedo-Romanian","art":"Artificial languages","asm":"Assamese","ast":"Leonese","ath":"Athapascan languages","aus":"Australian languages","map":"Austronesian languages","ava":"Avaric","ave":"Avestan","awa":"Awadhi","aym":"Aymara","aze":"Azerbaijani","ban":"Balinese","bat":"Baltic languages","bal":"Baluchi","bam":"Bambara","bai":"Bamileke languages","bad":"Banda languages","bnt":"Bantu languages","bas":"Basa","bak":"Bashkir","baq":"Basque","eus":"Basque","btk":"Batak languages","bej":"Beja","bel":"Belarusian","bem":"Bemba","ben":"Bengali","ber":"Berber languages","bho":"Bhojpuri","bih":"Bihari languages","bik":"Bikol","byn":"Blin","bin":"Edo","bis":"Bislama","zbl":"Blissymbols","nob":"Norwegian Bokmål","bos":"Bosnian","bra":"Braj","bre":"Breton","bug":"Buginese","bul":"Bulgarian","bua":"Buriat","bur":"Burmese","mya":"Burmese","cad":"Caddo","spa":"Spanish","cat":"Valencian","cau":"Caucasian languages","ceb":"Cebuano","cel":"Celtic languages","cai":"Central American Indian languages","khm":"Central Khmer","chg":"Chagatai","cmc":"Chamic languages","cha":"Chamorro","che":"Chechen","chr":"Cherokee","nya":"Nyanja","chy":"Cheyenne","chb":"Chibcha","chi":"Chinese","zho":"Chinese","chn":"Chinook jargon","chp":"Dene Suline","cho":"Choctaw","zha":"Zhuang","chu":"Old Slavonic","chk":"Chuukese","chv":"Chuvash","nwc":"Old Newari","syc":"Classical Syriac","rar":"Rarotongan","cop":"Coptic","cor":"Cornish","cos":"Corsican","cre":"Cree","mus":"Creek","crp":"Creoles and pidgins","cpe":"Creoles and pidgins, English based","cpf":"Creoles and pidgins, French-based","cpp":"Creoles and pidgins, Portuguese-based","crh":"Crimean Turkish","hrv":"Croatian","cus":"Cushitic languages","cze":"Czech","ces":"Czech","dak":"Dakota","dan":"Danish","dar":"Dargwa","del":"Delaware","div":"Maldivian","zza":"Zazaki","din":"Dinka","doi":"Dogri","dgr":"Dogrib","dra":"Dravidian languages","dua":"Duala","dut":"Flemish","nld":"Flemish","dum":"Dutch, Middle (ca.1050-1350)","dyu":"Dyula","dzo":"Dzongkha","frs":"Eastern Frisian","efi":"Efik","egy":"Egyptian (Ancient)","eka":"Ekajuk","elx":"Elamite","eng":"English","enm":"English, Middle (1100-1500)","ang":"English, Old (ca.450-1100)","myv":"Erzya","epo":"Esperanto","est":"Estonian","ewe":"Ewe","ewo":"Ewondo","fan":"Fang","fat":"Fanti","fao":"Faroese","fij":"Fijian","fil":"Pilipino","fin":"Finnish","fiu":"Finno-Ugrian languages","fon":"Fon","fre":"French","fra":"French","frm":"French, Middle (ca.1400-1600)","fro":"French, Old (842-ca.1400)","fur":"Friulian","ful":"Fulah","gaa":"Ga","gla":"Scottish Gaelic","car":"Galibi Carib","glg":"Galician","lug":"Ganda","gay":"Gayo","gba":"Gbaya","gez":"Geez","geo":"Georgian","kat":"Georgian","ger":"German","deu":"German","nds":"Saxon, Low","gmh":"German, Middle High (ca.1050-1500)","goh":"German, Old High (ca.750-1050)","gem":"Germanic languages","kik":"Kikuyu","gil":"Gilbertese","gon":"Gondi","gor":"Gorontalo","got":"Gothic","grb":"Grebo","grc":"Greek, Ancient (to 1453)","gre":"Greek, Modern (1453-)","ell":"Greek, Modern (1453-)","kal":"Kalaallisut","grn":"Guarani","guj":"Gujarati","gwi":"Gwich'in","hai":"Haida","hat":"Haitian Creole","hau":"Hausa","haw":"Hawaiian","heb":"Hebrew","her":"Herero","hil":"Hiligaynon","him":"Western Pahari languages","hin":"Hindi","hmo":"Hiri Motu","hit":"Hittite","hmn":"Mong","hun":"Hungarian","hup":"Hupa","iba":"Iban","ice":"Icelandic","isl":"Icelandic","ido":"Ido","ibo":"Igbo","ijo":"Ijo languages","ilo":"Iloko","arc":"Official Aramaic (700-300 BCE)","smn":"Inari Sami","inc":"Indic languages","ine":"Indo-European languages","ind":"Indonesian","inh":"Ingush","ina":"Interlingua (International Auxiliary Language Association)","ile":"Occidental","iku":"Inuktitut","ipk":"Inupiaq","ira":"Iranian languages","gle":"Irish","mga":"Irish, Middle (900-1200)","sga":"Irish, Old (to 900)","iro":"Iroquoian languages","ita":"Italian","jpn":"Japanese","jav":"Javanese","kac":"Kachin","jrb":"Judeo-Arabic","jpr":"Judeo-Persian","kbd":"Kabardian","kab":"Kabyle","xal":"Oirat","kam":"Kamba","kan":"Kannada","kau":"Kanuri","pam":"Pampanga","kaa":"Kara-Kalpak","krc":"Karachay-Balkar","krl":"Karelian","kar":"Karen languages","kas":"Kashmiri","csb":"Kashubian","kaw":"Kawi","kaz":"Kazakh","kha":"Khasi","khi":"Khoisan languages","kho":"Sakan","kmb":"Kimbundu","kin":"Kinyarwanda","kir":"Kyrgyz","tlh":"tlhIngan-Hol","kom":"Komi","kon":"Kongo","kok":"Konkani","kor":"Korean","kos":"Kosraean","kpe":"Kpelle","kro":"Kru languages","kua":"Kwanyama","kum":"Kumyk","kur":"Kurdish","kru":"Kurukh","kut":"Kutenai","lad":"Ladino","lah":"Lahnda","lam":"Lamba","day":"Land Dayak languages","lao":"Lao","lat":"Latin","lav":"Latvian","ltz":"Luxembourgish","lez":"Lezghian","lim":"Limburgish","lin":"Lingala","lit":"Lithuanian","jbo":"Lojban","dsb":"Lower Sorbian","loz":"Lozi","lub":"Luba-Katanga","lua":"Luba-Lulua","lui":"Luiseno","smj":"Lule Sami","lun":"Lunda","luo":"Luo (Kenya and Tanzania)","lus":"Lushai","mac":"Macedonian","mkd":"Macedonian","mad":"Madurese","mag":"Magahi","mai":"Maithili","mak":"Makasar","mlg":"Malagasy","may":"Malay","msa":"Malay","mal":"Malayalam","mlt":"Maltese","mnc":"Manchu","mdr":"Mandar","man":"Mandingo","mni":"Manipuri","mno":"Manobo languages","glv":"Manx","mao":"Maori","mri":"Maori","arn":"Mapudungun","mar":"Marathi","chm":"Mari","mah":"Marshallese","mwr":"Marwari","mas":"Masai","myn":"Mayan languages","men":"Mende","mic":"Micmac","min":"Minangkabau","mwl":"Mirandese","moh":"Mohawk","mdf":"Moksha","rum":"Romanian","ron":"Romanian","mkh":"Mon-Khmer languages","lol":"Mongo","mon":"Mongolian","mos":"Mossi","mul":"Multiple languages","mun":"Munda languages","nqo":"N'Ko","nah":"Nahuatl languages","nau":"Nauru","nav":"Navajo","nde":"North Ndebele","nbl":"South Ndebele","ndo":"Ndonga","nap":"Neapolitan","new":"Newari","nep":"Nepali","nia":"Nias","nic":"Niger-Kordofanian languages","ssa":"Nilo-Saharan languages","niu":"Niuean","zxx":"Not applicable","nog":"Nogai","non":"Norse, Old","nai":"North American Indian languages","frr":"Northern Frisian","sme":"Northern Sami","nso":"Sotho, Northern","nor":"Norwegian","nno":"Nynorsk, Norwegian","nub":"Nubian languages","iii":"Sichuan Yi","nym":"Nyamwezi","nyn":"Nyankole","nyo":"Nyoro","nzi":"Nzima","oci":"Occitan (post 1500)","pro":"Provençal, Old (to 1500)","oji":"Ojibwa","ori":"Oriya","orm":"Oromo","osa":"Osage","oss":"Ossetic","oto":"Otomian languages","pal":"Pahlavi","pau":"Palauan","pli":"Pali","pag":"Pangasinan","pan":"Punjabi","pap":"Papiamento","paa":"Papuan languages","pus":"Pushto","per":"Persian","fas":"Persian","peo":"Persian, Old (ca.600-400 B.C.)","phi":"Philippine languages","phn":"Phoenician","pon":"Pohnpeian","pol":"Polish","por":"Portuguese","pra":"Prakrit languages","que":"Quechua","raj":"Rajasthani","rap":"Rapanui","qaa-qtz":"Reserved for local use","roa":"Romance languages","roh":"Romansh","rom":"Romany","run":"Rundi","rus":"Russian","sal":"Salishan languages","sam":"Samaritan Aramaic","smi":"Sami languages","smo":"Samoan","sad":"Sandawe","sag":"Sango","san":"Sanskrit","sat":"Santali","srd":"Sardinian","sas":"Sasak","sco":"Scots","sel":"Selkup","sem":"Semitic languages","srp":"Serbian","srr":"Serer","shn":"Shan","sna":"Shona","scn":"Sicilian","sid":"Sidamo","sgn":"Sign Languages","bla":"Siksika","snd":"Sindhi","sin":"Sinhalese","sit":"Sino-Tibetan languages","sio":"Siouan languages","sms":"Skolt Sami","den":"Slave (Athapascan)","sla":"Slavic languages","slo":"Slovak","slk":"Slovak","slv":"Slovenian","sog":"Sogdian","som":"Somali","son":"Songhai languages","snk":"Soninke","wen":"Sorbian languages","sot":"Sotho, Southern","sai":"South American Indian languages","alt":"Southern Altai","sma":"Southern Sami","srn":"Sranan Tongo","suk":"Sukuma","sux":"Sumerian","sun":"Sundanese","sus":"Susu","swa":"Swahili","ssw":"Swati","swe":"Swedish","syr":"Syriac","tgl":"Tagalog","tah":"Tahitian","tai":"Tai languages","tgk":"Tajik","tmh":"Tamashek","tam":"Tamil","tat":"Tatar","tel":"Telugu","ter":"Tereno","tet":"Tetum","tha":"Thai","tib":"Tibetan","bod":"Tibetan","tig":"Tigre","tir":"Tigrinya","tem":"Timne","tiv":"Tiv","tli":"Tlingit","tpi":"Tok Pisin","tkl":"Tokelau","tog":"Tonga (Nyasa)","ton":"Tonga (Tonga Islands)","tsi":"Tsimshian","tso":"Tsonga","tsn":"Tswana","tum":"Tumbuka","tup":"Tupi languages","tur":"Turkish","ota":"Turkish, Ottoman (1500-1928)","tuk":"Turkmen","tvl":"Tuvalu","tyv":"Tuvinian","twi":"Twi","udm":"Udmurt","uga":"Ugaritic","uig":"Uyghur","ukr":"Ukrainian","umb":"Umbundu","mis":"Uncoded languages","und":"Undetermined","hsb":"Upper Sorbian","urd":"Urdu","uzb":"Uzbek","vai":"Vai","ven":"Venda","vie":"Vietnamese","vol":"Volapük","vot":"Votic","wak":"Wakashan languages","wln":"Walloon","war":"Waray","was":"Washo","wel":"Welsh","cym":"Welsh","fry":"Western Frisian","wal":"Wolaytta","wol":"Wolof","xho":"Xhosa","sah":"Yakut","yao":"Yao","yap":"Yapese","yid":"Yiddish","yor":"Yoruba","ypk":"Yupik languages","znd":"Zande languages","zap":"Zapotec","zen":"Zenaga","zul":"Zulu","zun":"Zuni"}, + ISO3166_1 = {"AF":"AFGHANISTAN","AX":"ÅLAND ISLANDS","AL":"ALBANIA","DZ":"ALGERIA","AS":"AMERICAN SAMOA","AD":"ANDORRA","AO":"ANGOLA","AI":"ANGUILLA","AQ":"ANTARCTICA","AG":"ANTIGUA AND BARBUDA","AR":"ARGENTINA","AM":"ARMENIA","AW":"ARUBA","AU":"AUSTRALIA","AT":"AUSTRIA","AZ":"AZERBAIJAN","BS":"BAHAMAS","BH":"BAHRAIN","BD":"BANGLADESH","BB":"BARBADOS","BY":"BELARUS","BE":"BELGIUM","BZ":"BELIZE","BJ":"BENIN","BM":"BERMUDA","BT":"BHUTAN","BO":"BOLIVIA, PLURINATIONAL STATE OF","BQ":"BONAIRE, SINT EUSTATIUS AND SABA","BA":"BOSNIA AND HERZEGOVINA","BW":"BOTSWANA","BV":"BOUVET ISLAND","BR":"BRAZIL","IO":"BRITISH INDIAN OCEAN TERRITORY","BN":"BRUNEI DARUSSALAM","BG":"BULGARIA","BF":"BURKINA FASO","BI":"BURUNDI","KH":"CAMBODIA","CM":"CAMEROON","CA":"CANADA","CV":"CAPE VERDE","KY":"CAYMAN ISLANDS","CF":"CENTRAL AFRICAN REPUBLIC","TD":"CHAD","CL":"CHILE","CN":"CHINA","CX":"CHRISTMAS ISLAND","CC":"COCOS (KEELING) ISLANDS","CO":"COLOMBIA","KM":"COMOROS","CG":"CONGO","CD":"CONGO, THE DEMOCRATIC REPUBLIC OF THE","CK":"COOK ISLANDS","CR":"COSTA RICA","CI":"CÔTE D'IVOIRE","HR":"CROATIA","CU":"CUBA","CW":"CURAÇAO","CY":"CYPRUS","CZ":"CZECH REPUBLIC","DK":"DENMARK","DJ":"DJIBOUTI","DM":"DOMINICA","DO":"DOMINICAN REPUBLIC","EC":"ECUADOR","EG":"EGYPT","SV":"EL SALVADOR","GQ":"EQUATORIAL GUINEA","ER":"ERITREA","EE":"ESTONIA","ET":"ETHIOPIA","FK":"FALKLAND ISLANDS (MALVINAS)","FO":"FAROE ISLANDS","FJ":"FIJI","FI":"FINLAND","FR":"FRANCE","GF":"FRENCH GUIANA","PF":"FRENCH POLYNESIA","TF":"FRENCH SOUTHERN TERRITORIES","GA":"GABON","GM":"GAMBIA","GE":"GEORGIA","DE":"GERMANY","GH":"GHANA","GI":"GIBRALTAR","GR":"GREECE","GL":"GREENLAND","GD":"GRENADA","GP":"GUADELOUPE","GU":"GUAM","GT":"GUATEMALA","GG":"GUERNSEY","GN":"GUINEA","GW":"GUINEA-BISSAU","GY":"GUYANA","HT":"HAITI","HM":"HEARD ISLAND AND MCDONALD ISLANDS","VA":"HOLY SEE (VATICAN CITY STATE)","HN":"HONDURAS","HK":"HONG KONG","HU":"HUNGARY","IS":"ICELAND","IN":"INDIA","ID":"INDONESIA","IR":"IRAN, ISLAMIC REPUBLIC OF","IQ":"IRAQ","IE":"IRELAND","IM":"ISLE OF MAN","IL":"ISRAEL","IT":"ITALY","JM":"JAMAICA","JP":"JAPAN","JE":"JERSEY","JO":"JORDAN","KZ":"KAZAKHSTAN","KE":"KENYA","KI":"KIRIBATI","KP":"KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF","KR":"KOREA, REPUBLIC OF","KW":"KUWAIT","KG":"KYRGYZSTAN","LA":"LAO PEOPLE'S DEMOCRATIC REPUBLIC","LV":"LATVIA","LB":"LEBANON","LS":"LESOTHO","LR":"LIBERIA","LY":"LIBYA","LI":"LIECHTENSTEIN","LT":"LITHUANIA","LU":"LUXEMBOURG","MO":"MACAO","MK":"MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF","MG":"MADAGASCAR","MW":"MALAWI","MY":"MALAYSIA","MV":"MALDIVES","ML":"MALI","MT":"MALTA","MH":"MARSHALL ISLANDS","MQ":"MARTINIQUE","MR":"MAURITANIA","MU":"MAURITIUS","YT":"MAYOTTE","MX":"MEXICO","FM":"MICRONESIA, FEDERATED STATES OF","MD":"MOLDOVA, REPUBLIC OF","MC":"MONACO","MN":"MONGOLIA","ME":"MONTENEGRO","MS":"MONTSERRAT","MA":"MOROCCO","MZ":"MOZAMBIQUE","MM":"MYANMAR","NA":"NAMIBIA","NR":"NAURU","NP":"NEPAL","NL":"NETHERLANDS","NC":"NEW CALEDONIA","NZ":"NEW ZEALAND","NI":"NICARAGUA","NE":"NIGER","NG":"NIGERIA","NU":"NIUE","NF":"NORFOLK ISLAND","MP":"NORTHERN MARIANA ISLANDS","NO":"NORWAY","OM":"OMAN","PK":"PAKISTAN","PW":"PALAU","PS":"PALESTINIAN TERRITORY, OCCUPIED","PA":"PANAMA","PG":"PAPUA NEW GUINEA","PY":"PARAGUAY","PE":"PERU","PH":"PHILIPPINES","PN":"PITCAIRN","PL":"POLAND","PT":"PORTUGAL","PR":"PUERTO RICO","QA":"QATAR","RE":"RÉUNION","RO":"ROMANIA","RU":"RUSSIAN FEDERATION","RW":"RWANDA","BL":"SAINT BARTHÉLEMY","SH":"SAINT HELENA, ASCENSION AND TRISTAN DA CUNHA","KN":"SAINT KITTS AND NEVIS","LC":"SAINT LUCIA","MF":"SAINT MARTIN (FRENCH PART)","PM":"SAINT PIERRE AND MIQUELON","VC":"SAINT VINCENT AND THE GRENADINES","WS":"SAMOA","SM":"SAN MARINO","ST":"SAO TOME AND PRINCIPE","SA":"SAUDI ARABIA","SN":"SENEGAL","RS":"SERBIA","SC":"SEYCHELLES","SL":"SIERRA LEONE","SG":"SINGAPORE","SX":"SINT MAARTEN (DUTCH PART)","SK":"SLOVAKIA","SI":"SLOVENIA","SB":"SOLOMON ISLANDS","SO":"SOMALIA","ZA":"SOUTH AFRICA","GS":"SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS","SS":"SOUTH SUDAN","ES":"SPAIN","LK":"SRI LANKA","SD":"SUDAN","SR":"SURINAME","SJ":"SVALBARD AND JAN MAYEN","SZ":"SWAZILAND","SE":"SWEDEN","CH":"SWITZERLAND","SY":"SYRIAN ARAB REPUBLIC","TW":"TAIWAN, PROVINCE OF CHINA","TJ":"TAJIKISTAN","TZ":"TANZANIA, UNITED REPUBLIC OF","TH":"THAILAND","TL":"TIMOR-LESTE","TG":"TOGO","TK":"TOKELAU","TO":"TONGA","TT":"TRINIDAD AND TOBAGO","TN":"TUNISIA","TR":"TURKEY","TM":"TURKMENISTAN","TC":"TURKS AND CAICOS ISLANDS","TV":"TUVALU","UG":"UGANDA","UA":"UKRAINE","AE":"UNITED ARAB EMIRATES","GB":"UNITED KINGDOM","US":"UNITED STATES","UM":"UNITED STATES MINOR OUTLYING ISLANDS","UY":"URUGUAY","UZ":"UZBEKISTAN","VU":"VANUATU","VE":"VENEZUELA, BOLIVARIAN REPUBLIC OF","VN":"VIET NAM","VG":"VIRGIN ISLANDS, BRITISH","VI":"VIRGIN ISLANDS, U.S.","WF":"WALLIS AND FUTUNA","EH":"WESTERN SAHARA","YE":"YEMEN","ZM":"ZAMBIA","ZW":"ZIMBABWE"}; + if (parts.length > 2) throw new SyntaxError('Unexpected number of segments ' + parts.length); + if (parts.length > 1) + return (ISO639_1[parts[0]] || ISO639_2[parts[0]] || parts[0]) + ', ' + (ISO3166_1[parts[1]] || parts[1]); + if (parts.length > 0) + return ISO639_1[parts[0]] || ISO639_2[parts[0]] || ISO3166_1[parts[0]] || parts[0]; + return ''; +} \ No newline at end of file diff --git a/src/qml/fonts/NotoSans.ttf b/src/qml/fonts/NotoSans.ttf new file mode 100644 index 0000000..9dd1019 Binary files /dev/null and b/src/qml/fonts/NotoSans.ttf differ diff --git a/src/qml/fonts/README.md b/src/qml/fonts/README.md new file mode 100644 index 0000000..dfafb9a --- /dev/null +++ b/src/qml/fonts/README.md @@ -0,0 +1,2 @@ +# Fonts +- Noto Sans font comes from https://www.google.com/get/noto/ \ No newline at end of file diff --git a/src/qml/icons/README.md b/src/qml/icons/README.md new file mode 100644 index 0000000..f8179c4 --- /dev/null +++ b/src/qml/icons/README.md @@ -0,0 +1,3 @@ +# Icons +- Icons where all from https://material.io/tools/icons/?style=baseline +- Exceptions: none yet \ No newline at end of file diff --git a/src/qml/icons/backward.svg b/src/qml/icons/backward.svg new file mode 100644 index 0000000..aecc262 --- /dev/null +++ b/src/qml/icons/backward.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/qml/icons/forward.svg b/src/qml/icons/forward.svg new file mode 100644 index 0000000..0a1962c --- /dev/null +++ b/src/qml/icons/forward.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/qml/icons/fullscreen.svg b/src/qml/icons/fullscreen.svg new file mode 100644 index 0000000..8c97c23 --- /dev/null +++ b/src/qml/icons/fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/qml/icons/next.svg b/src/qml/icons/next.svg new file mode 100644 index 0000000..058e9b1 --- /dev/null +++ b/src/qml/icons/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/qml/icons/pause.svg b/src/qml/icons/pause.svg new file mode 100644 index 0000000..ef758c0 --- /dev/null +++ b/src/qml/icons/pause.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/qml/icons/play.svg b/src/qml/icons/play.svg new file mode 100755 index 0000000..19bda45 --- /dev/null +++ b/src/qml/icons/play.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/qml/icons/playlist.svg b/src/qml/icons/playlist.svg new file mode 100644 index 0000000..3a9b867 --- /dev/null +++ b/src/qml/icons/playlist.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/qml/icons/prev.svg b/src/qml/icons/prev.svg new file mode 100644 index 0000000..bf27a6f --- /dev/null +++ b/src/qml/icons/prev.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/qml/icons/settings.svg b/src/qml/icons/settings.svg new file mode 100644 index 0000000..7a01c33 --- /dev/null +++ b/src/qml/icons/settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/qml/icons/subtitles.svg b/src/qml/icons/subtitles.svg new file mode 100644 index 0000000..6553a5a --- /dev/null +++ b/src/qml/icons/subtitles.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/qml/icons/volume-down.svg b/src/qml/icons/volume-down.svg new file mode 100644 index 0000000..51aab93 --- /dev/null +++ b/src/qml/icons/volume-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/qml/icons/volume-mute.svg b/src/qml/icons/volume-mute.svg new file mode 100644 index 0000000..83c7c8a --- /dev/null +++ b/src/qml/icons/volume-mute.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/qml/icons/volume-up.svg b/src/qml/icons/volume-up.svg new file mode 100644 index 0000000..9f81966 --- /dev/null +++ b/src/qml/icons/volume-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/qml/main.qml b/src/qml/main.qml new file mode 100644 index 0000000..bd8f655 --- /dev/null +++ b/src/qml/main.qml @@ -0,0 +1,640 @@ +import QtQuick 2.11 +import QtQuick.Controls 2.4 +import QtGraphicalEffects 1.0 +import QtQuick.Dialogs 1.3 +import QtQuick.Layouts 1.11 +import QtQuick.Window 2.11 +import player 1.0 + +import "codes.js" as LanguageCodes + +ApplicationWindow { + id: mainWindow + title: "Qt Quick Controls 2" + visible: true + width: 720 + height: 480 + + property int lastScreenVisibility + + function updatePlayPauseIcon() { + var paused = renderer.getProperty("pause") + if (paused) { + playPauseButton.icon.source = "qrc:/player/icons/play.svg" + } else { + playPauseButton.icon.source = "qrc:/player/icons/pause.svg" + } + } + + function updateVolume() { + var muted = renderer.getProperty("mute") + var volume = renderer.getProperty("volume") + + if (muted || volume === 0) { + volumeButton.icon.source = "qrc:/player/icons/volume-mute.svg" + } else { + if (volume < 25) { + volumeButton.icon.source = "qrc:/player/icons/volume-down.svg" + } else { + volumeButton.icon.source = "qrc:/player/icons/volume-up.svg" + } + } + } + + function updatePrev() { + var playlist_pos = renderer.getProperty("playlist-pos") + if (playlist_pos > 0) { + playlistPrevButton.visible = true + playlistPrevButton.width = playPauseButton.width + } else { + playlistPrevButton.visible = false + playlistPrevButton.width = 0 + } + } + + function updateControls() { + updatePrev() + updatePlayPauseIcon() + updateVolume() + } + + function updatePlayPause() { + renderer.command(["cycle", "pause"]) + updatePlayPauseIcon() + } + + function setSubtitle(sub) { + console.log(sub) + } + + function tracksMenuUpdate() { + var tracks = renderer.getProperty("track-list/count") + var track = 0 + subModel.clear() + audioModel.clear() + vidModel.clear() + + var aid = renderer.getProperty("aid") + var sid = renderer.getProperty("sid") + var vid = renderer.getProperty("vid") + + console.log("Updating Track Menu, Total Tracks: " + tracks) + for (track = 0; track <= tracks; track++) { + var trackID = renderer.getProperty("track-list/" + track + "/id") + var trackType = renderer.getProperty( + "track-list/" + track + "/type") + var trackLang = LanguageCodes.localeCodeToEnglish( + String(renderer.getProperty( + "track-list/" + track + "/lang"))) + var trackTitle = renderer.getProperty( + "track-list/" + track + "/title") + if (trackType == "sub") { + subModel.append({ + key: trackLang, + value: trackID + }) + if (renderer.getProperty("track-list/" + track + "/selected")) { + subList.currentIndex = subList.count + } + } else if (trackType == "audio") { + audioModel.append({ + key: (trackTitle === undefined ? "" : trackTitle + " ") + + trackLang, + value: trackID + }) + if (renderer.getProperty("track-list/" + track + "/selected")) { + audioList.currentIndex = audioList.count + } + } else if (trackType == "video") { + vidModel.append({ + key: "Video " + trackID, + value: trackID + }) + if (renderer.getProperty("track-list/" + track + "/selected")) { + vidList.currentIndex = vidList.count + } + } + } + } + + MpvObject { + id: renderer + anchors.fill: parent + Component.onCompleted: { + var args = Qt.application.arguments + var len = Qt.application.arguments.length + var argNo = 0 + renderer.setOption("ytdl-format", "bestvideo[width<=" + Screen.width + "][height<=" + Screen.height + "]+bestaudio") + if (len > 1) { + for (argNo = 0; argNo < len; argNo++) { + var argument = args[argNo] + if (argument.startsWith("--")) { + argument = argument.substr(2) + if (argument.length > 0) { + var splitArg = argument.split(/=(.+)/) + renderer.setOption(splitArg[0], splitArg[1]) + } + } else { + renderer.command(["loadfile", argument, "append-play"]) + } + } + } + } + + FontLoader { + id: notoFont + source: "fonts/NotoSans.ttf" + } + + function createTimestamp(d) { + d = Number(d) + var h = Math.floor(d / 3600) + var m = Math.floor(d % 3600 / 60) + var s = Math.floor(d % 3600 % 60) + + var hour = h > 0 ? h + ":" : "" + var minute = m + ":" + var second = s > 10 ? s : "0" + s + return hour + minute + second + } + + function setProgressBarEnd(val) { + progressBar.to = val + } + + function setProgressBarValue(val) { + timeLabel.text = createTimestamp(val) + " / " + createTimestamp( + progressBar.to) + progressBar.value = val + } + + function setTitle() { + titleLabel.text = renderer.getProperty("media-title") + } + } + + Dialog { + id: loadDialog + title: "URL / File Path" + standardButtons: StandardButton.Cancel | StandardButton.Open + + onAccepted: { + renderer.command(["loadfile", pathText.text]) + pathText.text = "" + } + + TextField { + id: pathText + text: "/home/kitteh/babyshark.mkv" + placeholderText: qsTr("URL / File Path") + } + } + + MouseArea { + id: mouseAreaBar + x: 0 + y: parent.height + width: parent.width + height: controlsBar.height + progressBar.height + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + hoverEnabled: true + onEntered: { + updateControls() + controlsBar.visible = true + controlsBackground.visible = true + titleBar.visible = true + titleBackground.visible = true + controlsBar.height = 35 + } + } + + MouseArea { + id: mouseAreaPlayer + width: parent.width + anchors.bottom: mouseAreaBar.top + anchors.bottomMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.top: titleBar.bottom + anchors.topMargin: 0 + hoverEnabled: true + onClicked: loadDialog.open() + onEntered: { + if (subtitlesMenu.visible) { + return + } else { + controlsBar.visible = false + controlsBackground.visible = false + titleBar.visible = false + titleBackground.visible = false + controlsBar.height = 0 + } + } + } + + Rectangle { + id: titleBackground + height: titleBar.height + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + Layout.fillWidth: true + Layout.fillHeight: true + color: "black" + opacity: 0.6 + } + + Rectangle { + id: titleBar + height: renderer.height / 16 + anchors.right: parent.right + anchors.rightMargin: parent.width / 128 + anchors.left: parent.left + anchors.leftMargin: parent.width / 128 + anchors.top: parent.top + + visible: true + color: "transparent" + + Text { + id: titleLabel + text: "Title" + color: "white" + width: parent.width + height: parent.height + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.bottomMargin: 4 + anchors.topMargin: 4 + anchors.top: parent.top + font.family: notoFont.name + fontSizeMode: Text.Fit + minimumPixelSize: 10 + font.pixelSize: 72 + verticalAlignment: Text.AlignVCenter + renderType: Text.NativeRendering + opacity: 1 + } + } + + Rectangle { + id: controlsBackground + height: controlsBar.height + (progressBar.topPadding * 2) - (progressBackground.height * 2) + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + Layout.fillWidth: true + Layout.fillHeight: true + color: "black" + opacity: 0.6 + } + + Rectangle { + id: controlsBar + height: renderer.height / 16 + anchors.right: parent.right + anchors.rightMargin: parent.width / 128 + anchors.left: parent.left + anchors.leftMargin: parent.width / 128 + anchors.bottom: parent.bottom + anchors.bottomMargin: 1 + visible: true + color: "transparent" + + Rectangle { + id: subtitlesMenuBackground + height: controlsBar.height + (progressBar.topPadding * 2) + - (progressBackground.height * 2) + anchors.fill: subtitlesMenu + Layout.fillWidth: true + Layout.fillHeight: true + visible: false + color: "black" + opacity: 0.6 + radius: 5 + } + + Rectangle { + id: subtitlesMenu + color: "transparent" + width: childrenRect.width + height: childrenRect.height + visible: false + anchors.right: subtitlesButton.right + anchors.bottom: progressBar.top + radius: 5 + + Text { + id: audioLabel + anchors.left: parent.left + anchors.right: parent.right + text: "Audio" + color: "white" + font.family: notoFont.name + font.pixelSize: 14 + renderType: Text.NativeRendering + horizontalAlignment: Text.AlignHCenter + opacity: 1 + } + ComboBox { + id: audioList + textRole: "key" + anchors.top: audioLabel.bottom + model: ListModel { + id: audioModel + } + onActivated: { + renderer.command(["set", "aid", String(audioModel.get( + index).value)]) + } + opacity: 1 + } + Text { + id: subLabel + anchors.left: parent.left + anchors.right: parent.right + text: "Subtitles" + color: "white" + font.family: notoFont.name + font.pixelSize: 14 + anchors.top: audioList.bottom + renderType: Text.NativeRendering + horizontalAlignment: Text.AlignHCenter + opacity: 1 + } + ComboBox { + id: subList + textRole: "key" + anchors.top: subLabel.bottom + model: ListModel { + id: subModel + } + onActivated: { + renderer.command(["set", "sid", String(subModel.get( + index).value)]) + } + opacity: 1 + } + Text { + id: vidLabel + anchors.left: parent.left + anchors.right: parent.right + text: "Video" + color: "white" + font.family: notoFont.name + font.pixelSize: 14 + anchors.top: subList.bottom + renderType: Text.NativeRendering + horizontalAlignment: Text.AlignHCenter + opacity: 1 + } + ComboBox { + id: vidList + textRole: "key" + anchors.top: vidLabel.bottom + model: ListModel { + id: vidModel + } + onActivated: { + renderer.command(["set", "vid", String(vidModel.get( + index).value)]) + } + opacity: 1 + } + } + + Slider { + id: progressBar + to: 1 + value: 0.0 + palette.dark: "#f00" + anchors.bottom: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottomMargin: 0 + + bottomPadding: 0 + + onMoved: { + renderer.command(["seek", progressBar.value, "absolute"]) + } + + background: Rectangle { + id: progressBackground + x: progressBar.leftPadding + y: progressBar.topPadding + progressBar.availableHeight / 2 - height / 2 + implicitHeight: (renderer.height / 256) < 2 ? 2 : renderer.height / 256 + width: progressBar.availableWidth + height: implicitHeight + color: Qt.rgba(255, 255, 255, 0.4) + + Rectangle { + width: progressBar.visualPosition * parent.width + height: parent.height + color: "red" + opacity: 1 + } + } + + handle: Rectangle { + x: progressBar.leftPadding + progressBar.visualPosition + * (progressBar.availableWidth - width) + y: progressBar.topPadding + progressBar.availableHeight / 2 - height / 2 + implicitWidth: 12 + implicitHeight: 12 + radius: 12 + color: "red" + border.color: "red" + } + } + + Button { + id: playlistPrevButton + icon.name: "prev" + icon.source: "icons/prev.svg" + icon.color: "white" + display: AbstractButton.IconOnly + visible: false + width: 0 + onClicked: { + renderer.command(["playlist-prev"]) + updatePrev() + } + background: Rectangle { + color: "transparent" + } + } + + Button { + id: playPauseButton + icon.name: "pause" + icon.source: "icons/pause.svg" + icon.color: "white" + display: AbstractButton.IconOnly + anchors.left: playlistPrevButton.right + onClicked: { + updatePlayPause() + } + background: Rectangle { + color: "transparent" + } + } + + Button { + id: playlistNextButton + icon.name: "next" + icon.source: "icons/next.svg" + icon.color: "white" + display: AbstractButton.IconOnly + anchors.left: playPauseButton.right + onClicked: { + renderer.command(["playlist-next", "force"]) + } + background: Rectangle { + color: "transparent" + } + } + + Button { + id: volumeButton + icon.name: "volume-up" + icon.source: "icons/volume-up.svg" + icon.color: "white" + display: AbstractButton.IconOnly + anchors.left: playlistNextButton.right + onClicked: { + renderer.command(["cycle", "mute"]) + updateVolume() + } + background: Rectangle { + color: "transparent" + } + } + Slider { + id: volumeBar + to: 100 + value: 100 + palette.dark: "#f00" + + implicitWidth: Math.max( + background ? background.implicitWidth : 0, + (handle ? handle.implicitWidth : 0) + + leftPadding + rightPadding) + implicitHeight: Math.max( + background ? background.implicitHeight : 0, + (handle ? handle.implicitHeight : 0) + + topPadding + bottomPadding) + + anchors.left: volumeButton.right + anchors.top: parent.top + anchors.bottom: parent.bottom + onMoved: { + renderer.command(["set", "volume", Math.round( + volumeBar.value).toString()]) + updateVolume() + } + + handle: Rectangle { + x: volumeBar.leftPadding + volumeBar.visualPosition + * (volumeBar.availableWidth - width) + y: volumeBar.topPadding + volumeBar.availableHeight / 2 - height / 2 + implicitWidth: 12 + implicitHeight: 12 + radius: 12 + color: "#f6f6f6" + border.color: "#f6f6f6" + } + + background: Rectangle { + x: volumeBar.leftPadding + y: volumeBar.topPadding + volumeBar.availableHeight / 2 - height / 2 + implicitWidth: 60 + implicitHeight: 3 + width: volumeBar.availableWidth + height: implicitHeight + color: "#33333311" + Rectangle { + width: volumeBar.visualPosition * parent.width + height: parent.height + color: "white" + } + } + } + + Text { + id: timeLabel + text: "0:00 / 0:00" + color: "white" + anchors.left: volumeBar.right + anchors.bottom: parent.bottom + anchors.top: parent.top + padding: 5 + font.family: notoFont.name + font.pixelSize: 12 + verticalAlignment: Text.AlignVCenter + renderType: Text.NativeRendering + } + + Button { + id: subtitlesButton + icon.name: "subtitles" + icon.source: "icons/subtitles.svg" + icon.color: "white" + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + anchors.right: settingsButton.left + display: AbstractButton.IconOnly + onClicked: { + tracksMenuUpdate() + subtitlesMenu.visible = !subtitlesMenu.visible + subtitlesMenuBackground.visible = !subtitlesMenuBackground.visible + } + background: Rectangle { + color: "transparent" + } + } + + Button { + id: settingsButton + icon.name: "settings" + icon.source: "icons/settings.svg" + icon.color: "white" + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + anchors.right: fullscreenButton.left + display: AbstractButton.IconOnly + onClicked: { + loadDialog.open() + } + background: Rectangle { + color: "transparent" + } + } + + Button { + id: fullscreenButton + icon.name: "fullscreen" + icon.source: "icons/fullscreen.svg" + icon.color: "white" + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + anchors.right: parent.right + display: AbstractButton.IconOnly + onClicked: { + if (mainWindow.visibility != Window.FullScreen) { + lastScreenVisibility = mainWindow.visibility + mainWindow.visibility = Window.FullScreen + } else { + mainWindow.visibility = lastScreenVisibility + } + } + + background: Rectangle { + color: "transparent" + } + } + + //} + } +} + diff --git a/src/qml/qml.qrc b/src/qml/qml.qrc new file mode 100644 index 0000000..517a557 --- /dev/null +++ b/src/qml/qml.qrc @@ -0,0 +1,20 @@ + + + main.qml + icons/play.svg + icons/pause.svg + icons/forward.svg + icons/backward.svg + icons/settings.svg + icons/fullscreen.svg + icons/volume-up.svg + icons/volume-mute.svg + icons/volume-down.svg + icons/next.svg + icons/prev.svg + icons/subtitles.svg + fonts/NotoSans.ttf + icons/playlist.svg + codes.js + +