Initial Commit!
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
build
|
||||||
|
|
35
.travis.yml
Normal file
|
@ -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)/
|
10
KittehPlayer.desktop
Normal file
|
@ -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
|
BIN
KittehPlayer.png
Normal file
After Width: | Height: | Size: 236 KiB |
35
KittehPlayer.pro
Normal file
|
@ -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
|
373
LICENSE.txt
Normal file
|
@ -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.
|
14
src/config.h
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#ifndef CONFIG_H
|
||||||
|
#define CONFIG_H
|
||||||
|
|
||||||
|
#include <mpv/client.h>
|
||||||
|
|
||||||
|
#if MPV_CLIENT_API_VERSION >= MPV_MAKE_VERSION(1, 28)
|
||||||
|
#define USE_RENDER
|
||||||
|
#else
|
||||||
|
#warning "Using deprecated MPV..."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif // CONFIG_H
|
38
src/main.cpp
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <clocale>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "mpvobject.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QMainWindow>
|
||||||
|
|
||||||
|
#include <QQmlApplicationEngine>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QtGui/QOpenGLFramebufferObject>
|
||||||
|
#include <QQuickView>
|
||||||
|
|
||||||
|
|
||||||
|
int main( int argc, char *argv[] )
|
||||||
|
{
|
||||||
|
setenv("QT_QUICK_CONTROLS_STYLE","Desktop",1);
|
||||||
|
QApplication app(argc, argv);
|
||||||
|
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||||
|
qmlRegisterType<MpvObject>("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();
|
||||||
|
}
|
6
src/main.h
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#ifndef MAIN_H
|
||||||
|
#define MAIN_H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif // MAIN_H
|
352
src/mpvobject.cpp
Normal file
|
@ -0,0 +1,352 @@
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <clocale>
|
||||||
|
|
||||||
|
#include "mpvobject.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QOpenGLContext>
|
||||||
|
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QtQuick/QQuickWindow>
|
||||||
|
#include <QtQuick/QQuickView>
|
||||||
|
#include <QQuickFramebufferObject>
|
||||||
|
#include <QOpenGLContext>
|
||||||
|
#include <QtQuick/QQuickWindow>
|
||||||
|
#include <QtQuick/QQuickView>
|
||||||
|
#include <QtGui/QOpenGLFramebufferObject>
|
||||||
|
|
||||||
|
#include <QJsonDocument>
|
||||||
|
|
||||||
|
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<void *>(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<char *>(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<int>(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<MpvObject *>(this));
|
||||||
|
#else
|
||||||
|
return new MpvRenderer(this);
|
||||||
|
#endif
|
||||||
|
}
|
78
src/mpvobject.h
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
#ifndef MPVOBJECT_H
|
||||||
|
#define MPVOBJECT_H
|
||||||
|
|
||||||
|
#include <QQuickFramebufferObject>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <mpv/client.h>
|
||||||
|
|
||||||
|
#ifdef USE_RENDER
|
||||||
|
#include <mpv/render_gl.h>
|
||||||
|
#else
|
||||||
|
#include <mpv/opengl_cb.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <mpv/qthelper.hpp>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QOpenGLContext>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
|
||||||
|
#include <QQuickFramebufferObject>
|
||||||
|
|
||||||
|
#include <QtQuick/QQuickWindow>
|
||||||
|
#include <QtQuick/QQuickView>
|
||||||
|
|
||||||
|
#include <QProcess>
|
||||||
|
|
||||||
|
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
|
13
src/qml/codes.js
Normal file
BIN
src/qml/fonts/NotoSans.ttf
Normal file
2
src/qml/fonts/README.md
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# Fonts
|
||||||
|
- Noto Sans font comes from https://www.google.com/get/noto/
|
3
src/qml/icons/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Icons
|
||||||
|
- Icons where all from https://material.io/tools/icons/?style=baseline
|
||||||
|
- Exceptions: none yet
|
1
src/qml/icons/backward.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M24 10V2L14 12l10 10v-8c6.6 0 12 5.4 12 12s-5.4 12-12 12-12-5.4-12-12H8c0 8.8 7.2 16 16 16s16-7.2 16-16-7.2-16-16-16zm-2.2 22h-1.7v-6.5l-2 .6v-1.4l3.5-1.3h.2V32zm8.5-3.5c0 .6-.1 1.2-.2 1.6s-.3.8-.6 1.1-.6.5-.9.7-.7.2-1.2.2-.8-.1-1.2-.2-.7-.4-.9-.7-.5-.7-.6-1.1-.2-1-.2-1.6V27c0-.6.1-1.2.2-1.6s.3-.8.6-1.1.6-.5.9-.7.7-.2 1.2-.2.8.1 1.2.2.7.4.9.7.5.7.6 1.1.2 1 .2 1.6v1.5zm-1.6-1.7c0-.4 0-.7-.1-1s-.1-.5-.2-.6-.2-.3-.4-.3-.3-.1-.5-.1-.4 0-.5.1-.3.2-.4.3-.2.4-.2.6-.1.6-.1 1v1.9c0 .4 0 .7.1 1s.1.5.2.6.2.3.4.3.3.1.5.1.4 0 .5-.1.3-.2.4-.3.2-.4.2-.6.1-.6.1-1v-1.9z"/></svg>
|
After Width: | Height: | Size: 660 B |
1
src/qml/icons/forward.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M8 26c0 8.8 7.2 16 16 16s16-7.2 16-16h-4c0 6.6-5.4 12-12 12s-12-5.4-12-12 5.4-12 12-12v8l10-10L24 2v8c-8.8 0-16 7.2-16 16zm13.7 6H20v-6.5l-2 .6v-1.4l3.5-1.3h.2V32zm8.5-3.5c0 .6-.1 1.2-.2 1.6s-.3.8-.6 1.1-.6.5-.9.7-.7.2-1.2.2-.8-.1-1.2-.2-.7-.4-.9-.7-.5-.7-.6-1.1-.2-1-.2-1.6V27c0-.6.1-1.2.2-1.6s.3-.8.6-1.1.6-.5.9-.7.7-.2 1.2-.2.8.1 1.2.2.7.4.9.7.5.7.6 1.1.2 1 .2 1.6v1.5zm-1.7-1.7c0-.4 0-.7-.1-1s-.1-.5-.2-.6-.2-.3-.4-.3-.3-.1-.5-.1-.4 0-.5.1-.3.2-.4.3-.2.4-.2.6-.1.6-.1 1v1.9c0 .4 0 .7.1 1s.1.5.2.6.2.3.4.3.3.1.5.1.4 0 .5-.1.3-.2.4-.3.2-.4.2-.6.1-.6.1-1v-1.9z"/></svg>
|
After Width: | Height: | Size: 662 B |
1
src/qml/icons/fullscreen.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M14 28h-4v10h10v-4h-6v-6zm-4-8h4v-6h6v-4H10v10zm24 14h-6v4h10V28h-4v6zm-6-24v4h6v6h4V10H28z"/></svg>
|
After Width: | Height: | Size: 192 B |
1
src/qml/icons/next.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M12 36l17-12-17-12v24zm20-24v24h4V12h-4z"/></svg>
|
After Width: | Height: | Size: 141 B |
1
src/qml/icons/pause.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M12 38h8V10h-8v28zm16-28v28h8V10h-8z"/></svg>
|
After Width: | Height: | Size: 137 B |
1
src/qml/icons/play.svg
Executable file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M16 10v28l22-14z"/></svg>
|
After Width: | Height: | Size: 117 B |
1
src/qml/icons/playlist.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="-12 -12 48 48" enable-background="new -12 -12 48 48"><path d="M26 6H-8v4h34V6zm0-8H-8v4h34v-4zM-8 18h26v-4H-8v4zm30-4v12l10-6-10-6z"/></svg>
|
After Width: | Height: | Size: 212 B |
1
src/qml/icons/prev.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M12 12h4v24h-4zm7 12l17 12V12z"/></svg>
|
After Width: | Height: | Size: 131 B |
1
src/qml/icons/settings.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M38.86 25.95c.08-.64.14-1.29.14-1.95s-.06-1.31-.14-1.95l4.23-3.31c.38-.3.49-.84.24-1.28l-4-6.93c-.25-.43-.77-.61-1.22-.43l-4.98 2.01c-1.03-.79-2.16-1.46-3.38-1.97L29 4.84c-.09-.47-.5-.84-1-.84h-8c-.5 0-.91.37-.99.84l-.75 5.3c-1.22.51-2.35 1.17-3.38 1.97L9.9 10.1c-.45-.17-.97 0-1.22.43l-4 6.93c-.25.43-.14.97.24 1.28l4.22 3.31C9.06 22.69 9 23.34 9 24s.06 1.31.14 1.95l-4.22 3.31c-.38.3-.49.84-.24 1.28l4 6.93c.25.43.77.61 1.22.43l4.98-2.01c1.03.79 2.16 1.46 3.38 1.97l.75 5.3c.08.47.49.84.99.84h8c.5 0 .91-.37.99-.84l.75-5.3c1.22-.51 2.35-1.17 3.38-1.97l4.98 2.01c.45.17.97 0 1.22-.43l4-6.93c.25-.43.14-.97-.24-1.28l-4.22-3.31zM24 31c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z"/></svg>
|
After Width: | Height: | Size: 787 B |
1
src/qml/icons/subtitles.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M40 8H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zM8 24h8v4H8v-4zm20 12H8v-4h20v4zm12 0h-8v-4h8v4zm0-8H20v-4h20v4z"/></svg>
|
After Width: | Height: | Size: 255 B |
1
src/qml/icons/volume-down.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M37 24c0-3.53-2.04-6.58-5-8.05v16.11c2.96-1.48 5-4.53 5-8.06zm-27-6v12h8l10 10V8L18 18h-8z"/></svg>
|
After Width: | Height: | Size: 191 B |
1
src/qml/icons/volume-mute.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M33 24c0-3.53-2.04-6.58-5-8.05v4.42l4.91 4.91c.06-.42.09-.85.09-1.28zm5 0c0 1.88-.41 3.65-1.08 5.28l3.03 3.03C41.25 29.82 42 27 42 24c0-8.56-5.99-15.72-14-17.54v4.13c5.78 1.72 10 7.07 10 13.41zM8.55 6L6 8.55 15.45 18H6v12h8l10 10V26.55l8.51 8.51c-1.34 1.03-2.85 1.86-4.51 2.36v4.13c2.75-.63 5.26-1.89 7.37-3.62L39.45 42 42 39.45l-18-18L8.55 6zM24 8l-4.18 4.18L24 16.36V8z"/></svg>
|
After Width: | Height: | Size: 472 B |
1
src/qml/icons/volume-up.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M6 18v12h8l10 10V8L14 18H6zm27 6c0-3.53-2.04-6.58-5-8.05v16.11c2.96-1.48 5-4.53 5-8.06zM28 6.46v4.13c5.78 1.72 10 7.07 10 13.41s-4.22 11.69-10 13.41v4.13c8.01-1.82 14-8.97 14-17.54S36.01 8.28 28 6.46z"/></svg>
|
After Width: | Height: | Size: 301 B |
640
src/qml/main.qml
Normal file
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
20
src/qml/qml.qrc
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<RCC>
|
||||||
|
<qresource prefix="/player">
|
||||||
|
<file>main.qml</file>
|
||||||
|
<file>icons/play.svg</file>
|
||||||
|
<file>icons/pause.svg</file>
|
||||||
|
<file>icons/forward.svg</file>
|
||||||
|
<file>icons/backward.svg</file>
|
||||||
|
<file>icons/settings.svg</file>
|
||||||
|
<file>icons/fullscreen.svg</file>
|
||||||
|
<file>icons/volume-up.svg</file>
|
||||||
|
<file>icons/volume-mute.svg</file>
|
||||||
|
<file>icons/volume-down.svg</file>
|
||||||
|
<file>icons/next.svg</file>
|
||||||
|
<file>icons/prev.svg</file>
|
||||||
|
<file>icons/subtitles.svg</file>
|
||||||
|
<file>fonts/NotoSans.ttf</file>
|
||||||
|
<file>icons/playlist.svg</file>
|
||||||
|
<file>codes.js</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|