moved from github
5
.clang-format
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
Language: Cpp
|
||||
BasedOnStyle: WebKit
|
||||
...
|
||||
|
40
.gitignore
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
build
|
||||
mpv-build
|
||||
*.o
|
||||
KittehPlayer
|
||||
moc_*
|
||||
*AppImage*
|
||||
.qmake.stash
|
||||
qrc_qml.cpp
|
||||
appdir
|
||||
AppDir
|
||||
Makefile
|
||||
linuxdeploy*
|
||||
other_libs
|
||||
sffmpeg
|
||||
*.qmake.stash
|
||||
*.qmlc
|
||||
*.jsc
|
||||
src_qml*
|
||||
qmlcache*
|
||||
qrc_src*
|
||||
discord-rpc
|
||||
*.kate-swp
|
||||
|
||||
CMakeLists.txt.user
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
CMakeScripts
|
||||
Testing
|
||||
Makefile
|
||||
cmake_install.cmake
|
||||
install_manifest.txt
|
||||
compile_commands.json
|
||||
CTestTestfile.cmake
|
||||
_deps
|
||||
*_autogen
|
||||
*.core
|
||||
.core
|
||||
*qmlcache*
|
||||
cmake_install.cmake
|
||||
spdlog
|
29
.travis.yml
Normal file
|
@ -0,0 +1,29 @@
|
|||
language: cpp
|
||||
compiler: gcc
|
||||
sudo: require
|
||||
dist: bionic
|
||||
|
||||
before_install:
|
||||
- mkdir -p $HOME/.cache/apt/partial
|
||||
- sudo rm -rf /var/cache/apt/archives
|
||||
- sudo ln -s $HOME/.cache/apt /var/cache/apt/archives
|
||||
- sudo add-apt-repository ppa:beineri/opt-qt-5.12.6-bionic -y
|
||||
- sudo apt-get update
|
||||
|
||||
install:
|
||||
- sudo apt-get -y install build-essential git qt512-meta-minimal qt512quickcontrols qt512quickcontrols2 qt512svg qt512x11extras qt512graphicaleffects qt512svg libgl1-mesa-dev libmpv-dev libgl1-mesa-dev x11proto-xext-dev libx11-dev python3-setuptools
|
||||
- sudo apt-get build-dep mpv
|
||||
- source /opt/qt*/bin/qt*-env.sh
|
||||
|
||||
script:
|
||||
- time bash scripts/makeappimage.sh
|
||||
|
||||
after_success:
|
||||
- time bash scripts/upload.sh
|
||||
|
||||
branches:
|
||||
except:
|
||||
- # Do not build tags that we create when we upload to GitHub Releases
|
||||
- /^(?i:continuous)/
|
||||
|
||||
|
12
.vscode/c_cpp_properties.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Linux",
|
||||
"compileCommands": "${workspaceFolder}/build/compile_commands.json",
|
||||
"intelliSenseMode": "gcc-x64",
|
||||
"cStandard": "c11",
|
||||
"cppStandard": "c++17"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
110
CMakeLists.txt
Normal file
|
@ -0,0 +1,110 @@
|
|||
cmake_minimum_required(VERSION 3.5.1)
|
||||
|
||||
project(VideoPlayer)
|
||||
include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR})
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
include(ExternalProject)
|
||||
|
||||
find_package(Qt5Core REQUIRED)
|
||||
find_package(Qt5Gui REQUIRED)
|
||||
find_package(Qt5Concurrent REQUIRED)
|
||||
|
||||
find_package(Qt5 CONFIG REQUIRED COMPONENTS Qml Quick Gui Widgets Core X11Extras)
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
option(QML_DEBUG "enable qml debug" OFF)
|
||||
if(QML_DEBUG)
|
||||
add_definitions(-DQT_QML_DEBUG)
|
||||
endif()
|
||||
|
||||
option(OLD_UBUNTU "old ubuntu" OFF)
|
||||
|
||||
find_package(Qt5QuickCompiler)
|
||||
if(Qt5QuickCompiler_FOUND)
|
||||
qtquick_compiler_add_resources(qml_QRC src/qml/qml.qrc)
|
||||
else()
|
||||
qt5_add_resources(qml_QRC src/qml/qml.qrc)
|
||||
endif()
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(MPV REQUIRED mpv)
|
||||
pkg_check_modules(X11 x11)
|
||||
pkg_check_modules(Xext xext)
|
||||
|
||||
|
||||
option(PINEPHONE "for pinephone/lima gpu" OFF)
|
||||
if(PINEPHONE)
|
||||
add_definitions(-DPINEPHONE)
|
||||
endif()
|
||||
|
||||
option(USE_EXTERNAL_SPDLOG "use external spdlog" OFF)
|
||||
|
||||
if(USE_EXTERNAL_SPDLOG)
|
||||
include_directories(${EXTERNAL_SPDLOG_PATH}/include)
|
||||
include_directories(${EXTERNAL_SPDLOG_PATH})
|
||||
include_directories(/app)
|
||||
else()
|
||||
execute_process(
|
||||
COMMAND git clone --depth 1 https://github.com/gabime/spdlog.git
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
)
|
||||
include_directories(${CMAKE_BINARY_DIR}/spdlog/include)
|
||||
endif()
|
||||
|
||||
if(MPV_VERSION VERSION_GREATER "1.27.0")
|
||||
set(SOURCES ${SOURCES} src/Backends/MPV/MPVBackend.cpp)
|
||||
else()
|
||||
add_definitions(-DDISABLE_MPV_RENDER_API)
|
||||
endif()
|
||||
|
||||
|
||||
if(X11_FOUND AND Xext_FOUND)
|
||||
add_definitions(-DENABLE_X11)
|
||||
endif(X11_FOUND AND Xext_FOUND)
|
||||
|
||||
link_directories(/usr/local/lib)
|
||||
#src/Backends/DirectMPV/DirectMPVBackend.cpp
|
||||
|
||||
set(SOURCES
|
||||
src/main.cpp
|
||||
src/utils.cpp
|
||||
src/enums.cpp
|
||||
src/Process.cpp
|
||||
src/ThumbnailCache.cpp
|
||||
src/logger.cpp
|
||||
src/qmldebugger.cpp
|
||||
src/registerTypes.cpp
|
||||
src/Backends/MPVCommon/MPVCommon.cpp
|
||||
src/Backends/MPVNoFBO/MPVNoFBOBackend.cpp
|
||||
${SOURCES}
|
||||
)
|
||||
|
||||
set(CMAKE_BUILD_TYPE DEBUG)
|
||||
|
||||
option(DEBUG "debugging out" OFF)
|
||||
if(DEBUG)
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_CXX_FLAGS} -ggdb -g3 -Og")
|
||||
else()
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_CXX_FLAGS} -s")
|
||||
endif()
|
||||
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-command-line-argument")
|
||||
|
||||
add_executable(VideoPlayer ${SOURCES} ${qml_QRC})
|
||||
set_property(TARGET VideoPlayer PROPERTY CXX_STANDARD 14)
|
||||
|
||||
# Use the Qml/Quick modules from Qt 5.
|
||||
target_link_libraries(VideoPlayer
|
||||
${MPV_LIBRARIES}
|
||||
${X11_LIBRARIES}
|
||||
${Xext_LIBRARIES}
|
||||
Qt5::X11Extras
|
||||
)
|
||||
include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS} ${Qt5Concurrent_INCLUDE_DIRS})
|
||||
qt5_use_modules(VideoPlayer Qml Quick Core Gui Widgets X11Extras)
|
||||
|
||||
install (TARGETS ${PROJECT_NAME} DESTINATION bin)
|
||||
install (FILES "${PROJECT_NAME}.desktop" DESTINATION share/applications)
|
||||
install (FILES "${PROJECT_NAME}.png" DESTINATION share/icons/hicolor/256x256/apps)
|
25
DOCS.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
# VideoPlayer
|
||||
A video player with many themes based on Qt, QML and libmpv.
|
||||
|
||||
## Config Location
|
||||
- On linux the config file will be located at `~/.config/VideoPlayer/VideoPlayer.conf`
|
||||
|
||||
## Supported Languages
|
||||
- `english` English
|
||||
- `spanish` Español
|
||||
- `german` Deutsch
|
||||
- `french` Française
|
||||
- `italian` Italiano
|
||||
- `russian` Русский
|
||||
- `norwegian` Norwegian
|
||||
- `tokipona` toki pona
|
||||
- `telugu` తెలుగు
|
||||
- `vietnamese` Tiếng Việt
|
||||
- Left = config value; Right = language name
|
||||
- If your language isn't listed above then please either contact me (details at bottom of readme) or make a PR with the translations which are located in the file `src/qml/utils/translations.js`.
|
||||
|
||||
## MPV Config
|
||||
- Since VideoPlayer's default backend is based on libmpv, you can use the mpv.conf from `~/.config/mpv/mpv.conf`.
|
||||
|
||||
## SVP Support
|
||||
- VideoPlayer works with SVP, you just need to follow the same guides as for MPV.
|
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.
|
43
README.md
Normal file
|
@ -0,0 +1,43 @@
|
|||
# VideoPlayer
|
||||
|
||||
![made with c++](https://forthebadge.com/images/badges/made-with-c-plus-plus.svg)
|
||||
|
||||
![libmpv 2.29+](https://img.shields.io/badge/libmpv-2.29+-blue.svg?logo=qt&style=for-the-badge)
|
||||
|
||||
![qt 5.12](https://img.shields.io/badge/Qt-5.12-41cd52.svg?logo=qt&style=for-the-badge)
|
||||
|
||||
|
||||
A video player based on Qt, QML and libmpv with themes for many online video players..
|
||||
|
||||
## Themes
|
||||
Screenshots for youtube, niconico and roosterteeth/videojs themes are in screenshots folder
|
||||
|
||||
## How to install
|
||||
### From source
|
||||
#### Dependencies
|
||||
##### Arch Linux
|
||||
```
|
||||
pacman -S git cmake qt5-svg qt5-declarative qt5-quickcontrols qt5-quickcontrols2 qt5-graphicaleffects mpv
|
||||
```
|
||||
##### Ubuntu Bionic
|
||||
```
|
||||
sudo add-apt-repository ppa:beineri/opt-qt-5.12.6-bionic -y
|
||||
sudo apt update
|
||||
sudo apt install build-essential git qt512-meta-minimal qt512quickcontrols qt512quickcontrols2 qt512svg qt512x11extras qt512graphicaleffects qt512svg libgl1-mesa-dev libmpv-dev
|
||||
```
|
||||
##### Debian
|
||||
```
|
||||
sudo apt install build-essential cmake qtquickcontrols2-5-dev qtbase5-dev qtdeclarative5-dev libqt5x11extras5-dev libmpv-dev qml-module-qtquick-controls qml-module-qtquick-controls2 qml-module-qtquick-extras qml-module-qtquick-layouts qml-module-qtquick-dialogs qml-module-qtquick-privatewidgets qml-module-qtquick-localstorage qml-module-qt-labs-settings qml-module-qt-labs-platform qtbase5-private-dev libqt5svg5
|
||||
```
|
||||
- Note that I don't know if this is the full list yet, pop a issue up if building fails.
|
||||
|
||||
#### Instructions
|
||||
- Clone repo
|
||||
- `mkdir build && cd build`
|
||||
- `cmake .. -DCMAKE_INSTALL_PREFIX=/usr`
|
||||
- `make`
|
||||
- `sudo make install`
|
||||
- The finished player will then be installed and you can launch it using `VideoPlayer`
|
||||
|
||||
## Configuration
|
||||
- For docs on VideoPlayer please view DOCS.md or `man VideoPlayer`
|
10
VideoPlayer.desktop
Normal file
|
@ -0,0 +1,10 @@
|
|||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=VideoPlayer
|
||||
Comment=Video Player
|
||||
Icon=VideoPlayer
|
||||
Exec=VideoPlayer %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,videoplayer
|
BIN
VideoPlayer.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
1
VideoPlayer.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 180 180" width="180pt" height="180pt"><defs><clipPath id="_clipPath_pedkB2BO9LJqqpfzYxiy2CNxBWzVm7YQ"><rect width="180" height="180"/></clipPath></defs><g clip-path="url(#_clipPath_pedkB2BO9LJqqpfzYxiy2CNxBWzVm7YQ)"><path d="M 39.207 35.457 L 139.793 35.457 C 145.886 35.457 150.833 40.403 150.833 46.497 L 150.833 147.083 C 150.833 153.176 145.886 158.123 139.793 158.123 L 39.207 158.123 C 33.114 158.123 28.167 153.176 28.167 147.083 L 28.167 46.497 C 28.167 40.403 33.114 35.457 39.207 35.457 Z" style="fill:none;stroke:#F45884;stroke-width:5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:2;"/><polygon points="72.681,35.362,45.563,35.362,59.122,11.877" fill="none" vector-effect="non-scaling-stroke" stroke-width="5" stroke="rgb(244,88,132)" stroke-linejoin="miter" stroke-linecap="square" stroke-miterlimit="3"/><path d="M 45.978 46.139 L 133.567 46.139 C 137.502 46.139 140.697 49.333 140.697 53.268 L 140.697 140.857 C 140.697 144.792 137.502 147.987 133.567 147.987 L 45.978 147.987 C 42.043 147.987 38.849 144.792 38.849 140.857 L 38.849 53.268 C 38.849 49.333 42.043 46.139 45.978 46.139 Z" style="fill:none;stroke:#626262;stroke-width:4;stroke-linecap:square;stroke-miterlimit:2;"/><polygon points="136.388,35.362,109.27,35.362,122.829,11.877" fill="none" vector-effect="non-scaling-stroke" stroke-width="5" stroke="rgb(244,88,132)" stroke-linejoin="miter" stroke-linecap="square" stroke-miterlimit="3"/><polygon points="120.167,100.314,71.188,128.592,71.188,72.036" fill="rgb(98,98,98)"/></g></svg>
|
After Width: | Height: | Size: 1.7 KiB |
8
format-code.sh
Executable file
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env bash
|
||||
set -x
|
||||
SOURCE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||
pushd $SOURCE_DIR
|
||||
find . -name "*.qml" -exec qmlfmt -i 2 -w {} \;
|
||||
clang-format -style=file -i $(find src -name "*.cpp" -o -name "*.hpp" -o -name "*.c" -o -name "*.h")
|
||||
#find . -name "*.cpp" -o -name "*.hpp" -o -name "*.c" -o -name "*.h" -exec clang-format -style=file -i {} \;
|
||||
popd
|
BIN
screenshots/NicoNico.png
Normal file
After Width: | Height: | Size: 106 KiB |
BIN
screenshots/RoosterTeeth.png
Normal file
After Width: | Height: | Size: 642 KiB |
BIN
screenshots/YouTube.png
Normal file
After Width: | Height: | Size: 124 KiB |
9
scripts/rendersvgs.sh
Executable file
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -x
|
||||
SOURCE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||
cd ${SOURCE_DIR}/../src/qml/icons
|
||||
|
||||
for file in `find . -name "*.svg"`; do
|
||||
rendersvg "$file" "$(echo $file | sed s/.svg/.png/)"
|
||||
done
|
332
src/Backends/MPV/MPVBackend.cpp
Normal file
|
@ -0,0 +1,332 @@
|
|||
#include "src/Backends/MPV/MPVBackend.hpp"
|
||||
#include "src/Backends/MPVCommon/MPVCommon.hpp"
|
||||
#include "src/qthelper.hpp"
|
||||
#include "src/utils.hpp"
|
||||
#include <QByteArray>
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QEvent>
|
||||
#include <QGuiApplication>
|
||||
#include <QIcon>
|
||||
#include <QMetaObject>
|
||||
#include <QObject>
|
||||
#include <QOpenGLContext>
|
||||
#include <QOpenGLFramebufferObject>
|
||||
#include <QQuickWindow>
|
||||
#include <QtCore/qglobal.h>
|
||||
#include <clocale>
|
||||
#include <iostream>
|
||||
#include <mpv/render_gl.h>
|
||||
#include <stdexcept>
|
||||
#include <stdio.h>
|
||||
class QQuickItem;
|
||||
class QSize;
|
||||
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
#ifdef ENABLE_X11
|
||||
#include <QX11Info> // IWYU pragma: keep
|
||||
#include <QtX11Extras/QX11Info> // IWYU pragma: keep
|
||||
#include <X11/Xlib.h> // IWYU pragma: keep
|
||||
#include <X11/Xutil.h> // IWYU pragma: keep
|
||||
#include <qx11info_x11.h> // IWYU pragma: keep
|
||||
#endif
|
||||
#include <qpa/qplatformnativeinterface.h> // IWYU pragma: keep
|
||||
#endif
|
||||
|
||||
bool usedirect = false;
|
||||
|
||||
namespace {
|
||||
|
||||
void wakeup(void* ctx)
|
||||
{
|
||||
QCoreApplication::postEvent((MPVBackend*)ctx, new QEvent(QEvent::User));
|
||||
}
|
||||
|
||||
void on_mpv_redraw(void* ctx)
|
||||
{
|
||||
QMetaObject::invokeMethod(
|
||||
reinterpret_cast<MPVBackend*>(ctx), "update", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
static void*
|
||||
get_proc_address_mpv(void* ctx, const char* name)
|
||||
{
|
||||
return reinterpret_cast<void*>(
|
||||
reinterpret_cast<QOpenGLContext*>(ctx)->getProcAddress(QByteArray(name)));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class MpvRenderer : public QQuickFramebufferObject::Renderer {
|
||||
MPVBackend* obj;
|
||||
|
||||
public:
|
||||
MpvRenderer(MPVBackend* new_obj)
|
||||
: obj{ new_obj }
|
||||
{
|
||||
if (usedirect) {
|
||||
int r = mpv_opengl_cb_init_gl(obj->mpv_gl_cb, NULL, get_proc_address_mpv, QOpenGLContext::currentContext());
|
||||
if (r < 0) {
|
||||
std::cout << "No." << std::endl;
|
||||
throw std::runtime_error("failed to initialize mpv GL context");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 && !usedirect) {
|
||||
mpv_opengl_init_params gl_init_params{ get_proc_address_mpv,
|
||||
QOpenGLContext::currentContext(),
|
||||
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 },
|
||||
{ MPV_RENDER_PARAM_INVALID, nullptr }
|
||||
};
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
#ifdef ENABLE_X11
|
||||
if (QGuiApplication::platformName().contains("xcb")) {
|
||||
params[2].type = MPV_RENDER_PARAM_X11_DISPLAY;
|
||||
params[2].data = QX11Info::display();
|
||||
}
|
||||
#endif
|
||||
if (QGuiApplication::platformName().contains("wayland")) {
|
||||
params[2].type = MPV_RENDER_PARAM_WL_DISPLAY;
|
||||
auto* native = QGuiApplication::platformNativeInterface();
|
||||
params[2].data = native->nativeResourceForWindow("display", NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (mpv_render_context_create(&obj->mpv_gl, obj->mpv, params) < 0) {
|
||||
std::cout << "Failed to use render API, try setting Backend/direct to true in settings." << std::endl;
|
||||
throw std::runtime_error("failed to initialize mpv GL context");
|
||||
}
|
||||
mpv_render_context_set_update_callback(obj->mpv_gl, on_mpv_redraw, obj);
|
||||
}
|
||||
QMetaObject::invokeMethod(obj, "startPlayer");
|
||||
|
||||
return QQuickFramebufferObject::Renderer::createFramebufferObject(size);
|
||||
}
|
||||
|
||||
void render()
|
||||
{
|
||||
obj->window()->resetOpenGLState();
|
||||
QOpenGLFramebufferObject* fbo = framebufferObject();
|
||||
if (usedirect) {
|
||||
mpv_opengl_cb_draw(obj->mpv_gl_cb, fbo->handle(), fbo->width(), fbo->height());
|
||||
} else {
|
||||
mpv_opengl_fbo mpfbo{ .fbo = static_cast<int>(fbo->handle()),
|
||||
.w = fbo->width(),
|
||||
.h = fbo->height(),
|
||||
.internal_format = 0 };
|
||||
int flip_y{ 0 };
|
||||
mpv_render_param params[] = { { MPV_RENDER_PARAM_OPENGL_FBO, &mpfbo },
|
||||
{ MPV_RENDER_PARAM_FLIP_Y, &flip_y },
|
||||
{ MPV_RENDER_PARAM_INVALID, nullptr } };
|
||||
mpv_render_context_render(obj->mpv_gl, params);
|
||||
}
|
||||
|
||||
obj->window()->resetOpenGLState();
|
||||
}
|
||||
};
|
||||
|
||||
MPVBackend::MPVBackend(QQuickItem* parent)
|
||||
: QQuickFramebufferObject(parent)
|
||||
, mpv{ mpv_create() }
|
||||
, mpv_gl(nullptr)
|
||||
, mpv_gl_cb(nullptr)
|
||||
|
||||
{
|
||||
if (!mpv)
|
||||
throw std::runtime_error("could not create mpv context");
|
||||
|
||||
QSettings settings;
|
||||
usedirect = settings.value("Backend/direct", false).toBool();
|
||||
|
||||
mpv_set_option_string(mpv, "terminal", "true");
|
||||
mpv_set_option_string(mpv, "msg-level", "all=v");
|
||||
|
||||
// Fix?
|
||||
mpv_set_option_string(mpv, "ytdl", "yes");
|
||||
|
||||
mpv_set_option_string(mpv, "slang", "en");
|
||||
|
||||
mpv_set_option_string(mpv, "config", "yes");
|
||||
mpv_observe_property(mpv, 0, "tracks-menu", MPV_FORMAT_NONE);
|
||||
mpv_observe_property(mpv, 0, "chapter-list", MPV_FORMAT_NODE);
|
||||
mpv_observe_property(mpv, 0, "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, "audio-device-list", MPV_FORMAT_NODE);
|
||||
mpv_observe_property(mpv, 0, "playlist-pos", MPV_FORMAT_DOUBLE);
|
||||
mpv_observe_property(mpv, 0, "volume", MPV_FORMAT_NONE);
|
||||
mpv_observe_property(mpv, 0, "mute", MPV_FORMAT_NONE);
|
||||
mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
|
||||
mpv_observe_property(mpv, 0, "media-title", MPV_FORMAT_STRING);
|
||||
mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
|
||||
mpv_observe_property(mpv, 0, "demuxer-cache-duration", MPV_FORMAT_DOUBLE);
|
||||
mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_NODE);
|
||||
mpv_observe_property(mpv, 0, "playlist", MPV_FORMAT_NODE);
|
||||
mpv_observe_property(mpv, 0, "speed", MPV_FORMAT_DOUBLE);
|
||||
|
||||
mpv_request_log_messages(mpv, "v");
|
||||
|
||||
mpv_set_wakeup_callback(mpv, wakeup, this);
|
||||
|
||||
if (mpv_initialize(mpv) < 0)
|
||||
throw std::runtime_error("could not initialize mpv context");
|
||||
|
||||
if (usedirect) {
|
||||
mpv_set_option_string(mpv, "vo", "libmpv");
|
||||
mpv_gl_cb = (mpv_opengl_cb_context*)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
|
||||
if (!mpv_gl_cb)
|
||||
throw std::runtime_error("OpenGL not compiled in");
|
||||
mpv_opengl_cb_set_update_callback(mpv_gl_cb, on_mpv_redraw, (void*)this);
|
||||
} else {
|
||||
mpv_set_option_string(mpv, "vo", "libmpv");
|
||||
}
|
||||
|
||||
connect(this,
|
||||
&MPVBackend::onUpdate,
|
||||
this,
|
||||
&MPVBackend::doUpdate,
|
||||
Qt::QueuedConnection);
|
||||
connect(this,
|
||||
&MPVBackend::positionChanged,
|
||||
this,
|
||||
&MPVBackend::updateDurationString,
|
||||
Qt::QueuedConnection);
|
||||
connect(this,
|
||||
&MPVBackend::durationChanged,
|
||||
this,
|
||||
&MPVBackend::updateDurationString,
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
MPVBackend::~MPVBackend()
|
||||
{
|
||||
printf("Shutting down...\n");
|
||||
Utils::SetDPMS(true);
|
||||
command("write-watch-later-config");
|
||||
|
||||
if (usedirect && mpv_gl_cb) {
|
||||
mpv_opengl_cb_uninit_gl(mpv_gl_cb);
|
||||
} else if (mpv_gl) {
|
||||
mpv_render_context_free(mpv_gl);
|
||||
}
|
||||
|
||||
mpv_terminate_destroy(mpv);
|
||||
printf("MPV terminated.\n");
|
||||
}
|
||||
|
||||
void MPVBackend::on_update(void* ctx)
|
||||
{
|
||||
MPVBackend* self = (MPVBackend*)ctx;
|
||||
emit self->onUpdate();
|
||||
}
|
||||
|
||||
void MPVBackend::doUpdate()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
QVariant
|
||||
MPVBackend::getProperty(const QString& name) const
|
||||
{
|
||||
return mpv::qt::get_property_variant(mpv, name);
|
||||
}
|
||||
|
||||
void MPVBackend::command(const QVariant& params)
|
||||
{
|
||||
mpv::qt::node_builder node(params);
|
||||
mpv_command_node(mpv, node.node(), nullptr);
|
||||
}
|
||||
|
||||
void MPVBackend::setProperty(const QString& name, const QVariant& value)
|
||||
{
|
||||
mpv::qt::node_builder node(value);
|
||||
qDebug() << "Setting property" << name << "to" << value;
|
||||
mpv_set_property(mpv, name.toUtf8().data(), MPV_FORMAT_NODE, node.node());
|
||||
}
|
||||
|
||||
void MPVBackend::setOption(const QString& name, const QVariant& value)
|
||||
{
|
||||
mpv::qt::set_option_variant(mpv, name, value);
|
||||
}
|
||||
|
||||
QVariant
|
||||
MPVBackend::playerCommand(const Enums::Commands& cmd)
|
||||
{
|
||||
return playerCommand(cmd, QVariant("NoArgProvided"));
|
||||
}
|
||||
|
||||
QVariant
|
||||
MPVBackend::playerCommand(const Enums::Commands& cmd, const QVariant& args)
|
||||
{
|
||||
return MPVCommon::playerCommand(this, cmd, args);
|
||||
}
|
||||
|
||||
QString
|
||||
MPVBackend::getStats()
|
||||
{
|
||||
return MPVCommon::getStats(this);
|
||||
}
|
||||
|
||||
void MPVBackend::updateDurationString(int numTime)
|
||||
{
|
||||
QMetaMethod metaMethod = sender()->metaObject()->method(senderSignalIndex());
|
||||
MPVCommon::updateDurationString(this, numTime, metaMethod);
|
||||
}
|
||||
|
||||
void MPVBackend::toggleOnTop()
|
||||
{
|
||||
onTop = !onTop;
|
||||
Utils::AlwaysOnTop(window()->winId(), onTop);
|
||||
}
|
||||
|
||||
bool MPVBackend::event(QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::User) {
|
||||
on_mpv_events();
|
||||
}
|
||||
return QObject::event(event);
|
||||
}
|
||||
|
||||
void MPVBackend::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);
|
||||
}
|
||||
}
|
||||
|
||||
QVariantMap
|
||||
MPVBackend::getAudioDevices(const QVariant& drivers) const
|
||||
{
|
||||
return MPVCommon::getAudioDevices(drivers);
|
||||
}
|
||||
|
||||
void MPVBackend::handle_mpv_event(mpv_event* event)
|
||||
{
|
||||
MPVCommon::handle_mpv_event(this, event);
|
||||
}
|
||||
|
||||
QQuickFramebufferObject::Renderer*
|
||||
MPVBackend::createRenderer() const
|
||||
{
|
||||
window()->setIcon(QIcon(":/icon.png"));
|
||||
window()->setPersistentOpenGLContext(true);
|
||||
window()->setPersistentSceneGraph(true);
|
||||
return new MpvRenderer(const_cast<MPVBackend*>(this));
|
||||
}
|
99
src/Backends/MPV/MPVBackend.hpp
Normal file
|
@ -0,0 +1,99 @@
|
|||
#ifndef MPVBackend_H
|
||||
#define MPVBackend_H
|
||||
|
||||
#include "src/backendinterface.hpp"
|
||||
#include "src/enums.hpp"
|
||||
#include <QEvent>
|
||||
#include <QMetaType>
|
||||
#include <QObject>
|
||||
#include <QQuickFramebufferObject>
|
||||
#include <QQuickItem>
|
||||
#include <QSettings>
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
#include <mpv/client.h>
|
||||
#include <mpv/opengl_cb.h>
|
||||
#include <mpv/render.h>
|
||||
|
||||
class MPVBackend
|
||||
: public QQuickFramebufferObject,
|
||||
public BackendInterface {
|
||||
Q_INTERFACES(BackendInterface)
|
||||
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool logging READ logging WRITE setLogging)
|
||||
|
||||
mpv_handle* mpv;
|
||||
mpv_render_context* mpv_gl;
|
||||
mpv_opengl_cb_context* mpv_gl_cb;
|
||||
|
||||
QSettings settings;
|
||||
bool onTop = false;
|
||||
bool m_logging = true;
|
||||
|
||||
friend class MpvRenderer;
|
||||
|
||||
public:
|
||||
static void on_update(void* ctx);
|
||||
|
||||
MPVBackend(QQuickItem* parent = 0);
|
||||
virtual ~MPVBackend();
|
||||
virtual Renderer* createRenderer() const;
|
||||
|
||||
void setLogging(bool a)
|
||||
{
|
||||
if (a != m_logging) {
|
||||
m_logging = a;
|
||||
}
|
||||
}
|
||||
bool logging() const { return m_logging; }
|
||||
|
||||
int lastTime = 0;
|
||||
double lastSpeed = 0;
|
||||
QString totalDurationString;
|
||||
QString lastPositionString;
|
||||
|
||||
public slots:
|
||||
QVariant playerCommand(const Enums::Commands& command, const QVariant& args);
|
||||
QVariant playerCommand(const Enums::Commands& command);
|
||||
void toggleOnTop();
|
||||
QString getStats();
|
||||
// Optional but handy for MPV or custom backend settings.
|
||||
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;
|
||||
// Just used for adding missing audio devices to list.
|
||||
QVariantMap getAudioDevices(const QVariant& drivers) const;
|
||||
bool event(QEvent* event);
|
||||
|
||||
signals:
|
||||
void onUpdate();
|
||||
void mpv_events();
|
||||
void onMpvEvent(mpv_event* event);
|
||||
// All below required for Player API
|
||||
void playStatusChanged(const Enums::PlayStatus& status);
|
||||
void volumeStatusChanged(const Enums::VolumeStatus& status);
|
||||
void volumeChanged(const int& volume);
|
||||
void durationChanged(const double& duration);
|
||||
void positionChanged(const double& position);
|
||||
void cachedDurationChanged(const double& duration);
|
||||
void playlistPositionChanged(const double& position);
|
||||
void titleChanged(const QString& title);
|
||||
void durationStringChanged(const QString& string);
|
||||
void tracksChanged(const QVariantList& tracks);
|
||||
void audioDevicesChanged(const QVariantMap& devices);
|
||||
void playlistChanged(const QVariantList& devices);
|
||||
void chaptersChanged(const QVariantList& devices);
|
||||
void speedChanged(const double& speed);
|
||||
|
||||
private slots:
|
||||
void doUpdate();
|
||||
void on_mpv_events();
|
||||
void updateDurationString(int numTime);
|
||||
|
||||
private:
|
||||
void handle_mpv_event(mpv_event* event);
|
||||
};
|
||||
|
||||
#endif
|
574
src/Backends/MPVCommon/MPVCommon.cpp
Normal file
|
@ -0,0 +1,574 @@
|
|||
#include "src/Backends/MPVCommon/MPVCommon.hpp"
|
||||
#include "spdlog/logger.h"
|
||||
#include "src/backendinterface.hpp"
|
||||
#include "src/logger.h"
|
||||
#include "src/utils.hpp"
|
||||
#include <QByteArray>
|
||||
#include <QCoreApplication>
|
||||
#include <QJsonObject>
|
||||
#include <QList>
|
||||
#include <QLocale>
|
||||
#include <QMap>
|
||||
#include <QMetaMethod>
|
||||
#include <QMetaObject>
|
||||
#include <QObject>
|
||||
#include <QSettings>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <spdlog/fmt/fmt.h>
|
||||
#include <string.h>
|
||||
|
||||
auto mpvLogger = initLogger("mpv");
|
||||
|
||||
QString humanSize(uint64_t bytes)
|
||||
{
|
||||
const char* suffix[5] = { "B", "KB", "MB", "GB", "TB" };
|
||||
char length = sizeof(suffix) / sizeof(suffix[0]);
|
||||
|
||||
int i = 0;
|
||||
double dblBytes = bytes;
|
||||
|
||||
if (bytes > 1024) {
|
||||
for (i = 0; (bytes / 1024) > 0 && i < length - 1; i++, bytes /= 1024)
|
||||
dblBytes = bytes / 1024.0;
|
||||
}
|
||||
|
||||
static char output[200];
|
||||
sprintf(output, "%.02lf %s", dblBytes, suffix[i]);
|
||||
return QString(output);
|
||||
}
|
||||
|
||||
static inline QVariant mpvnode_to_variant(const mpv_node* node)
|
||||
{
|
||||
if (!node) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
switch (node->format) {
|
||||
case MPV_FORMAT_STRING:
|
||||
return QVariant(QString::fromUtf8(node->u.string));
|
||||
case MPV_FORMAT_FLAG:
|
||||
return QVariant(static_cast<bool>(node->u.flag));
|
||||
case MPV_FORMAT_INT64:
|
||||
return QVariant(static_cast<qlonglong>(node->u.int64));
|
||||
case MPV_FORMAT_DOUBLE:
|
||||
return QVariant(node->u.double_);
|
||||
case MPV_FORMAT_NODE_ARRAY: {
|
||||
mpv_node_list* list = node->u.list;
|
||||
QVariantList qlist;
|
||||
for (int n = 0; n < list->num; n++)
|
||||
qlist.append(mpvnode_to_variant(&list->values[n]));
|
||||
return QVariant(qlist);
|
||||
}
|
||||
case MPV_FORMAT_NODE_MAP: {
|
||||
mpv_node_list* list = node->u.list;
|
||||
QVariantMap qmap;
|
||||
for (int n = 0; n < list->num; n++) {
|
||||
qmap.insert(QString::fromUtf8(list->keys[n]),
|
||||
mpvnode_to_variant(&list->values[n]));
|
||||
}
|
||||
return QVariant(qmap);
|
||||
}
|
||||
default: // MPV_FORMAT_NONE, unknown values (e.g. future extensions)
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
namespace MPVCommon {
|
||||
QString getStats(BackendInterface* b)
|
||||
{
|
||||
QString stats;
|
||||
stats = "<style> blockquote { text-indent: 0px; margin-left:40px; margin-top: 0px; "
|
||||
"margin-bottom: 0px; padding-bottom: 0px; padding-top: 0px; padding-left: "
|
||||
"0px; } b span p br { margin-bottom: 0px; margin-top: 0px; padding-top: "
|
||||
"0px; padding-botom: 0px; text-indent: 0px; } </style>";
|
||||
QString filename = b->getProperty("filename").toString();
|
||||
// File Info
|
||||
stats += "<b>File:</b> " + filename;
|
||||
stats += "<blockquote>";
|
||||
QString title = b->getProperty("media-title").toString();
|
||||
if (title != filename) {
|
||||
stats += "<b>Title:</b> " + title + "<br>";
|
||||
}
|
||||
QString fileFormat = b->getProperty("file-format").toString();
|
||||
stats += "<b>Format/Protocol:</b> " + fileFormat + "<br>";
|
||||
double cacheUsed = b->getProperty("cache-used").toDouble();
|
||||
int demuxerSecs = b->getProperty("demuxer-cache-duration").toInt();
|
||||
QVariantMap demuxerState = b->getProperty("demuxer-cache-state").toMap();
|
||||
int demuxerCache = demuxerState.value("fw-bytes", QVariant(0)).toInt();
|
||||
|
||||
if (demuxerSecs + demuxerCache + cacheUsed > 0) {
|
||||
QString cacheStats;
|
||||
cacheStats += "<b>Total Cache:</b> ";
|
||||
cacheStats += humanSize(demuxerCache + cacheUsed);
|
||||
cacheStats += " (<b>Demuxer:</b> ";
|
||||
|
||||
cacheStats += humanSize(demuxerCache);
|
||||
cacheStats += ", ";
|
||||
cacheStats += QString::number(demuxerSecs) + "s) ";
|
||||
double cacheSpeed = b->getProperty("cache-speed").toDouble();
|
||||
if (cacheSpeed > 0) {
|
||||
cacheStats += "<b>Speed:</b> ";
|
||||
cacheStats += humanSize(demuxerSecs);
|
||||
cacheStats += "/s";
|
||||
}
|
||||
cacheStats += "<br>";
|
||||
stats += cacheStats;
|
||||
}
|
||||
QString fileSize = humanSize(b->getProperty("file-size").toInt()).remove("-");
|
||||
stats += "<b>Size:</b> " + fileSize + "<br>";
|
||||
|
||||
stats += "</blockquote>";
|
||||
// Video Info
|
||||
QVariant videoParams = b->getProperty("video-params");
|
||||
if (videoParams.isNull()) {
|
||||
videoParams = b->getProperty("video-out-params");
|
||||
}
|
||||
if (!videoParams.isNull()) {
|
||||
stats += "<b>Video:</b> " + b->getProperty("video-codec").toString();
|
||||
stats += "<blockquote>";
|
||||
QString avsync = QString::number(b->getProperty("avsync").toDouble(), 'f', 3);
|
||||
stats += "<b>A-V:</b> " + QString(avsync) + "<br>";
|
||||
|
||||
stats += "<b>Dropped Frames:</b> ";
|
||||
int dFDC = b->getProperty("decoder-frame-drop-count").toInt();
|
||||
if (dFDC > 0) {
|
||||
stats += QString::number(dFDC) + " (decoder) ";
|
||||
}
|
||||
int fDC = b->getProperty("frame-drop-count").toInt();
|
||||
if (fDC > 0) {
|
||||
stats += QString::number(fDC) + " (output)";
|
||||
}
|
||||
stats += "<br>";
|
||||
|
||||
int dFPS = b->getProperty("display-fps").toInt();
|
||||
int eDFPS = b->getProperty("estimated-display-fps").toInt();
|
||||
if ((dFPS + eDFPS) > 0) {
|
||||
stats += "<b>Display FPS:</b> ";
|
||||
|
||||
if (dFPS > 0) {
|
||||
stats += QString::number(dFPS);
|
||||
stats += " (specified) ";
|
||||
}
|
||||
if (eDFPS > 0) {
|
||||
stats += QString::number(eDFPS);
|
||||
stats += " (estimated)";
|
||||
}
|
||||
stats += "<br>";
|
||||
}
|
||||
|
||||
int cFPS = b->getProperty("container-fps").toInt();
|
||||
int eVFPS = b->getProperty("estimated-vf-fps").toInt();
|
||||
if ((cFPS + eVFPS) > 0) {
|
||||
stats += "<b>FPS:</b> ";
|
||||
|
||||
if (cFPS > 0) {
|
||||
stats += QString::number(cFPS);
|
||||
stats += " (specified) ";
|
||||
}
|
||||
if (eVFPS > 0) {
|
||||
stats += QString::number(eVFPS);
|
||||
stats += " (estimated)";
|
||||
}
|
||||
stats += "<br>";
|
||||
}
|
||||
QVariantMap vPM = videoParams.toMap();
|
||||
stats += "<b>Native Resolution:</b> ";
|
||||
stats += vPM["w"].toString() + " x " + vPM["h"].toString();
|
||||
stats += "<br>";
|
||||
|
||||
stats += "<b>Window Scale:</b> ";
|
||||
stats += vPM["window-scale"].toString();
|
||||
stats += "<br>";
|
||||
|
||||
stats += "<b>Aspect Ratio:</b> ";
|
||||
stats += vPM["aspect"].toString();
|
||||
stats += "<br>";
|
||||
|
||||
stats += "<b>Pixel Format:</b> ";
|
||||
stats += vPM["pixelformat"].toString();
|
||||
stats += "<br>";
|
||||
|
||||
stats += "<b>Primaries:</b> ";
|
||||
stats += vPM["primaries"].toString();
|
||||
stats += " <b>Colormatrix:</b> ";
|
||||
stats += vPM["colormatrix"].toString();
|
||||
stats += "<br>";
|
||||
|
||||
stats += "<b>Levels:</b> ";
|
||||
stats += vPM["colorlevels"].toString();
|
||||
double sigPeak = vPM.value("sig-peak", QVariant(0.0)).toInt();
|
||||
if (sigPeak > 0) {
|
||||
stats += " (HDR Peak: " + QString::number(sigPeak) + ")";
|
||||
}
|
||||
stats += "<br>";
|
||||
|
||||
stats += "<b>Gamma:</b> ";
|
||||
stats += vPM["gamma"].toString();
|
||||
stats += "<br>";
|
||||
|
||||
int pVB = b->getProperty("packet-video-bitrate").toInt();
|
||||
if (pVB > 0) {
|
||||
stats += "<b>Bitrate:</b> ";
|
||||
stats += humanSize(pVB) + "/s";
|
||||
stats += "<br>";
|
||||
}
|
||||
|
||||
stats += "</blockquote>";
|
||||
}
|
||||
QVariant audioParams = b->getProperty("audio-params");
|
||||
if (audioParams.isNull()) {
|
||||
audioParams = b->getProperty("audio-out-params");
|
||||
}
|
||||
if (!audioParams.isNull()) {
|
||||
stats += "<b>Audio:</b> " + b->getProperty("audio-codec").toString();
|
||||
stats += "<blockquote>";
|
||||
QVariantMap aPM = audioParams.toMap();
|
||||
|
||||
stats += "<b>Format:</b> ";
|
||||
stats += aPM["format"].toString();
|
||||
stats += "<br>";
|
||||
|
||||
stats += "<b>Sample Rate:</b> ";
|
||||
stats += aPM["samplerate"].toString() + " Hz";
|
||||
stats += "<br>";
|
||||
|
||||
stats += "<b>Channels:</b> ";
|
||||
stats += aPM["chanel-count"].toString();
|
||||
stats += "<br>";
|
||||
|
||||
int pAB = b->getProperty("packet-audio-bitrate").toInt();
|
||||
if (pAB > 0) {
|
||||
stats += "<b>Bitrate:</b> ";
|
||||
stats += humanSize(pAB) + "/s";
|
||||
stats += "<br>";
|
||||
}
|
||||
|
||||
stats += "</blockquote>";
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
QVariant playerCommand(BackendInterface* b, const Enums::Commands& cmd, const QVariant& args)
|
||||
{
|
||||
switch (cmd) {
|
||||
case Enums::Commands::TogglePlayPause: {
|
||||
b->command(QVariantList() << "cycle"
|
||||
<< "pause");
|
||||
break;
|
||||
}
|
||||
case Enums::Commands::ToggleMute: {
|
||||
b->command(QVariantList() << "cycle"
|
||||
<< "mute");
|
||||
break;
|
||||
}
|
||||
case Enums::Commands::SetAudioDevice: {
|
||||
b->setProperty("audio-device", args.toString());
|
||||
break;
|
||||
}
|
||||
case Enums::Commands::SetVolume: {
|
||||
b->command(QVariantList() << "set"
|
||||
<< "volume" << args);
|
||||
break;
|
||||
}
|
||||
|
||||
case Enums::Commands::AddVolume: {
|
||||
|
||||
b->command(QVariantList() << "add"
|
||||
<< "volume" << args);
|
||||
break;
|
||||
}
|
||||
|
||||
case Enums::Commands::AddSpeed: {
|
||||
|
||||
QString speedString = QString::number(b->getProperty("speed").toDouble() + args.toDouble());
|
||||
QVariant newSpeed = QVariant(speedString.left(speedString.lastIndexOf('.') + 2));
|
||||
|
||||
b->playerCommand(Enums::Commands::SetSpeed, newSpeed);
|
||||
break;
|
||||
}
|
||||
|
||||
case Enums::Commands::SubtractSpeed: {
|
||||
|
||||
QString speedString = QString::number(b->getProperty("speed").toDouble() - args.toDouble());
|
||||
QVariant newSpeed = QVariant(speedString.left(speedString.lastIndexOf('.') + 2));
|
||||
b->playerCommand(Enums::Commands::SetSpeed, newSpeed);
|
||||
break;
|
||||
}
|
||||
|
||||
case Enums::Commands::ChangeSpeed: {
|
||||
|
||||
b->playerCommand(
|
||||
Enums::Commands::SetSpeed,
|
||||
QVariant(b->getProperty("speed").toDouble() * args.toDouble()));
|
||||
break;
|
||||
}
|
||||
|
||||
case Enums::Commands::SetSpeed: {
|
||||
|
||||
b->command(QVariantList() << "set"
|
||||
<< "speed" << args.toString());
|
||||
break;
|
||||
}
|
||||
case Enums::Commands::ToggleStats: {
|
||||
|
||||
b->command(QVariantList() << "script-binding"
|
||||
<< "stats/display-stats-toggle");
|
||||
break;
|
||||
}
|
||||
case Enums::Commands::NextAudioTrack: {
|
||||
|
||||
b->command(QVariantList() << "cycle"
|
||||
<< "audio");
|
||||
break;
|
||||
}
|
||||
case Enums::Commands::NextSubtitleTrack: {
|
||||
|
||||
b->command(QVariantList() << "cycle"
|
||||
<< "sub");
|
||||
|
||||
break;
|
||||
}
|
||||
case Enums::Commands::NextVideoTrack: {
|
||||
b->command(QVariantList() << "cycle"
|
||||
<< "video");
|
||||
break;
|
||||
}
|
||||
case Enums::Commands::PreviousPlaylistItem: {
|
||||
|
||||
b->command(QVariantList() << "playlist-prev");
|
||||
|
||||
break;
|
||||
}
|
||||
case Enums::Commands::NextPlaylistItem: {
|
||||
|
||||
b->command(QVariantList() << "playlist-next"
|
||||
<< "force");
|
||||
break;
|
||||
}
|
||||
case Enums::Commands::LoadFile: {
|
||||
b->command(QVariantList() << "loadfile" << args);
|
||||
|
||||
break;
|
||||
}
|
||||
case Enums::Commands::AppendFile: {
|
||||
|
||||
b->command(QVariantList() << "loadfile" << args << "append-play");
|
||||
break;
|
||||
}
|
||||
case Enums::Commands::SeekAbsolute: {
|
||||
|
||||
b->command(QVariantList() << "seek" << args << "absolute");
|
||||
|
||||
break;
|
||||
}
|
||||
case Enums::Commands::Seek: {
|
||||
|
||||
b->command(QVariantList() << "seek" << args);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Enums::Commands::ForwardFrame: {
|
||||
|
||||
b->command(QVariantList() << "frame-step");
|
||||
|
||||
break;
|
||||
}
|
||||
case Enums::Commands::BackwardFrame: {
|
||||
|
||||
b->command(QVariantList() << "frame-back-step");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Enums::Commands::SetTrack: {
|
||||
|
||||
b->command(QVariantList() << "set" << args.toList()[0] << args.toList()[1]);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Enums::Commands::SetPlaylistPos: {
|
||||
|
||||
b->command(QVariantList() << "set"
|
||||
<< "playlist-pos" << args);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Enums::Commands::ForcePause: {
|
||||
|
||||
b->command(QVariantList() << "set"
|
||||
<< "pause"
|
||||
<< "yes");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Enums::Commands::PreviousChapter: {
|
||||
|
||||
b->command(QVariantList() << "add" << "chapter" << "-1");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Enums::Commands::NextChapter: {
|
||||
|
||||
b->command(QVariantList() << "add" << "chapter" << "1");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
//qDebug() << "Command not found: " << cmd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return QVariant("NoOutput");
|
||||
}
|
||||
|
||||
void updateDurationString(BackendInterface* b, int numTime, QMetaMethod metaMethod)
|
||||
{
|
||||
QVariant speed = b->getProperty("speed");
|
||||
QSettings settings;
|
||||
if (metaMethod.name() == "positionChanged") {
|
||||
if (speed != b->lastSpeed) {
|
||||
b->lastSpeed = speed.toDouble();
|
||||
} else {
|
||||
if (numTime == b->lastTime) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
b->lastTime = numTime;
|
||||
b->lastPositionString = Utils::createTimestamp(b->lastTime);
|
||||
} else if (metaMethod.name() == "durationChanged") {
|
||||
b->totalDurationString = Utils::createTimestamp(numTime);
|
||||
}
|
||||
QString durationString;
|
||||
durationString += b->lastPositionString;
|
||||
durationString += " / ";
|
||||
durationString += b->totalDurationString;
|
||||
if (b->lastSpeed != 1) {
|
||||
if (settings.value("Appearance/themeName", "").toString() != "RoosterTeeth") {
|
||||
durationString += " (" + speed.toString() + "x)";
|
||||
}
|
||||
}
|
||||
emit b->durationStringChanged(durationString);
|
||||
}
|
||||
|
||||
void handle_mpv_event(BackendInterface* b, 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;
|
||||
emit b->positionChanged(time);
|
||||
}
|
||||
} else if (strcmp(prop->name, "duration") == 0) {
|
||||
if (prop->format == MPV_FORMAT_DOUBLE) {
|
||||
double time = *(double*)prop->data;
|
||||
emit b->durationChanged(time);
|
||||
}
|
||||
} else if (strcmp(prop->name, "mute") == 0 || strcmp(prop->name, "volume") == 0) {
|
||||
double volume = b->getProperty("volume").toDouble();
|
||||
bool mute = b->getProperty("mute").toBool();
|
||||
if (mute || volume == 0) {
|
||||
emit b->volumeStatusChanged(Enums::VolumeStatus::Muted);
|
||||
} else {
|
||||
if (volume < 25) {
|
||||
emit b->volumeStatusChanged(Enums::VolumeStatus::Low);
|
||||
} else {
|
||||
emit b->volumeStatusChanged(Enums::VolumeStatus::Normal);
|
||||
}
|
||||
}
|
||||
// emit volumeChanged(volume);
|
||||
} else if (strcmp(prop->name, "media-title") == 0) {
|
||||
if (prop->format == MPV_FORMAT_STRING) {
|
||||
char* title = *(char**)prop->data;
|
||||
emit b->titleChanged(QString(title));
|
||||
}
|
||||
} else if (strcmp(prop->name, "demuxer-cache-duration") == 0) {
|
||||
if (prop->format == MPV_FORMAT_DOUBLE) {
|
||||
double duration = *(double*)prop->data;
|
||||
emit b->cachedDurationChanged(duration);
|
||||
}
|
||||
} else if (strcmp(prop->name, "playlist-pos") == 0) {
|
||||
if (prop->format == MPV_FORMAT_DOUBLE) {
|
||||
double pos = *(double*)prop->data;
|
||||
emit b->playlistPositionChanged(pos);
|
||||
}
|
||||
} else if (strcmp(prop->name, "pause") == 0) {
|
||||
mpv_node* nod = (mpv_node*)prop->data;
|
||||
if (mpvnode_to_variant(nod).toBool()) {
|
||||
emit b->playStatusChanged(Enums::PlayStatus::Paused);
|
||||
// Utils::SetScreensaver(window()->winId(), true);
|
||||
} else {
|
||||
emit b->playStatusChanged(Enums::PlayStatus::Playing);
|
||||
// Utils::SetScreensaver(window()->winId(), false);
|
||||
}
|
||||
} else if (strcmp(prop->name, "track-list") == 0) {
|
||||
mpv_node* nod = (mpv_node*)prop->data;
|
||||
emit b->tracksChanged(mpvnode_to_variant(nod).toList());
|
||||
} else if (strcmp(prop->name, "audio-device-list") == 0) {
|
||||
mpv_node* nod = (mpv_node*)prop->data;
|
||||
emit b->audioDevicesChanged(b->getAudioDevices(mpvnode_to_variant(nod)));
|
||||
} else if (strcmp(prop->name, "playlist") == 0) {
|
||||
mpv_node* nod = (mpv_node*)prop->data;
|
||||
emit b->playlistChanged(mpvnode_to_variant(nod).toList());
|
||||
} else if (strcmp(prop->name, "chapter-list") == 0) {
|
||||
mpv_node* nod = (mpv_node*)prop->data;
|
||||
emit b->chaptersChanged(mpvnode_to_variant(nod).toList());
|
||||
} else if (strcmp(prop->name, "speed") == 0) {
|
||||
double speed = *(double*)prop->data;
|
||||
emit b->speedChanged(speed);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MPV_EVENT_LOG_MESSAGE: {
|
||||
struct mpv_event_log_message* msg = (struct mpv_event_log_message*)event->data;
|
||||
QString logMsg = "[" + QString(msg->prefix) + "] " + QString(msg->text).trimmed();
|
||||
QString msgLevel = QString(msg->level);
|
||||
if (msgLevel.startsWith("d")) {
|
||||
mpvLogger->info("{}", logMsg.toStdString());
|
||||
} else if (msgLevel.startsWith("t")) {
|
||||
mpvLogger->warn("{}", logMsg.toStdString());
|
||||
} else if (msgLevel.startsWith("v")) {
|
||||
mpvLogger->trace("{}", logMsg.toStdString());
|
||||
} else if (msgLevel.startsWith("i")) {
|
||||
mpvLogger->info("{}", logMsg.toStdString());
|
||||
} else {
|
||||
mpvLogger->warn("What: {}", logMsg.toStdString());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case MPV_EVENT_SHUTDOWN: {
|
||||
qApp->exit();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVariantMap getAudioDevices(const QVariant& drivers)
|
||||
{
|
||||
QVariantMap newDrivers;
|
||||
if (drivers.isNull()) {
|
||||
return newDrivers;
|
||||
}
|
||||
|
||||
QSequentialIterable iterable = drivers.value<QSequentialIterable>();
|
||||
foreach (const QVariant& v, iterable) {
|
||||
QVariantMap item = v.toMap();
|
||||
newDrivers[item["description"].toString()] = item;
|
||||
}
|
||||
return newDrivers;
|
||||
}
|
||||
}
|
21
src/Backends/MPVCommon/MPVCommon.hpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#ifndef MPVCommon_H
|
||||
#define MPVCommon_H
|
||||
|
||||
#include "src/enums.hpp"
|
||||
#include <QMetaType>
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
#include <mpv/client.h>
|
||||
class BackendInterface;
|
||||
class QMetaMethod;
|
||||
|
||||
namespace MPVCommon {
|
||||
|
||||
QString getStats(BackendInterface* b);
|
||||
QVariant playerCommand(BackendInterface* b, const Enums::Commands& cmd, const QVariant& args);
|
||||
void updateDurationString(BackendInterface* b, int numTime, QMetaMethod metaMethod);
|
||||
void handle_mpv_event(BackendInterface* b, mpv_event* event);
|
||||
QVariantMap getAudioDevices(const QVariant& drivers);
|
||||
}
|
||||
|
||||
#endif
|
294
src/Backends/MPVNoFBO/MPVNoFBOBackend.cpp
Normal file
|
@ -0,0 +1,294 @@
|
|||
#include "src/Backends/MPVNoFBO/MPVNoFBOBackend.hpp"
|
||||
#include "src/Backends/MPVCommon/MPVCommon.hpp"
|
||||
#include "src/qthelper.hpp"
|
||||
#include "src/utils.hpp"
|
||||
#include <QApplication>
|
||||
#include <QByteArray>
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QEvent>
|
||||
#include <QIcon>
|
||||
#include <QMetaObject>
|
||||
#include <QOpenGLContext>
|
||||
#include <QQuickWindow>
|
||||
#include <clocale>
|
||||
#include <stdexcept>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void nofbowakeup(void* ctx)
|
||||
{
|
||||
QCoreApplication::postEvent((MPVNoFBOBackend*)ctx, new QEvent(QEvent::User));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
MPVNoFBORenderer::MPVNoFBORenderer(mpv_handle* a_mpv, mpv_opengl_cb_context* a_mpv_gl)
|
||||
: mpv(a_mpv)
|
||||
, mpv_gl(a_mpv_gl)
|
||||
, window(0)
|
||||
, size()
|
||||
{
|
||||
int r = mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address, NULL);
|
||||
if (r < 0)
|
||||
qDebug() << "could not initialize OpenGL";
|
||||
}
|
||||
|
||||
MPVNoFBORenderer::~MPVNoFBORenderer()
|
||||
{
|
||||
// Until this call is done, we need to make sure the player remains
|
||||
// alive. This is done implicitly with the mpv::qt::Handle instance
|
||||
// in this class.
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void MPVNoFBORenderer::paint()
|
||||
{
|
||||
window->resetOpenGLState();
|
||||
|
||||
// This uses 0 as framebuffer, which indicates that mpv will render directly
|
||||
// to the frontbuffer. Note that mpv will always switch framebuffers
|
||||
// explicitly. Some QWindow setups (such as using QQuickWidget) actually
|
||||
// want you to render into a FBO in the beforeRendering() signal, and this
|
||||
// code won't work there.
|
||||
// The negation is used for rendering with OpenGL's flipped coordinates.
|
||||
mpv_opengl_cb_draw(mpv_gl, 0, size.width(), -size.height());
|
||||
|
||||
window->resetOpenGLState();
|
||||
}
|
||||
|
||||
MPVNoFBOBackend::MPVNoFBOBackend(QQuickItem* parent)
|
||||
: QQuickItem(parent)
|
||||
, mpv_gl(0)
|
||||
, renderer(0)
|
||||
{
|
||||
mpv = mpv_create();
|
||||
if (!mpv)
|
||||
throw std::runtime_error("could not create mpv context");
|
||||
|
||||
mpv_set_option_string(mpv, "terminal", "no");
|
||||
mpv_set_option_string(mpv, "msg-level", "all=v");
|
||||
|
||||
// Fix?
|
||||
mpv_set_option_string(mpv, "ytdl", "yes");
|
||||
|
||||
mpv_set_option_string(mpv, "slang", "en");
|
||||
|
||||
mpv_set_option_string(mpv, "config", "yes");
|
||||
mpv_observe_property(mpv, 0, "tracks-menu", MPV_FORMAT_NONE);
|
||||
mpv_observe_property(mpv, 0, "chapter-list", MPV_FORMAT_NODE);
|
||||
mpv_observe_property(mpv, 0, "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, "audio-device-list", MPV_FORMAT_NODE);
|
||||
mpv_observe_property(mpv, 0, "playlist-pos", MPV_FORMAT_DOUBLE);
|
||||
mpv_observe_property(mpv, 0, "volume", MPV_FORMAT_NONE);
|
||||
mpv_observe_property(mpv, 0, "mute", MPV_FORMAT_NONE);
|
||||
mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
|
||||
mpv_observe_property(mpv, 0, "media-title", MPV_FORMAT_STRING);
|
||||
mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
|
||||
mpv_observe_property(mpv, 0, "demuxer-cache-duration", MPV_FORMAT_DOUBLE);
|
||||
mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_NODE);
|
||||
mpv_observe_property(mpv, 0, "playlist", MPV_FORMAT_NODE);
|
||||
mpv_observe_property(mpv, 0, "speed", MPV_FORMAT_DOUBLE);
|
||||
mpv_set_wakeup_callback(mpv, nofbowakeup, this);
|
||||
|
||||
if (mpv_initialize(mpv) < 0)
|
||||
throw std::runtime_error("could not initialize mpv context");
|
||||
|
||||
// Make use of the MPV_SUB_API_OPENGL_CB API.
|
||||
mpv::qt::set_option_variant(mpv, "vo", "opengl-cb");
|
||||
|
||||
// Setup the callback that will make QtQuick update and redraw if there
|
||||
// is a new video frame. Use a queued connection: this makes sure the
|
||||
// doUpdate() function is run on the GUI thread.
|
||||
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, MPVNoFBOBackend::on_update, (void*)this);
|
||||
|
||||
connect(this,
|
||||
&MPVNoFBOBackend::onUpdate,
|
||||
this,
|
||||
&MPVNoFBOBackend::doUpdate,
|
||||
Qt::QueuedConnection);
|
||||
connect(this,
|
||||
&MPVNoFBOBackend::positionChanged,
|
||||
&MPVNoFBOBackend::updateDurationString);
|
||||
connect(this,
|
||||
&MPVNoFBOBackend::durationChanged,
|
||||
&MPVNoFBOBackend::updateDurationString);
|
||||
|
||||
connect(this,
|
||||
&QQuickItem::windowChanged,
|
||||
this,
|
||||
&MPVNoFBOBackend::handleWindowChanged);
|
||||
}
|
||||
|
||||
MPVNoFBOBackend::~MPVNoFBOBackend()
|
||||
{
|
||||
printf("Shutting down...\n");
|
||||
qApp->quit();
|
||||
printf("MPV terminated.\n");
|
||||
}
|
||||
|
||||
void MPVNoFBOBackend::sync()
|
||||
{
|
||||
|
||||
if (!renderer) {
|
||||
window()->setIcon(QIcon(":/icon.png"));
|
||||
renderer = new MPVNoFBORenderer(mpv, mpv_gl);
|
||||
connect(window(),
|
||||
&QQuickWindow::beforeRendering,
|
||||
renderer,
|
||||
&MPVNoFBORenderer::paint,
|
||||
Qt::DirectConnection);
|
||||
QMetaObject::invokeMethod(this, "startPlayer");
|
||||
}
|
||||
renderer->window = window();
|
||||
renderer->size = window()->size() * window()->devicePixelRatio();
|
||||
}
|
||||
|
||||
void MPVNoFBOBackend::swapped()
|
||||
{
|
||||
mpv_opengl_cb_report_flip(mpv_gl, 0);
|
||||
}
|
||||
|
||||
void MPVNoFBOBackend::cleanup()
|
||||
{
|
||||
if (renderer) {
|
||||
delete renderer;
|
||||
renderer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void MPVNoFBOBackend::on_update(void* ctx)
|
||||
{
|
||||
MPVNoFBOBackend* self = (MPVNoFBOBackend*)ctx;
|
||||
emit self->onUpdate();
|
||||
}
|
||||
|
||||
void MPVNoFBOBackend::doUpdate()
|
||||
{
|
||||
window()->update();
|
||||
update();
|
||||
}
|
||||
|
||||
QVariant
|
||||
MPVNoFBOBackend::getProperty(const QString& name) const
|
||||
{
|
||||
return mpv::qt::get_property_variant(mpv, name);
|
||||
}
|
||||
|
||||
void MPVNoFBOBackend::command(const QVariant& params)
|
||||
{
|
||||
mpv::qt::command_variant(mpv, params);
|
||||
}
|
||||
|
||||
void MPVNoFBOBackend::setProperty(const QString& name, const QVariant& value)
|
||||
{
|
||||
mpv::qt::set_property_variant(mpv, name, value);
|
||||
}
|
||||
|
||||
void MPVNoFBOBackend::setOption(const QString& name, const QVariant& value)
|
||||
{
|
||||
mpv::qt::set_option_variant(mpv, name, value);
|
||||
}
|
||||
|
||||
void MPVNoFBOBackend::launchAboutQt()
|
||||
{
|
||||
QApplication* qapp = qobject_cast<QApplication*>(QCoreApplication::instance());
|
||||
qapp->aboutQt();
|
||||
}
|
||||
|
||||
QVariant
|
||||
MPVNoFBOBackend::playerCommand(const Enums::Commands& cmd)
|
||||
{
|
||||
return playerCommand(cmd, QVariant("NoArgProvided"));
|
||||
}
|
||||
|
||||
QVariant
|
||||
MPVNoFBOBackend::playerCommand(const Enums::Commands& cmd,
|
||||
const QVariant& args)
|
||||
{
|
||||
return MPVCommon::playerCommand(this, cmd, args);
|
||||
}
|
||||
|
||||
void MPVNoFBOBackend::handleWindowChanged(QQuickWindow* win)
|
||||
{
|
||||
if (!win)
|
||||
return;
|
||||
connect(win,
|
||||
&QQuickWindow::beforeSynchronizing,
|
||||
this,
|
||||
&MPVNoFBOBackend::sync,
|
||||
Qt::DirectConnection);
|
||||
connect(win,
|
||||
&QQuickWindow::sceneGraphInvalidated,
|
||||
this,
|
||||
&MPVNoFBOBackend::cleanup,
|
||||
Qt::DirectConnection);
|
||||
connect(win,
|
||||
&QQuickWindow::frameSwapped,
|
||||
this,
|
||||
&MPVNoFBOBackend::swapped,
|
||||
Qt::DirectConnection);
|
||||
win->setClearBeforeRendering(false);
|
||||
}
|
||||
|
||||
void MPVNoFBOBackend::toggleOnTop()
|
||||
{
|
||||
onTop = !onTop;
|
||||
Utils::AlwaysOnTop(window()->winId(), onTop);
|
||||
}
|
||||
|
||||
bool MPVNoFBOBackend::event(QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::User) {
|
||||
on_mpv_events();
|
||||
}
|
||||
return QObject::event(event);
|
||||
}
|
||||
|
||||
void MPVNoFBOBackend::on_mpv_events()
|
||||
{
|
||||
while (mpv) {
|
||||
mpv_event* event = mpv_wait_event(mpv, 0);
|
||||
if (event->event_id == MPV_EVENT_NONE) {
|
||||
break;
|
||||
}
|
||||
handle_mpv_event(event);
|
||||
}
|
||||
}
|
||||
|
||||
void MPVNoFBOBackend::updateDurationString(int numTime)
|
||||
{
|
||||
QMetaMethod metaMethod = sender()->metaObject()->method(senderSignalIndex());
|
||||
MPVCommon::updateDurationString(this, numTime, metaMethod);
|
||||
}
|
||||
|
||||
QVariantMap
|
||||
MPVNoFBOBackend::getAudioDevices(const QVariant& drivers) const
|
||||
{
|
||||
return MPVCommon::getAudioDevices(drivers);
|
||||
}
|
||||
|
||||
void MPVNoFBOBackend::handle_mpv_event(mpv_event* event)
|
||||
{
|
||||
MPVCommon::handle_mpv_event(this, event);
|
||||
}
|
||||
|
||||
QString
|
||||
MPVNoFBOBackend::getStats()
|
||||
{
|
||||
return MPVCommon::getStats(this);
|
||||
}
|
118
src/Backends/MPVNoFBO/MPVNoFBOBackend.hpp
Normal file
|
@ -0,0 +1,118 @@
|
|||
#ifndef MPVNoFBOBackend_H
|
||||
#define MPVNoFBOBackend_H
|
||||
|
||||
#include "src/backendinterface.hpp"
|
||||
#include "src/enums.hpp"
|
||||
#include <QEvent>
|
||||
#include <QMetaType>
|
||||
#include <QObject>
|
||||
#include <QQuickItem>
|
||||
#include <QQuickWindow>
|
||||
#include <QSettings>
|
||||
#include <QSize>
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
#include <mpv/client.h>
|
||||
#include <mpv/opengl_cb.h>
|
||||
|
||||
class MPVNoFBORenderer : public QObject {
|
||||
Q_OBJECT
|
||||
mpv_handle* mpv;
|
||||
mpv_opengl_cb_context* mpv_gl;
|
||||
|
||||
public:
|
||||
QQuickWindow* window;
|
||||
QSize size;
|
||||
|
||||
friend class MpvObject;
|
||||
MPVNoFBORenderer(mpv_handle* a_mpv, mpv_opengl_cb_context* a_mpv_gl);
|
||||
virtual ~MPVNoFBORenderer();
|
||||
public slots:
|
||||
void paint();
|
||||
};
|
||||
|
||||
class MPVNoFBOBackend
|
||||
: public QQuickItem,
|
||||
public BackendInterface {
|
||||
Q_INTERFACES(BackendInterface)
|
||||
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool logging READ logging WRITE setLogging)
|
||||
|
||||
mpv_handle* mpv;
|
||||
mpv_opengl_cb_context* mpv_gl;
|
||||
MPVNoFBORenderer* renderer;
|
||||
bool onTop = false;
|
||||
bool m_logging = true;
|
||||
QSettings settings;
|
||||
|
||||
public:
|
||||
static void on_update(void* ctx);
|
||||
|
||||
void setLogging(bool a)
|
||||
{
|
||||
if (a != m_logging) {
|
||||
m_logging = a;
|
||||
}
|
||||
}
|
||||
bool logging() const { return m_logging; }
|
||||
|
||||
MPVNoFBOBackend(QQuickItem* parent = 0);
|
||||
virtual ~MPVNoFBOBackend();
|
||||
|
||||
int lastTime = 0;
|
||||
double lastSpeed = 0;
|
||||
QString totalDurationString;
|
||||
QString lastPositionString;
|
||||
|
||||
public slots:
|
||||
QVariant playerCommand(const Enums::Commands& command, const QVariant& args);
|
||||
QVariant playerCommand(const Enums::Commands& command);
|
||||
void launchAboutQt();
|
||||
void toggleOnTop();
|
||||
QString getStats();
|
||||
// Optional but handy for MPV or custom backend settings.
|
||||
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;
|
||||
|
||||
void sync();
|
||||
void swapped();
|
||||
void cleanup();
|
||||
|
||||
// Just used for adding missing audio devices to list.
|
||||
QVariantMap getAudioDevices(const QVariant& drivers) const;
|
||||
|
||||
bool event(QEvent* event);
|
||||
|
||||
signals:
|
||||
void onUpdate();
|
||||
void mpv_events();
|
||||
// All below required for Player API
|
||||
void playStatusChanged(const Enums::PlayStatus& status);
|
||||
void volumeStatusChanged(const Enums::VolumeStatus& status);
|
||||
void volumeChanged(const int& volume);
|
||||
void durationChanged(const double& duration);
|
||||
void positionChanged(const double& position);
|
||||
void cachedDurationChanged(const double& duration);
|
||||
void playlistPositionChanged(const double& position);
|
||||
void titleChanged(const QString& title);
|
||||
void durationStringChanged(const QString& string);
|
||||
void tracksChanged(const QVariantList& tracks);
|
||||
void audioDevicesChanged(const QVariantMap& devices);
|
||||
void playlistChanged(const QVariantList& devices);
|
||||
void chaptersChanged(const QVariantList& devices);
|
||||
void speedChanged(const double& speed);
|
||||
|
||||
private slots:
|
||||
void doUpdate();
|
||||
void on_mpv_events();
|
||||
void updateDurationString(int numTime);
|
||||
void handleWindowChanged(QQuickWindow* win);
|
||||
|
||||
private:
|
||||
void handle_mpv_event(mpv_event* event);
|
||||
};
|
||||
|
||||
#endif
|
25
src/Process.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include "Process.h"
|
||||
#include <QStringList>
|
||||
#include <QVariant>
|
||||
class QObject;
|
||||
|
||||
Process::Process(QObject* parent)
|
||||
: QProcess(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void Process::start(const QString& program, const QVariantList& arguments)
|
||||
{
|
||||
QStringList args;
|
||||
|
||||
for (int i = 0; i < arguments.length(); i++)
|
||||
args << arguments[i].toString();
|
||||
|
||||
QProcess::start(program, args);
|
||||
}
|
||||
|
||||
QString
|
||||
Process::getOutput()
|
||||
{
|
||||
return QProcess::readAllStandardOutput();
|
||||
}
|
19
src/Process.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
#ifndef Process_H
|
||||
#define Process_H
|
||||
|
||||
#include <QMetaType>
|
||||
#include <QObject>
|
||||
#include <QProcess>
|
||||
#include <QString>
|
||||
|
||||
class Process : public QProcess {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Process(QObject* parent = 0);
|
||||
|
||||
Q_INVOKABLE void start(const QString& program, const QVariantList& arguments);
|
||||
|
||||
Q_INVOKABLE QString getOutput();
|
||||
};
|
||||
#endif
|
66
src/ThumbnailCache.cpp
Normal file
|
@ -0,0 +1,66 @@
|
|||
#include "ThumbnailCache.h"
|
||||
#include <QBuffer>
|
||||
#include <QByteArray>
|
||||
#include <QCryptographicHash>
|
||||
#include <QFileInfo>
|
||||
#include <QIODevice>
|
||||
#include <QImage>
|
||||
#include <QImageReader>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QStandardPaths>
|
||||
#include <QUrl>
|
||||
|
||||
ThumbnailCache::ThumbnailCache(QObject* parent)
|
||||
: QObject(parent)
|
||||
, manager(new QNetworkAccessManager(this))
|
||||
{
|
||||
cacheFolder = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/thumbs");
|
||||
if (!cacheFolder.exists()) {
|
||||
cacheFolder.mkpath(".");
|
||||
}
|
||||
}
|
||||
|
||||
void ThumbnailCache::addURL(const QString& name, const QString& mediaURL)
|
||||
{
|
||||
|
||||
QString hashedURL = QString(
|
||||
QCryptographicHash::hash(name.toUtf8(), QCryptographicHash::Md5).toHex());
|
||||
QString cacheFilename = hashedURL + ".jpg";
|
||||
QString cachedFilePath = cacheFolder.absoluteFilePath(cacheFilename);
|
||||
if (cacheFolder.exists(cacheFilename)) {
|
||||
emit thumbnailReady(name, mediaURL, "file://" + cachedFilePath);
|
||||
return;
|
||||
}
|
||||
|
||||
QString url(mediaURL);
|
||||
QFileInfo isFile = QFileInfo(url);
|
||||
if (isFile.exists()) {
|
||||
QImageReader reader(url);
|
||||
QImage image = reader.read();
|
||||
|
||||
image.save(cachedFilePath, "JPG");
|
||||
|
||||
emit thumbnailReady(name, mediaURL, "file://" + cachedFilePath);
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkRequest request(url);
|
||||
|
||||
QNetworkReply* reply = manager->get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, [=] {
|
||||
QByteArray response_data = reply->readAll();
|
||||
|
||||
QBuffer buffer(&response_data);
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
|
||||
QImageReader reader(&buffer);
|
||||
QImage image = reader.read();
|
||||
|
||||
image.save(cachedFilePath, "JPG");
|
||||
|
||||
emit thumbnailReady(name, mediaURL, "file://" + cachedFilePath);
|
||||
});
|
||||
}
|
25
src/ThumbnailCache.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
#ifndef ThumbnailCache_H
|
||||
#define ThumbnailCache_H
|
||||
#include <QDir>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class ThumbnailCache : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ThumbnailCache(QObject* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
Q_INVOKABLE void addURL(const QString& name, const QString& url);
|
||||
|
||||
signals:
|
||||
void thumbnailReady(const QString& name, const QString& url,
|
||||
const QString& filePath);
|
||||
|
||||
private:
|
||||
QNetworkAccessManager* manager;
|
||||
QDir cacheFolder;
|
||||
};
|
||||
#endif
|
46
src/backendinterface.hpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
#ifndef BackendInterface_H
|
||||
#define BackendInterface_H
|
||||
#include "enums.hpp"
|
||||
#include <QObject>
|
||||
class BackendInterface {
|
||||
public:
|
||||
virtual ~BackendInterface(){};
|
||||
int lastTime = 0;
|
||||
double lastSpeed = 0;
|
||||
QString totalDurationString;
|
||||
QString lastPositionString;
|
||||
|
||||
public slots:
|
||||
// All 5 required for Player API
|
||||
virtual QVariant playerCommand(const Enums::Commands& command,
|
||||
const QVariant& args)
|
||||
= 0;
|
||||
virtual QVariant playerCommand(const Enums::Commands& command) = 0;
|
||||
virtual void toggleOnTop() = 0;
|
||||
// Optional but handy for MPV or custom backend settings.
|
||||
virtual void command(const QVariant& params) = 0;
|
||||
virtual void setProperty(const QString& name, const QVariant& value) = 0;
|
||||
virtual void setOption(const QString& name, const QVariant& value) = 0;
|
||||
virtual QVariant getProperty(const QString& name) const = 0;
|
||||
virtual QVariantMap getAudioDevices(const QVariant& drivers) const = 0;
|
||||
|
||||
signals:
|
||||
// All below required for Player API
|
||||
virtual void playStatusChanged(const Enums::PlayStatus& status) = 0;
|
||||
virtual void volumeStatusChanged(const Enums::VolumeStatus& status) = 0;
|
||||
virtual void volumeChanged(const int& volume) = 0;
|
||||
virtual void durationChanged(const double& duration) = 0;
|
||||
virtual void positionChanged(const double& position) = 0;
|
||||
virtual void cachedDurationChanged(const double& duration) = 0;
|
||||
virtual void playlistPositionChanged(const double& position) = 0;
|
||||
virtual void titleChanged(const QString& title) = 0;
|
||||
virtual void durationStringChanged(const QString& string) = 0;
|
||||
virtual void tracksChanged(const QVariantList& tracks) = 0;
|
||||
virtual void audioDevicesChanged(const QVariantMap& devices) = 0;
|
||||
virtual void playlistChanged(const QVariantList& devices) = 0;
|
||||
virtual void chaptersChanged(const QVariantList& devices) = 0;
|
||||
virtual void speedChanged(const double& speed) = 0;
|
||||
};
|
||||
Q_DECLARE_INTERFACE(BackendInterface, "NamedKitten.BackendInterface");
|
||||
|
||||
#endif
|
1
src/enums.cpp
Normal file
|
@ -0,0 +1 @@
|
|||
#include "enums.hpp"
|
62
src/enums.hpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
#ifndef ENUMS_HPP
|
||||
#define ENUMS_HPP
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
namespace Enums {
|
||||
Q_NAMESPACE
|
||||
enum class PlayStatus : int {
|
||||
Playing = 0,
|
||||
Paused = 1
|
||||
};
|
||||
Q_ENUM_NS(PlayStatus)
|
||||
enum class VolumeStatus : int {
|
||||
Muted = 0,
|
||||
Low = 1,
|
||||
Normal = 2
|
||||
};
|
||||
Q_ENUM_NS(VolumeStatus)
|
||||
enum class Commands : int {
|
||||
TogglePlayPause = 0,
|
||||
ToggleMute = 1,
|
||||
SetAudioDevice = 2,
|
||||
AddVolume = 3,
|
||||
SetVolume = 4,
|
||||
AddSpeed = 5,
|
||||
SubtractSpeed = 6,
|
||||
ChangeSpeed = 7,
|
||||
SetSpeed = 8,
|
||||
ToggleStats = 9,
|
||||
NextAudioTrack = 10,
|
||||
NextVideoTrack = 11,
|
||||
NextSubtitleTrack = 12,
|
||||
PreviousPlaylistItem = 13,
|
||||
NextPlaylistItem = 14,
|
||||
LoadFile = 15,
|
||||
AppendFile = 16,
|
||||
Seek = 17,
|
||||
SeekAbsolute = 18,
|
||||
ForwardFrame = 19,
|
||||
BackwardFrame = 20,
|
||||
SetTrack = 21,
|
||||
SetPlaylistPos = 22,
|
||||
ForcePause = 23,
|
||||
PreviousChapter = 24,
|
||||
NextChapter = 25,
|
||||
};
|
||||
Q_ENUM_NS(Commands)
|
||||
|
||||
enum class Backends : int {
|
||||
MPVBackend = 0,
|
||||
DirectMPVBackend = 1
|
||||
};
|
||||
Q_ENUM_NS(Backends)
|
||||
}
|
||||
|
||||
// Forces meta generation.
|
||||
class Dummy : public QObject {
|
||||
Q_OBJECT
|
||||
};
|
||||
|
||||
#endif
|
43
src/logger.cpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
// clang-format off
|
||||
#include <spdlog/spdlog.h> // IWYU pragma: export
|
||||
#include <spdlog/sinks/basic_file_sink.h> // IWYU pragma: export
|
||||
#include <spdlog/sinks/daily_file_sink.h> // IWYU pragma: export
|
||||
#include <spdlog/sinks/stdout_color_sinks.h> // IWYU pragma: export
|
||||
#include "spdlog/common.h"
|
||||
#include "spdlog/details/file_helper-inl.h"
|
||||
#include "spdlog/sinks/ansicolor_sink-inl.h"
|
||||
#include "spdlog/sinks/base_sink-inl.h"
|
||||
#include "spdlog/sinks/basic_file_sink-inl.h"
|
||||
#include "spdlog/spdlog-inl.h"
|
||||
// clang-format on
|
||||
#include <QByteArray>
|
||||
#include <QSettings>
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
#include <iosfwd> // IWYU pragma: keep
|
||||
#include <iterator>
|
||||
#include <memory> // IWYU pragma: keep
|
||||
#include <vector>
|
||||
|
||||
std::shared_ptr<spdlog::logger>
|
||||
initLogger(std::string name)
|
||||
{
|
||||
QSettings settings("VideoPlayer", "VideoPlayer");
|
||||
|
||||
QString logFile = settings.value("Logging/logFile", "/tmp/VideoPlayer.log").toString();
|
||||
|
||||
std::vector<spdlog::sink_ptr> sinks;
|
||||
|
||||
|
||||
sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>());
|
||||
auto fileLogger = std::make_shared<spdlog::sinks::basic_file_sink_mt>(logFile.toUtf8().constData());
|
||||
fileLogger->set_level(spdlog::level::trace);
|
||||
sinks.push_back(fileLogger);
|
||||
auto console = std::make_shared<spdlog::logger>(name, begin(sinks), end(sinks));
|
||||
console->set_pattern("[%l][%n] %v%$");
|
||||
spdlog::register_logger(console);
|
||||
console->set_level(spdlog::level::info);
|
||||
|
||||
|
||||
return spdlog::get(name);
|
||||
}
|
12
src/logger.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#ifndef LOGGER_HPP
|
||||
#define LOGGER_HPP
|
||||
#include <iosfwd> // IWYU pragma: keep
|
||||
#include <memory> // IWYU pragma: keep
|
||||
#include <spdlog/sinks/basic_file_sink.h> // IWYU pragma: keep
|
||||
#include <spdlog/sinks/daily_file_sink.h> // IWYU pragma: keep
|
||||
#include <spdlog/sinks/stdout_color_sinks.h> // IWYU pragma: keep
|
||||
#include <spdlog/spdlog.h> // IWYU pragma: keep
|
||||
|
||||
std::shared_ptr<spdlog::logger> initLogger(std::string name);
|
||||
|
||||
#endif
|
135
src/main.cpp
Normal file
|
@ -0,0 +1,135 @@
|
|||
#include <QApplication>
|
||||
#include <QProcess>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QtGlobal>
|
||||
#include <QtQml>
|
||||
#include <locale.h>
|
||||
#ifdef QT_QML_DEBUG
|
||||
#warning "QML Debugging Enabled!!!"
|
||||
#include <QQmlDebug>
|
||||
#endif
|
||||
#include "logger.h"
|
||||
#include "spdlog/logger.h"
|
||||
#include <QSettings>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QVariant>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <spdlog/fmt/fmt.h>
|
||||
#include <fstream>
|
||||
|
||||
extern void registerTypes();
|
||||
|
||||
#ifdef WIN32
|
||||
#include "setenv_mingw.hpp"
|
||||
#endif
|
||||
|
||||
auto qmlLogger = initLogger("qml");
|
||||
auto miscLogger = initLogger("misc");
|
||||
|
||||
void spdLogger(QtMsgType type, const QMessageLogContext& context, const QString& msg)
|
||||
{
|
||||
std::string localMsg = msg.toUtf8().constData();
|
||||
std::shared_ptr<spdlog::logger> logger;
|
||||
if (QString(context.category).startsWith(QString("qml"))) {
|
||||
logger = qmlLogger;
|
||||
} else {
|
||||
logger = miscLogger;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
logger->debug("{}", localMsg);
|
||||
break;
|
||||
case QtInfoMsg:
|
||||
logger->info("{}", localMsg);
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
logger->warn("{}", localMsg);
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
logger->critical("{}", localMsg);
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
logger->critical("{}", localMsg);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
qInstallMessageHandler(spdLogger);
|
||||
|
||||
auto launcherLogger = initLogger("launcher");
|
||||
launcherLogger->info("Starting up!");
|
||||
|
||||
setenv("QT_QUICK_CONTROLS_STYLE", "Desktop", 1);
|
||||
QApplication app(argc, argv);
|
||||
|
||||
app.setOrganizationName("VideoPlayer");
|
||||
app.setOrganizationDomain("owo.monster");
|
||||
app.setApplicationName("VideoPlayer");
|
||||
|
||||
#ifdef QT_QML_DEBUG
|
||||
// Allows debug.
|
||||
QQmlDebuggingEnabler enabler;
|
||||
#endif
|
||||
|
||||
QSettings settings;
|
||||
|
||||
bool ranFirstTimeSetup = settings.value("Setup/ranSetup", false).toBool();
|
||||
|
||||
#ifdef __linux__
|
||||
bool pinephone;
|
||||
#ifdef PINEPHONE
|
||||
pinephone = true;
|
||||
#else
|
||||
pinephone = false;
|
||||
#endif
|
||||
|
||||
// There once was a hacky piece of a code, now its gone.
|
||||
// TODO: launch a opengl window or use offscreen to see if GL_ARB_framebuffer_object
|
||||
// can be found
|
||||
if (!(settings.value("Backend/disableSunxiCheck", false).toBool() || ranFirstTimeSetup) || pinephone ) {
|
||||
std::string buf;
|
||||
std::ifstream modulesFd;
|
||||
|
||||
modulesFd.open("/proc/modules");
|
||||
|
||||
if(modulesFd.is_open()) {
|
||||
while(!modulesFd.eof()) {
|
||||
std::getline(modulesFd, buf);
|
||||
if(buf.find("sunxi") != std::string::npos || buf.find("sun8i") != std::string::npos) {
|
||||
launcherLogger->info("Running on sunxi, switching to NoFBO.");
|
||||
settings.setValue("Appearance/clickToPause", false);
|
||||
settings.setValue("Appearance/doubleTapToSeek", true);
|
||||
settings.setValue("Appearance/scaleFactor", 2.2);
|
||||
settings.setValue("Appearance/subtitlesFontSize", 38);
|
||||
settings.setValue("Appearance/uiFadeTimer", 0);
|
||||
settings.setValue("Backend/fbo", false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
launcherLogger->info("(THIS IS NOT AN ERROR) Cant open /proc/modules.");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
settings.setValue("Setup/ranSetup", true);
|
||||
|
||||
QString newpath = QProcessEnvironment::systemEnvironment().value("APPDIR", "") + "/usr/bin:" + QProcessEnvironment::systemEnvironment().value("PATH", "");
|
||||
setenv("PATH", newpath.toUtf8().constData(), 1);
|
||||
|
||||
registerTypes();
|
||||
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
launcherLogger->info("Loading player...");
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
|
||||
|
||||
return app.exec();
|
||||
}
|
77
src/qml/Dialogs/DebugDialog.qml
Normal file
|
@ -0,0 +1,77 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Dialogs 1.3
|
||||
import QtQuick.Window 2.2
|
||||
import player 1.0
|
||||
|
||||
Dialog {
|
||||
id: debugDialog
|
||||
title: "Debug"
|
||||
height: 480
|
||||
width: 720
|
||||
modality: Qt.NonModal
|
||||
standardButtons: Dialog.NoButton
|
||||
|
||||
Component {
|
||||
id: delegate
|
||||
Item {
|
||||
width: 200; height: 30
|
||||
Label {
|
||||
text: theOutput
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: modelly
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: output
|
||||
model: modelly
|
||||
delegate: delegate
|
||||
height: 50
|
||||
width: parent.width
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: input.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TextField {
|
||||
id: input
|
||||
width: parent.width
|
||||
height: 40
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
text: "h"
|
||||
function doJsEval() {
|
||||
var output;
|
||||
try {
|
||||
let result = eval(input.text)
|
||||
output = result instanceof Array ? "[" + String(result) + "]" : String(result)
|
||||
modelly.append({theOutput: output})
|
||||
} catch (e) {
|
||||
output = String(e)
|
||||
modelly.append({theOutput: output})
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
doJsEval()
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
Action {
|
||||
shortcut: "Ctrl+Shift+i"
|
||||
onTriggered: {
|
||||
debugDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
220
src/qml/Dialogs/PlaylistDialog.qml
Normal file
|
@ -0,0 +1,220 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Dialogs 1.3
|
||||
import QtQuick.Window 2.2
|
||||
import player 1.0
|
||||
|
||||
Dialog {
|
||||
id: playlistDialog
|
||||
title: "Playlist"
|
||||
height: Math.max(480, childrenRect.height * playlistListView.count)
|
||||
width: 720
|
||||
modality: Qt.NonModal
|
||||
property int thumbnailJobsRunning: 0
|
||||
property variant thumbnailJobs: []
|
||||
property int titleJobsRunning: 0
|
||||
property variant titleJobs: []
|
||||
|
||||
function addThumbnailToCache(name, output) {
|
||||
output = output.replace("maxresdefault", "sddefault").split('\n')[0]
|
||||
thumbnailCache.addURL(name, output)
|
||||
thumbnailJobs.shift()
|
||||
thumbnailJobsRunning -= 1
|
||||
}
|
||||
|
||||
ThumbnailCache {
|
||||
id: thumbnailCache
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: false
|
||||
id: titleGetter
|
||||
signal titleFound(string name, string title)
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 500
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
running: true
|
||||
onTriggered: {
|
||||
if (thumbnailJobsRunning < 2) {
|
||||
if (thumbnailJobs.length > 0) {
|
||||
if (thumbnailJobs[0].startsWith(
|
||||
"https://www.youtube.com/playlist?list=")) {
|
||||
thumbnailJobs.shift()
|
||||
return
|
||||
}
|
||||
var component = Qt.createComponent("ThumbnailProcess.qml")
|
||||
var thumbnailerProcess = component.createObject(playlistDialog, {
|
||||
"name": thumbnailJobs[0]
|
||||
})
|
||||
if (String(thumbnailJobs[0]).indexOf("://") !== -1) {
|
||||
|
||||
thumbnailerProcess.start("youtube-dl",
|
||||
["--get-thumbnail", thumbnailJobs[0]])
|
||||
} else {
|
||||
thumbnailerProcess.start(
|
||||
"ffmpegthumbnailer",
|
||||
["-i", thumbnailJobs[0], "-o", "/tmp/" + Qt.md5(
|
||||
thumbnailJobs[0]) + ".png"])
|
||||
}
|
||||
|
||||
thumbnailJobsRunning += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 100
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
running: true
|
||||
onTriggered: {
|
||||
if (titleJobsRunning < 5) {
|
||||
if (titleJobs.length > 0) {
|
||||
if (titleJobs[0].startsWith("https://www.youtube.com/playlist?list=")) {
|
||||
titleJobs.shift()
|
||||
return
|
||||
}
|
||||
var component = Qt.createComponent("TitleProcess.qml")
|
||||
var titleProcess = component.createObject(playlistDialog, {
|
||||
"name": titleJobs[0]
|
||||
})
|
||||
titleProcess.start("youtube-dl", ["--get-title", titleJobs[0]])
|
||||
titleJobs.shift()
|
||||
titleJobsRunning += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: player
|
||||
onPlaylistChanged: function (playlist) {
|
||||
playlistModel.clear()
|
||||
thumbnailJobs = []
|
||||
titleJobs = []
|
||||
titleJobsRunning = 0
|
||||
thumbnailJobsRunning = 0
|
||||
for (var thing in playlist) {
|
||||
var item = playlist[thing]
|
||||
playlistModel.append({
|
||||
"playlistItemTitle": item["title"],
|
||||
"playlistItemFilename": item["filename"],
|
||||
"current": item["current"],
|
||||
"playlistPos": thing
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: playlistDelegate
|
||||
Item {
|
||||
id: playlistItem
|
||||
property string itemURL: ""
|
||||
property string itemTitle: ""
|
||||
width: playlistDialog.width
|
||||
height: childrenRect.height
|
||||
function getText(title, filename) {
|
||||
var itemText = ""
|
||||
if (title.length > 0) {
|
||||
itemText += '<b>Title:</b> ' + title + "<br>"
|
||||
}
|
||||
if (filename.length > 0) {
|
||||
itemText += '<b>Filename:</b> ' + filename
|
||||
}
|
||||
return itemText
|
||||
}
|
||||
Connections {
|
||||
target: thumbnailCache
|
||||
onThumbnailReady: function (name, url, path) {
|
||||
console.error(name,url,path,playlistItem.itemURL)
|
||||
if (name == playlistItem.itemURL) {
|
||||
thumbnail.source = path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: titleGetter
|
||||
onTitleFound: function (name, title) {
|
||||
if (name == playlistItem.itemURL) {
|
||||
titleJobsRunning -= 1
|
||||
playlistItem.itemTitle = title
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: thumbnail
|
||||
source: ""
|
||||
height: source.toString().length > 1 ? 144 : 0
|
||||
width: source.toString().length > 1 ? 256 : 0
|
||||
}
|
||||
|
||||
Button {
|
||||
width: parent.width - 20
|
||||
id: playlistItemButton
|
||||
font.pixelSize: 12
|
||||
padding: 0
|
||||
anchors.left: thumbnail.right
|
||||
bottomPadding: 0
|
||||
contentItem: Text {
|
||||
id: playlistItemText
|
||||
font: parent.font
|
||||
color: "white"
|
||||
text: playlistItem.getText(itemTitle, itemURL)
|
||||
height: parent.height
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
player.playerCommand(Enums.Commands.SetPlaylistPos, playlistPos)
|
||||
}
|
||||
background: Rectangle {
|
||||
color: current ? "orange" : "transparent"
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (typeof playlistItemTitle !== "undefined") {
|
||||
playlistItem.itemTitle = playlistItemTitle
|
||||
} else {
|
||||
playlistDialog.titleJobs.push(playlistItemFilename)
|
||||
}
|
||||
if (typeof playlistItemFilename !== "undefined") {
|
||||
playlistItem.itemURL = playlistItemFilename
|
||||
} else {
|
||||
playlistItem.itemURL = ""
|
||||
}
|
||||
playlistDialog.thumbnailJobs.push(playlistItemFilename)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: playlistListView
|
||||
anchors.fill: parent
|
||||
model: ListModel {
|
||||
id: playlistModel
|
||||
}
|
||||
delegate: playlistDelegate
|
||||
highlight: Item {}
|
||||
snapMode: ListView.SnapToItem
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
active: playlistListView.count > 1 ? true : true
|
||||
}
|
||||
focus: true
|
||||
}
|
||||
Component.onCompleted: {
|
||||
playlistDialog.open()
|
||||
}
|
||||
}
|
140
src/qml/Dialogs/SettingsDialog.qml
Normal file
|
@ -0,0 +1,140 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Dialogs 1.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Window 2.2
|
||||
import player 1.0
|
||||
|
||||
Dialog {
|
||||
id: settingsDialog
|
||||
title: translate.getTranslation("SETTINGS", i18n.language)
|
||||
height: 100
|
||||
width: 720
|
||||
modality: Qt.NonModal
|
||||
|
||||
signal done
|
||||
|
||||
ScrollView {
|
||||
id: content
|
||||
height: parent.height
|
||||
width: parent.width
|
||||
clip: true
|
||||
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
|
||||
Item {
|
||||
id: settingsContent
|
||||
implicitHeight: childrenRect.height
|
||||
implicitWidth: childrenRect.width
|
||||
ColumnLayout {
|
||||
Text {
|
||||
height: 30
|
||||
text: translate.getTranslation("LANGUAGE", i18n.language)
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
LanguageSettings {
|
||||
Layout.leftMargin: 30
|
||||
}
|
||||
Text {
|
||||
height: 30
|
||||
text: translate.getTranslation("APPEARANCE", i18n.language)
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
CheckBox {
|
||||
checked: appearance.titleOnlyOnFullscreen
|
||||
onClicked: appearance.titleOnlyOnFullscreen = !appearance.titleOnlyOnFullscreen
|
||||
text: translate.getTranslation("TITLE_ONLY_ON_FULLSCREEN",
|
||||
i18n.language)
|
||||
Layout.leftMargin: 30
|
||||
}
|
||||
CheckBox {
|
||||
checked: appearance.doubleTapToSeek
|
||||
onClicked: appearance.doubleTapToSeek = !appearance.doubleTapToSeek
|
||||
text: translate.getTranslation("DOUBLE_TAP_TO_SEEK", i18n.language)
|
||||
Layout.leftMargin: 30
|
||||
}
|
||||
Item {
|
||||
Layout.leftMargin: 30
|
||||
Layout.bottomMargin: 10
|
||||
height: 30
|
||||
Text {
|
||||
id: seekByLabel
|
||||
height: 30
|
||||
text: translate.getTranslation("DOUBLE_TAP_TO_SEEK_BY",
|
||||
i18n.language)
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
TextField {
|
||||
id: seekBy
|
||||
anchors.left: seekByLabel.right
|
||||
anchors.leftMargin: 10
|
||||
validator: IntValidator {}
|
||||
inputMethodHints: Qt.ImhFormattedNumbersOnly
|
||||
text: appearance.doubleTapToSeekBy
|
||||
function setSeekBy() {
|
||||
appearance.doubleTapToSeekBy = parseInt(seekBy.text)
|
||||
}
|
||||
onEditingFinished: setSeekBy()
|
||||
}
|
||||
}
|
||||
Item {
|
||||
height: 30
|
||||
Layout.bottomMargin: 10
|
||||
Layout.leftMargin: 30
|
||||
Text {
|
||||
id: fontLabel
|
||||
height: 30
|
||||
text: translate.getTranslation("FONT", i18n.language)
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
TextField {
|
||||
id: fontInput
|
||||
anchors.left: fontLabel.right
|
||||
anchors.leftMargin: 10
|
||||
text: appearance.fontName
|
||||
function setFont() {
|
||||
appearance.fontName = fontInput.text
|
||||
}
|
||||
onEditingFinished: setFont()
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.leftMargin: 30
|
||||
Layout.bottomMargin: 10
|
||||
height: 30
|
||||
Text {
|
||||
id: uiFadeTimeLabel
|
||||
height: 30
|
||||
text: translate.getTranslation("UI_FADE_TIME", i18n.language)
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
TextField {
|
||||
id: uiFadeTimeInput
|
||||
anchors.left: uiFadeTimeLabel.right
|
||||
anchors.leftMargin: 10
|
||||
validator: IntValidator {
|
||||
bottom: 0
|
||||
}
|
||||
inputMethodHints: Qt.ImhFormattedNumbersOnly
|
||||
text: appearance.uiFadeTimer
|
||||
function setUIFadeTime() {
|
||||
appearance.uiFadeTimer = parseInt(uiFadeTimeInput.text)
|
||||
}
|
||||
onEditingFinished: setUIFadeTime()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: settingsDialog
|
||||
onAccepted: {
|
||||
seekBy.setSeekBy()
|
||||
fontInput.setFont()
|
||||
uiFadeTimeInput.setUIFadeTime()
|
||||
settingsDialog.done()
|
||||
}
|
||||
}
|
||||
Component.onCompleted: {
|
||||
settingsDialog.open()
|
||||
}
|
||||
}
|
34
src/qml/Dialogs/SettingsItems/LanguageSettings.qml
Normal file
|
@ -0,0 +1,34 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import "translations.js" as Translations
|
||||
|
||||
ComboBox {
|
||||
id: languageSelector
|
||||
height: 30
|
||||
editable: false
|
||||
pressed: true
|
||||
model: Object.keys(Translations.languages).map(function (key) {
|
||||
return Translations.languages[key]
|
||||
})
|
||||
delegate: ItemDelegate {
|
||||
height: 25
|
||||
width: languageSelector.width
|
||||
contentItem: Text {
|
||||
text: modelData
|
||||
color: "#21be2b"
|
||||
font: languageSelector.font
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
highlighted: languageSelector.highlightedIndex === index
|
||||
}
|
||||
onActivated: {
|
||||
console.warn(currentText)
|
||||
i18n.language = Object.keys(Translations.languages).filter(function (key) {
|
||||
return Translations.languages[key] === currentText
|
||||
})[0]
|
||||
}
|
||||
Component.onCompleted: {
|
||||
currentIndex = languageSelector.find(Translations.languages[i18n.language])
|
||||
}
|
||||
}
|
12
src/qml/Items/AudioDeviceItem.qml
Normal file
|
@ -0,0 +1,12 @@
|
|||
import QtQuick.Controls 2.3
|
||||
import player 1.0
|
||||
|
||||
Action {
|
||||
property string deviceID: "none"
|
||||
checkable: false
|
||||
checked: false
|
||||
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.SetAudioDevice, deviceID)
|
||||
}
|
||||
}
|
22
src/qml/Items/ChapterMarkerItem.qml
Normal file
|
@ -0,0 +1,22 @@
|
|||
import QtQuick 2.0
|
||||
|
||||
Rectangle {
|
||||
id: chapterMarker
|
||||
property int time: 0
|
||||
color: getAppearanceValueForTheme(appearance.themeName, "chapterMarkerColor")
|
||||
width: 4
|
||||
height: parent.height
|
||||
x: progressBar.background.width / progressBar.to * time
|
||||
z: 90000
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: player
|
||||
onChaptersChanged: {
|
||||
chapterMarker.destroy()
|
||||
}
|
||||
}
|
||||
}
|
26
src/qml/Items/CustomMenuItem.qml
Normal file
|
@ -0,0 +1,26 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
MenuItem {
|
||||
id: menuItem
|
||||
implicitHeight: 20
|
||||
|
||||
contentItem: Text {
|
||||
text: menuItem.text
|
||||
opacity: 1
|
||||
color: menuItem.highlighted ? "#5a50da" : "white"
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
font {
|
||||
family: appearance.fontName
|
||||
bold: menuItem.highlighted
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
opacity: 1
|
||||
color: menuItem.highlighted ? "#c0c0f0" : "transparent"
|
||||
}
|
||||
}
|
13
src/qml/Items/ThumbnailProcess.qml
Normal file
|
@ -0,0 +1,13 @@
|
|||
import player 1.0
|
||||
|
||||
Process {
|
||||
id: thumbnailerProcess
|
||||
property string name: ""
|
||||
onFinished: function () {
|
||||
if (String(name).indexOf("://") !== -1) {
|
||||
playlistDialog.addThumbnailToCache(name, getOutput())
|
||||
} else {
|
||||
playlistDialog.addThumbnailToCache(name, "/tmp/" + Qt.md5(name) + ".png")
|
||||
}
|
||||
}
|
||||
}
|
9
src/qml/Items/TitleProcess.qml
Normal file
|
@ -0,0 +1,9 @@
|
|||
import player 1.0
|
||||
|
||||
Process {
|
||||
id: titleProcess
|
||||
property string name: ""
|
||||
onReadyRead: function () {
|
||||
titleGetter.titleFound(name, getOutput())
|
||||
}
|
||||
}
|
14
src/qml/Items/TrackItem.qml
Normal file
|
@ -0,0 +1,14 @@
|
|||
import QtQuick.Controls 2.3
|
||||
import player 1.0
|
||||
|
||||
Action {
|
||||
id: trackItem
|
||||
property string trackType: "none"
|
||||
property string trackID: "none"
|
||||
checkable: true
|
||||
checked: false
|
||||
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.SetTrack, [trackType, trackID])
|
||||
}
|
||||
}
|
8
src/qml/UIComponents/Controls/BackwardButton.qml
Normal file
|
@ -0,0 +1,8 @@
|
|||
import player 1.0
|
||||
|
||||
SmoothButton {
|
||||
iconSource: "icons/" + appearance.themeName + "/backward.svg"
|
||||
onClicked: {
|
||||
player.playerCommand(Enums.Commands.Seek, "-10")
|
||||
}
|
||||
}
|
8
src/qml/UIComponents/Controls/ForwardButton.qml
Normal file
|
@ -0,0 +1,8 @@
|
|||
import player 1.0
|
||||
|
||||
SmoothButton {
|
||||
iconSource: "icons/" + appearance.themeName + "/forward.svg"
|
||||
onClicked: {
|
||||
player.playerCommand(Enums.Commands.Seek, "10")
|
||||
}
|
||||
}
|
6
src/qml/UIComponents/Controls/FullscreenButton.qml
Normal file
|
@ -0,0 +1,6 @@
|
|||
SmoothButton {
|
||||
iconSource: "icons/" + appearance.themeName + "/fullscreen.svg"
|
||||
onClicked: {
|
||||
toggleFullscreen()
|
||||
}
|
||||
}
|
17
src/qml/UIComponents/Controls/PlayPauseButton.qml
Normal file
|
@ -0,0 +1,17 @@
|
|||
import QtQuick 2.0
|
||||
import player 1.0
|
||||
|
||||
SmoothButton {
|
||||
property var playing: Enums.PlayStatus.Playing
|
||||
iconSource: "icons/" + appearance.themeName
|
||||
+ (playing == Enums.PlayStatus.Playing ? "/pause.svg" : "/play.svg")
|
||||
onClicked: {
|
||||
player.playerCommand(Enums.Commands.TogglePlayPause)
|
||||
}
|
||||
Connections {
|
||||
target: player
|
||||
onPlayStatusChanged: function (status) {
|
||||
playing = status
|
||||
}
|
||||
}
|
||||
}
|
8
src/qml/UIComponents/Controls/PlaylistNextButton.qml
Normal file
|
@ -0,0 +1,8 @@
|
|||
import player 1.0
|
||||
|
||||
SmoothButton {
|
||||
iconSource: "icons/" + appearance.themeName + "/next.svg"
|
||||
onClicked: {
|
||||
player.playerCommand(Enums.Commands.NextPlaylistItem)
|
||||
}
|
||||
}
|
23
src/qml/UIComponents/Controls/PlaylistPrevButton.qml
Normal file
|
@ -0,0 +1,23 @@
|
|||
import QtQuick 2.0
|
||||
import player 1.0
|
||||
|
||||
SmoothButton {
|
||||
id: playlistPrevButton
|
||||
iconSource: "icons/" + appearance.themeName + "/prev.svg"
|
||||
visible: appearance.themeName == "Youtube" ? false : true
|
||||
onClicked: {
|
||||
player.playerCommand(Enums.Commands.PreviousPlaylistItem)
|
||||
}
|
||||
Connections {
|
||||
target: player
|
||||
onPlaylistPositionChanged: function (position) {
|
||||
if (appearance.themeName == "YouTube") {
|
||||
if (position != 0) {
|
||||
visible = true
|
||||
} else {
|
||||
visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
21
src/qml/UIComponents/Controls/SettingsButton.qml
Normal file
|
@ -0,0 +1,21 @@
|
|||
import player 1.0
|
||||
|
||||
SmoothButton {
|
||||
id: settingsButton
|
||||
iconSource: "icons/" + appearance.themeName + "/settings.svg"
|
||||
onClicked: {
|
||||
switch (appearance.themeName) {
|
||||
case "YouTube":
|
||||
appearance.themeName = "RoosterTeeth"
|
||||
break
|
||||
case "RoosterTeeth":
|
||||
appearance.themeName = "Niconico"
|
||||
break
|
||||
case "Niconico":
|
||||
appearance.themeName = "YouTube"
|
||||
break
|
||||
default:
|
||||
appearance.themeName = "YouTube"
|
||||
}
|
||||
}
|
||||
}
|
50
src/qml/UIComponents/Controls/SmoothButton.qml
Normal file
|
@ -0,0 +1,50 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
Control {
|
||||
id: root
|
||||
hoverEnabled: true
|
||||
property alias iconSource: buttonImage.source
|
||||
property alias containsMouse: mouseArea.containsMouse
|
||||
|
||||
background: null
|
||||
|
||||
focusPolicy: Qt.NoFocus
|
||||
|
||||
signal clicked
|
||||
leftPadding: root.height / (appearance.themeName == "Niconico" ? 2.5 : 12)
|
||||
rightPadding: root.leftPadding
|
||||
|
||||
contentItem: Image {
|
||||
id: buttonImage
|
||||
smooth: false
|
||||
fillMode: Image.PreserveAspectFit
|
||||
sourceSize.height: Math.floor(
|
||||
root.parent.height / (appearance.themeName == "Niconico" ? 1.8 : 1.25))
|
||||
sourceSize.width: Math.floor(
|
||||
root.parent.height / (appearance.themeName == "Niconico" ? 1.8 : 1.25))
|
||||
}
|
||||
ColorOverlay {
|
||||
id: colorOverlay
|
||||
anchors.fill: buttonImage
|
||||
source: buttonImage
|
||||
color: getAppearanceValueForTheme(appearance.themeName, "buttonColor")
|
||||
cached: true
|
||||
Binding on color {
|
||||
when: root.hovered
|
||||
value: root.hovered ? getAppearanceValueForTheme(
|
||||
appearance.themeName,
|
||||
"buttonHoverColor") : getAppearanceValueForTheme(
|
||||
appearance.themeName, "buttonColor")
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
}
|
32
src/qml/UIComponents/Controls/SpeedText.qml
Normal file
|
@ -0,0 +1,32 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import player 1.0
|
||||
|
||||
Text {
|
||||
id: speedText
|
||||
text: "1x"
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: speedStatusMouseArea.containsMouse ? getAppearanceValueForTheme(
|
||||
appearance.themeName,
|
||||
"buttonHoverColor") : getAppearanceValueForTheme(
|
||||
appearance.themeName,
|
||||
"buttonColor")
|
||||
font {
|
||||
family: appearance.fontName
|
||||
pixelSize: layout.height / 2.5
|
||||
}
|
||||
Connections {
|
||||
target: player
|
||||
onSpeedChanged: function (speed) {
|
||||
text = String(speed) + "x"
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
id: speedStatusMouseArea
|
||||
anchors.fill: parent
|
||||
height: parent.height
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
}
|
||||
}
|
18
src/qml/UIComponents/Controls/TimeLabel.qml
Normal file
|
@ -0,0 +1,18 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
Text {
|
||||
id: timeLabel
|
||||
text: "0:00 / 0:00"
|
||||
color: "white"
|
||||
font.family: appearance.fontName
|
||||
font.pixelSize: layout.height / 2.5
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
renderType: Text.NativeRendering
|
||||
Connections {
|
||||
target: player
|
||||
onDurationStringChanged: function (durationString) {
|
||||
timeLabel.text = durationString
|
||||
}
|
||||
}
|
||||
}
|
71
src/qml/UIComponents/Controls/VerticalVolume.qml
Normal file
|
@ -0,0 +1,71 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import player 1.0
|
||||
|
||||
Rectangle {
|
||||
id: volumeSliderArea
|
||||
height: visible ? 70 : 0
|
||||
color: getAppearanceValueForTheme(appearance.themeName, "mainBackground")
|
||||
visible: false
|
||||
Slider {
|
||||
id: volumeSlider
|
||||
anchors.fill: parent
|
||||
to: 100
|
||||
value: 100
|
||||
|
||||
orientation: Qt.Vertical
|
||||
|
||||
implicitWidth: Math.max(
|
||||
background ? background.implicitWidth : 0,
|
||||
(handle ? handle.implicitWidth : 0) + leftPadding + rightPadding)
|
||||
implicitHeight: Math.max(background.implicitHeight,
|
||||
handle.implicitHeight + topPadding + bottomPadding)
|
||||
|
||||
padding: 6
|
||||
|
||||
Connections {
|
||||
target: player
|
||||
onVolumeChanged: function (volume) {
|
||||
volumeSlider.value = volume
|
||||
}
|
||||
}
|
||||
|
||||
onMoved: {
|
||||
player.playerCommand(Enums.Commands.SetVolume,
|
||||
Math.round(volumeSlider.value).toString())
|
||||
}
|
||||
|
||||
handle: Rectangle {
|
||||
x: volumeSlider.leftPadding + ((volumeSlider.availableWidth - width) / 2)
|
||||
y: volumeSlider.topPadding + (volumeSlider.visualPosition
|
||||
* (volumeSlider.availableHeight - height))
|
||||
implicitWidth: 10
|
||||
implicitHeight: 10
|
||||
radius: width / 2
|
||||
color: "white"
|
||||
border.width: 0
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
x: volumeSlider.leftPadding + ((volumeSlider.availableWidth - width) / 2)
|
||||
y: volumeSlider.topPadding
|
||||
implicitWidth: 4
|
||||
implicitHeight: 70
|
||||
width: implicitWidth
|
||||
height: volumeSlider.availableHeight
|
||||
radius: 3
|
||||
color: getAppearanceValueForTheme(appearance.themeName,
|
||||
"progressBackgroundColor")
|
||||
|
||||
Rectangle {
|
||||
y: volumeSlider.visualPosition * parent.height
|
||||
width: 4
|
||||
height: volumeSlider.position * parent.height
|
||||
|
||||
radius: 3
|
||||
color: getAppearanceValueForTheme(appearance.themeName,
|
||||
"volumeSliderBackground")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
207
src/qml/UIComponents/Controls/VideoProgress.qml
Normal file
|
@ -0,0 +1,207 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import player 1.0
|
||||
|
||||
Slider {
|
||||
id: progressBar
|
||||
objectName: "progressBar"
|
||||
property string currentMediaURL: ""
|
||||
property bool playing: false
|
||||
property bool center: false
|
||||
to: 1
|
||||
value: 0.0
|
||||
|
||||
Rectangle {
|
||||
id: timestampBox
|
||||
visible: false
|
||||
width: hoverProgressLabel.width
|
||||
height: hoverProgressLabel.height
|
||||
z: 100
|
||||
color: getAppearanceValueForTheme(appearance.themeName, "mainBackground")
|
||||
Text {
|
||||
id: hoverProgressLabel
|
||||
text: "0:00"
|
||||
color: "white"
|
||||
font.family: appearance.fontName
|
||||
font.pixelSize: mainWindow.virtualHeight / 50
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: player
|
||||
onPlayStatusChanged: function (status) {
|
||||
if (status == Enums.PlayStatus.Playing) {
|
||||
progressBar.playing = true
|
||||
} else if (status == Enums.PlayStatus.Paused) {
|
||||
progressBar.playing = false
|
||||
}
|
||||
}
|
||||
onPositionChanged: function (position) {
|
||||
if (!pressed) {
|
||||
progressBar.value = position
|
||||
}
|
||||
}
|
||||
onDurationChanged: function (duration) {
|
||||
progressBar.to = duration
|
||||
}
|
||||
onCachedDurationChanged: function (duration) {
|
||||
cachedLength.duration = duration
|
||||
}
|
||||
}
|
||||
onMoved: {
|
||||
player.playerCommand(Enums.Commands.SeekAbsolute, value)
|
||||
}
|
||||
|
||||
function getProgressBarHeight(nyan, isMouse) {
|
||||
var x = fun.nyanCat ? mainWindow.virtualHeight / 64 : mainWindow.virtualHeight / 380
|
||||
if (appearance.themeName == "Niconico" && !fun.nyanCat) {
|
||||
return x * 2
|
||||
} else if (isMouse & !fun.nyanCat) {
|
||||
return x * 2
|
||||
} else {
|
||||
return x
|
||||
}
|
||||
}
|
||||
function getHandleVisibility(themeName, isMouse) {
|
||||
if (fun.nyanCat) {
|
||||
return true
|
||||
}
|
||||
if (appearance.themeName == "Niconico") {
|
||||
return isMouse
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
id: mouseAreaProgressBar
|
||||
width: progressBar.width
|
||||
height: parent.height
|
||||
anchors.fill: parent
|
||||
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
z: 100
|
||||
property string currentTime: ""
|
||||
|
||||
onEntered: timestampBox.visible = true
|
||||
onExited: timestampBox.visible = false
|
||||
|
||||
onPositionChanged: {
|
||||
// code taken from https://github.com/qt/qtquickcontrols2/blob/39892547145ba4e73bebee86352bd384732b5d19/src/quicktemplates2/qquickslider.cpp#L138
|
||||
var a = ((mouseAreaProgressBar.mouseX - (handleRect.width / 2))
|
||||
/ (progressBar.availableWidth - handleRect.width)) * progressBar.to
|
||||
hoverProgressLabel.text = utils.createTimestamp(a)
|
||||
timestampBox.x = mouseAreaProgressBar.mouseX - (timestampBox.width / 2)
|
||||
timestampBox.y = progressBackground.y - timestampBox.height * 2
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: progressBar.center ? (progressBar.height / 2) - (height / 2) : 0
|
||||
id: progressBackground
|
||||
z: 30
|
||||
width: progressBar.availableWidth
|
||||
height: progressBar.getProgressBarHeight(fun.nyanCat,
|
||||
mouseAreaProgressBar.containsMouse)
|
||||
color: getAppearanceValueForTheme(appearance.themeName,
|
||||
"progressBackgroundColor")
|
||||
|
||||
ProgressBar {
|
||||
id: cachedLength
|
||||
background: null
|
||||
contentItem: Item {
|
||||
Rectangle {
|
||||
width: cachedLength.visualPosition * parent.width
|
||||
height: parent.height
|
||||
color: getAppearanceValueForTheme(appearance.themeName,
|
||||
"progressCachedColor")
|
||||
AnimatedImage {
|
||||
visible: fun.nyanCat
|
||||
height: parent.height
|
||||
id: nyancacheimation
|
||||
smooth: false
|
||||
anchors.fill: parent
|
||||
source: "qrc:/icons/nyancache.gif"
|
||||
fillMode: Image.TileHorizontally
|
||||
}
|
||||
}
|
||||
}
|
||||
z: 40
|
||||
to: progressBar.to
|
||||
property int duration
|
||||
value: progressBar.value + duration
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
id: chapterMarkers
|
||||
z: 60
|
||||
Connections {
|
||||
target: player
|
||||
onChaptersChanged: function (chapters) {
|
||||
for (var i = 0, len = chapters.length; i < len; i++) {
|
||||
var component = Qt.createComponent("ChapterMarkerItem.qml")
|
||||
|
||||
var marker = component.createObject(chapterMarkers, {
|
||||
"time": chapters[i]["time"]
|
||||
})
|
||||
if (marker == null) {
|
||||
console.error(component.errorString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: progressLength
|
||||
z: 50
|
||||
anchors.left: progressBackground.left
|
||||
width: progressBar.visualPosition * parent.width
|
||||
height: parent.height
|
||||
color: getAppearanceValueForTheme(appearance.themeName,
|
||||
"progressSliderColor")
|
||||
Image {
|
||||
visible: fun.nyanCat
|
||||
id: rainbow
|
||||
anchors.fill: parent
|
||||
height: parent.height
|
||||
width: parent.width
|
||||
source: "qrc:/icons/rainbow.png"
|
||||
fillMode: Image.TileHorizontally
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handle: Rectangle {
|
||||
z: 70
|
||||
id: handleRect
|
||||
x: progressBar.visualPosition * (progressBar.availableWidth - width)
|
||||
anchors.verticalCenter: parent.background.verticalCenter
|
||||
implicitHeight: radius
|
||||
implicitWidth: radius
|
||||
radius: mainWindow.virtualHeight / 59
|
||||
color: appearance.themeName
|
||||
== "RoosterTeeth" ? "white" : fun.nyanCat ? "transparent" : getAppearanceValueForTheme(
|
||||
appearance.themeName,
|
||||
"progressSliderColor")
|
||||
visible: getHandleVisibility(appearance.themeName,
|
||||
mouseAreaProgressBar.containsMouse)
|
||||
AnimatedImage {
|
||||
z: 80
|
||||
visible: fun.nyanCat
|
||||
paused: progressBar.pressed
|
||||
height: mainWindow.virtualHeight / 28
|
||||
id: nyanimation
|
||||
smooth: false
|
||||
anchors.centerIn: parent
|
||||
source: "qrc:/icons/nyancat.gif"
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
}
|
||||
}
|
22
src/qml/UIComponents/Controls/VolumeButton.qml
Normal file
|
@ -0,0 +1,22 @@
|
|||
import QtQuick 2.0
|
||||
import player 1.0
|
||||
|
||||
SmoothButton {
|
||||
id: volumeButton
|
||||
iconSource: "icons/" + appearance.themeName + "/volume-up.svg"
|
||||
onClicked: {
|
||||
player.playerCommand(Enums.Commands.ToggleMute)
|
||||
}
|
||||
Connections {
|
||||
target: player
|
||||
onVolumeStatusChanged: function (status) {
|
||||
if (status == Enums.VolumeStatus.Muted) {
|
||||
volumeButton.iconSource = "qrc:/icons/" + appearance.themeName + "/volume-mute.svg"
|
||||
} else if (status == Enums.VolumeStatus.Low) {
|
||||
volumeButton.iconSource = "qrc:/icons/" + appearance.themeName + "/volume-down.svg"
|
||||
} else if (status == Enums.VolumeStatus.Normal) {
|
||||
volumeButton.iconSource = "qrc:/icons/" + appearance.themeName + "/volume-up.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
69
src/qml/UIComponents/Controls/VolumeSlider.qml
Normal file
|
@ -0,0 +1,69 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import player 1.0
|
||||
|
||||
Slider {
|
||||
id: volumeBar
|
||||
to: 100
|
||||
value: 100
|
||||
palette.dark: "#f00"
|
||||
hoverEnabled: true
|
||||
|
||||
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)
|
||||
onMoved: {
|
||||
player.playerCommand(Enums.Commands.SetVolume,
|
||||
Math.round(volumeBar.value).toString())
|
||||
}
|
||||
Connections {
|
||||
target: player
|
||||
onVolumeChanged: function (volume) {
|
||||
volumeBar.value = volume
|
||||
}
|
||||
}
|
||||
handle: Rectangle {
|
||||
x: volumeBar.leftPadding + volumeBar.visualPosition * (volumeBar.availableWidth - width)
|
||||
y: volumeBar.topPadding + volumeBar.availableHeight / 2 - height / 2
|
||||
implicitWidth: height
|
||||
implicitHeight: layout.height / 2.6
|
||||
radius: height
|
||||
visible: appearance.themeName == "Niconico" ? false : true
|
||||
color: "#f6f6f6"
|
||||
border.color: "#f6f6f6"
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
x: volumeBar.leftPadding
|
||||
y: volumeBar.topPadding + volumeBar.availableHeight / 2 - height / 2
|
||||
implicitWidth: layout.width / 11
|
||||
implicitHeight: appearance.themeName == "Niconico" ? layout.height / 6 : layout.height / 10
|
||||
width: volumeBar.availableWidth
|
||||
height: implicitHeight
|
||||
color: getAppearanceValueForTheme(appearance.themeName,
|
||||
"progressBackgroundColor")
|
||||
Rectangle {
|
||||
width: volumeBar.visualPosition * parent.width
|
||||
height: parent.height
|
||||
color: getAppearanceValueForTheme(appearance.themeName,
|
||||
"volumeSliderBackground")
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
acceptedButtons: Qt.NoButton
|
||||
z: 10
|
||||
anchors.fill: parent
|
||||
propagateComposedEvents: true
|
||||
onWheel: {
|
||||
if (wheel.angleDelta.y < 0) {
|
||||
volumeBar.value -= 5
|
||||
} else {
|
||||
volumeBar.value += 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
src/qml/UIComponents/ControlsBar/ControlsBar.qml
Normal file
|
@ -0,0 +1,87 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import player 1.0
|
||||
|
||||
Item {
|
||||
id: controlsBarItem
|
||||
property var combinedHeight: progressBar.height + controlsBackground.height
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: appearance
|
||||
onThemeNameChanged: setControlsTheme(appearance.themeName)
|
||||
}
|
||||
|
||||
function setControlsTheme(themeName) {
|
||||
for (var i = 0; i < controlsBar.children.length; ++i) {
|
||||
if (controlsBar.children[i].objectName == "buttonLayout") {
|
||||
controlsBar.children[i].destroy()
|
||||
}
|
||||
}
|
||||
|
||||
var component = Qt.createComponent(themeName + "ButtonLayout.qml")
|
||||
if (component.status == Component.Error) {
|
||||
console.error("Error loading component: " + component.errorString())
|
||||
}
|
||||
component.createObject(controlsBar, {})
|
||||
}
|
||||
|
||||
VideoProgress {
|
||||
id: progressBar
|
||||
visible: mainWindow.controlsShowing
|
||||
&& appearance.themeName != "RoosterTeeth"
|
||||
bottomPadding: 0
|
||||
rightPadding: 0
|
||||
leftPadding: 0
|
||||
z: 20
|
||||
anchors {
|
||||
bottom: controlsBackground.top
|
||||
left: controlsBackground.left
|
||||
right: controlsBackground.right
|
||||
leftMargin: parent.width / 128
|
||||
rightMargin: parent.width / 128
|
||||
bottomMargin: 0
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: controlsBackground
|
||||
height: controlsBar.visible ? controlsBar.height
|
||||
+ (appearance.themeName
|
||||
== "RoosterTeeth" ? 0 : progressBar.topPadding) : 0
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: getAppearanceValueForTheme(appearance.themeName, "mainBackground")
|
||||
visible: mainWindow.controlsShowing
|
||||
z: 10
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: controlsBar
|
||||
height: mainWindow.controlsShowing ? mainWindow.virtualHeight / 20 : 0
|
||||
visible: mainWindow.controlsShowing
|
||||
z: 30
|
||||
anchors {
|
||||
right: parent.right
|
||||
rightMargin: parent.width / 128
|
||||
left: parent.left
|
||||
leftMargin: parent.width / 128
|
||||
bottom: parent.bottom
|
||||
bottomMargin: 0
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
setControlsTheme(appearance.themeName)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
import QtQuick 2.0
|
||||
import player 1.0
|
||||
|
||||
Item {
|
||||
objectName: "buttonLayout"
|
||||
id: layout
|
||||
anchors.fill: controlsBar
|
||||
|
||||
PlayPauseButton {
|
||||
id: playPauseButton
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
VolumeButton {
|
||||
id: volumeButton
|
||||
anchors {
|
||||
left: playPauseButton.right
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
VolumeSlider {
|
||||
anchors {
|
||||
left: volumeButton.right
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
|
||||
PlaylistPrevButton {
|
||||
id: playlistPrevButton
|
||||
anchors {
|
||||
right: backwardButton.left
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
BackwardButton {
|
||||
id: backwardButton
|
||||
anchors {
|
||||
right: timeLabel.left
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
TimeLabel {
|
||||
id: timeLabel
|
||||
anchors {
|
||||
centerIn: parent
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
ForwardButton {
|
||||
id: forwardButton
|
||||
anchors {
|
||||
left: timeLabel.right
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
PlaylistNextButton {
|
||||
id: playlistNextButton
|
||||
anchors {
|
||||
left: forwardButton.right
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
|
||||
FullscreenButton {
|
||||
id: fullscreenButton
|
||||
anchors {
|
||||
right: settingsButton.left
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
SettingsButton {
|
||||
id: settingsButton
|
||||
anchors {
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
import QtQuick 2.0
|
||||
import player 1.0
|
||||
|
||||
Item {
|
||||
objectName: "buttonLayout"
|
||||
id: layout
|
||||
anchors.fill: controlsBar
|
||||
|
||||
PlayPauseButton {
|
||||
id: playPauseButton
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseAreaVolumeArea
|
||||
anchors {
|
||||
right: volumeSliderArea.right
|
||||
bottom: volumeButton.bottom
|
||||
left: volumeButton.left
|
||||
}
|
||||
height: parent.height + (volumeSliderArea.visible ? volumeSliderArea.height : 0)
|
||||
hoverEnabled: true
|
||||
z: 500
|
||||
propagateComposedEvents: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
onEntered: {
|
||||
mouseAreaPlayerTimer.stop()
|
||||
}
|
||||
onExited: {
|
||||
mouseAreaPlayerTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
VolumeButton {
|
||||
id: volumeButton
|
||||
anchors {
|
||||
left: playPauseButton.right
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
hoverEnabled: true
|
||||
}
|
||||
|
||||
VerticalVolume {
|
||||
id: volumeSliderArea
|
||||
anchors {
|
||||
bottom: volumeButton.top
|
||||
left: volumeButton.left
|
||||
right: volumeButton.right
|
||||
}
|
||||
width: volumeButton.width
|
||||
visible: mouseAreaVolumeArea.containsMouse || volumeButton.hovered
|
||||
}
|
||||
|
||||
TimeLabel {
|
||||
id: timeLabel
|
||||
anchors {
|
||||
left: volumeButton.right
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
|
||||
VideoProgress {
|
||||
id: videoProgressRoosterTeeth
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: timeLabel.right
|
||||
leftMargin: parent.width / 128
|
||||
right: speedText.left
|
||||
rightMargin: parent.width / 128
|
||||
}
|
||||
rightPadding: 0
|
||||
leftPadding: 0
|
||||
height: parent.height
|
||||
to: progressBar.to
|
||||
value: progressBar.value
|
||||
center: true
|
||||
}
|
||||
|
||||
SpeedText {
|
||||
id: speedText
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
right: fullscreenButton.left
|
||||
}
|
||||
}
|
||||
|
||||
FullscreenButton {
|
||||
id: fullscreenButton
|
||||
anchors {
|
||||
right: settingsButton.left
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
SettingsButton {
|
||||
id: settingsButton
|
||||
anchors {
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
}
|
92
src/qml/UIComponents/ControlsLayouts/YouTubeButtonLayout.qml
Normal file
|
@ -0,0 +1,92 @@
|
|||
import QtQuick 2.0
|
||||
import player 1.0
|
||||
|
||||
Item {
|
||||
objectName: "buttonLayout"
|
||||
id: layout
|
||||
anchors.fill: controlsBar
|
||||
height: parent.height
|
||||
|
||||
PlaylistPrevButton {
|
||||
id: playlistPrevButton
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
width: visible ? playlistNextButton.width : 0
|
||||
}
|
||||
PlayPauseButton {
|
||||
id: playPauseButton
|
||||
anchors {
|
||||
left: playlistPrevButton.right
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
leftPadding: 14
|
||||
}
|
||||
PlaylistNextButton {
|
||||
id: playlistNextButton
|
||||
anchors {
|
||||
left: playPauseButton.right
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseAreaVolumeArea
|
||||
anchors.fill: volumeSlider
|
||||
width: volumeSlider.width
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
z: 100
|
||||
}
|
||||
|
||||
VolumeButton {
|
||||
id: volumeButton
|
||||
anchors {
|
||||
left: playlistNextButton.right
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
z: 50
|
||||
}
|
||||
VolumeSlider {
|
||||
id: volumeSlider
|
||||
anchors {
|
||||
left: volumeButton.right
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
height: parent.height
|
||||
visible: mouseAreaVolumeArea.containsMouse || volumeButton.hovered
|
||||
width: visible ? implicitWidth : 0
|
||||
}
|
||||
TimeLabel {
|
||||
anchors {
|
||||
left: volumeSlider.right
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
leftMargin: parent.width / 128
|
||||
}
|
||||
}
|
||||
|
||||
SettingsButton {
|
||||
id: settingsButton
|
||||
anchors {
|
||||
right: fullscreenButton.left
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
FullscreenButton {
|
||||
id: fullscreenButton
|
||||
anchors {
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
}
|
12
src/qml/UIComponents/MenuBar/CustomMenu.qml
Normal file
|
@ -0,0 +1,12 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
Menu {
|
||||
width: 300
|
||||
background: Rectangle {
|
||||
implicitWidth: parent.width
|
||||
implicitHeight: 10
|
||||
color: getAppearanceValueForTheme(appearance.themeName, "mainBackground")
|
||||
}
|
||||
delegate: CustomMenuItem {}
|
||||
}
|
603
src/qml/UIComponents/MenuBar/MainMenu.qml
Normal file
|
@ -0,0 +1,603 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Dialogs 1.3
|
||||
import Qt.labs.platform 1.0 as LabsPlatform
|
||||
import player 1.0
|
||||
|
||||
import "codes.js" as LanguageCodes
|
||||
|
||||
MenuBar {
|
||||
id: menuBar
|
||||
height: mainWindow.virtualHeight / 32
|
||||
function anythingOpen() {
|
||||
for (var i = 0, len = menuBar.count; i < len; i++) {
|
||||
if (menuBar.menuAt(i).opened) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: player
|
||||
onTracksChanged: function (tracks) {
|
||||
menuBar.updateTracks(tracks)
|
||||
}
|
||||
}
|
||||
|
||||
function updateTracks(tracks) {
|
||||
var newTracks = tracks
|
||||
var trackMenus = [audioMenu, videoMenu, subMenu]
|
||||
for (var a = 0; a < trackMenus.length; a++) {
|
||||
var menu = trackMenus[a]
|
||||
for (var i = 0, len = menu.count; i < len; i++) {
|
||||
var action = menu.actionAt(i)
|
||||
if (action) {
|
||||
if (action.trackID != "no") {
|
||||
menu.removeAction(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0, len = newTracks.length; i < len; i++) {
|
||||
var track = newTracks[i]
|
||||
var trackID = track["id"]
|
||||
var trackType = track["type"]
|
||||
var trackLang = LanguageCodes.localeCodeToEnglish(String(track["lang"]))
|
||||
trackLang = trackLang == undefined ? "" : trackLang
|
||||
var trackTitle = track["title"] == undefined ? "" : track["title"] + " "
|
||||
var component = Qt.createComponent("TrackItem.qml")
|
||||
var menu, menuGroup, itemText, type;
|
||||
if (trackType == "sub") {
|
||||
menu = subMenu;
|
||||
menuGroup = subMenuGroup;
|
||||
itemText = trackLang;
|
||||
} else if (trackType == "audio") {
|
||||
menu = audioMenu;
|
||||
menuGroup = audioMenuGroup;
|
||||
itemText = trackTitle + trackLang;
|
||||
} else if (trackType == "video") {
|
||||
menu = videoMenu;
|
||||
menuGroup = videoMenuGroup;
|
||||
itemText = "Video " + trackID + trackTitle;
|
||||
}
|
||||
var action = component.createObject(menu, {
|
||||
"text": itemText,
|
||||
"trackID": String(trackID),
|
||||
"trackType": trackType == "sub" ? "sid" : trackType == "video" ? "vid" : "aid",
|
||||
"checked": track["selected"]
|
||||
})
|
||||
action.ActionGroup.group = menuGroup
|
||||
menu.addAction(action)
|
||||
}
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: fileDialog
|
||||
title: translate.getTranslation("OPEN_FILE", i18n.language)
|
||||
nameFilters: ["All files (*)"]
|
||||
selectMultiple: false
|
||||
onAccepted: {
|
||||
player.playerCommand(Enums.Commands.LoadFile, String(fileDialog.fileUrl))
|
||||
fileDialog.close()
|
||||
}
|
||||
onRejected: {
|
||||
fileDialog.close()
|
||||
}
|
||||
}
|
||||
|
||||
Dialog {
|
||||
id: loadDialog
|
||||
title: translate.getTranslation("URL_FILE_PATH", i18n.language)
|
||||
standardButtons: StandardButton.Cancel | StandardButton.Open
|
||||
onAccepted: {
|
||||
player.playerCommand(Enums.Commands.LoadFile, pathText.text)
|
||||
pathText.text = ""
|
||||
}
|
||||
TextField {
|
||||
id: pathText
|
||||
placeholderText: translate.getTranslation("URL_FILE_PATH", i18n.language)
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: playlistDialogLoader
|
||||
active: false
|
||||
source: "PlaylistDialog.qml"
|
||||
}
|
||||
Connections {
|
||||
target: playlistDialogLoader.item
|
||||
onDone: {
|
||||
playlistDialogLoader.active = false
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: settingsDialogLoader
|
||||
active: false
|
||||
source: "SettingsDialog.qml"
|
||||
}
|
||||
Connections {
|
||||
target: settingsDialogLoader.item
|
||||
onDone: {
|
||||
settingsDialogLoader.active = false
|
||||
}
|
||||
}
|
||||
|
||||
delegate: MenuBarItem {
|
||||
id: menuBarItem
|
||||
padding: 4
|
||||
topPadding: padding
|
||||
leftPadding: padding
|
||||
rightPadding: padding
|
||||
bottomPadding: padding
|
||||
|
||||
contentItem: Text {
|
||||
id: menuBarItemText
|
||||
text: menuBarItem.text
|
||||
font {
|
||||
family: appearance.fontName
|
||||
pixelSize: menuBar.height / 2
|
||||
bold: menuBarItem.highlighted
|
||||
}
|
||||
opacity: 1
|
||||
color: menuBarItem.highlighted ? "#5a50da" : "white"
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 10
|
||||
implicitHeight: 10
|
||||
opacity: 1
|
||||
color: menuBarItem.highlighted ? "#c0c0f0" : "transparent"
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
width: parent.width
|
||||
implicitHeight: 10
|
||||
color: getAppearanceValueForTheme(appearance.themeName, "mainBackground")
|
||||
}
|
||||
|
||||
CustomMenu {
|
||||
id: fileMenuBarItem
|
||||
title: translate.getTranslation("FILE_MENU", i18n.language)
|
||||
font.family: appearance.fontName
|
||||
|
||||
Action {
|
||||
text: translate.getTranslation("OPEN_FILE", i18n.language)
|
||||
onTriggered: fileDialog.open()
|
||||
shortcut: keybinds.openFile
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("OPEN_URL", i18n.language)
|
||||
onTriggered: loadDialog.open()
|
||||
shortcut: keybinds.openURI
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("UPDATE_APPIMAGE", i18n.language)
|
||||
onTriggered: utils.updateAppImage()
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("EXIT", i18n.language)
|
||||
onTriggered: Qt.quit()
|
||||
shortcut: keybinds.quit
|
||||
}
|
||||
}
|
||||
|
||||
CustomMenu {
|
||||
id: playbackMenuBarItem
|
||||
title: translate.getTranslation("PLAYBACK", i18n.language)
|
||||
Action {
|
||||
text: translate.getTranslation("PLAY_PAUSE", i18n.language)
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.TogglePlayPause)
|
||||
}
|
||||
shortcut: String(keybinds.playPause)
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("REWIND_10S", i18n.language)
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.Seek, "-10")
|
||||
}
|
||||
shortcut: keybinds.rewind10
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("FORWARD_10S", i18n.language)
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.Seek, "10")
|
||||
}
|
||||
shortcut: keybinds.forward10
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("REWIND_5S", i18n.language)
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.Seek, "-5")
|
||||
}
|
||||
shortcut: keybinds.rewind5
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("FORWARD_5S", i18n.language)
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.Seek, "5")
|
||||
}
|
||||
shortcut: keybinds.forward5
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("SPEED_DECREASE_POINT_ONE", i18n.language)
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.SubtractSpeed, 0.1)
|
||||
}
|
||||
shortcut: keybinds.decreaseSpeedByPointOne
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("SPEED_INCREASE_POINT_ONE", i18n.language)
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.AddSpeed, 0.1)
|
||||
}
|
||||
shortcut: keybinds.increaseSpeedByPointOne
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("HALVE_SPEED", i18n.language)
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.ChangeSpeed, 0.5)
|
||||
}
|
||||
shortcut: keybinds.halveSpeed
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("DOUBLE_SPEED", i18n.language)
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.ChangeSpeed, 2)
|
||||
}
|
||||
shortcut: keybinds.doubleSpeed
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("FORWARD_FRAME", i18n.language)
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.ForwardFrame)
|
||||
}
|
||||
shortcut: keybinds.forwardFrame
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("BACKWARD_FRAME", i18n.language)
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.BackwardFrame)
|
||||
}
|
||||
shortcut: keybinds.backwardFrame
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("PREVIOUS_CHAPTER", i18n.language)
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.PreviousChapter)
|
||||
}
|
||||
shortcut: keybinds.previousChapter
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("NEXT_CHAPTER", i18n.language)
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.NextChapter)
|
||||
}
|
||||
shortcut: keybinds.nextChapter
|
||||
}
|
||||
}
|
||||
|
||||
CustomMenu {
|
||||
id: audioMenuBarItem
|
||||
title: translate.getTranslation("AUDIO", i18n.language)
|
||||
Action {
|
||||
text: translate.getTranslation("CYCLE_AUDIO_TRACK", i18n.language)
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.NextAudioTrack)
|
||||
}
|
||||
shortcut: keybinds.cycleAudio
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("INCREASE_VOLUME", i18n.language)
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.AddVolume, "2")
|
||||
}
|
||||
shortcut: keybinds.increaseVolume
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("DECREASE_VOLUME", i18n.language)
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.AddVolume, "-2")
|
||||
}
|
||||
shortcut: keybinds.decreaseVolume
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("MUTE_VOLUME", i18n.language)
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.ToggleMute)
|
||||
}
|
||||
shortcut: keybinds.mute
|
||||
}
|
||||
|
||||
MenuSeparator {}
|
||||
|
||||
CustomMenu {
|
||||
title: translate.getTranslation("AUDIO_DEVICES", i18n.language)
|
||||
id: audioDeviceMenu
|
||||
objectName: "audioDeviceMenu"
|
||||
|
||||
Connections {
|
||||
target: player
|
||||
onAudioDevicesChanged: function (ad) {
|
||||
audioDeviceMenu.updateAudioDevices(ad)
|
||||
}
|
||||
}
|
||||
function updateAudioDevices(audioDevices) {
|
||||
for (var i = 0, len = audioDeviceMenu.count; i < len; i++) {
|
||||
audioDeviceMenu.takeAction(0)
|
||||
}
|
||||
for (var thing in audioDevices) {
|
||||
var audioDevice = audioDevices[thing]
|
||||
var component = Qt.createComponent("AudioDeviceItem.qml")
|
||||
var action = component.createObject(audioDeviceMenu, {
|
||||
"text": audioDevices[thing]["description"],
|
||||
"deviceID": String(
|
||||
audioDevices[thing]["name"])
|
||||
})
|
||||
action.ActionGroup.group = audioDeviceMenuGroup
|
||||
audioDeviceMenu.addAction(action)
|
||||
}
|
||||
}
|
||||
ScrollView {
|
||||
clip: true
|
||||
ActionGroup {
|
||||
id: audioDeviceMenuGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MenuSeparator {}
|
||||
|
||||
CustomMenu {
|
||||
title: translate.getTranslation("AUDIO", i18n.language)
|
||||
id: audioMenu
|
||||
ActionGroup {
|
||||
id: audioMenuGroup
|
||||
}
|
||||
TrackItem {
|
||||
text: translate.getTranslation("DISABLE_TRACK", i18n.language)
|
||||
trackType: "aid"
|
||||
trackID: "no"
|
||||
ActionGroup.group: audioMenuGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomMenu {
|
||||
id: videoMenuBarItem
|
||||
title: translate.getTranslation("VIDEO", i18n.language)
|
||||
Action {
|
||||
text: translate.getTranslation("CYCLE_VIDEO", i18n.language)
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.NextVideoTrack)
|
||||
}
|
||||
shortcut: keybinds.cycleVideo
|
||||
}
|
||||
MenuSeparator {}
|
||||
|
||||
CustomMenu {
|
||||
title: translate.getTranslation("VIDEO", i18n.language)
|
||||
id: videoMenu
|
||||
ActionGroup {
|
||||
id: videoMenuGroup
|
||||
}
|
||||
TrackItem {
|
||||
text: translate.getTranslation("DISABLE_TRACK", i18n.language)
|
||||
trackType: "vid"
|
||||
trackID: "no"
|
||||
ActionGroup.group: videoMenuGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
CustomMenu {
|
||||
id: subsMenuBarItem
|
||||
title: translate.getTranslation("SUBTITLES", i18n.language)
|
||||
Action {
|
||||
text: translate.getTranslation("CYCLE_SUB_TRACK", i18n.language)
|
||||
onTriggered: {
|
||||
player.playerCommand(Enums.Commands.NextSubtitleTrack)
|
||||
}
|
||||
shortcut: keybinds.cycleSub
|
||||
}
|
||||
MenuSeparator {}
|
||||
|
||||
CustomMenu {
|
||||
title: translate.getTranslation("SUBTITLES", i18n.language)
|
||||
id: subMenu
|
||||
ActionGroup {
|
||||
id: subMenuGroup
|
||||
}
|
||||
TrackItem {
|
||||
text: translate.getTranslation("DISABLE_TRACK", i18n.language)
|
||||
trackType: "sid"
|
||||
trackID: "no"
|
||||
ActionGroup.group: subMenuGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomMenu {
|
||||
id: viewMenuBarItem
|
||||
title: translate.getTranslation("VIEW", i18n.language)
|
||||
|
||||
CustomMenu {
|
||||
title: translate.getTranslation("THEME", i18n.language)
|
||||
id: themeMenu
|
||||
Action {
|
||||
text: "YouTube"
|
||||
onTriggered: appearance.themeName = text
|
||||
checkable: true
|
||||
checked: appearance.themeName == text
|
||||
}
|
||||
Action {
|
||||
text: "Niconico"
|
||||
onTriggered: appearance.themeName = text
|
||||
checkable: true
|
||||
checked: appearance.themeName == text
|
||||
}
|
||||
Action {
|
||||
text: "RoosterTeeth"
|
||||
onTriggered: appearance.themeName = text
|
||||
checkable: true
|
||||
checked: appearance.themeName == text
|
||||
}
|
||||
}
|
||||
|
||||
Action {
|
||||
text: translate.getTranslation("FULLSCREEN", i18n.language)
|
||||
onTriggered: {
|
||||
toggleFullscreen()
|
||||
}
|
||||
shortcut: keybinds.fullscreen
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("STATS", i18n.language)
|
||||
onTriggered: {
|
||||
statsForNerdsText.visible = !statsForNerdsText.visible
|
||||
}
|
||||
shortcut: keybinds.statsForNerds
|
||||
}
|
||||
|
||||
Action {
|
||||
text: translate.getTranslation("TOGGLE_NYAN_CAT", i18n.language)
|
||||
onTriggered: {
|
||||
fun.nyanCat = !fun.nyanCat
|
||||
}
|
||||
shortcut: keybinds.nyanCat
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("TOGGLE_ALWAYS_ON_TOP", i18n.language)
|
||||
onTriggered: {
|
||||
player.toggleOnTop()
|
||||
}
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("PLAYLIST_MENU", i18n.language)
|
||||
onTriggered: {
|
||||
playlistDialogLoader.active = true
|
||||
}
|
||||
}
|
||||
Action {
|
||||
text: translate.getTranslation("SETTINGS", i18n.language)
|
||||
onTriggered: {
|
||||
settingsDialogLoader.active = true
|
||||
}
|
||||
}
|
||||
Action {
|
||||
// Pretty sure for legal reasons this is needed unless I buy a Qt License
|
||||
text: translate.getTranslation("ABOUT_QT", i18n.language)
|
||||
onTriggered: {
|
||||
utils.launchAboutQt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: skipToNinthDuration
|
||||
property var duration: 0
|
||||
Connections {
|
||||
target: player
|
||||
onDurationChanged: function (duration) {
|
||||
skipToNinthDuration.duration = duration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function skipToNinth(val) {
|
||||
var skipto = 0
|
||||
if (val != 0) {
|
||||
skipto = Math.floor(skipToNinthDuration.duration / 9 * val)
|
||||
}
|
||||
player.playerCommand(Enums.Commands.SeekAbsolute, skipto)
|
||||
}
|
||||
|
||||
Action {
|
||||
onTriggered: skipToNinth(parseInt(shortcut))
|
||||
shortcut: "1"
|
||||
}
|
||||
Action {
|
||||
onTriggered: skipToNinth(parseInt(shortcut))
|
||||
shortcut: "2"
|
||||
}
|
||||
Action {
|
||||
onTriggered: skipToNinth(parseInt(shortcut))
|
||||
shortcut: "3"
|
||||
}
|
||||
Action {
|
||||
onTriggered: skipToNinth(parseInt(shortcut))
|
||||
shortcut: "4"
|
||||
}
|
||||
Action {
|
||||
onTriggered: skipToNinth(parseInt(shortcut))
|
||||
shortcut: "5"
|
||||
}
|
||||
Action {
|
||||
onTriggered: skipToNinth(parseInt(shortcut))
|
||||
shortcut: "6"
|
||||
}
|
||||
Action {
|
||||
onTriggered: skipToNinth(parseInt(shortcut))
|
||||
shortcut: "7"
|
||||
}
|
||||
Action {
|
||||
onTriggered: skipToNinth(parseInt(shortcut))
|
||||
shortcut: "8"
|
||||
}
|
||||
Action {
|
||||
onTriggered: skipToNinth(parseInt(shortcut))
|
||||
shortcut: "9"
|
||||
}
|
||||
Action {
|
||||
onTriggered: skipToNinth(parseInt(shortcut))
|
||||
shortcut: "0"
|
||||
}
|
||||
|
||||
Action {
|
||||
onTriggered: player.command(keybinds.customKeybind0Command)
|
||||
shortcut: keybinds.customKeybind0
|
||||
}
|
||||
Action {
|
||||
onTriggered: player.command(keybinds.customKeybind1Command)
|
||||
shortcut: keybinds.customKeybind1
|
||||
}
|
||||
Action {
|
||||
onTriggered: player.command(keybinds.customKeybind2Command)
|
||||
shortcut: keybinds.customKeybind2
|
||||
}
|
||||
Action {
|
||||
onTriggered: player.command(keybinds.customKeybind3Command)
|
||||
shortcut: keybinds.customKeybind3
|
||||
}
|
||||
Action {
|
||||
onTriggered: player.command(keybinds.customKeybind4Command)
|
||||
shortcut: keybinds.customKeybind4
|
||||
}
|
||||
Action {
|
||||
onTriggered: player.command(keybinds.customKeybind5Command)
|
||||
shortcut: keybinds.customKeybind5
|
||||
}
|
||||
Action {
|
||||
onTriggered: player.command(keybinds.customKeybind6Command)
|
||||
shortcut: keybinds.customKeybind6
|
||||
}
|
||||
Action {
|
||||
onTriggered: player.command(keybinds.customKeybind7Command)
|
||||
shortcut: keybinds.customKeybind7
|
||||
}
|
||||
Action {
|
||||
onTriggered: player.command(keybinds.customKeybind8Command)
|
||||
shortcut: keybinds.customKeybind8
|
||||
}
|
||||
Action {
|
||||
onTriggered: player.command(keybinds.customKeybind9Command)
|
||||
shortcut: keybinds.customKeybind9
|
||||
}
|
||||
}
|
68
src/qml/UIComponents/MenuBar/MenuTitleBar.qml
Normal file
|
@ -0,0 +1,68 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
Item {
|
||||
id: menuTitleBar
|
||||
height: menuBar.height
|
||||
visible: mainWindow.controlsShowing
|
||||
|
||||
function anythingOpen() {
|
||||
return menuBar.anythingOpen()
|
||||
}
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
}
|
||||
|
||||
MainMenu {
|
||||
id: menuBar
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: menuBar.height
|
||||
color: getAppearanceValueForTheme(appearance.themeName, "mainBackground")
|
||||
anchors {
|
||||
right: parent.right
|
||||
left: menuBar.right
|
||||
top: parent.top
|
||||
}
|
||||
|
||||
Text {
|
||||
id: titleLabel
|
||||
objectName: "titleLabel"
|
||||
text: translate.getTranslation("TITLE", i18n.language)
|
||||
color: "white"
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
fontSizeMode: Text.VerticalFit
|
||||
opacity: 1
|
||||
visible: menuTitleBar.visible
|
||||
&& ((!appearance.titleOnlyOnFullscreen)
|
||||
|| (mainWindow.visibility == Window.FullScreen
|
||||
|| mainWindow.visibility == Window.Maximized))
|
||||
font {
|
||||
family: appearance.fontName
|
||||
bold: true
|
||||
pixelSize: appearance.scaleFactor * (height - anchors.topMargin - anchors.bottomMargin - 2)
|
||||
}
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: 4
|
||||
bottom: parent.bottom
|
||||
bottomMargin: 4
|
||||
top: parent.top
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: player
|
||||
onTitleChanged: function (title) {
|
||||
titleLabel.text = title
|
||||
mainWindow.title = "VideoPlayer - " + title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
src/qml/Utils/Translator.qml
Normal file
|
@ -0,0 +1,20 @@
|
|||
import QtQuick 2.0
|
||||
import "translations.js" as Translations
|
||||
|
||||
Item {
|
||||
function getTranslation(code, language) {
|
||||
var lang = Translations.translations[language]
|
||||
if (lang == undefined || lang == "undefined") {
|
||||
return "TranslationNotFound"
|
||||
}
|
||||
var text = String(Translations.translations[i18n.language][code])
|
||||
if (text == "undefined") {
|
||||
console.warn(code, "missing for language", language)
|
||||
}
|
||||
var args = Array.prototype.slice.call(arguments, 1)
|
||||
var i = 0
|
||||
return text.replace(/%s/g, function () {
|
||||
return args[i++]
|
||||
})
|
||||
}
|
||||
}
|
954
src/qml/Utils/codes.js
Normal file
|
@ -0,0 +1,954 @@
|
|||
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",
|
||||
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 ''
|
||||
}
|
471
src/qml/Utils/translations.js
Normal file
|
@ -0,0 +1,471 @@
|
|||
var languages = {
|
||||
"english": "English",
|
||||
"spanish": "Español",
|
||||
"german": "Deutsch",
|
||||
"french": "Française",
|
||||
"italian": "Italiano",
|
||||
"russian": "Русский",
|
||||
"norwegian": "Norwegian",
|
||||
"tokipona": "toki pona",
|
||||
"telugu": "తెలుగు",
|
||||
"vietnamese": "Tiếng Việt"
|
||||
}
|
||||
|
||||
var translations = {
|
||||
english: {
|
||||
OPEN_FILE: "Open file",
|
||||
URL_FILE_PATH: "URL / File Path",
|
||||
FILE_MENU: "File",
|
||||
OPEN_URL: "Open URL",
|
||||
EXIT: "Exit",
|
||||
PLAYBACK: "Playback",
|
||||
PLAY_PAUSE: "Play/Pause",
|
||||
REWIND_10S: "Rewind 10s",
|
||||
FORWARD_10S: "Forward 10s",
|
||||
REWIND_5S: "Rewind 5s",
|
||||
FORWARD_5S: "Forward 5s",
|
||||
SPEED_DECREASE_POINT_ONE: "Speed -0.1",
|
||||
SPEED_INCREASE_POINT_ONE: "Speed +0.1",
|
||||
HALVE_SPEED: "Halve Speed",
|
||||
DOUBLE_SPEED: "Double Speed",
|
||||
BACKWARD_FRAME: "Back a frame",
|
||||
FORWARD_FRAME: "Forward a frame",
|
||||
AUDIO: "Audio",
|
||||
INCREASE_VOLUME: "Increase Volume",
|
||||
DECREASE_VOLUME: "Decrease Volume",
|
||||
MUTE_VOLUME: "Mute",
|
||||
VIDEO: "Video",
|
||||
CYCLE_VIDEO: "Cycle video",
|
||||
SUBTITLES: "Subtitles",
|
||||
CYCLE_SUB_TRACK: "Cycle Subtitle Track",
|
||||
CYCLE_AUDIO_TRACK: "Cycle Audio Track",
|
||||
VIEW: "View",
|
||||
FULLSCREEN: "Fullscreen",
|
||||
STATS: "Statistics",
|
||||
TOGGLE_NYAN_CAT: "Toggle Nyan Cat",
|
||||
ABOUT: "About",
|
||||
ABOUT_QT: "About Qt",
|
||||
TITLE: "Title",
|
||||
TOGGLE_ALWAYS_ON_TOP: "Toggle Always On Top",
|
||||
DISABLE_TRACK: "Disable Track",
|
||||
AUDIO_DEVICES: "Audio Devices",
|
||||
PLAYLIST_MENU: "Playlist Menu",
|
||||
THEME: "Theme",
|
||||
SETTINGS: "Settings",
|
||||
LANGUAGE: "Language",
|
||||
APPEARANCE: "Appearance",
|
||||
TITLE_ONLY_ON_FULLSCREEN: "Show title when only in fullscreen",
|
||||
CLICK_TO_PAUSE: "Tap/Click to pause",
|
||||
DOUBLE_TAP_TO_SEEK: "Double Tap/Click to seek",
|
||||
DOUBLE_TAP_TO_SEEK_BY: "Seek by",
|
||||
FONT: "Font",
|
||||
SUBTITLES_FONT_SIZE: "Subtitles Font Size",
|
||||
UI_FADE_TIME: "UI Fade Time (ms)",
|
||||
UPDATE_APPIMAGE: "Update (AppImage Only)",
|
||||
PREVIOUS_CHAPTER: "Previous Chapter",
|
||||
NEXT_CHAPTER: "Next Chapter"
|
||||
},
|
||||
spanish: {
|
||||
OPEN_FILE: "Abrir archivo",
|
||||
URL_FILE_PATH: "URL / Dirección de archivo",
|
||||
FILE_MENU: "Archivo",
|
||||
OPEN_URL: "Abrir URL",
|
||||
EXIT: "Salir",
|
||||
PLAYBACK: "Volver a reproducir",
|
||||
PLAY_PAUSE: "Reproducir/Pausar",
|
||||
REWIND_10S: "Retrasar 10s",
|
||||
FORWARD_10S: "Adelantar 10s",
|
||||
REWIND_5S: "Retrasar 5s",
|
||||
FORWARD_5S: "Adelantar 5s",
|
||||
SPEED_DECREASE_POINT_ONE: "Velocidad -0.1",
|
||||
SPEED_INCREASE_POINT_ONE: "Velocidad +0.1",
|
||||
HALVE_SPEED: "Dividir velocidad a la mitad",
|
||||
DOUBLE_SPEED: "Duplicar velocidad",
|
||||
BACKWARD_FRAME: "Retrasar un frame",
|
||||
FORWARD_FRAME: "Adelantar un frame",
|
||||
AUDIO: "Audio",
|
||||
INCREASE_VOLUME: "Subir Volumen",
|
||||
DECREASE_VOLUME: "Bajar Volumen",
|
||||
MUTE_VOLUME: "Silenciar",
|
||||
VIDEO: "Vídeo",
|
||||
CYCLE_VIDEO: "Siguiente vídeo",
|
||||
SUBTITLES: "Subtítulos",
|
||||
CYCLE_SUB_TRACK: "Siguientes subtítulos",
|
||||
CYCLE_AUDIO_TRACK: "Siguiente pista de audio",
|
||||
VIEW: "Ver",
|
||||
FULLSCREEN: "Pantalla completa",
|
||||
STATS: "Estadísticas",
|
||||
TOGGLE_NYAN_CAT: "Activar Nyan Cat",
|
||||
ABOUT: "Ayuda",
|
||||
ABOUT_QT: "Sobre Qt",
|
||||
TITLE: "Título",
|
||||
TOGGLE_ALWAYS_ON_TOP: "Fijar ventana",
|
||||
DISABLE_TRACK: "Eliminar pista"
|
||||
},
|
||||
german: {
|
||||
OPEN_FILE: "Datei öffnen",
|
||||
URL_FILE_PATH: "URL / Datei-Pfad",
|
||||
FILE_MENU: "Datei",
|
||||
OPEN_URL: "URL öffnen",
|
||||
EXIT: "Beenden",
|
||||
PLAYBACK: "Wiedergabe",
|
||||
PLAY_PAUSE: "Wiedergabe/Pause",
|
||||
REWIND_10S: "Rückspulen um 10s",
|
||||
FORWARD_10S: "Vorspulen um 10s",
|
||||
REWIND_5S: "Rückspulen um 5s",
|
||||
FORWARD_5S: "Vorspulen um 5s",
|
||||
SPEED_DECREASE_POINT_ONE: "Geschwindigkeit -0.1",
|
||||
SPEED_INCREASE_POINT_ONE: "Geschwindigkeit +0.1",
|
||||
HALVE_SPEED: "Halbe Geschwindigkeit",
|
||||
DOUBLE_SPEED: "Doppelte Geschwindigkeit",
|
||||
BACKWARD_FRAME: "Ein Bild zurück",
|
||||
FORWARD_FRAME: "Ein Bild vor",
|
||||
AUDIO: "Audio",
|
||||
INCREASE_VOLUME: "Lautstärke erhöhen",
|
||||
DECREASE_VOLUME: "Lautstärke verringern",
|
||||
MUTE_VOLUME: "Stumm",
|
||||
VIDEO: "Video",
|
||||
CYCLE_VIDEO: "Wechsle Video",
|
||||
SUBTITLES: "Untertitel",
|
||||
CYCLE_SUB_TRACK: "Wechsle Untertitel-Spur",
|
||||
CYCLE_AUDIO_TRACK: "Wechsle Audiospur",
|
||||
VIEW: "Ansicht",
|
||||
FULLSCREEN: "Vollbild",
|
||||
STATS: "Statistiken",
|
||||
TOGGLE_NYAN_CAT: "Schalte Nyan Cat An/Aus",
|
||||
ABOUT: "Über",
|
||||
ABOUT_QT: "Über QT",
|
||||
TITLE: "Titel",
|
||||
TOGGLE_ALWAYS_ON_TOP: "Immer im Vordergrund",
|
||||
DISABLE_TRACK: "Spur deaktivieren",
|
||||
AUDIO_DEVICES: "Audiogeräte",
|
||||
PLAYLIST_MENU: "Wiedergabeliste",
|
||||
THEME: "Darstellung",
|
||||
SETTINGS: "Einstellungen",
|
||||
LANGUAGE: "Sprache",
|
||||
APPEARANCE: "Aussehen",
|
||||
TITLE_ONLY_ON_FULLSCREEN: "Titel nur im Vollbildmodus anzeigen",
|
||||
CLICK_TO_PAUSE: "Berühren/Klicken zum Pausieren",
|
||||
DOUBLE_TAP_TO_SEEK: "Doppeltap/Klicken zum Spulen",
|
||||
DOUBLE_TAP_TO_SEEK_BY: "Spulen um (s)",
|
||||
FONT: "Schriftart",
|
||||
SUBTITLES_FONT_SIZE: "Untertitel-Schriftgröße",
|
||||
UI_FADE_TIME: "Ausblendzeit Benutzeroberfläche (ms)"
|
||||
},
|
||||
french: {
|
||||
OPEN_FILE: "Ouvrir le fichier",
|
||||
URL_FILE_PATH: "URL / Chemin du fichier",
|
||||
FILE_MENU: "Fichier",
|
||||
OPEN_URL: "Ouvrir l'URL",
|
||||
EXIT: "Fermer",
|
||||
PLAYBACK: "Lecture",
|
||||
PLAY_PAUSE: "Jouer/Mettre en pause",
|
||||
REWIND_10S: "Reculer de 10s",
|
||||
FORWARD_10S: "Avancer de 10s",
|
||||
REWIND_5S: "Reculer de 5s",
|
||||
FORWARD_5S: "Avancer de 5s",
|
||||
SPEED_DECREASE_POINT_ONE: "Vitesse -0.1",
|
||||
SPEED_INCREASE_POINT_ONE: "Vitesse +0.1",
|
||||
HALVE_SPEED: "Réduire de moitié la vitesse",
|
||||
DOUBLE_SPEED: "Doubler la vitesse",
|
||||
BACKWARD_FRAME: "Reculer d'une image",
|
||||
FORWARD_FRAME: "Avancer d'une image",
|
||||
AUDIO: "Audio",
|
||||
CYCLE_AUDIO_TRACK: "Chancer de piste audio",
|
||||
INCREASE_VOLUME: "Augmenter le volume",
|
||||
DECREASE_VOLUME: "Réduire le volume",
|
||||
MUTE_VOLUME: "Muet",
|
||||
VIDEO: "Vidéo",
|
||||
CYCLE_VIDEO: "Changer de vidéo",
|
||||
SUBTITLES: "Sous-titres",
|
||||
CYCLE_SUB_TRACK: "Changer de piste de sous-titres",
|
||||
VIEW: "Voir",
|
||||
FULLSCREEN: "Plein écran",
|
||||
STATS: "Statistiques",
|
||||
TOGGLE_NYAN_CAT: "basculer Nyan Cat",
|
||||
ABOUT: "A propos",
|
||||
ABOUT_QT: "A propos de Qt",
|
||||
TITLE: "Titre",
|
||||
TOGGLE_ALWAYS_ON_TOP: "Toujours au-dessu",
|
||||
DISABLE_TRACK: "Désactiver la piste",
|
||||
AUDIO_DEVICES: "Périphériques audio",
|
||||
PLAYLIST_MENU: "Menu de la liste de lecture",
|
||||
THEME: "Thème"
|
||||
},
|
||||
italian: {
|
||||
OPEN_FILE: "Apri file",
|
||||
URL_FILE_PATH: "URL / Percorso file",
|
||||
FILE_MENU: "File",
|
||||
OPEN_URL: "Apri URL",
|
||||
EXIT: "Esci",
|
||||
PLAYBACK: "Riproduzione",
|
||||
PLAY_PAUSE: "Play/Pausa",
|
||||
REWIND_10S: "Indietro 10s",
|
||||
FORWARD_10S: "Avanti 10s",
|
||||
REWIND_5S: "Indietro 5s",
|
||||
FORWARD_5S: "Avanti 5s",
|
||||
SPEED_DECREASE_POINT_ONE: "Velocità -0.1",
|
||||
SPEED_INCREASE_POINT_ONE: "Velocità +0.1",
|
||||
HALVE_SPEED: "Dimezza velocità",
|
||||
DOUBLE_SPEED: "Raddoppia velocità",
|
||||
BACKWARD_FRAME: "Indietro di un fotogramma",
|
||||
FORWARD_FRAME: "Avanti di un fotogramma",
|
||||
AUDIO: "Audio",
|
||||
INCREASE_VOLUME: "Aumenta Volume",
|
||||
DECREASE_VOLUME: "Diminuisci Volume",
|
||||
MUTE_VOLUME: "Muto",
|
||||
VIDEO: "Video",
|
||||
CYCLE_VIDEO: "Ripeti video",
|
||||
SUBTITLES: "Sottotitoli",
|
||||
CYCLE_SUB_TRACK: "Ripeti traccia sottotitoli",
|
||||
CYCLE_AUDIO_TRACK: "Ripeti traccia audio",
|
||||
VIEW: "Vedi",
|
||||
FULLSCREEN: "Schermo intero",
|
||||
STATS: "Statistiche",
|
||||
TOGGLE_NYAN_CAT: "Attiva Nyan Cat",
|
||||
ABOUT: "Info",
|
||||
ABOUT_QT: "Info su Qt",
|
||||
TITLE: "Titolo",
|
||||
TOGGLE_ALWAYS_ON_TOP: "Mantieni in primo piano",
|
||||
DISABLE_TRACK: "Disabilita traccia",
|
||||
AUDIO_DEVICES: "Dispositivi audio",
|
||||
PLAYLIST_MENU: "Menu Playlist",
|
||||
THEME: "Tema",
|
||||
SETTINGS: "Impostazioni",
|
||||
LANGUAGE: "Lingua",
|
||||
APPEARANCE: "Appearance", //gonna need more context for this
|
||||
TITLE_ONLY_ON_FULLSCREEN: "Mostra il titolo solo in modalità a schermo intero", //kinda long but can't think of a shorter way
|
||||
CLICK_TO_PAUSE: "Tap/Click per mettere in pausa",
|
||||
DOUBLE_TAP_TO_SEEK: "Doppio Tap/Click per andare avanti",
|
||||
DOUBLE_TAP_TO_SEEK_BY: "vai avanti di (secondi)",
|
||||
FONT: "Font",
|
||||
SUBTITLES_FONT_SIZE: "Dimensione Sottotitoli",
|
||||
UI_FADE_TIME: "Fade Interfaccia (millisecondi)"
|
||||
},
|
||||
russian: {
|
||||
OPEN_FILE: "Открыть...",
|
||||
URL_FILE_PATH: "Ввести URL / путь к файлу...",
|
||||
FILE_MENU: "Файл",
|
||||
OPEN_URL: "Открыть URL...",
|
||||
EXIT: "Выход",
|
||||
PLAYBACK: "Воспроизведение",
|
||||
PLAY_PAUSE: "Воспроизвести/Остановить",
|
||||
REWIND_10S: "Перемотать назад на 10 секунд",
|
||||
FORWARD_10S: "Перемотать вперед на 10 секунд",
|
||||
REWIND_5S: "Перемотать назад на 5 секунд",
|
||||
FORWARD_5S: "Перемотать вперед на 5 секунд",
|
||||
SPEED_DECREASE_POINT_ONE: "Уменьшить скорость на 0.1",
|
||||
SPEED_INCREASE_POINT_ONE: "Увеличить скорость на 0.1",
|
||||
HALVE_SPEED: "Замедлить воспроизведение в 2 раза",
|
||||
DOUBLE_SPEED: "Ускорить воспроизведение в 2 раза",
|
||||
BACKWARD_FRAME: "Вернуться к предыдущему кадру",
|
||||
FORWARD_FRAME: "Перейти к следующему кадру",
|
||||
AUDIO: "Звук",
|
||||
INCREASE_VOLUME: "Увеличить громкость",
|
||||
DECREASE_VOLUME: "Уменьшить громкость",
|
||||
MUTE_VOLUME: "Заглушить звук",
|
||||
VIDEO: "Видео",
|
||||
CYCLE_VIDEO: "Зациклить воспроизведение видео",
|
||||
SUBTITLES: "Субтитры",
|
||||
CYCLE_SUB_TRACK: "Зациклить дорожку субтитров",
|
||||
CYCLE_AUDIO_TRACK: "Зациклить аудиодорожку",
|
||||
VIEW: "Вид",
|
||||
FULLSCREEN: "Во весь экран",
|
||||
STATS: "Статистика",
|
||||
TOGGLE_NYAN_CAT: "Нян-кот",
|
||||
ABOUT: "О программе",
|
||||
ABOUT_QT: "О используемой версии Qt...",
|
||||
TITLE: "Заголовок",
|
||||
SETTINGS: "Настройки"
|
||||
},
|
||||
norwegian: {
|
||||
OPEN_FILE: "Åpne Fil",
|
||||
URL_FILE_PATH: "Lenke / Filbane",
|
||||
FILE_MENU: "Fil",
|
||||
OPEN_URL: "Åpne lenke",
|
||||
EXIT: "Avslutt",
|
||||
PLAYBACK: "Avspilling",
|
||||
PLAY_PAUSE: "Spill/Pause",
|
||||
REWIND_10S: "Spol Tilbake 10s",
|
||||
FORWARD_10S: "Spol Frem 10s",
|
||||
REWIND_5S: "Spol Tilbake 5s",
|
||||
FORWARD_5S: "Spol Frem 5s",
|
||||
SPEED_DECREASE_POINT_ONE: "Hastighet -0.1",
|
||||
SPEED_INCREASE_POINT_ONE: "Hastighet +0.1",
|
||||
HALVE_SPEED: "Halv Hastighet",
|
||||
DOUBLE_SPEED: "Dobbel Hastighet",
|
||||
BACKWARD_FRAME: "Gå Tilbake Én Ramme",
|
||||
FORWARD_FRAME: "Gå Frem Én Ramme",
|
||||
AUDIO: "Lydspor",
|
||||
INCREASE_VOLUME: "Øk Lydstyrke",
|
||||
DECREASE_VOLUME: "Senk Lydstyrke",
|
||||
MUTE_VOLUME: "Demp",
|
||||
VIDEO: "Videospor",
|
||||
CYCLE_VIDEO: "Sirkuler Gjennom Videospor",
|
||||
SUBTITLES: "Undertekster",
|
||||
CYCLE_SUB_TRACK: "Sirkuler Gjennom Undertekster",
|
||||
CYCLE_AUDIO_TRACK: "Sirkuler Gjennom Lydspor",
|
||||
VIEW: "Vis",
|
||||
FULLSCREEN: "Fullskjerm",
|
||||
STATS: "Statistikk",
|
||||
TOGGLE_NYAN_CAT: "Skru Av/På Nyan Cat",
|
||||
ABOUT: "Om",
|
||||
ABOUT_QT: "Om Qt",
|
||||
TITLE: "Tittel",
|
||||
TOGGLE_ALWAYS_ON_TOP: "Alltid Øverst",
|
||||
DISABLE_TRACK: "Skru Av Spor"
|
||||
},
|
||||
tokipona: {
|
||||
OPEN_FILE: "open e lipu",
|
||||
URL_FILE_PATH: "URL / nasin lipu",
|
||||
FILE_MENU: "lipu",
|
||||
OPEN_URL: "open e URL",
|
||||
EXIT: "pini",
|
||||
PLAYBACK: "tawa",
|
||||
PLAY_PAUSE: "tawa/awen",
|
||||
REWIND_10S: "tawa monsi mute",
|
||||
FORWARD_10S: "tawa sinpin mute",
|
||||
REWIND_5S: "tawa monsi lili",
|
||||
FORWARD_5S: "tawa sinpin lili",
|
||||
SPEED_DECREASE_POINT_ONE: "lili lili e tawa",
|
||||
SPEED_INCREASE_POINT_ONE: "mute lili e tawa",
|
||||
HALVE_SPEED: "lili mute e tawa",
|
||||
DOUBLE_SPEED: "mute mute e tawa",
|
||||
BACKWARD_FRAME: "monsi pi sitelen wan",
|
||||
FORWARD_FRAME: "sinpin pi sitelen wan",
|
||||
AUDIO: "kalama",
|
||||
INCREASE_VOLUME: "mute e kalama",
|
||||
DECREASE_VOLUME: "lili e kalama",
|
||||
MUTE_VOLUME: "awen e kalama",
|
||||
VIDEO: "sitelen tawa",
|
||||
CYCLE_VIDEO: "ante e sitelen tawa",
|
||||
SUBTITLES: "sitelen anpa",
|
||||
CYCLE_SUB_TRACK: "ante e sitelen anpa",
|
||||
CYCLE_AUDIO_TRACK: "ante e kalama",
|
||||
VIEW: "lukin",
|
||||
FULLSCREEN: "lupa lukin ale",
|
||||
STATS: "nanpa",
|
||||
TOGGLE_NYAN_CAT: "open anu awen e soweli Nian",
|
||||
ABOUT: "ni li seme?",
|
||||
ABOUT_QT: "QT li seme?",
|
||||
TITLE: "nimi",
|
||||
TOGGLE_ALWAYS_ON_TOP: "sewi ala sewi e lipu",
|
||||
DISABLE_TRACK: "awen e linja",
|
||||
AUDIO_DEVICES: "ila kalama",
|
||||
PLAYLIST_MENU: "lipu musi",
|
||||
THEME: "len ijo",
|
||||
SETTINGS: "ken ijo",
|
||||
LANGUAGE: "toki",
|
||||
APPEARANCE: "lukin ijo",
|
||||
TITLE_ONLY_ON_FULLSCREEN: "lupa lukin ale taso la, sitelen e nimi",
|
||||
CLICK_TO_PAUSE: "luka la, awen",
|
||||
DOUBLE_TAP_TO_SEEK: "luka tu la, lukin",
|
||||
DOUBLE_TAP_TO_SEEK_BY: "luka tu la, lukin kepeken",
|
||||
FONT: "sitelen",
|
||||
SUBTITLES_FONT_SIZE: "suli pi sitelen anpa",
|
||||
UI_FADE_TIME: "tenpo pi wawa UI",
|
||||
UPDATE_APPIMAGE: "sin e ijo (AppImage taso)"
|
||||
},
|
||||
telugu: {
|
||||
OPEN_FILE: "ఫైల్ తెరువు",
|
||||
URL_FILE_PATH: "లింకు / ఫైల్ ఉన్న ప్రదేశం",
|
||||
FILE_MENU: "ఫైల్ మెనూ",
|
||||
OPEN_URL: "URL తెరువు",
|
||||
EXIT: "నిష్క్రమణ",
|
||||
PLAYBACK: "ప్లేబ్యాక్",
|
||||
PLAY_PAUSE: "ప్లే/పౌస్",
|
||||
REWIND_10S: "10సె వెనక్కి",
|
||||
FORWARD_10S: "10సె ముందుకి",
|
||||
REWIND_5S: "5సె వెనక్కి",
|
||||
FORWARD_5S: "5సె వెనక్కి",
|
||||
SPEED_DECREASE_POINT_ONE: "వేగం -0.1",
|
||||
SPEED_INCREASE_POINT_ONE: "వేగం +0.1",
|
||||
HALVE_SPEED: "వేగం సగానికి",
|
||||
DOUBLE_SPEED: "వేగం రెట్టింపు",
|
||||
BACKWARD_FRAME: "ఫ్రేము వెనక్కి",
|
||||
FORWARD_FRAME: "ఫ్రేము ముందుకి",
|
||||
AUDIO: "ఆడియో మెనూ",
|
||||
INCREASE_VOLUME: "ధ్వని పెంచు",
|
||||
DECREASE_VOLUME: "ధ్వని తగ్గించు",
|
||||
MUTE_VOLUME: "మ్యూట్ చేయి",
|
||||
VIDEO: "వీడియో మెనూ",
|
||||
CYCLE_VIDEO: "వీడియో ని సైకిల్ చేయి",
|
||||
SUBTITLES: "ఉపశీర్షిక/subtitles",
|
||||
CYCLE_SUB_TRACK: "ఉపశీర్షిక ట్రాక్ ని సైకిల్ చేయి",
|
||||
CYCLE_AUDIO_TRACK: "ఆడియో ట్రాక్ ని సైకిల్ చేయి",
|
||||
VIEW: "దర్శన",
|
||||
FULLSCREEN: "స్క్రీన్ నింపు",
|
||||
STATS: "గణాంకాలు",
|
||||
TOGGLE_NYAN_CAT: "Nyan Cat నుండి/కు మార్చు",
|
||||
ABOUT: "మా గురుంచి",
|
||||
ABOUT_QT: "Qt గురించి",
|
||||
TITLE: "టైటిల్",
|
||||
TOGGLE_ALWAYS_ON_TOP: "పైవైపు నుండి/కు మార్చు",
|
||||
DISABLE_TRACK: "ట్రాక్ ని ఆపు",
|
||||
AUDIO_DEVICES: "ఆడియో పరికరాలు",
|
||||
PLAYLIST_MENU: "ప్లేయలిస్ట్ మెనూ",
|
||||
THEME: "థీమ్ ",
|
||||
SETTINGS: "సెట్టింగులు",
|
||||
LANGUAGE: "భాష ",
|
||||
APPEARANCE: "ప్రదర్శన",
|
||||
TITLE_ONLY_ON_FULLSCREEN: "స్క్రీన్ నింపినప్పుడే టైటిల్ చూపు",
|
||||
CLICK_TO_PAUSE: "పౌస్ చేయడానికి క్లిక్/టాప్ చేయండి",
|
||||
DOUBLE_TAP_TO_SEEK: "సీక్ చేయడానికి డబల్ టాప్/క్లిక్ చేయండి",
|
||||
DOUBLE_TAP_TO_SEEK_BY: "సీక్ ఆధారం",
|
||||
FONT: "పదాలు/ఫాంట్",
|
||||
SUBTITLES_FONT_SIZE: "సుబ్టైటల్ పదాలు/ఫాంట్ సైజు",
|
||||
UI_FADE_TIME: "UI వీడిపోవు(మిల్లీ సె)",
|
||||
UPDATE_APPIMAGE: "అప్డేట్ చేయి (AppImage Only)"
|
||||
},
|
||||
vietnamese: {
|
||||
OPEN_FILE: "Mở Tập Tin",
|
||||
URL_FILE_PATH: "URL / Đường dẫn tập tin",
|
||||
FILE_MENU: "Tập Tin",
|
||||
OPEN_URL: "Mở URL",
|
||||
EXIT: "Thoát",
|
||||
PLAYBACK: "Phát lại",
|
||||
PLAY_PAUSE: "Phát/Tạm dừng",
|
||||
REWIND_10S: "Tua lại 10 giây",
|
||||
FORWARD_10S: "Tua tiến 10 giây",
|
||||
REWIND_5S: "Tua lại 5 giây",
|
||||
FORWARD_5S: "Tua tiến 5 giây",
|
||||
SPEED_DECREASE_POINT_ONE: "Tốc độ -0.1",
|
||||
SPEED_INCREASE_POINT_ONE: "Tốc độ +0.1",
|
||||
HALVE_SPEED: "Tốc độ phân đôi",
|
||||
DOUBLE_SPEED: "Tốc độ nhân đôi",
|
||||
BACKWARD_FRAME: "Khung trước",
|
||||
FORWARD_FRAME: "Khung tiếp theo",
|
||||
AUDIO: "Âm thanh",
|
||||
INCREASE_VOLUME: "Tăng âm lượng",
|
||||
DECREASE_VOLUME: "Giảm âm lượng",
|
||||
MUTE_VOLUME: "Tắt âm thanh",
|
||||
VIDEO: "Video",
|
||||
CYCLE_VIDEO: "Đảo video",
|
||||
SUBTITLES: "Phụ đề",
|
||||
CYCLE_SUB_TRACK: "Đảo Subtitle Track",
|
||||
CYCLE_AUDIO_TRACK: "Đảo Audio Track",
|
||||
VIEW: "Nhìn",
|
||||
FULLSCREEN: "Toàn màn hình",
|
||||
STATS: "Thống kê",
|
||||
TOGGLE_NYAN_CAT: "Bật/Tắt Nyan Cat",
|
||||
ABOUT: "Giới thiệu",
|
||||
ABOUT_QT: "Giới thiệu về Qt",
|
||||
TITLE: "Tiêu đề",
|
||||
TOGGLE_ALWAYS_ON_TOP: "Bật/Tắt luôn ở trên",
|
||||
DISABLE_TRACK: "Tắt Track",
|
||||
AUDIO_DEVICES: "Thiết bị Audio",
|
||||
PLAYLIST_MENU: "Menu danh sách phát",
|
||||
THEME: "Diện mạo",
|
||||
SETTINGS: "Tùy chỉnh",
|
||||
LANGUAGE: "Ngôn ngữ",
|
||||
APPEARANCE: "Giao diện",
|
||||
TITLE_ONLY_ON_FULLSCREEN: "Chỉ hiện thị tiêu đè khi ở toàn màn hình",
|
||||
CLICK_TO_PAUSE: "Chạm/Click để tạm dừng",
|
||||
DOUBLE_TAP_TO_SEEK: "Chạm/Click đúp để lướt",
|
||||
DOUBLE_TAP_TO_SEEK_BY: "Lướt đến",
|
||||
FONT: "Kiểu chữ",
|
||||
SUBTITLES_FONT_SIZE: "Kích thước kiểu chữ của phụ đề",
|
||||
UI_FADE_TIME: "Thời gian làm mợ giao diện (ms)",
|
||||
UPDATE_APPIMAGE: "Cập nhật (Chỉ dành cho AppImage)"
|
||||
}
|
||||
}
|
BIN
src/qml/icon.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
2
src/qml/icons/Niconico/backward.svg
Normal file
|
@ -0,0 +1,2 @@
|
|||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="PlayerSeekBackwardButton-icon"><path d="M18.3 29A38 38 0 1 1 23 76.7a4 4 0 0 0-5.7 0l-2.8 2.8a4 4 0 0 0 0 5.7A50 50 0 1 0 8 22.8l-2-1.2a4 4 0 0 0-6 3.5v18.2a4 4 0 0 0 6 3.5L21.7 38a4 4 0 0 0 .2-7L18.3 29zM42 66a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2V40h-2a2 2 0 0 1-2-2v-4c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2v32zm32 0a2 2 0 0 1-2 2H52a2 2 0 0 1-2-2V34c0-1.1.9-2 2-2h20a2 2 0 0 1 2 2v32zm-8-26h-8v20h8V40z"></path></svg>
|
||||
|
After Width: | Height: | Size: 564 B |
1
src/qml/icons/Niconico/forward.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="PlayerSeekForwardButton-icon"><path d="M81.7 29A38 38 0 1 0 77 76.7a4 4 0 0 1 5.7 0l2.8 2.8a4 4 0 0 1 0 5.7A50 50 0 1 1 92 22.8l2-1.2a4 4 0 0 1 6 3.5v18.2a4 4 0 0 1-6 3.5L78.3 38a4 4 0 0 1-.2-7l3.7-2.1zM37 66a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2V40h-2a2 2 0 0 1-2-2v-4c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2v32zm32 0a2 2 0 0 1-2 2H47a2 2 0 0 1-2-2V34c0-1.1.9-2 2-2h20a2 2 0 0 1 2 2v32zm-8-26h-8v20h8V40z"></path></svg>
|
After Width: | Height: | Size: 561 B |
1
src/qml/icons/Niconico/fullscreen.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="EnableFullScreenButton-icon"><path d="M60 92a4 4 0 0 1 4-4h15.5L59 67.5a4 4 0 0 1 0-5.7l2.8-2.8a4 4 0 0 1 5.7 0L88 79.5V64a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v32a4 4 0 0 1-4 4H64a4 4 0 0 1-4-4v-4zM40 8a4 4 0 0 1-4 4H20.5L41 32.5a4 4 0 0 1 0 5.7L38.2 41a4 4 0 0 1-5.7 0L12 20.5V36a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4h32a4 4 0 0 1 4 4v4z"></path></svg>
|
After Width: | Height: | Size: 512 B |
1
src/qml/icons/Niconico/next.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="PlayerSkipNextButton-icon"><path d="M34.6 46.6a4 4 0 0 1 0 6.8L6 71.2A4 4 0 0 1 0 67.8V32.2a4 4 0 0 1 6.1-3.4l28.5 17.8zM74.6 46.6a4 4 0 0 1 0 6.8L46 71.2a4 4 0 0 1-6.1-3.4V32.2a4 4 0 0 1 6.1-3.4l28.5 17.8zM100 4a4 4 0 0 0-4-4h-8a4 4 0 0 0-4 4v92a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4V4z"></path></svg>
|
After Width: | Height: | Size: 452 B |
1
src/qml/icons/Niconico/pause.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg viewBox="0 0 88 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="PlayerPauseButton-icon"><path d="M32 4a4 4 0 0 0-4-4H4a4 4 0 0 0-4 4v92a4 4 0 0 0 4 4h24a4 4 0 0 0 4-4V4zM88 4a4 4 0 0 0-4-4H60a4 4 0 0 0-4 4v92a4 4 0 0 0 4 4h24a4 4 0 0 0 4-4V4z"></path></svg>
|
After Width: | Height: | Size: 350 B |
1
src/qml/icons/Niconico/play.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="PlayerPlayButton-icon"><path d="M95 42a8.9 8.9 0 0 1 0 16L12.9 99A8.9 8.9 0 0 1 0 91.2V9a8.9 8.9 0 0 1 12.8-8C33.2 11.1 73 31 95.1 42.1z"></path></svg>
|
After Width: | Height: | Size: 309 B |
1
src/qml/icons/Niconico/prev.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="SeekToHeadButton-icon"><path d="M65.4 46.6a4 4 0 0 0 0 6.8L94 71.2a4 4 0 0 0 6.1-3.4V32.2a4 4 0 0 0-6.1-3.4L65.4 46.6zM25.4 46.6a4 4 0 0 0 0 6.8L54 71.2a4 4 0 0 0 6.1-3.4V32.2a4 4 0 0 0-6.1-3.4L25.4 46.6zM0 4a4 4 0 0 1 4-4h8a4 4 0 0 1 4 4v92a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4z"></path></svg>
|
After Width: | Height: | Size: 447 B |
1
src/qml/icons/Niconico/settings.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="PlayerOptionButton-icon"><path d="M56.5 3.2A4 4 0 0 0 53 0H47a4 4 0 0 0-3.6 3L41 14a4 4 0 0 1-2.6 2.9c-1.3.4-2.5 1-3.7 1.5a4 4 0 0 1-3.9-.2l-9.3-5.8a4 4 0 0 0-4.8.4 52.2 52.2 0 0 0-4 4 4 4 0 0 0-.4 4.8l5.8 9.3a4 4 0 0 1 .2 4c-.6 1.1-1 2.3-1.5 3.6a4 4 0 0 1-2.9 2.6L3.2 43.5A4 4 0 0 0 0 47V53a4 4 0 0 0 3 3.6L14 59a4 4 0 0 1 2.9 2.6c.4 1.3 1 2.5 1.5 3.7a4 4 0 0 1-.2 3.9l-5.8 9.3a4 4 0 0 0 .4 4.8c1.2 1.4 2.6 2.8 4 4a4 4 0 0 0 4.8.4l9.3-5.8a4 4 0 0 1 4-.2c1.1.6 2.3 1 3.6 1.5a4 4 0 0 1 2.6 3l2.5 10.6A4 4 0 0 0 47 100H53a4 4 0 0 0 3.6-3L59 86a4 4 0 0 1 2.6-2.9c1.3-.4 2.5-1 3.7-1.5a4 4 0 0 1 3.9.2l9.3 5.8a4 4 0 0 0 4.8-.4c1.4-1.2 2.8-2.6 4-4a4 4 0 0 0 .4-4.8L82 69.2a4 4 0 0 1-.2-4c.6-1.1 1-2.3 1.5-3.6a4 4 0 0 1 3-2.6l10.6-2.5A4 4 0 0 0 100 53V47a4 4 0 0 0-3-3.6L86 41a4 4 0 0 1-2.9-2.6c-.4-1.3-1-2.5-1.5-3.7a4 4 0 0 1 .2-3.9l5.8-9.3a4 4 0 0 0-.4-4.8 52.2 52.2 0 0 0-4-4 4 4 0 0 0-4.8-.4L69.2 18a4 4 0 0 1-4 .2c-1.1-.6-2.3-1-3.6-1.5a4 4 0 0 1-2.6-2.9L56.5 3.2zm-6.5 27a19.9 19.9 0 0 1 0 39.7 19.9 19.9 0 1 1 0-39.7z"></path></svg>
|
After Width: | Height: | Size: 1.2 KiB |
1
src/qml/icons/Niconico/volume-down.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="MuteVideoButton-icon"><path d="M24.1 24L45.2 1.3a3.7 3.7 0 0 1 4.4-1C51 1.2 52 2.8 52 4.6v91c0 1.8-1 3.4-2.4 4.1-1.5.7-3.2.4-4.4-.9L24.2 76H8a8 8 0 0 1-8-8V32a8 8 0 0 1 8-8h16.1zm51.4-9.3l.5-.7 1.5-1.5a4 4 0 0 1 5.4-.2 49.9 49.9 0 0 1 0 75.3 4 4 0 0 1-5.4-.2A75 75 0 0 1 76 86a4 4 0 0 1 .2-5.8 40 40 0 0 0 9-11.1V69l.1-.2a39.3 39.3 0 0 0 4.4-14v-.3L90 50c0-10.6-4.1-20.2-10.8-27.3l-.1-.2a44 44 0 0 0-1-1H78l-.1-.2a35 35 0 0 0-2-1.8 4 4 0 0 1-.6-.8v-.3a4 4 0 0 1 .1-3.6v-.1zM62.2 27.8l1-1a4 4 0 0 1 5.4-.4l2.5 2.2a30 30 0 0 1-2.7 45v.1a4 4 0 0 1-2.2.7c-1.1 0-2.1-.4-3-1.1l-.9-1-.5-.5a4 4 0 0 1-.6-.8l-.1-.2a4 4 0 0 1 1-5A20 20 0 0 0 70 50a20 20 0 0 0-7.8-15.8A4 4 0 0 1 62 28l.2-.2z"></path></svg>
|
After Width: | Height: | Size: 854 B |
1
src/qml/icons/Niconico/volume-mute.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="UnMuteVideoButton-icon"><path d="M24.1 24L45.2 1.3a3.7 3.7 0 0 1 4.4-1C51 1.2 52 2.8 52 4.6v91c0 1.8-1 3.4-2.4 4.1-1.5.7-3.2.4-4.4-.9L24.2 76H8a8 8 0 0 1-8-8V32a8 8 0 0 1 8-8h16.1zM80 42.7l11.5-11.4a4.4 4.4 0 0 1 6.1 0l1.1 1a4.4 4.4 0 0 1 0 6.2L87.3 50l11.4 11.5a4.4 4.4 0 0 1 0 6.1l-1 1.1a4.4 4.4 0 0 1-6.2 0L80 57.3 68.5 68.7a4.4 4.4 0 0 1-6.1 0l-1.1-1a4.4 4.4 0 0 1 0-6.2L72.7 50 61.3 38.5a4.4 4.4 0 0 1 0-6.1l1-1.1a4.4 4.4 0 0 1 6.2 0L80 42.7z"></path></svg>
|
After Width: | Height: | Size: 620 B |
1
src/qml/icons/Niconico/volume-up.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="MuteVideoButton-icon"><path d="M24.1 24L45.2 1.3a3.7 3.7 0 0 1 4.4-1C51 1.2 52 2.8 52 4.6v91c0 1.8-1 3.4-2.4 4.1-1.5.7-3.2.4-4.4-.9L24.2 76H8a8 8 0 0 1-8-8V32a8 8 0 0 1 8-8h16.1zm51.4-9.3l.5-.7 1.5-1.5a4 4 0 0 1 5.4-.2 49.9 49.9 0 0 1 0 75.3 4 4 0 0 1-5.4-.2A75 75 0 0 1 76 86a4 4 0 0 1 .2-5.8 40 40 0 0 0 9-11.1V69l.1-.2a39.3 39.3 0 0 0 4.4-14v-.3L90 50c0-10.6-4.1-20.2-10.8-27.3l-.1-.2a44 44 0 0 0-1-1H78l-.1-.2a35 35 0 0 0-2-1.8 4 4 0 0 1-.6-.8v-.3a4 4 0 0 1 .1-3.6v-.1zM62.2 27.8l1-1a4 4 0 0 1 5.4-.4l2.5 2.2a30 30 0 0 1-2.7 45v.1a4 4 0 0 1-2.2.7c-1.1 0-2.1-.4-3-1.1l-.9-1-.5-.5a4 4 0 0 1-.6-.8l-.1-.2a4 4 0 0 1 1-5A20 20 0 0 0 70 50a20 20 0 0 0-7.8-15.8A4 4 0 0 1 62 28l.2-.2z"></path></svg>
|
After Width: | Height: | Size: 854 B |
1
src/qml/icons/RoosterTeeth/backward.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="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: 640 B |
1
src/qml/icons/RoosterTeeth/forward.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="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: 642 B |
1
src/qml/icons/RoosterTeeth/fullscreen.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M14 28h-4v10h10v-4h-6v-6zm-4-8h4v-6h6v-4H10v10zm24 14h-6v4h10V28h-4v6zm-6-24v4h6v6h4V10H28z"/></svg>
|
After Width: | Height: | Size: 172 B |
1
src/qml/icons/RoosterTeeth/next.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M12 36l17-12-17-12v24zm20-24v24h4V12h-4z"/></svg>
|
After Width: | Height: | Size: 121 B |
1
src/qml/icons/RoosterTeeth/pause.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M12 38h8V10h-8v28zm16-28v28h8V10h-8z"/></svg>
|
After Width: | Height: | Size: 117 B |
1
src/qml/icons/RoosterTeeth/play.svg
Executable file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M16 10v28l22-14z"/></svg>
|
After Width: | Height: | Size: 97 B |
1
src/qml/icons/RoosterTeeth/playlist.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="-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: 174 B |
1
src/qml/icons/RoosterTeeth/prev.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M12 12h4v24h-4zm7 12l17 12V12z"/></svg>
|
After Width: | Height: | Size: 111 B |
1
src/qml/icons/RoosterTeeth/settings.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24" fill="black" width="18px" height="18px"><g><path d="M0,0h24v24H0V0z" fill="none"/><path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></g></svg>
|
After Width: | Height: | Size: 1 KiB |
1
src/qml/icons/RoosterTeeth/subtitles.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="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: 235 B |
1
src/qml/icons/RoosterTeeth/volume-down.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="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: 171 B |
1
src/qml/icons/RoosterTeeth/volume-mute.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="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.13a17.94 17.94 0 007.37-3.62L39.45 42 42 39.45l-18-18L8.55 6zM24 8l-4.18 4.18L24 16.36V8z"/></svg>
|
After Width: | Height: | Size: 449 B |
1
src/qml/icons/RoosterTeeth/volume-up.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="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: 281 B |
1
src/qml/icons/YouTube/backward.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="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: 640 B |
1
src/qml/icons/YouTube/forward.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="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: 642 B |
1
src/qml/icons/YouTube/fullscreen.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M14 28h-4v10h10v-4h-6v-6zm-4-8h4v-6h6v-4H10v10zm24 14h-6v4h10V28h-4v6zm-6-24v4h6v6h4V10H28z"/></svg>
|
After Width: | Height: | Size: 172 B |
1
src/qml/icons/YouTube/next.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M12 36l17-12-17-12v24zm20-24v24h4V12h-4z"/></svg>
|
After Width: | Height: | Size: 121 B |
1
src/qml/icons/YouTube/pause.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M12 38h8V10h-8v28zm16-28v28h8V10h-8z"/></svg>
|
After Width: | Height: | Size: 117 B |
1
src/qml/icons/YouTube/play.svg
Executable file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M16 10v28l22-14z"/></svg>
|
After Width: | Height: | Size: 97 B |
1
src/qml/icons/YouTube/playlist.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="-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: 174 B |
1
src/qml/icons/YouTube/prev.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M12 12h4v24h-4zm7 12l17 12V12z"/></svg>
|
After Width: | Height: | Size: 111 B |
1
src/qml/icons/YouTube/settings.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24" fill="black" width="18px" height="18px"><g><path d="M0,0h24v24H0V0z" fill="none"/><path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></g></svg>
|
After Width: | Height: | Size: 1 KiB |