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 |