├── .gitmodules ├── CMakeLists.txt ├── README.md ├── cmake └── FindMPV.cmake ├── resources ├── font │ ├── FREEWARE.txt │ ├── LICENSE.txt │ ├── emoji.ttf │ ├── keymap_keyboard.ttf │ ├── keymap_ps.ttf │ ├── keymap_xbox.ttf │ ├── switch_font.ttf │ └── switch_icons.ttf ├── i18n │ ├── en-US │ │ ├── hints.json │ │ ├── iso639.json │ │ └── main.json │ └── zh-Hans │ │ ├── hints.json │ │ ├── iso639.json │ │ └── main.json ├── icon │ ├── icon.jpg │ ├── icon.png │ ├── logo.gif │ └── logo.png ├── img │ └── sys │ │ ├── battery_back_dark.svg │ │ ├── battery_back_light.svg │ │ ├── cursor.png │ │ ├── ethernet_dark.svg │ │ ├── ethernet_light.svg │ │ ├── wifi_0_dark.svg │ │ ├── wifi_0_light.svg │ │ ├── wifi_1_dark.svg │ │ ├── wifi_1_light.svg │ │ ├── wifi_2_dark.svg │ │ ├── wifi_2_light.svg │ │ ├── wifi_3_dark.svg │ │ └── wifi_3_light.svg ├── material │ ├── LICENSE.txt │ └── MaterialIcons-Regular.ttf ├── shaders │ ├── fill_aa_fsh.dksh │ ├── fill_fsh.dksh │ └── fill_vsh.dksh ├── svg │ ├── arrow-left-right.svg │ ├── bpx-svg-sprite-pause.svg │ ├── bpx-svg-sprite-play.svg │ ├── bpx-svg-sprite-setting.svg │ ├── bpx-svg-sprite-volume-off.svg │ ├── bpx-svg-sprite-volume.svg │ ├── ico-dark-file.svg │ ├── ico-dark-folder.svg │ ├── ico-light-file.svg │ ├── ico-light-folder.svg │ ├── player-lock.svg │ ├── player-unlock.svg │ └── sun-fill.svg └── xml │ ├── activity │ ├── main.xml │ └── video.xml │ ├── fragment │ ├── local_driver.xml │ └── player_setting.xml │ └── view │ └── video_view.xml ├── scripts ├── build_switch.sh ├── build_switch_deko3d.sh └── deps │ ├── deko3d-8939ff80f94d061dbc7d107e08b8e3be53e2938b-1-any.pkg.tar.zst │ ├── hacBrewPack-3.05-1-any.pkg.tar.zst │ ├── libuam-f8c9eef01ffe06334d530393d636d69e2b52744b-1-any.pkg.tar.zst │ ├── switch-ffmpeg-6.1-5-any.pkg.tar.zst │ ├── switch-libass-0.17.1-1-any.pkg.tar.zst │ ├── switch-libmpv-0.36.0-2-any.pkg.tar.zst │ ├── switch-libmpv_deko3d-0.36.0-1-any.pkg.tar.zst │ └── switch-nspmini-48d4fc2-1-any.pkg.tar.xz ├── src ├── include │ ├── activity │ │ ├── main_activity.hpp │ │ └── video_activity.hpp │ ├── fragment │ │ ├── local_driver.hpp │ │ └── player_setting.hpp │ ├── utils │ │ ├── gesture_helper.hpp │ │ ├── intent.hpp │ │ ├── presenter.h │ │ ├── register.hpp │ │ └── utils.hpp │ └── view │ │ ├── mpv_core.hpp │ │ └── video_view.hpp └── source │ ├── activity │ ├── main_activity.cpp │ └── video_activity.cpp │ ├── fragment │ ├── local_driver.cpp │ └── player_setting.cpp │ ├── main.cpp │ ├── utils │ ├── gesture_helper.cpp │ ├── intent.cpp │ ├── register.cpp │ └── utils.cpp │ └── view │ ├── mpv_core.cpp │ └── video_view.cpp └── thirdparty └── CMakeLists.txt /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "thirdparty/borealis"] 2 | path = thirdparty/borealis 3 | url = https://github.com/anonymous5l/borealis.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | set(PLATFORM "SWITCH" CACHE STRING "Platform for build target.") 4 | 5 | if (${PLATFORM} STREQUAL "SWITCH") 6 | set(PLATFORM_SWITCH ON PARENT_SCOPE BOOL "For borealis build.") 7 | elseif (${PLATFORM} STREQUAL "DESKTOP") 8 | set(PLATFORM_DESKTOP ON PARENT_SCOPE BOOL "For borealis build.") 9 | else () 10 | message(FATAL_ERROR "Please set build target. Example: -DPLATFORM=SWITCH or -DPLATFORM=DESKTOP") 11 | endif () 12 | 13 | # ui library 14 | set(BOREALIS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/borealis) 15 | set(BOREALIS_LIBRARY ${BOREALIS_DIR}/library) 16 | 17 | include(${BOREALIS_LIBRARY}/cmake/commonOption.cmake) 18 | 19 | # NintendoSwitch 20 | cmake_dependent_option(BUILTIN_NSP "Built in NSP forwarder" OFF "PLATFORM_SWITCH" OFF) 21 | 22 | # mpv related 23 | # If your system does not support OpenGL(ES), you can use software rendering, but it will affect performance. 24 | option(MPV_SW_RENDER "Using CPU to draw videos" OFF) 25 | if (MPV_SW_RENDER) 26 | list(APPEND APP_PLATFORM_OPTION -DMPV_SW_RENDER) 27 | endif () 28 | 29 | # On systems that do not support framebuffer, let MPV to draw to full screen and 30 | # then cover unnecessary areas with UI. 31 | option(MPV_NO_FB "Using system provided framebuffer" OFF) 32 | if (MPV_NO_FB) 33 | list(APPEND APP_PLATFORM_OPTION -DMPV_NO_FB) 34 | endif() 35 | 36 | # Bundle mpv.dll into wiliwili.exe (Windows only) 37 | cmake_dependent_option(MPV_BUNDLE_DLL "Bundle mpv.dll" OFF "USE_LIBROMFS;WIN32" OFF) 38 | if (MPV_BUNDLE_DLL) 39 | list(APPEND APP_PLATFORM_OPTION -DMPV_BUNDLE_DLL) 40 | list(APPEND APP_PLATFORM_LIB MemoryModule) 41 | endif () 42 | 43 | if (CMAKE_BUILD_TYPE STREQUAL Debug) 44 | add_definitions(-D_DEBUG) 45 | add_definitions(-D_GLIBCXX_ASSERTIONS) 46 | if (DEBUG_SANITIZER) 47 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer") 48 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -O0 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer") 49 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined,address") 50 | endif () 51 | endif () 52 | 53 | # toolchain 54 | include(${BOREALIS_LIBRARY}/cmake/toolchain.cmake) 55 | 56 | git_info(GIT_TAG_VERSION GIT_TAG_SHORT) 57 | list(APPEND APP_PLATFORM_OPTION -DBUILD_TAG_VERSION=${GIT_TAG_VERSION} -DBUILD_TAG_SHORT=${GIT_TAG_SHORT}) 58 | 59 | add_definitions(-DFONS_HASH_LUT_SIZE=4096) 60 | 61 | set(CMAKE_CXX_STANDARD 20) 62 | 63 | # project info 64 | project(nxplayer) 65 | set(VERSION_MAJOR "1") 66 | set(VERSION_MINOR "0") 67 | set(VERSION_REVISION "0") 68 | if (NOT VERSION_BUILD) 69 | set(VERSION_BUILD "0") 70 | endif () 71 | set(PROJECT_AUTHOR "anonymous5l") 72 | set(PACKAGE_NAME cn.anonymous5l.nxplayer) 73 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") 74 | set(PROJECT_RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/resources) 75 | set(APP_VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_REVISION}") 76 | set(PROJECT_ICON ${CMAKE_CURRENT_SOURCE_DIR}/resources/icon/icon.jpg) 77 | 78 | option(BRLS_UNITY_BUILD "Unity build" OFF) 79 | 80 | find_package(Threads REQUIRED) 81 | list(APPEND APP_PLATFORM_LIB ${CMAKE_THREAD_LIBS_INIT}) 82 | 83 | if (PLATFORM_DESKTOP) 84 | find_package(MPV REQUIRED) 85 | 86 | message(STATUS "Found libmpv: ${MPV_VERSION} ${MPV_INCLUDE_DIR} ${MPV_LIBRARY}") 87 | list(APPEND APP_PLATFORM_INCLUDE ${MPV_INCLUDE_DIR}) 88 | if (NOT MPV_BUNDLE_DLL) 89 | list(APPEND APP_PLATFORM_LIB ${MPV_LIBRARY}) 90 | endif () 91 | else () 92 | find_package(PkgConfig REQUIRED) 93 | 94 | pkg_search_module(MPV REQUIRED mpv) 95 | message(STATUS "Found libmpv: ${MPV_VERSION} ${MPV_INCLUDE_DIRS} ${MPV_STATIC_LIBRARIES}") 96 | list(APPEND APP_PLATFORM_INCLUDE ${MPV_INCLUDE_DIRS}) 97 | list(APPEND APP_PLATFORM_LIB ${MPV_STATIC_LIBRARIES}) 98 | link_directories(${MPV_LIBRARY_DIRS}) 99 | if (PLATFORM_IOS) 100 | list(APPEND APP_PLATFORM_LIB "-framework CoreMedia -framework CoreText -framework VideoToolbox") 101 | endif () 102 | endif () 103 | 104 | list(APPEND APP_PLATFORM_OPTION 105 | -DBUILD_PACKAGE_NAME=${PACKAGE_NAME} 106 | -DBUILD_VERSION_MAJOR=${VERSION_MAJOR} 107 | -DBUILD_VERSION_MINOR=${VERSION_MINOR} 108 | -DBUILD_VERSION_REVISION=${VERSION_REVISION} 109 | ) 110 | 111 | if (BUILTIN_NSP) 112 | list(APPEND APP_PLATFORM_LIB nsp) 113 | list(APPEND APP_PLATFORM_OPTION -DBUILTIN_NSP) 114 | add_subdirectory(scripts/switch-forwarder) 115 | endif () 116 | 117 | if (USE_LIBROMFS) 118 | add_libromfs(${PROJECT_NAME} ${PROJECT_RESOURCES}) 119 | endif () 120 | 121 | # set resources dir 122 | if (CUSTOM_RESOURCES_DIR) 123 | set(BRLS_RESOURCES_DIR ${CUSTOM_RESOURCES_DIR}) 124 | elseif (INSTALL) 125 | set(BRLS_RESOURCES_DIR ${CMAKE_INSTALL_PREFIX}/share/wiliwili) 126 | else () 127 | set(BRLS_RESOURCES_DIR ".") 128 | endif () 129 | 130 | file(GLOB_RECURSE MAIN_SRC src/source/*.cpp) 131 | if (PLATFORM_SWITCH) 132 | list(APPEND MAIN_SRC ${BOREALIS_LIBRARY}/lib/platforms/switch/switch_wrapper.c) 133 | endif () 134 | 135 | add_subdirectory(thirdparty) 136 | 137 | program_target(${PROJECT_NAME} "${MAIN_SRC}") 138 | 139 | # building release file 140 | if (PLATFORM_DESKTOP) 141 | # Copy resources to build dir 142 | add_custom_target(${PROJECT_NAME}.data 143 | COMMAND "${CMAKE_COMMAND}" -E copy_directory ${PROJECT_RESOURCES} ${CMAKE_BINARY_DIR}/resources 144 | COMMAND "echo" "copy resources" 145 | ) 146 | add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}.data) 147 | elseif (PLATFORM_SWITCH) 148 | set(BUILD_FONT_DIR ${CMAKE_BINARY_DIR}/resources/font) 149 | if (GIT_TAG_VERSION) 150 | string(SUBSTRING ${GIT_TAG_VERSION} 1 -1 APP_VERSION) 151 | endif () 152 | if (BUILTIN_NSP) 153 | add_dependencies(${PROJECT_NAME} nsp_forwarder.nsp) 154 | endif () 155 | if (USE_DEKO3D) 156 | gen_dksh("${PROJECT_RESOURCES}/shaders") 157 | endif () 158 | add_custom_target(${PROJECT_NAME}.nro 159 | DEPENDS ${PROJECT_NAME} 160 | COMMAND ${NX_NACPTOOL_EXE} --create ${PROJECT_NAME} ${PROJECT_AUTHOR} ${APP_VERSION} ${PROJECT_NAME}.nacp 161 | COMMAND ${CMAKE_COMMAND} -E copy_directory ${PROJECT_RESOURCES} ${CMAKE_BINARY_DIR}/resources 162 | COMMAND ${CMAKE_COMMAND} -E remove -f ${BUILD_FONT_DIR}/*.txt ${BUILD_FONT_DIR}/switch_font.ttf 163 | ${BUILD_FONT_DIR}/keymap*.ttf 164 | COMMAND ${NX_ELF2NRO_EXE} ${PROJECT_NAME}.elf ${PROJECT_NAME}.nro --icon=${PROJECT_ICON} 165 | --nacp=${PROJECT_NAME}.nacp --romfsdir=${CMAKE_BINARY_DIR}/resources 166 | ALL 167 | ) 168 | endif () 169 | 170 | target_include_directories(${PROJECT_NAME} PRIVATE src/include ${APP_PLATFORM_INCLUDE}) 171 | target_compile_options(${PROJECT_NAME} PRIVATE -ffunction-sections -fdata-sections -Wunused-variable ${APP_PLATFORM_OPTION}) 172 | target_link_libraries(${PROJECT_NAME} PRIVATE borealis ${APP_PLATFORM_LIB}) 173 | target_link_options(${PROJECT_NAME} PRIVATE ${APP_PLATFORM_LINK_OPTION}) 174 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Switch本地媒体播放器 2 | 3 | 非常感谢[xfangfang](https://github.com/xfangfang)贡献的[wiliwili](https://github.com/xfangfang/wiliwili)开源项目。本项目源码大部分基于该项目,如果有遇到跨平台编译问题请参考wiliwili项目的CMake文件。 4 | 目前仅支持Switch。 5 | 6 | ## 安装 7 | 8 | #### NintendoSwitch 9 | 10 | 1. [nxplayer-release](https://github.com/xfangfang/wiliwili/releases)下载对应版本`NRO`文件. 11 | 2. 放置`SD卡/switch`目录下. 12 | 3. 在主页打开任意游戏按住`R`键直至进入`hbmenu`,运行`nxplayer` 13 | 14 | ## 编译 15 | 16 | 推荐使用`Docker`编译。 至于为什么有两个驱动版本请参考[wiliwili](https://github.com/xfangfang/wiliwili/wiki#%E7%A1%AC%E4%BB%B6%E8%A7%A3%E7%A0%81) wiki下的硬件解码部分。 17 | 18 | ```bash 19 | 20 | git clone --recursive https://github.com/anonymous5l/nxplayer.git 21 | 22 | cd nxplayer 23 | 24 | ## OpenGL版本 25 | docker run --platform linux/amd64 --rm -v $(pwd):/data devkitpro/devkita64:20240324 \ 26 | bash -c "/data/scripts/build_switch.sh" 27 | 28 | ## deko3d版本 29 | docker run --platform linux/amd64 --rm -v $(pwd):/data devkitpro/devkita64:20240324 \ 30 | bash -c "/data/scripts/build_switch_deko3d.sh" 31 | ``` 32 | 33 | ## MPV配置 34 | 35 | Switch下可以将字幕字体文件放入`SD卡/config/mpv/`目录下,没有的话自己创建。字体文件名为`subfont.ttf`,具体配置目录参考[mpv](https://mpv.io/manual/master/#FILES)下的`Files`段。 36 | 不放置的话可能会导致`MPV`播放视频中文字体乱码。 -------------------------------------------------------------------------------- /cmake/FindMPV.cmake: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # CMake module to search for the mpv libraries. 3 | # 4 | # WARNING: This module is experimental work in progress. 5 | # 6 | # Based one FindVLC.cmake by: 7 | # Copyright (c) 2011 Michael Jansen 8 | # Modified by Tobias Hieta 9 | # 10 | # Redistribution and use is allowed according to the terms of the BSD license. 11 | # For details see the accompanying COPYING-CMAKE-SCRIPTS file. 12 | # 13 | ############################################################################### 14 | 15 | # 16 | ### Global Configuration Section 17 | # 18 | SET(_MPV_REQUIRED_VARS MPV_INCLUDE_DIR MPV_LIBRARY) 19 | 20 | # 21 | ### MPV uses pkgconfig. 22 | # 23 | if (PKG_CONFIG_FOUND) 24 | pkg_check_modules(PC_MPV QUIET mpv) 25 | endif (PKG_CONFIG_FOUND) 26 | 27 | # Used for macOS 28 | # brew tap xfangfang/wiliwili && brew install mpv-wiliwili 29 | if (APPLE) 30 | execute_process(COMMAND brew --prefix mpv-wiliwili 31 | TIMEOUT 5 32 | OUTPUT_VARIABLE HOMEBREW_MPV 33 | OUTPUT_STRIP_TRAILING_WHITESPACE 34 | ) 35 | if (NOT HOMEBREW_MPV) 36 | message(AUTHOR_WARNING "You can install mpv-wiliwili to reduce the size of dependencies, this is very useful when used with dylibbundler to package as a standalone app:\nbrew tap xfangfang/wiliwili && brew install mpv-wiliwili\nPlease refer to: https://github.com/xfangfang/wiliwili/discussions/151 for more information") 37 | endif() 38 | endif () 39 | 40 | # 41 | ### Look for the include files. 42 | # 43 | find_path( 44 | MPV_INCLUDE_DIR 45 | NAMES mpv/client.h 46 | HINTS 47 | ${HOMEBREW_MPV}/include 48 | ${PC_MPV_INCLUDEDIR} 49 | ${PC_MPV_INCLUDE_DIRS} # Unused for MPV but anyway 50 | PATH_SUFFIXES mpv 51 | ) 52 | 53 | # 54 | ### Look for the libraries 55 | # 56 | set(_MPV_LIBRARY_NAMES mpv) 57 | if (PC_MPV_LIBRARIES) 58 | set(_MPV_LIBRARY_NAMES ${PC_MPV_LIBRARIES}) 59 | endif (PC_MPV_LIBRARIES) 60 | 61 | foreach (l ${_MPV_LIBRARY_NAMES}) 62 | find_library( 63 | MPV_LIBRARY_${l} 64 | NAMES ${l} 65 | HINTS 66 | ${HOMEBREW_MPV}/lib 67 | ${PC_MPV_LIBDIR} 68 | ${PC_MPV_LIBRARY_DIRS} # Unused for MPV but anyway 69 | PATH_SUFFIXES lib${LIB_SUFFIX} 70 | ) 71 | list(APPEND MPV_LIBRARY ${MPV_LIBRARY_${l}}) 72 | endforeach () 73 | 74 | get_filename_component(_MPV_LIBRARY_DIR ${MPV_LIBRARY_mpv} PATH) 75 | mark_as_advanced(MPV_LIBRARY) 76 | 77 | set(MPV_LIBRARY_DIRS _MPV_LIBRARY_DIR) 78 | list(REMOVE_DUPLICATES MPV_LIBRARY_DIRS) 79 | 80 | mark_as_advanced(MPV_INCLUDE_DIR) 81 | mark_as_advanced(MPV_LIBRARY_DIRS) 82 | set(MPV_INCLUDE_DIRS ${MPV_INCLUDE_DIR}) 83 | 84 | # 85 | ### Check if everything was found and if the version is sufficient. 86 | # 87 | include(FindPackageHandleStandardArgs) 88 | find_package_handle_standard_args( 89 | MPV 90 | REQUIRED_VARS ${_MPV_REQUIRED_VARS} 91 | VERSION_VAR MPV_VERSION 92 | ) -------------------------------------------------------------------------------- /resources/font/FREEWARE.txt: -------------------------------------------------------------------------------- 1 | Xbox and PS gamepad font was Modified from: 2 | http://www.thealmightyguru.com/Wiki/index.php?title=Controller_Font 3 | 4 | © Copyright 2020: Dean Tersigni. This font is freeware. 5 | 6 | Thanks! -------------------------------------------------------------------------------- /resources/font/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font 2 | Name 'Source'. Source is a trademark of Adobe in the United States 3 | and/or other countries. 4 | 5 | This Font Software is licensed under the SIL Open Font License, 6 | Version 1.1. 7 | 8 | This license is copied below, and is also available with a FAQ at: 9 | http://scripts.sil.org/OFL 10 | 11 | ----------------------------------------------------------- 12 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 13 | ----------------------------------------------------------- 14 | 15 | PREAMBLE 16 | The goals of the Open Font License (OFL) are to stimulate worldwide 17 | development of collaborative font projects, to support the font 18 | creation efforts of academic and linguistic communities, and to 19 | provide a free and open framework in which fonts may be shared and 20 | improved in partnership with others. 21 | 22 | The OFL allows the licensed fonts to be used, studied, modified and 23 | redistributed freely as long as they are not sold by themselves. The 24 | fonts, including any derivative works, can be bundled, embedded, 25 | redistributed and/or sold with any software provided that any reserved 26 | names are not used by derivative works. The fonts and derivatives, 27 | however, cannot be released under any other type of license. The 28 | requirement for fonts to remain under this license does not apply to 29 | any document created using the fonts or their derivatives. 30 | 31 | DEFINITIONS 32 | "Font Software" refers to the set of files released by the Copyright 33 | Holder(s) under this license and clearly marked as such. This may 34 | include source files, build scripts and documentation. 35 | 36 | "Reserved Font Name" refers to any names specified as such after the 37 | copyright statement(s). 38 | 39 | "Original Version" refers to the collection of Font Software 40 | components as distributed by the Copyright Holder(s). 41 | 42 | "Modified Version" refers to any derivative made by adding to, 43 | deleting, or substituting -- in part or in whole -- any of the 44 | components of the Original Version, by changing formats or by porting 45 | the Font Software to a new environment. 46 | 47 | "Author" refers to any designer, engineer, programmer, technical 48 | writer or other person who contributed to the Font Software. 49 | 50 | PERMISSION & CONDITIONS 51 | Permission is hereby granted, free of charge, to any person obtaining 52 | a copy of the Font Software, to use, study, copy, merge, embed, 53 | modify, redistribute, and sell modified and unmodified copies of the 54 | Font Software, subject to the following conditions: 55 | 56 | 1) Neither the Font Software nor any of its individual components, in 57 | Original or Modified Versions, may be sold by itself. 58 | 59 | 2) Original or Modified Versions of the Font Software may be bundled, 60 | redistributed and/or sold with any software, provided that each copy 61 | contains the above copyright notice and this license. These can be 62 | included either as stand-alone text files, human-readable headers or 63 | in the appropriate machine-readable metadata fields within text or 64 | binary files as long as those fields can be easily viewed by the user. 65 | 66 | 3) No Modified Version of the Font Software may use the Reserved Font 67 | Name(s) unless explicit written permission is granted by the 68 | corresponding Copyright Holder. This restriction only applies to the 69 | primary font name as presented to the users. 70 | 71 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 72 | Software shall not be used to promote, endorse or advertise any 73 | Modified Version, except to acknowledge the contribution(s) of the 74 | Copyright Holder(s) and the Author(s) or with their explicit written 75 | permission. 76 | 77 | 5) The Font Software, modified or unmodified, in part or in whole, 78 | must be distributed entirely under this license, and must not be 79 | distributed under any other license. The requirement for fonts to 80 | remain under this license does not apply to any document created using 81 | the Font Software. 82 | 83 | TERMINATION 84 | This license becomes null and void if any of the above conditions are 85 | not met. 86 | 87 | DISCLAIMER 88 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 89 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 90 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 91 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 92 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 93 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 94 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 95 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 96 | OTHER DEALINGS IN THE FONT SOFTWARE. -------------------------------------------------------------------------------- /resources/font/emoji.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/resources/font/emoji.ttf -------------------------------------------------------------------------------- /resources/font/keymap_keyboard.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/resources/font/keymap_keyboard.ttf -------------------------------------------------------------------------------- /resources/font/keymap_ps.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/resources/font/keymap_ps.ttf -------------------------------------------------------------------------------- /resources/font/keymap_xbox.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/resources/font/keymap_xbox.ttf -------------------------------------------------------------------------------- /resources/font/switch_font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/resources/font/switch_font.ttf -------------------------------------------------------------------------------- /resources/font/switch_icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/resources/font/switch_icons.ttf -------------------------------------------------------------------------------- /resources/i18n/en-US/hints.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": "OK", 3 | "cancel": "Cancel", 4 | "back": "Back", 5 | "exit": "Exit", 6 | "exit_hint": "You will exit this app", 7 | "open": "Open", 8 | "on": "On", 9 | "off": "Off", 10 | "delete": "Delete", 11 | "save": "Save", 12 | "input": "Please input...", 13 | "submit": "Submit" 14 | } 15 | -------------------------------------------------------------------------------- /resources/i18n/en-US/iso639.json: -------------------------------------------------------------------------------- 1 | { 2 | "aar": "Afar", 3 | "abk": "Abkhazian", 4 | "ace": "Achinese", 5 | "ach": "Acoli", 6 | "ada": "Adangme", 7 | "ady": "Adyghe", 8 | "afa": "Afro-Asiatic", 9 | "afh": "Afrihili", 10 | "afr": "Afrikaans", 11 | "ain": "Ainu", 12 | "aka": "Akan", 13 | "akk": "Akkadian", 14 | "ale": "Aleut", 15 | "alg": "Algonquian", 16 | "alt": "Southern Altai", 17 | "amh": "Amharic", 18 | "ang": "English", 19 | "anp": "Angika", 20 | "apa": "Apache", 21 | "ara": "Arabic", 22 | "arc": "Official Aramaic", 23 | "arg": "Aragonese", 24 | "arn": "Mapudungun", 25 | "arp": "Arapaho", 26 | "art": "Artificial", 27 | "arw": "Arawak", 28 | "asm": "Assamese", 29 | "ast": "Asturian", 30 | "ath": "Athapascan", 31 | "aus": "Australian", 32 | "ava": "Avaric", 33 | "ave": "Avestan", 34 | "awa": "Awadhi", 35 | "aym": "Aymara", 36 | "aze": "Azerbaijani", 37 | "bad": "Banda", 38 | "bai": "Bamileke", 39 | "bak": "Bashkir", 40 | "bal": "Baluchi", 41 | "bam": "Bambara", 42 | "ban": "Balinese", 43 | "bas": "Basa", 44 | "bat": "Baltic", 45 | "bej": "Beja", 46 | "bel": "Belarusian", 47 | "bem": "Bemba", 48 | "ben": "Bengali", 49 | "ber": "Berber", 50 | "bho": "Bhojpuri", 51 | "bih": "Bihari", 52 | "bik": "Bikol", 53 | "bin": "Bini", 54 | "bis": "Bislama", 55 | "bla": "Siksika", 56 | "bnt": "Bantu", 57 | "bod": "Tibetan", 58 | "tib": "Tibetan", 59 | "bos": "Bosnian", 60 | "bra": "Braj", 61 | "bre": "Breton", 62 | "btk": "Batak", 63 | "bua": "Buriat", 64 | "bug": "Buginese", 65 | "bul": "Bulgarian", 66 | "byn": "Blin", 67 | "cad": "Caddo", 68 | "cai": "Central American Indian", 69 | "car": "Galibi Carib", 70 | "cat": "Catalan", 71 | "cau": "Caucasian", 72 | "ceb": "Cebuano", 73 | "cel": "Celtic", 74 | "ces": "Czech", 75 | "cze": "Czech", 76 | "cha": "Chamorro", 77 | "chb": "Chibcha", 78 | "che": "Chechen", 79 | "chg": "Chagatai", 80 | "chk": "Chuukese", 81 | "chm": "Mari", 82 | "chn": "Chinook jargon", 83 | "cho": "Choctaw", 84 | "chp": "Chipewyan", 85 | "chr": "Cherokee", 86 | "chu": "Church Slavic", 87 | "chv": "Chuvash", 88 | "chy": "Cheyenne", 89 | "cmc": "Chamic", 90 | "cnr": "Montenegrin", 91 | "cop": "Coptic", 92 | "cor": "Cornish", 93 | "cos": "Corsican", 94 | "cpe": "Creoles and pidgins, English based", 95 | "cpf": "Creoles and pidgins, French-based", 96 | "cpp": "Creoles and pidgins, Portuguese-based", 97 | "cre": "Cree", 98 | "crh": "Crimean Tatar", 99 | "crp": "Creoles and pidgins", 100 | "csb": "Kashubian", 101 | "cus": "Cushitic", 102 | "cym": "Welsh", 103 | "wel": "Welsh", 104 | "dak": "Dakota", 105 | "dan": "Danish", 106 | "dar": "Dargwa", 107 | "day": "Land Dayak", 108 | "del": "Delaware", 109 | "den": "Slave (Athapascan)", 110 | "deu": "German", 111 | "ger": "German", 112 | "dgr": "Dogrib", 113 | "din": "Dinka", 114 | "div": "Divehi", 115 | "doi": "Dogri", 116 | "dra": "Dravidian", 117 | "dsb": "Lower Sorbian", 118 | "dua": "Duala", 119 | "dum": "Dutch, Middle", 120 | "dyu": "Dyula", 121 | "dzo": "Dzongkha", 122 | "efi": "Efik", 123 | "egy": "Egyptian (Ancient)", 124 | "eka": "Ekajuk", 125 | "ell": "Greek, Modern", 126 | "gre": "Greek, Modern", 127 | "elx": "Elamite", 128 | "eng": "English", 129 | "enm": "English, Middle", 130 | "epo": "Esperanto", 131 | "est": "Estonian", 132 | "eus": "Basque", 133 | "baq": "Basque", 134 | "ewe": "Ewe", 135 | "ewo": "Ewondo", 136 | "fan": "Fang", 137 | "fao": "Faroese", 138 | "fas": "Persian", 139 | "per": "Persian", 140 | "fat": "Fanti", 141 | "fij": "Fijian", 142 | "fil": "Filipino", 143 | "fin": "Finnish", 144 | "fiu": "Finno-Ugrian", 145 | "fon": "Fon", 146 | "fra": "French", 147 | "fre": "French", 148 | "frm": "French, Middle ", 149 | "fro": "French, Old", 150 | "frr": "Northern Frisian", 151 | "frs": "East Frisian Low Saxon", 152 | "fry": "Western Frisian", 153 | "ful": "Fulah", 154 | "fur": "Friulian", 155 | "gaa": "Ga", 156 | "gay": "Gayo", 157 | "gba": "Gbaya", 158 | "gem": "Germanic", 159 | "gez": "Geez", 160 | "gil": "Gilbertese", 161 | "gla": "Gaelic", 162 | "gle": "Irish", 163 | "glg": "Galician", 164 | "glv": "Manx", 165 | "gmh": "German, Middle High", 166 | "goh": "German, Old High", 167 | "gon": "Gondi", 168 | "gor": "Gorontalo", 169 | "got": "Gothic", 170 | "grb": "Grebo", 171 | "grc": "Greek, Ancient", 172 | "grn": "Guarani", 173 | "gsw": "Swiss German", 174 | "guj": "Gujarati", 175 | "gwi": "Gwich'in", 176 | "hai": "Haida", 177 | "hat": "Haitian", 178 | "hau": "Hausa", 179 | "haw": "Hawaiian", 180 | "heb": "Hebrew", 181 | "her": "Herero", 182 | "hil": "Hiligaynon", 183 | "him": "Himachali", 184 | "hin": "Hindi", 185 | "hit": "Hittite", 186 | "hmn": "Hmong", 187 | "hmo": "Hiri Motu", 188 | "hrv": "Croatian", 189 | "hsb": "Upper Sorbian", 190 | "hun": "Hungarian", 191 | "hup": "Hupa", 192 | "hye": "Armenian", 193 | "arm": "Armenian", 194 | "iba": "Iban", 195 | "ibo": "Igbo", 196 | "ido": "Ido", 197 | "iii": "Sichuan Yi", 198 | "ijo": "Ijo", 199 | "iku": "Inuktitut", 200 | "ile": "Interlingue", 201 | "ilo": "Iloko", 202 | "ina": "Interlingua", 203 | "inc": "Indo-Aryan", 204 | "ind": "Indonesian", 205 | "ine": "Indo-European", 206 | "inh": "Ingush", 207 | "ipk": "Inupiaq", 208 | "ira": "Iranian", 209 | "iro": "Iroquoian", 210 | "isl": "Icelandic", 211 | "ice": "Icelandic", 212 | "ita": "Italian", 213 | "jav": "Javanese", 214 | "jbo": "Lojban", 215 | "jpn": "Japanese", 216 | "jpr": "Judeo-Persian", 217 | "jrb": "Judeo-Arabic", 218 | "kaa": "Kara-Kalpak", 219 | "kab": "Kabyle", 220 | "kac": "Kachin", 221 | "kal": "Kalaallisut", 222 | "kam": "Kamba", 223 | "kan": "Kannada", 224 | "kar": "Karen", 225 | "kas": "Kashmiri", 226 | "kat": "Georgian", 227 | "geo": "Georgian", 228 | "kau": "Kanuri", 229 | "kaw": "Kawi", 230 | "kaz": "Kazakh", 231 | "kbd": "Kabardian", 232 | "kha": "Khasi", 233 | "khi": "Khoisan", 234 | "khm": "Central Khmer", 235 | "kho": "Khotanese", 236 | "kik": "Kikuyu", 237 | "kin": "Kinyarwanda", 238 | "kir": "Kirghiz", 239 | "kmb": "Kimbundu", 240 | "kok": "Konkani", 241 | "kom": "Komi", 242 | "kon": "Kongo", 243 | "kor": "Korean", 244 | "kos": "Kosraean", 245 | "kpe": "Kpelle", 246 | "krc": "Karachay-Balkar", 247 | "krl": "Karelian", 248 | "kro": "Kru", 249 | "kru": "Kurukh", 250 | "kua": "Kuanyama", 251 | "kum": "Kumyk", 252 | "kur": "Kurdish", 253 | "kut": "Kutenai", 254 | "lad": "Ladino", 255 | "lah": "Lahnda", 256 | "lam": "Lamba", 257 | "lao": "Lao", 258 | "lat": "Latin", 259 | "lav": "Latvian", 260 | "lez": "Lezghian", 261 | "lim": "Limburgan", 262 | "lin": "Lingala", 263 | "lit": "Lithuanian", 264 | "lol": "Mongo", 265 | "loz": "Lozi", 266 | "ltz": "Luxembourgish", 267 | "lua": "Luba-Lulua", 268 | "lub": "Luba-Katanga", 269 | "lug": "Ganda", 270 | "lui": "Luiseno", 271 | "lun": "Lunda", 272 | "luo": "Luo (Kenya and Tanzania)", 273 | "lus": "Lushai", 274 | "mad": "Madurese", 275 | "mag": "Magahi", 276 | "mah": "Marshallese", 277 | "mai": "Maithili", 278 | "mak": "Makasar", 279 | "mal": "Malayalam", 280 | "man": "Mandingo", 281 | "map": "Austronesian", 282 | "mar": "Marathi", 283 | "mas": "Masai", 284 | "mdf": "Moksha", 285 | "mdr": "Mandar", 286 | "men": "Mende", 287 | "mga": "Irish, Middle", 288 | "mic": "Mi'kmaq", 289 | "min": "Minangkabau", 290 | "mis": "Uncoded", 291 | "mkd": "Macedonian", 292 | "mac": "Macedonian", 293 | "mkh": "Mon-Khmer", 294 | "mlg": "Malagasy", 295 | "mlt": "Maltese", 296 | "mnc": "Manchu", 297 | "mni": "Manipuri", 298 | "mno": "Manobo", 299 | "moh": "Mohawk", 300 | "mon": "Mongolian", 301 | "mos": "Mossi", 302 | "mri": "Māori", 303 | "mao": "Māori", 304 | "msa": "Malay", 305 | "may": "Malay", 306 | "mul": "Multiple", 307 | "mun": "Munda", 308 | "mus": "Creek", 309 | "mwl": "Mirandese", 310 | "mwr": "Marwari", 311 | "mya": "Burmese", 312 | "bur": "Burmese", 313 | "myn": "Mayan", 314 | "myv": "Erzya", 315 | "nah": "Nahuatl", 316 | "nai": "North American Indian", 317 | "nap": "Neapolitan", 318 | "nau": "Nauru", 319 | "nav": "Navajo", 320 | "nbl": "Ndebele, South", 321 | "nde": "Ndebele, North", 322 | "ndo": "Ndonga", 323 | "nds": "Low German", 324 | "nep": "Nepali", 325 | "new": "Nepal Bhasa", 326 | "nia": "Nias", 327 | "nic": "Niger-Kordofanian", 328 | "niu": "Niuean", 329 | "nld": "Dutch", 330 | "dut": "Dutch", 331 | "nno": "Norwegian Nynorsk", 332 | "nob": "Bokmål, Norwegian", 333 | "nog": "Nogai", 334 | "non": "Norse, Old", 335 | "nor": "Norwegian", 336 | "nqo": "N'Ko", 337 | "nso": "Pedi", 338 | "nub": "Nubian", 339 | "nwc": "Classical Newari", 340 | "nya": "Chichewa", 341 | "nym": "Nyamwezi", 342 | "nyn": "Nyankole", 343 | "nyo": "Nyoro", 344 | "nzi": "Nzima", 345 | "oci": "Occitan", 346 | "oji": "Ojibwa", 347 | "ori": "Oriya", 348 | "orm": "Oromo", 349 | "osa": "Osage", 350 | "oss": "Ossetian", 351 | "ota": "Turkish, Ottoman", 352 | "oto": "Otomian", 353 | "paa": "Papuan", 354 | "pag": "Pangasinan", 355 | "pal": "Pahlavi", 356 | "pam": "Pampanga", 357 | "pan": "Panjabi", 358 | "pap": "Papiamento", 359 | "pau": "Palauan", 360 | "peo": "Persian, Old", 361 | "phi": "Philippine", 362 | "phn": "Phoenician", 363 | "pli": "Pali", 364 | "pol": "Polish", 365 | "pon": "Pohnpeian", 366 | "por": "Portuguese", 367 | "pra": "Prakrit", 368 | "pro": "Provençal, Old", 369 | "pus": "Pushto", 370 | "que": "Quechua", 371 | "raj": "Rajasthani", 372 | "rap": "Rapanui", 373 | "rar": "Rarotongan", 374 | "roa": "Romance", 375 | "roh": "Romansh", 376 | "rom": "Romany", 377 | "ron": "Romanian", 378 | "rum": "Romanian", 379 | "run": "Rundi", 380 | "rup": "Aromanian", 381 | "rus": "Russian", 382 | "sad": "Sandawe", 383 | "sag": "Sango", 384 | "sah": "Yakut", 385 | "sai": "South American Indian", 386 | "sal": "Salishan", 387 | "sam": "Samaritan Aramaic", 388 | "san": "Sanskrit", 389 | "sas": "Sasak", 390 | "sat": "Santali", 391 | "scn": "Sicilian", 392 | "sco": "Scots", 393 | "sel": "Selkup", 394 | "sem": "Semitic", 395 | "sga": "Irish, Old", 396 | "sgn": "Sign", 397 | "shn": "Shan", 398 | "sid": "Sidamo", 399 | "sin": "Sinhala", 400 | "sio": "Siouan", 401 | "sit": "Sino-Tibetan", 402 | "sla": "Slavic", 403 | "slk": "Slovak", 404 | "slo": "Slovak", 405 | "slv": "Slovenian", 406 | "sma": "Southern Sami", 407 | "sme": "Northern Sami", 408 | "smi": "Sami", 409 | "smj": "Lule Sami", 410 | "smn": "Inari Sami", 411 | "smo": "Samoan", 412 | "sms": "Skolt Sami", 413 | "sna": "Shona", 414 | "snd": "Sindhi", 415 | "snk": "Soninke", 416 | "sog": "Sogdian", 417 | "som": "Somali", 418 | "son": "Songhai", 419 | "sot": "Sotho, Southern", 420 | "spa": "Spanish", 421 | "sqi": "Albanian", 422 | "alb": "Albanian", 423 | "srd": "Sardinian", 424 | "srn": "Sranan Tongo", 425 | "srp": "Serbian", 426 | "srr": "Serer", 427 | "ssa": "Nilo-Saharan", 428 | "ssw": "Swati", 429 | "suk": "Sukuma", 430 | "sun": "Sundanese", 431 | "sus": "Susu", 432 | "sux": "Sumerian", 433 | "swa": "Swahili", 434 | "swe": "Swedish", 435 | "syc": "Classical Syriac", 436 | "syr": "Syriac", 437 | "tah": "Tahitian", 438 | "tai": "Tai", 439 | "tam": "Tamil", 440 | "tat": "Tatar", 441 | "tel": "Telugu", 442 | "tem": "Timne", 443 | "ter": "Tereno", 444 | "tet": "Tetum", 445 | "tgk": "Tajik", 446 | "tgl": "Tagalog", 447 | "tha": "Thai", 448 | "tig": "Tigre", 449 | "tir": "Tigrinya", 450 | "tiv": "Tiv", 451 | "tkl": "Tokelau", 452 | "tlh": "Klingon", 453 | "tli": "Tlingit", 454 | "tmh": "Tamashek", 455 | "tog": "Tonga (Nyasa)", 456 | "ton": "Tonga (Tonga Islands)", 457 | "tpi": "Tok Pisin", 458 | "tsi": "Tsimshian", 459 | "tsn": "Tswana", 460 | "tso": "Tsonga", 461 | "tuk": "Turkmen", 462 | "tum": "Tumbuka", 463 | "tup": "Tupi", 464 | "tur": "Turkish", 465 | "tut": "Altaic", 466 | "tvl": "Tuvalu", 467 | "twi": "Twi", 468 | "tyv": "Tuvinian", 469 | "udm": "Udmurt", 470 | "uga": "Ugaritic", 471 | "uig": "Uighur", 472 | "ukr": "Ukrainian", 473 | "umb": "Umbundu", 474 | "und": "Undetermined", 475 | "urd": "Urdu", 476 | "uzb": "Uzbek", 477 | "vai": "Vai", 478 | "ven": "Venda", 479 | "vie": "Vietnamese", 480 | "vol": "Volapük", 481 | "vot": "Votic", 482 | "wak": "Wakashan", 483 | "wal": "Wolaitta", 484 | "war": "Waray", 485 | "was": "Washo", 486 | "wen": "Sorbian", 487 | "wln": "Walloon", 488 | "wol": "Wolof", 489 | "xal": "Kalmyk", 490 | "xho": "Xhosa", 491 | "yao": "Yao", 492 | "yap": "Yapese", 493 | "yid": "Yiddish", 494 | "yor": "Yoruba", 495 | "ypk": "Yupik", 496 | "zap": "Zapotec", 497 | "zbl": "Blissymbols", 498 | "zen": "Zenaga", 499 | "zgh": "Standard Moroccan Tamazight", 500 | "zha": "Zhuang", 501 | "zho": "Chinese", 502 | "chi": "Chinese", 503 | "znd": "Zande", 504 | "zul": "Zulu", 505 | "zun": "Zuni", 506 | "zxx": "No linguistic content", 507 | "zza": "Zaza" 508 | } -------------------------------------------------------------------------------- /resources/i18n/en-US/main.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabs": { 3 | "local": "Local Disk" 4 | }, 5 | "player": { 6 | "speed": "Speed", 7 | "current_speed": "Current Speed {}", 8 | "exit_hint": "Exit to home" 9 | }, 10 | "setting": { 11 | "header": "Player Setting", 12 | "subtitle": "Subtitle", 13 | "common": "Common", 14 | "sleep": "Timing Off", 15 | "aspect": { 16 | "header":"Video Aspect", 17 | "crop": "Crop", 18 | "stretch": "Stretch", 19 | "auto": "Auto" 20 | }, 21 | "equalizer": { 22 | "header": "Equalizer", 23 | "brightness": "Brightness", 24 | "contrast": "Contrast", 25 | "saturation": "Saturation", 26 | "gamma": "Gamma", 27 | "hue": "Hue", 28 | "reset": "Reset" 29 | }, 30 | "bottom_progress": "Always show progress bar", 31 | "track": { 32 | "audio": "Audio Track" 33 | } 34 | }, 35 | "common": { 36 | "minute": "Minute" 37 | } 38 | } -------------------------------------------------------------------------------- /resources/i18n/zh-Hans/hints.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": "确定", 3 | "back": "返回", 4 | "cancel": "取消", 5 | "exit": "退出", 6 | "exit_hint": "即将退出应用", 7 | "open": "打开", 8 | "on": "开", 9 | "off": "关", 10 | "delete": "删除", 11 | "save": "保存", 12 | "input": "请输入...", 13 | "submit": "提交" 14 | } 15 | -------------------------------------------------------------------------------- /resources/i18n/zh-Hans/iso639.json: -------------------------------------------------------------------------------- 1 | { 2 | "aar": "阿法尔语", 3 | "aav": "南亚语系", 4 | "abk": "阿布哈兹语", 5 | "ace": "亚齐语", 6 | "ach": "阿乔利语", 7 | "ada": "阿当梅语", 8 | "ady": "阿迪格语", 9 | "afa": "亚非语系", 10 | "afh": "阿弗里希利语", 11 | "afr": "阿非利堪斯语", 12 | "ain": "阿伊努语", 13 | "aka": "阿坎语", 14 | "akk": "阿卡德语", 15 | "alb": "阿尔巴尼亚语", 16 | "ale": "阿留申语", 17 | "alg": "阿尔冈昆语族", 18 | "alt": "南阿尔泰语", 19 | "alv": "大西洋-刚果语族", 20 | "amh": "阿姆哈拉语", 21 | "ang": "古英语", 22 | "anp": "安吉卡语", 23 | "apa": "阿帕切语支", 24 | "aqa": "阿拉卡卢夫语系", 25 | "aql": "阿尔吉克语系", 26 | "ara": "阿拉伯语", 27 | "arc": "阿拉米语", 28 | "arg": "阿拉贡语", 29 | "arm": "亚美尼亚语", 30 | "arn": "阿劳坎语", 31 | "arp": "阿拉帕霍语", 32 | "art": "人工语言", 33 | "arw": "阿拉瓦克语", 34 | "asm": "阿萨姆语", 35 | "ast": "阿斯图里亚斯语", 36 | "ath": "阿萨巴斯卡语支", 37 | "auf": "阿拉瓦语系", 38 | "aus": "澳大利亚语系", 39 | "ava": "阿瓦尔语", 40 | "ave": "阿维斯陀语", 41 | "awa": "阿瓦德语", 42 | "awd": "阿拉瓦克语系", 43 | "aym": "艾马拉语", 44 | "azc": "犹他-阿兹特克语系", 45 | "aze": "阿塞拜疆语", 46 | "bad": "班达语", 47 | "bai": "巴米累克语支", 48 | "bak": "巴什基尔语", 49 | "bal": "俾路支语", 50 | "bam": "班巴拉语", 51 | "ban": "巴厘语", 52 | "baq": "巴斯克语", 53 | "bas": "巴萨语", 54 | "bat": "波罗的语族", 55 | "bej": "贝贾语", 56 | "bel": "白俄罗斯语", 57 | "bem": "本巴语", 58 | "ben": "孟加拉语", 59 | "ber": "柏柏尔语族", 60 | "bho": "博杰普尔语", 61 | "bih": "比哈尔语", 62 | "bik": "比科尔语", 63 | "bin": "比尼语", 64 | "bis": "比斯拉马语", 65 | "bla": "西克西卡语", 66 | "bnt": "班图语支", 67 | "bod": "藏语", 68 | "bos": "波斯尼亚语", 69 | "bra": "布拉吉语", 70 | "bre": "布列塔尼语", 71 | "btk": "巴塔克语", 72 | "bua": "布里亚特语", 73 | "bug": "布吉语", 74 | "bul": "保加利亚语", 75 | "bur": "缅甸语", 76 | "byn": "比林语", 77 | "cad": "卡多语", 78 | "cai": "中美洲印第安诸语言", 79 | "car": "加勒比语系", 80 | "cat": "加泰罗尼亚语", 81 | "cau": "高加索语系", 82 | "cba": "奇布恰语系", 83 | "ccn": "北高加索语系", 84 | "ccs": "南高加索语系", 85 | "cdc": "乍得语系", 86 | "cdd": "卡多语系", 87 | "ceb": "宿务语", 88 | "cel": "凯尔特语族", 89 | "ces": "捷克语", 90 | "cha": "查莫罗语", 91 | "chb": "奇布查语", 92 | "che": "车臣语", 93 | "chg": "查加台语", 94 | "chi": "中文", 95 | "chk": "丘克语", 96 | "chm": "马里语", 97 | "chn": "奇努克混合语", 98 | "cho": "乔克托语", 99 | "chp": "奇佩维安语", 100 | "chr": "切罗基语", 101 | "chu": "古教会斯拉夫语", 102 | "chv": "楚瓦什语", 103 | "chy": "夏延语", 104 | "cmc": "占语支", 105 | "cnr": "黑山语", 106 | "cop": "科普特语", 107 | "cor": "康沃尔语", 108 | "cos": "科西嘉语", 109 | "cpe": "英语克里奥尔混合语", 110 | "cpf": "法语克里奥尔混合语", 111 | "cpp": "葡萄牙语克里奥尔混合语", 112 | "cre": "克里语", 113 | "crh": "克里米亚鞑靼语", 114 | "crp": "克里奥尔混合语", 115 | "csb": "卡舒比语", 116 | "csu": "中苏丹语系", 117 | "cus": "库希特语族", 118 | "cym": "威尔士语", 119 | "cze": "捷克语", 120 | "dak": "达科他语", 121 | "dan": "丹麦语", 122 | "dar": "达尔格瓦语", 123 | "day": "内陆达雅克语群", 124 | "del": "特拉华语", 125 | "den": "奴语", 126 | "deu": "德语", 127 | "dgr": "多格里布语", 128 | "din": "丁卡语", 129 | "div": "迪维希语", 130 | "dmn": "曼德语族", 131 | "doi": "多格拉语", 132 | "dra": "达罗毗荼语系", 133 | "dsb": "下索布语", 134 | "dua": "杜亚拉语", 135 | "dum": "中古荷兰语", 136 | "dut": "荷兰语", 137 | "dyu": "迪尤拉语", 138 | "dzo": "宗喀语", 139 | "efi": "埃菲克语", 140 | "egx": "埃及语族", 141 | "egy": "古埃及语", 142 | "eka": "埃克丘克语", 143 | "ell": "现代希腊语", 144 | "elx": "埃兰语", 145 | "eng": "英语", 146 | "enm": "中古英语", 147 | "epo": "世界语", 148 | "est": "爱沙尼亚语", 149 | "esx": "爱斯基摩-阿留申语系", 150 | "euq": "巴斯克语族", 151 | "eus": "巴斯克语", 152 | "ewe": "埃维语", 153 | "ewo": "埃翁多语", 154 | "fan": "芳语", 155 | "fao": "法罗斯语", 156 | "fas": "波斯语", 157 | "fat": "芳蒂语", 158 | "fij": "斐济语", 159 | "fil": "菲律宾语", 160 | "fin": "芬兰语", 161 | "fiu": "芬兰-乌戈尔语族", 162 | "fon": "丰语", 163 | "fox": "台湾南岛语言", 164 | "fra": "法语", 165 | "fre": "法语", 166 | "frm": "中古法语", 167 | "fro": "古法语", 168 | "frr": "北弗里西亚语", 169 | "frs": "东弗里西亚语", 170 | "fry": "弗里西亚语", 171 | "ful": "富拉语", 172 | "fur": "弗留利语", 173 | "gaa": "加语", 174 | "gay": "卡约语", 175 | "gba": "巴亚语", 176 | "gem": "日耳曼语族", 177 | "geo": "格鲁吉亚语", 178 | "ger": "德语", 179 | "gez": "吉兹语", 180 | "gil": "吉尔伯特语", 181 | "gla": "苏格兰盖尔语", 182 | "gle": "爱尔兰语", 183 | "glg": "加利西亚语", 184 | "glv": "马恩岛语", 185 | "gme": "东日耳曼语支", 186 | "gmh": "中古高地德语", 187 | "gmq": "北日耳曼语支", 188 | "gmw": "西日耳曼语支", 189 | "goh": "古高地德语", 190 | "gon": "贡德语", 191 | "gor": "哥伦打洛语", 192 | "got": "哥特语", 193 | "grb": "格列博语", 194 | "grc": "古典希腊语", 195 | "gre": "现代希腊语", 196 | "grk": "希腊语族", 197 | "grn": "瓜拉尼语", 198 | "gsw": "瑞士德语", 199 | "guj": "古吉拉特语", 200 | "gwi": "库臣语", 201 | "hai": "海达语", 202 | "hat": "海地克里奥尔语", 203 | "hau": "豪萨语", 204 | "haw": "夏威夷语", 205 | "heb": "希伯来语", 206 | "her": "赫雷罗语", 207 | "hil": "希利盖农语", 208 | "him": "喜马偕尔语", 209 | "hin": "印地语", 210 | "hit": "赫梯语", 211 | "hmn": "苗语", 212 | "hmo": "希里莫图语", 213 | "hmx": "苗瑶语系", 214 | "hok": "霍坎语系", 215 | "hrv": "克罗地亚语", 216 | "hsb": "上索布语", 217 | "hun": "匈牙利语", 218 | "hup": "胡帕语", 219 | "hye": "亚美尼亚语", 220 | "hyx": "亚美尼亚语系", 221 | "iba": "伊班语", 222 | "ibo": "伊博语", 223 | "ice": "冰岛语", 224 | "ido": "伊多语", 225 | "iii": "四川彝语", 226 | "iir": "印度-伊朗语族", 227 | "ijo": "伊乔语", 228 | "iku": "伊努伊特语", 229 | "ile": "西方国际语(国际语E)", 230 | "ilo": "伊洛卡诺语", 231 | "ina": "国际语(国际语A)", 232 | "inc": "印度-雅利安语支", 233 | "ind": "印尼语", 234 | "ine": "印欧语系", 235 | "inh": "印古什语", 236 | "ipk": "依努庇克语", 237 | "ira": "伊朗语支", 238 | "iro": "易洛魁语系", 239 | "isl": "冰岛语", 240 | "ita": "意大利语", 241 | "itc": "意大利语族", 242 | "jav": "爪哇语", 243 | "jbo": "逻辑语", 244 | "jpn": "日语", 245 | "jpr": "犹太-波斯语", 246 | "jpx": "日本语族", 247 | "jrb": "犹太-阿拉伯语", 248 | "kaa": "卡拉卡尔帕克语", 249 | "kab": "卡布列语", 250 | "kac": "景颇语", 251 | "kal": "格陵兰语", 252 | "kam": "坎巴语", 253 | "kan": "卡纳达语", 254 | "kar": "克伦语", 255 | "kas": "克什米尔语", 256 | "kat": "格鲁吉亚语", 257 | "kau": "卡努里语", 258 | "kaw": "卡威语", 259 | "kaz": "哈萨克语", 260 | "kbd": "卡巴尔达语", 261 | "kdo": "科尔多凡语族", 262 | "kha": "卡西语", 263 | "khi": "科依桑语系", 264 | "khm": "高棉语", 265 | "kho": "和阗语", 266 | "kik": "基库尤语", 267 | "kin": "基尼阿万达语", 268 | "kir": "吉尔吉斯语", 269 | "kmb": "金邦杜语", 270 | "kok": "孔卡尼语", 271 | "kom": "科米语", 272 | "kon": "刚果语", 273 | "kor": "朝鲜语", 274 | "kos": "科斯拉伊语", 275 | "kpe": "克佩勒语", 276 | "krc": "卡拉恰伊-巴尔卡尔语", 277 | "krl": "卡累利阿语", 278 | "kro": "克鲁语", 279 | "kru": "库卢克语", 280 | "kua": "宽亚玛语", 281 | "kum": "库梅克语", 282 | "kur": "库尔德语", 283 | "kut": "库特内语", 284 | "lad": "拉迪诺语", 285 | "lah": "拉亨达语", 286 | "lam": "兰巴语", 287 | "lao": "老挝语", 288 | "lat": "拉丁语", 289 | "lav": "拉脱维亚语", 290 | "lez": "列兹金语", 291 | "lim": "林堡语", 292 | "lin": "林加拉语", 293 | "lit": "立陶宛语", 294 | "lol": "芒戈语", 295 | "loz": "洛齐语", 296 | "ltz": "卢森堡语", 297 | "lua": "卢巴-卢拉语", 298 | "lub": "卢巴-加丹加语", 299 | "lug": "干达语", 300 | "lui": "卢伊塞诺语", 301 | "lun": "隆达语", 302 | "luo": "卢奥语", 303 | "lus": "卢萨语", 304 | "mac": "马其顿语", 305 | "mad": "马都拉语", 306 | "mag": "摩揭陀语", 307 | "mah": "马绍尔语", 308 | "mai": "米德勒语", 309 | "mak": "望加锡语", 310 | "mal": "马拉雅拉姆语", 311 | "man": "曼丁哥语", 312 | "mao": "毛利语", 313 | "map": "南岛语系", 314 | "mar": "马拉地语", 315 | "mas": "马萨伊语", 316 | "may": "马来语", 317 | "mdf": "莫克沙语", 318 | "mdr": "曼达语", 319 | "men": "门德语", 320 | "mga": "中古爱尔兰语", 321 | "mic": "米克马克语", 322 | "min": "米南卡保语", 323 | "mis": "未被编码的语言", 324 | "mkd": "马其顿语", 325 | "mkh": "孟-高棉语族", 326 | "mlg": "马达加斯加语", 327 | "mlt": "马耳他语", 328 | "mnc": "满语", 329 | "mni": "曼尼普尔语", 330 | "mno": "马诺博诸语言", 331 | "moh": "莫霍克语", 332 | "mol": "摩尔达维亚语", 333 | "mon": "蒙古语", 334 | "mos": "莫西语", 335 | "mri": "毛利语", 336 | "msa": "马来语", 337 | "mul": "多种语言", 338 | "mun": "蒙达语族", 339 | "mus": "克里克语", 340 | "mwl": "米兰德斯语", 341 | "mwr": "马尔瓦里语", 342 | "mya": "缅甸语", 343 | "myn": "玛雅语系", 344 | "myv": "埃尔齐亚语", 345 | "nah": "纳瓦特尔语", 346 | "nai": "北美洲印第安诸语言", 347 | "nap": "拿坡里语", 348 | "nau": "瑙鲁语", 349 | "nav": "纳瓦霍语", 350 | "nbl": "南恩德贝莱语", 351 | "nde": "北恩德贝莱语", 352 | "ndo": "恩敦加语", 353 | "nds": "低地撒克逊语", 354 | "nep": "尼泊尔语", 355 | "new": "尼瓦尔语", 356 | "ngf": "跨新几内亚语系", 357 | "nia": "尼亚斯语", 358 | "nic": "尼日尔-科尔多凡语系", 359 | "niu": "纽埃语", 360 | "nld": "荷兰语", 361 | "nno": "新挪威语", 362 | "nob": "挪威布克莫尔语", 363 | "nog": "诺盖语", 364 | "non": "古诺尔斯语", 365 | "nor": "挪威语", 366 | "nqo": "西非书面语言", 367 | "nso": "北索托语", 368 | "nub": "努比亚语支", 369 | "nwc": "古典尼瓦尔语", 370 | "nya": "尼扬贾语", 371 | "nym": "尼扬韦齐语", 372 | "nyn": "尼扬科勒语", 373 | "nyo": "尼奥罗语", 374 | "nzi": "恩济马语", 375 | "oci": "奥克西唐语", 376 | "oji": "奥吉布瓦语", 377 | "omq": "欧托-曼格语系", 378 | "omv": "奥摩语族", 379 | "ori": "奥利亚语", 380 | "orm": "奥罗莫语", 381 | "osa": "欧塞奇语", 382 | "oss": "奥塞梯语", 383 | "ota": "奥斯曼土耳其语", 384 | "oto": "奥托米语族", 385 | "paa": "巴布亚诸语言", 386 | "pag": "邦阿西楠语", 387 | "pal": "中古波斯语", 388 | "pam": "邦板牙语", 389 | "pan": "旁遮普语", 390 | "pap": "帕皮亚门托语", 391 | "pau": "帕劳语", 392 | "peo": "古波斯语", 393 | "per": "波斯语", 394 | "phi": "菲律宾诸语言", 395 | "phn": "腓尼基语", 396 | "plf": "中部马来-波利尼西亚语族", 397 | "pli": "巴利语", 398 | "pol": "波兰语", 399 | "pon": "波纳佩语", 400 | "por": "葡萄牙语", 401 | "poz": "马来-波利尼西亚语族", 402 | "pqe": "东部马来-波利尼西亚语族", 403 | "pqw": "西部马来-波利尼西亚语族", 404 | "pra": "普拉克里特诸语言", 405 | "pro": "古普罗旺斯语", 406 | "pus": "普什图语", 407 | "que": "克丘亚语", 408 | "qwe": "克丘亚语族", 409 | "raj": "拉贾斯坦语", 410 | "rap": "复活节岛语", 411 | "rar": "拉罗汤加语", 412 | "roa": "罗曼语族", 413 | "roh": "罗曼什语", 414 | "rom": "罗姆语", 415 | "ron": "罗马尼亚语", 416 | "rum": "罗马尼亚语", 417 | "run": "基隆迪语", 418 | "rup": "阿罗马尼亚语", 419 | "rus": "俄语", 420 | "sad": "桑达韦语", 421 | "sag": "桑戈语", 422 | "sah": "雅库特语", 423 | "sai": "南美洲印第安诸语言", 424 | "sal": "萨利什语系", 425 | "sam": "萨马利亚阿拉米语", 426 | "san": "梵语", 427 | "sas": "萨萨克语", 428 | "sat": "桑塔利语", 429 | "scc": "塞尔维亚语", 430 | "scn": "西西里语", 431 | "sco": "苏格兰语", 432 | "scr": "克罗地亚语", 433 | "sdv": "东部苏丹语支", 434 | "sel": "塞尔库普语", 435 | "sem": "闪米特语族", 436 | "sga": "古爱尔兰语", 437 | "sgn": "手语", 438 | "shn": "掸语", 439 | "sid": "锡达莫语", 440 | "sin": "僧加罗语", 441 | "sio": "苏语系", 442 | "sit": "汉藏语系", 443 | "sla": "斯拉夫语族", 444 | "slk": "斯洛伐克语", 445 | "slo": "斯洛伐克语", 446 | "slv": "斯洛文尼亚语", 447 | "sma": "南萨米语", 448 | "sme": "北萨米语", 449 | "smi": "萨米语支(中的其他语言)", 450 | "smj": "吕勒萨米语", 451 | "smn": "伊纳里萨米语", 452 | "smo": "萨摩亚语", 453 | "sms": "斯科尔特萨米语", 454 | "sna": "修纳语", 455 | "snd": "信德语", 456 | "snk": "索宁克语", 457 | "sog": "粟特语", 458 | "som": "索马里语", 459 | "son": "桑海语", 460 | "sot": "南索托语", 461 | "spa": "西班牙语", 462 | "sqi": "阿尔巴尼亚语", 463 | "srd": "撒丁语", 464 | "srn": "苏里南汤加语", 465 | "srp": "塞尔维亚语", 466 | "srr": "塞雷尔语", 467 | "ssa": "尼罗-撒哈拉语系", 468 | "ssw": "斯威士兰语", 469 | "suk": "苏库马语", 470 | "sun": "巽他语", 471 | "sus": "苏苏语", 472 | "sux": "苏美尔语", 473 | "swa": "斯瓦希里语", 474 | "swe": "瑞典语", 475 | "syc": "古典叙利亚语", 476 | "syr": "古叙利亚语", 477 | "tah": "塔希提语", 478 | "tai": "壮侗语系", 479 | "tam": "泰米尔语", 480 | "tat": "塔塔尔语", 481 | "tbq": "藏缅语族", 482 | "tel": "泰卢固语", 483 | "tem": "滕内语", 484 | "ter": "特列纳语", 485 | "tet": "特塔姆语", 486 | "tgk": "塔吉克语", 487 | "tgl": "塔加洛语", 488 | "tha": "泰语", 489 | "tib": "藏语", 490 | "tig": "提格雷语", 491 | "tir": "提格里尼亚语", 492 | "tiv": "蒂夫语", 493 | "tkl": "托克劳语", 494 | "tlh": "克林贡语", 495 | "tli": "特林吉特语", 496 | "tmh": "塔马舍克语", 497 | "tog": "汤加语 (尼亚萨)", 498 | "ton": "汤加语 (汤加岛)", 499 | "tpi": "托克皮辛语", 500 | "trk": "突厥语族", 501 | "tsi": "钦西安语", 502 | "tsn": "茨瓦纳语", 503 | "tso": "聪加语", 504 | "tuk": "土库曼语", 505 | "tum": "奇图姆布卡语", 506 | "tup": "图皮语系", 507 | "tur": "土耳其语", 508 | "tut": "阿尔泰语系", 509 | "tuw": "满-通古斯语族", 510 | "tvl": "图瓦卢语", 511 | "twi": "契维语", 512 | "tyv": "图瓦语", 513 | "udm": "乌德穆尔特语", 514 | "uga": "乌加里特语", 515 | "uig": "维吾尔语", 516 | "ukr": "乌克兰语", 517 | "umb": "翁本杜语", 518 | "und": "未确定的语言", 519 | "urd": "乌尔都语", 520 | "urj": "乌拉尔语系", 521 | "uzb": "乌兹别克语", 522 | "vai": "瓦伊语", 523 | "ven": "文达语", 524 | "vie": "越南语", 525 | "vol": "沃拉普克语", 526 | "vot": "瓦佳语", 527 | "wak": "瓦卡什语系", 528 | "wal": " ", 529 | "war": "瓦赖语", 530 | "was": "瓦肖语", 531 | "wel": "威尔士语", 532 | "wen": "索布诸语言", 533 | "wln": "瓦龙语", 534 | "wol": "沃洛夫语", 535 | "xal": "卡尔梅克语", 536 | "xgn": "蒙古语族", 537 | "xho": "科萨语", 538 | "xnd": "纳-德内语系", 539 | "yao": "瑶语 (马拉维)", 540 | "yap": "雅浦语", 541 | "yid": "依地语", 542 | "yor": "约鲁巴语", 543 | "ypk": "尤皮克语系", 544 | "zap": "萨波特克语", 545 | "zbl": "布利斯符号", 546 | "zen": "哲纳加语", 547 | "zgh": "标准摩洛哥柏柏尔语", 548 | "zha": "壮语", 549 | "zho": "中文", 550 | "zhx": "汉语语系", 551 | "zle": "东斯拉夫语支", 552 | "zls": "南斯拉夫语支", 553 | "zlw": "西斯拉夫语支", 554 | "znd": "赞德诸语言", 555 | "zul": "祖鲁语", 556 | "zun": "祖尼语", 557 | "zxx": "没有语言内容", 558 | "zza": "扎扎其语" 559 | } -------------------------------------------------------------------------------- /resources/i18n/zh-Hans/main.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabs": { 3 | "local": "本地文件" 4 | }, 5 | "player": { 6 | "speed": "倍速", 7 | "current_speed": "{} 倍速播放中", 8 | "exit_hint": "退出当前播放吗" 9 | }, 10 | "setting": { 11 | "header": "播放设置", 12 | "subtitle": "字幕", 13 | "common": "通用", 14 | "sleep": "定时关闭", 15 | "aspect": { 16 | "header":"视频比例", 17 | "crop": "填充", 18 | "stretch": "拉伸", 19 | "auto": "自动" 20 | }, 21 | "equalizer": { 22 | "header": "均衡器", 23 | "brightness": "亮度", 24 | "contrast": "对比度", 25 | "saturation": "饱和度", 26 | "gamma": "伽马值", 27 | "hue": "色调", 28 | "reset": "重置" 29 | }, 30 | "bottom_progress": "底部固定显示进度条", 31 | "track": { 32 | "audio": "音轨" 33 | } 34 | }, 35 | "common": { 36 | "minute": "分钟" 37 | } 38 | } -------------------------------------------------------------------------------- /resources/icon/icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/resources/icon/icon.jpg -------------------------------------------------------------------------------- /resources/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/resources/icon/icon.png -------------------------------------------------------------------------------- /resources/icon/logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/resources/icon/logo.gif -------------------------------------------------------------------------------- /resources/icon/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/resources/icon/logo.png -------------------------------------------------------------------------------- /resources/img/sys/battery_back_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /resources/img/sys/battery_back_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /resources/img/sys/cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/resources/img/sys/cursor.png -------------------------------------------------------------------------------- /resources/img/sys/ethernet_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/img/sys/ethernet_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/img/sys/wifi_0_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/img/sys/wifi_0_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/img/sys/wifi_1_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/img/sys/wifi_1_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/img/sys/wifi_2_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/img/sys/wifi_2_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/img/sys/wifi_3_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/img/sys/wifi_3_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/material/LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /resources/material/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/resources/material/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /resources/shaders/fill_aa_fsh.dksh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/resources/shaders/fill_aa_fsh.dksh -------------------------------------------------------------------------------- /resources/shaders/fill_fsh.dksh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/resources/shaders/fill_fsh.dksh -------------------------------------------------------------------------------- /resources/shaders/fill_vsh.dksh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/resources/shaders/fill_vsh.dksh -------------------------------------------------------------------------------- /resources/svg/arrow-left-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/svg/bpx-svg-sprite-pause.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/svg/bpx-svg-sprite-play.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/svg/bpx-svg-sprite-setting.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/svg/bpx-svg-sprite-volume-off.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/svg/bpx-svg-sprite-volume.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/svg/ico-dark-file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/svg/ico-dark-folder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/svg/ico-light-file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/svg/ico-light-folder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/svg/player-lock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/svg/player-unlock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/svg/sun-fill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/xml/activity/main.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /resources/xml/activity/video.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/xml/fragment/local_driver.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | -------------------------------------------------------------------------------- /resources/xml/fragment/player_setting.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 17 | 18 | 23 | 24 | 29 | 30 | 38 | 39 | 46 | 47 | 54 | 55 | 56 | 57 | 64 | 65 | 72 | 73 | 74 | 75 | 80 | 85 | 86 | 88 | 89 | 91 | 92 | 94 | 95 | 96 | 101 | 106 | 109 | 112 | 115 | 118 | 121 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /resources/xml/view/video_view.xml: -------------------------------------------------------------------------------- 1 | 10 | 17 | 18 | 25 | 26 | 37 | 49 | 50 | 51 | 59 | 68 | 74 | 75 | 76 | 77 | 78 | 88 | 93 | 98 | 99 | 100 | 105 | 106 | 110 | 111 | 120 | 121 | 122 | 129 | 147 | 153 | 154 | 155 | 161 | 162 | 171 | 172 | 177 | 185 | 191 | 199 | 200 | 204 | 211 | 222 | 223 | 224 | 231 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 272 | 279 | 280 | -------------------------------------------------------------------------------- /scripts/build_switch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BUILD_DIR=cmake-build-switch 4 | 5 | cd "$(dirname $0)/.." 6 | git config --global --add safe.directory `pwd` 7 | 8 | PKGS=( 9 | "switch-libass-0.17.1-1-any.pkg.tar.zst" 10 | "switch-ffmpeg-6.1-5-any.pkg.tar.zst" 11 | "switch-libmpv-0.36.0-2-any.pkg.tar.zst" 12 | "switch-nspmini-48d4fc2-1-any.pkg.tar.xz" 13 | "hacBrewPack-3.05-1-any.pkg.tar.zst" 14 | ) 15 | for PKG in "${PKGS[@]}"; do 16 | dkp-pacman -U --noconfirm ./scripts/deps/${PKG} 17 | done 18 | 19 | cmake -B ${BUILD_DIR} \ 20 | -DCMAKE_BUILD_TYPE=Release \ 21 | -DBUILTIN_NSP=OFF \ 22 | -DPLATFORM=SWITCH \ 23 | -DUSE_DEKO3D=OFF \ 24 | -DBRLS_UNITY_BUILD=OFF \ 25 | -DCMAKE_UNITY_BUILD_BATCH_SIZE=16 26 | 27 | make -C ${BUILD_DIR} nxplayer.nro -j$(nproc) 28 | -------------------------------------------------------------------------------- /scripts/build_switch_deko3d.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BUILD_DIR=cmake-build-switch-deko3d 4 | 5 | cd "$(dirname $0)/.." 6 | git config --global --add safe.directory `pwd` 7 | 8 | PKGS=( 9 | "deko3d-8939ff80f94d061dbc7d107e08b8e3be53e2938b-1-any.pkg.tar.zst" 10 | "libuam-f8c9eef01ffe06334d530393d636d69e2b52744b-1-any.pkg.tar.zst" 11 | "switch-libass-0.17.1-1-any.pkg.tar.zst" 12 | "switch-ffmpeg-6.1-5-any.pkg.tar.zst" 13 | "switch-libmpv_deko3d-0.36.0-1-any.pkg.tar.zst" 14 | "switch-nspmini-48d4fc2-1-any.pkg.tar.xz" 15 | "hacBrewPack-3.05-1-any.pkg.tar.zst" 16 | ) 17 | for PKG in "${PKGS[@]}"; do 18 | dkp-pacman -U --noconfirm ./scripts/deps/${PKG} 19 | done 20 | 21 | cmake -B ${BUILD_DIR} \ 22 | -DCMAKE_BUILD_TYPE=Release \ 23 | -DBUILTIN_NSP=OFF \ 24 | -DPLATFORM=SWITCH \ 25 | -DUSE_DEKO3D=ON \ 26 | -DBRLS_UNITY_BUILD=OFF \ 27 | -DCMAKE_UNITY_BUILD_BATCH_SIZE=16 28 | 29 | make -C ${BUILD_DIR} nxplayer.nro -j$(nproc) 30 | -------------------------------------------------------------------------------- /scripts/deps/deko3d-8939ff80f94d061dbc7d107e08b8e3be53e2938b-1-any.pkg.tar.zst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/scripts/deps/deko3d-8939ff80f94d061dbc7d107e08b8e3be53e2938b-1-any.pkg.tar.zst -------------------------------------------------------------------------------- /scripts/deps/hacBrewPack-3.05-1-any.pkg.tar.zst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/scripts/deps/hacBrewPack-3.05-1-any.pkg.tar.zst -------------------------------------------------------------------------------- /scripts/deps/libuam-f8c9eef01ffe06334d530393d636d69e2b52744b-1-any.pkg.tar.zst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/scripts/deps/libuam-f8c9eef01ffe06334d530393d636d69e2b52744b-1-any.pkg.tar.zst -------------------------------------------------------------------------------- /scripts/deps/switch-ffmpeg-6.1-5-any.pkg.tar.zst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/scripts/deps/switch-ffmpeg-6.1-5-any.pkg.tar.zst -------------------------------------------------------------------------------- /scripts/deps/switch-libass-0.17.1-1-any.pkg.tar.zst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/scripts/deps/switch-libass-0.17.1-1-any.pkg.tar.zst -------------------------------------------------------------------------------- /scripts/deps/switch-libmpv-0.36.0-2-any.pkg.tar.zst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/scripts/deps/switch-libmpv-0.36.0-2-any.pkg.tar.zst -------------------------------------------------------------------------------- /scripts/deps/switch-libmpv_deko3d-0.36.0-1-any.pkg.tar.zst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/scripts/deps/switch-libmpv_deko3d-0.36.0-1-any.pkg.tar.zst -------------------------------------------------------------------------------- /scripts/deps/switch-nspmini-48d4fc2-1-any.pkg.tar.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymous5l/nxplayer/29655a4854a4d6ed8f1ccdd506ffcfbb12c22d95/scripts/deps/switch-nspmini-48d4fc2-1-any.pkg.tar.xz -------------------------------------------------------------------------------- /src/include/activity/main_activity.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anonymous on 2024/4/3. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | class MainActivity : public brls::Activity { 10 | public: 11 | // Declare that the content of this activity is the given XML file 12 | CONTENT_FROM_XML_RES("activity/main.xml"); 13 | }; 14 | -------------------------------------------------------------------------------- /src/include/activity/video_activity.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anonymous on 2024/4/8. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | #include "view/video_view.hpp" 11 | 12 | class VideoActivity : public brls::Activity { 13 | public: 14 | // Declare that the content of this activity is the given XML file 15 | CONTENT_FROM_XML_RES("activity/video.xml"); 16 | 17 | VideoActivity(std::string name, std::string path) : 18 | name(std::move(name)), 19 | path(std::move(path)) {}; 20 | 21 | void onContentAvailable() override; 22 | private: 23 | std::string name; 24 | std::string path; 25 | 26 | BRLS_BIND(VideoView, video, "video"); 27 | }; 28 | -------------------------------------------------------------------------------- /src/include/fragment/local_driver.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anonymous on 2024/4/5. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | using namespace brls::literals; 11 | 12 | const std::vector extensions = { 13 | ".8svx", ".aac", ".ac3", ".aif", 14 | ".asf", ".avi", ".dv", ".flv", 15 | ".m2ts", ".m2v", ".m4a", ".mkv", 16 | ".mov", ".mp3", ".mp4", ".mpeg", 17 | ".mpg", ".mts", ".ogg", ".rmvb", 18 | ".swf", ".ts", ".vob", ".wav", 19 | ".wma", ".wmv", ".flac", ".m3u", 20 | ".m3u8", ".webm", ".jpg", ".gif", 21 | ".png", ".iso", 22 | }; 23 | 24 | class Item { 25 | public: 26 | bool is_up; 27 | std::filesystem::path path; 28 | bool is_directory; 29 | uintmax_t file_size; 30 | }; 31 | 32 | const std::string recyclerCellXML = R"xml( 33 | 40 | 41 | 47 | 48 | 58 | 59 | )xml"; 60 | 61 | class RecyclerCell : public brls::RecyclerCell 62 | { 63 | public: 64 | RecyclerCell(); 65 | 66 | void setItem(std::string icon, Item *item); 67 | 68 | static RecyclerCell* create(); 69 | private: 70 | BRLS_BIND(brls::Label, filepath, "filepath"); 71 | BRLS_BIND(brls::Image, fileicon, "fileicon"); 72 | }; 73 | 74 | class DirectoryDataSource : public brls::RecyclerDataSource 75 | { 76 | public: 77 | DirectoryDataSource(const std::filesystem::path& path = std::filesystem::current_path()); 78 | 79 | void reloadData(const std::filesystem::path& path); 80 | 81 | int numberOfSections(brls::RecyclerFrame* recycler) override; 82 | int numberOfRows(brls::RecyclerFrame* recycler, int section) override; 83 | brls::RecyclerCell* cellForRow(brls::RecyclerFrame* recycler, brls::IndexPath index) override; 84 | void didSelectRowAt(brls::RecyclerFrame* recycler, brls::IndexPath indexPath) override; 85 | Item *getItem(int index); 86 | private: 87 | std::vector items; 88 | }; 89 | 90 | class LocalDriver : public brls::Box { 91 | public: 92 | LocalDriver(); 93 | 94 | static brls::View *create(); 95 | private: 96 | BRLS_BIND(brls::RecyclerFrame, recycler, "recycler"); 97 | }; 98 | -------------------------------------------------------------------------------- /src/include/fragment/player_setting.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anonymous on 2024/4/8. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include "view/video_view.hpp" 9 | 10 | using namespace brls::literals; 11 | 12 | class PlayerSetting : public brls::Box { 13 | public: 14 | PlayerSetting(MPVCore *core); 15 | 16 | void draw(NVGcontext* vg, float x, float y, float width, float height, brls::Style style, 17 | brls::FrameContext* ctx); 18 | private: 19 | BRLS_BIND(brls::SelectorCell, cellVideoAspect, "setting/video/aspect"); 20 | BRLS_BIND(brls::BooleanCell, cellProgress, "setting/video/progress"); 21 | BRLS_BIND(brls::DetailCell, cellSleep, "setting/sleep"); 22 | 23 | BRLS_BIND(brls::SliderCell, cellEqualizerBrightness, "setting/equalizer/brightness"); 24 | BRLS_BIND(brls::SliderCell, cellEqualizerContrast, "setting/equalizer/contrast"); 25 | BRLS_BIND(brls::SliderCell, cellEqualizerSaturation, "setting/equalizer/saturation"); 26 | BRLS_BIND(brls::SliderCell, cellEqualizerGamma, "setting/equalizer/gamma"); 27 | BRLS_BIND(brls::SliderCell, cellEqualizerHue, "setting/equalizer/hue"); 28 | BRLS_BIND(brls::RadioCell, cellEqualizerReset, "setting/equalizer/reset"); 29 | 30 | BRLS_BIND(brls::Header, trackAudioHeader, "setting/track/audio/header"); 31 | BRLS_BIND(brls::Box, trackAudioBox, "setting/track/audio/box"); 32 | 33 | BRLS_BIND(brls::Header, subtitleHeader, "setting/video/subtitle/header"); 34 | BRLS_BIND(brls::Box, subtitleBox, "setting/video/subtitle/box"); 35 | 36 | MPVCore *core; 37 | int selectedVideoAspect = 0; 38 | 39 | void updateSleepContent(size_t ts); 40 | void setupTrack(); 41 | void registerHideBackground(brls::View *view); 42 | void setupEqualizerSetting(brls::SliderCell* cell, const std::string& title, int initValue, const std::function& callback); 43 | }; 44 | -------------------------------------------------------------------------------- /src/include/utils/gesture_helper.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by fang on 2024/1/8. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "utils/presenter.h" 10 | 11 | enum class OsdGestureType { 12 | TAP, 13 | DOUBLE_TAP_START, 14 | DOUBLE_TAP_END, 15 | LONG_PRESS_START, 16 | LONG_PRESS_END, 17 | LONG_PRESS_CANCEL, 18 | HORIZONTAL_PAN_START, 19 | HORIZONTAL_PAN_UPDATE, 20 | HORIZONTAL_PAN_END, 21 | HORIZONTAL_PAN_CANCEL, 22 | LEFT_VERTICAL_PAN_START, 23 | LEFT_VERTICAL_PAN_UPDATE, 24 | LEFT_VERTICAL_PAN_END, 25 | LEFT_VERTICAL_PAN_CANCEL, 26 | RIGHT_VERTICAL_PAN_START, 27 | RIGHT_VERTICAL_PAN_UPDATE, 28 | RIGHT_VERTICAL_PAN_END, 29 | RIGHT_VERTICAL_PAN_CANCEL, 30 | NONE, 31 | }; 32 | 33 | struct OsdGestureStatus { 34 | OsdGestureType osdGestureType; // Gesture type 35 | brls::GestureState state; // Gesture state 36 | brls::Point position; // Current position 37 | float deltaX, deltaY; 38 | }; 39 | typedef brls::Event OsdGestureEvent; 40 | 41 | class OsdGestureRecognizer : public brls::GestureRecognizer, public Presenter { 42 | public: 43 | explicit OsdGestureRecognizer(const OsdGestureEvent::Callback& respond); 44 | 45 | brls::GestureState recognitionLoop(brls::TouchState touch, brls::MouseState mouse, brls::View* view, 46 | brls::Sound* soundToPlay) override; 47 | 48 | // Get current state of recognizer 49 | OsdGestureStatus getCurrentStatus(); 50 | 51 | // Get tap gesture event 52 | [[nodiscard]] OsdGestureEvent getTapGestureEvent() const { return tapEvent; } 53 | 54 | private: 55 | OsdGestureEvent tapEvent; 56 | brls::Point position{}; 57 | brls::Point delta{}; 58 | float deltaX{}, deltaY{}; 59 | int64_t startTime{}; 60 | int64_t endTime{}; 61 | size_t iter{}; 62 | OsdGestureType osdGestureType{OsdGestureType::NONE}; 63 | brls::GestureState lastState{brls::GestureState::END}; 64 | }; -------------------------------------------------------------------------------- /src/include/utils/intent.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anonymous on 2024/4/3. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | class Intent { 10 | public: 11 | static void openMain(); 12 | static void openVideo(std::string name, std::string url); 13 | 14 | // static void _registerFullscreen(brls::Activity* activity); 15 | }; 16 | 17 | 18 | //#if defined(__linux__) || defined(_WIN32) || defined(__APPLE__) 19 | //#define ALLOW_FULLSCREEN 20 | //#endif 21 | // 22 | //#ifdef ALLOW_FULLSCREEN 23 | //#define registerFullscreen(activity) Intent::_registerFullscreen(activity) 24 | //#else 25 | //#define registerFullscreen(activity) (void)activity 26 | //#endif 27 | -------------------------------------------------------------------------------- /src/include/utils/presenter.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by fang on 2022/7/22. 3 | // 4 | 5 | // Thanks to XITRIX's patch to borealis 6 | // https://github.com/XITRIX/borealis/commit/c309f22bd451aaf59fbbb15c5f2345104582e8a8 7 | 8 | #pragma once 9 | 10 | // 检查异步返回时组件是否已经被销毁 11 | #define ASYNC_RETAIN \ 12 | if (!deletionToken && !deletionTokenCounter) { \ 13 | deletionToken = new bool(false); \ 14 | deletionTokenCounter = new int(0); \ 15 | } \ 16 | (*deletionTokenCounter)++; \ 17 | bool* token = deletionToken; \ 18 | int* tokenCounter = deletionTokenCounter; 19 | 20 | #define ASYNC_RELEASE \ 21 | bool release = *token; \ 22 | int counter = *tokenCounter; \ 23 | if (counter > 0) { \ 24 | (*tokenCounter)--; \ 25 | if (*tokenCounter == 0) { \ 26 | delete token; \ 27 | delete tokenCounter; \ 28 | if (!release) { \ 29 | deletionToken = nullptr; \ 30 | deletionTokenCounter = nullptr; \ 31 | } \ 32 | } \ 33 | } \ 34 | if (release) return; 35 | 36 | #define ASYNC_TOKEN this, token, tokenCounter 37 | 38 | class Presenter { 39 | public: 40 | virtual ~Presenter() { 41 | if (deletionToken) *deletionToken = true; 42 | } 43 | 44 | protected: 45 | bool* deletionToken = nullptr; 46 | int* deletionTokenCounter = nullptr; 47 | bool requesting = false; 48 | }; -------------------------------------------------------------------------------- /src/include/utils/register.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anonymous on 2024/4/5. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace Register { 10 | void initCustomView(); 11 | void initCustomTheme(); 12 | }; -------------------------------------------------------------------------------- /src/include/utils/utils.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anonymous on 2024/4/8. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace Utils { 10 | inline std::string pre0(size_t num, size_t length) { 11 | std::string str = std::to_string(num); 12 | if (length <= str.length()) { 13 | return str; 14 | } 15 | return std::string(length - str.length(), '0') + str; 16 | } 17 | 18 | size_t getUnixTime(); 19 | std::string sec2Time(size_t t); 20 | 21 | static inline time_t unix_time() { return std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); } 22 | } 23 | -------------------------------------------------------------------------------- /src/include/view/mpv_core.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by fang on 2022/8/12. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "utils/utils.hpp" 19 | 20 | #if defined(MPV_SW_RENDER) 21 | #elif defined(BOREALIS_USE_DEKO3D) 22 | #include 23 | #elif defined(BOREALIS_USE_D3D11) 24 | #include 25 | #elif defined(BOREALIS_USE_OPENGL) 26 | #include 27 | #if defined(__PSV__) || defined(PS4) 28 | #include 29 | #else 30 | #include 31 | #endif 32 | #ifdef __SDL2__ 33 | #include 34 | #else 35 | #define GLFW_INCLUDE_NONE 36 | #include 37 | #endif 38 | #if defined(USE_GL2) 39 | // 对于 OpenGL 2.0 平台,不支持独立创建 framebuffer 40 | #define MPV_NO_FB 41 | #endif 42 | #if !defined(MPV_NO_FB) && !defined(MPV_SW_RENDER) && !defined(USE_GL2) 43 | // 将视频绘制到独立的 framebuffer 44 | #define MPV_USE_FB 45 | #if !defined(USE_GLES2) && !defined(USE_GLES3) 46 | // 虽然 gles3 理论上也支持 vao 但是部分平台上实际不支持(比如 ANGLE) 47 | #define MPV_USE_VAO 48 | #endif 49 | struct GLShader { 50 | GLuint prog; 51 | GLuint vbo; 52 | GLuint ebo; 53 | #ifdef MPV_USE_VAO 54 | GLuint vao; 55 | #endif 56 | }; 57 | #endif 58 | #endif 59 | 60 | #ifdef MPV_BUNDLE_DLL 61 | #include 62 | typedef int (*mpvSetOptionStringFunc)(mpv_handle *ctx, const char *name, const char *data); 63 | typedef int (*mpvObservePropertyFunc)(mpv_handle *mpv, uint64_t reply_userdata, const char *name, mpv_format format); 64 | typedef mpv_handle *(*mpvCreateFunc)(void); 65 | typedef int (*mpvInitializeFunc)(mpv_handle *ctx); 66 | typedef void (*mpvTerminateDestroyFunc)(mpv_handle *ctx); 67 | typedef void (*mpvSetWakeupCallbackFunc)(mpv_handle *ctx, void (*cb)(void *d), void *d); 68 | typedef int (*mpvCommandStringFunc)(mpv_handle *ctx, const char *args); 69 | typedef const char *(*mpvErrorStringFunc)(int error); 70 | typedef mpv_event *(*mpvWaitEventFunc)(mpv_handle *ctx, double timeout); 71 | typedef int (*mpvGetPropertyFunc)(mpv_handle *ctx, const char *name, mpv_format format, void *data); 72 | typedef int (*mpvCommandAsyncFunc)(mpv_handle *ctx, uint64_t reply_userdata, const char **args); 73 | typedef char *(*mpvGetPropertyStringFunc)(mpv_handle *ctx, const char *name); 74 | typedef void (*mpvFreeNodeContentsFunc)(mpv_node *node); 75 | typedef int (*mpvSetOptionFunc)(mpv_handle *ctx, const char *name, mpv_format format, void *data); 76 | typedef void (*mpvFreeFunc)(void *data); 77 | typedef int (*mpvRenderContextCreateFunc)(mpv_render_context **res, mpv_handle *mpv, mpv_render_param *params); 78 | typedef void (*mpvRenderContextSetUpdateCallbackFunc)(mpv_render_context *ctx, mpv_render_update_fn callback, 79 | void *callback_ctx); 80 | typedef int (*mpvRenderContextRenderFunc)(mpv_render_context *ctx, mpv_render_param *params); 81 | typedef void (*mpvRenderContextReportSwapFunc)(mpv_render_context *ctx); 82 | typedef uint64_t (*mpvRenderContextUpdateFunc)(mpv_render_context *ctx); 83 | typedef void (*mpvRenderContextFreeFunc)(mpv_render_context *ctx); 84 | 85 | extern mpvSetOptionStringFunc mpvSetOptionString; 86 | extern mpvObservePropertyFunc mpvObserveProperty; 87 | extern mpvCreateFunc mpvCreate; 88 | extern mpvInitializeFunc mpvInitialize; 89 | extern mpvTerminateDestroyFunc mpvTerminateDestroy; 90 | extern mpvSetWakeupCallbackFunc mpvSetWakeupCallback; 91 | extern mpvCommandStringFunc mpvCommandString; 92 | extern mpvErrorStringFunc mpvErrorString; 93 | extern mpvWaitEventFunc mpvWaitEvent; 94 | extern mpvGetPropertyFunc mpvGetProperty; 95 | extern mpvCommandAsyncFunc mpvCommandAsync; 96 | extern mpvGetPropertyStringFunc mpvGetPropertyString; 97 | extern mpvFreeNodeContentsFunc mpvFreeNodeContents; 98 | extern mpvSetOptionFunc mpvSetOption; 99 | extern mpvFreeFunc mpvFree; 100 | extern mpvRenderContextCreateFunc mpvRenderContextCreate; 101 | extern mpvRenderContextSetUpdateCallbackFunc mpvRenderContextSetUpdateCallback; 102 | extern mpvRenderContextRenderFunc mpvRenderContextRender; 103 | extern mpvRenderContextReportSwapFunc mpvRenderContextReportSwap; 104 | extern mpvRenderContextUpdateFunc mpvRenderContextUpdate; 105 | extern mpvRenderContextFreeFunc mpvRenderContextFree; 106 | #else 107 | #define mpvSetOptionString mpv_set_option_string 108 | #define mpvObserveProperty mpv_observe_property 109 | #define mpvCreate mpv_create 110 | #define mpvInitialize mpv_initialize 111 | #define mpvTerminateDestroy mpv_terminate_destroy 112 | #define mpvSetWakeupCallback mpv_set_wakeup_callback 113 | #define mpvCommandString mpv_command_string 114 | #define mpvErrorString mpv_error_string 115 | #define mpvWaitEvent mpv_wait_event 116 | #define mpvGetProperty mpv_get_property 117 | #define mpvCommandAsync mpv_command_async 118 | #define mpvGetPropertyString mpv_get_property_string 119 | #define mpvFreeNodeContents mpv_free_node_contents 120 | #define mpvSetOption mpv_set_option 121 | #define mpvFree mpv_free 122 | #define mpvRenderContextCreate mpv_render_context_create 123 | #define mpvRenderContextSetUpdateCallback mpv_render_context_set_update_callback 124 | #define mpvRenderContextRender mpv_render_context_render 125 | #define mpvRenderContextReportSwap mpv_render_context_report_swap 126 | #define mpvRenderContextUpdate mpv_render_context_update 127 | #define mpvRenderContextFree mpv_render_context_free 128 | #endif 129 | 130 | typedef enum MpvEventEnum { 131 | MPV_LOADED, 132 | MPV_PAUSE, 133 | MPV_RESUME, 134 | MPV_IDLE, 135 | MPV_STOP, 136 | MPV_FILE_ERROR, 137 | LOADING_START, 138 | LOADING_END, 139 | UPDATE_DURATION, 140 | UPDATE_PROGRESS, 141 | START_FILE, 142 | END_OF_FILE, 143 | CACHE_SPEED_CHANGE, 144 | VIDEO_SPEED_CHANGE, 145 | VIDEO_VOLUME_CHANGE, 146 | VIDEO_MUTE, 147 | VIDEO_UNMUTE, 148 | RESET, 149 | } MpvEventEnum; 150 | 151 | typedef brls::Event MPVEvent; 152 | 153 | class Track { 154 | public: 155 | int64_t id; 156 | std::string type; 157 | std::string title; 158 | std::string lang; 159 | std::string codec; 160 | bool selected; 161 | }; 162 | 163 | class MPVCore : public brls::Singleton { 164 | public: 165 | MPVCore(); 166 | 167 | ~MPVCore(); 168 | 169 | /// Get MPV States 170 | 171 | bool isStopped() const; 172 | 173 | bool isPlaying() const; 174 | 175 | bool isPaused() const; 176 | 177 | double getSpeed() const; 178 | 179 | double getPlaybackTime() const; 180 | 181 | std::string getCacheSpeed() const; 182 | 183 | int64_t getVolume() const; 184 | 185 | bool isValid(); 186 | 187 | // todo: remove these sync function 188 | std::string getString(const std::string &key); 189 | double getDouble(const std::string &key); 190 | int64_t getInt(const std::string &key); 191 | std::unordered_map getNodeMap(const std::string &key); 192 | 193 | std::vector getTracks(); 194 | 195 | /// Set MPV States 196 | 197 | /** 198 | * 设置播放链接 199 | * @param url 播放链接 200 | * @param extra 额外的参数,如 referrer、audio 等,详情见 mpv 文档 201 | * @param method 行为,默认为替换当前视频,详情见 mpv 文档 202 | */ 203 | void setUrl(const std::string &url, const std::string &extra = "", const std::string &method = "replace"); 204 | 205 | /** 206 | * 设置备用链接(可多次调用) 207 | * 内部使用 mpv 的播放列表实现。当前链接无法播放时,自动跳转到播放列表的下一项 208 | * 209 | * 注:如果是dash链接,同时存在备用的音频链接,可以将音频列表通过 extra 逐项传入, 210 | * 这样就能实现无论是音频还是视频,在播放失败时自动切换到备用链接 211 | * 212 | * 213 | * @param url 备用播放链接 214 | * @param extra 额外的参数,定义同上 215 | */ 216 | void setBackupUrl(const std::string &url, const std::string &extra = ""); 217 | 218 | void setVolume(int64_t value); 219 | void setVolume(const std::string &value); 220 | 221 | void setAudioId(int64_t aid); 222 | void setSubtitleId(int64_t aid); 223 | 224 | void resume(); 225 | 226 | void pause(); 227 | 228 | void stop(); 229 | 230 | /** 231 | * 跳转视频 232 | * @param p 秒 233 | */ 234 | void seek(int64_t p); 235 | void seek(const std::string &p); 236 | 237 | /** 238 | * 相对于当前播放的时间点跳转视频 239 | * @param p 秒 240 | */ 241 | void seekRelative(int64_t p); 242 | 243 | /** 244 | * 跳转视频到指定百分比位置 245 | * @param value 0-1 246 | */ 247 | void seekPercent(double value); 248 | 249 | /** 250 | * 设置视频播放速度 251 | * @param value 1.0 为元速 252 | */ 253 | void setSpeed(double value); 254 | 255 | void showOsdText(const std::string &value, int duration = 2000); 256 | 257 | /** 258 | * 强制设置视频比例 259 | * @param value -1 为自动, 可设置 16:9 或 1.333 这两种形式的字符串 260 | */ 261 | void setAspect(const std::string &value); 262 | 263 | /** 264 | * 设置视频亮度 265 | * @param value [-100, 100] 266 | */ 267 | void setBrightness(int value); 268 | 269 | void setContrast(int value); 270 | 271 | void setSaturation(int value); 272 | 273 | void setGamma(int value); 274 | 275 | void setHue(int value); 276 | 277 | int getBrightness() const; 278 | 279 | int getContrast() const; 280 | 281 | int getSaturation() const; 282 | 283 | int getGamma() const; 284 | 285 | int getHue() const; 286 | 287 | /** 288 | * 禁用系统锁屏 289 | */ 290 | static void disableDimming(bool disable); 291 | 292 | /** 293 | * 绘制视频 294 | * 295 | * 支持三种绘制方式 (通过编译参数切换) 296 | * 1. MPV_SW_RENDER: CPU绘制;优点:适合任意图形API,缺点:效率低。 297 | * wiliwili 的 UWP 版本因为用了dx12,所以使用此方式绘制。 298 | * 在向新的平台移植时推荐优先使用此方式绘制,便于移植,之后再考虑优化问题。 299 | * 2. MPV_NO_FB: 绘制到默认的 framebuffer 上; 性能适中。 300 | * 适合 树莓派 等只支持 OpenGL 2.0 的平台;因为不支持独立创建 framebuffer, 301 | * 所以让 mpv 先绘制到整个屏幕上,再用UI来覆盖。 302 | * 使用 deko3d (switch only) 时, 暂时也采用 MPV_NO_FB 方式绘制。 303 | * 3. 默认绘制方式: 将视频绘制到独立的 framebuffer 上, 304 | * 需要显示在屏幕上时再贴到默认的 framebuffer 上的某个位置;性能最佳。 305 | * 支持图形API OpenGL 3.2+ / OpenGL ES 2.0+。 306 | * 在部分平台上支持 direct rendering, 减少 CPU 和 GPU 间的拷贝进一步提升了性能。 307 | */ 308 | void draw(brls::Rect rect, float alpha = 1.0); 309 | 310 | mpv_render_context *getContext(); 311 | 312 | mpv_handle *getHandle(); 313 | 314 | /** 315 | * 播放器内部事件 316 | * 传递内容为: 事件类型 317 | */ 318 | MPVEvent *getEvent(); 319 | 320 | /** 321 | * 重启 MPV,用于某些需要重启才能设置的选项 322 | */ 323 | void restart(); 324 | 325 | void reset(); 326 | 327 | void setShader(const std::string &profile, const std::string &shaders, bool showHint = true); 328 | 329 | void clearShader(bool showHint = true); 330 | 331 | /// Send command to mpv 332 | template 333 | void command_async(Args &&...args) { 334 | if (!mpv) { 335 | brls::Logger::error("mpv is not initialized"); 336 | return; 337 | } 338 | std::vector commands = {fmt::format("{}", std::forward(args))...}; 339 | 340 | std::vector res; 341 | res.reserve(commands.size() + 1); 342 | for (auto &i : commands) { 343 | res.emplace_back(i.c_str()); 344 | } 345 | res.emplace_back(nullptr); 346 | 347 | mpvCommandAsync(mpv, 0, res.data()); 348 | } 349 | 350 | // core states 351 | int64_t duration = 0; // second 352 | int64_t cache_speed = 0; // Bps 353 | int64_t volume = 100; 354 | double video_speed = 0; 355 | bool video_paused = false; 356 | bool video_stopped = true; 357 | bool video_seeking = false; 358 | bool video_playing = false; 359 | bool video_eof = false; 360 | float video_aspect = -1; 361 | double playback_time = 0; 362 | double percent_pos = 0; 363 | int64_t video_progress = 0; 364 | int mpv_error_code = 0; 365 | std::string hwCurrent; 366 | std::string filepath; 367 | std::string currentShaderProfile; // 当前着色器脚本名 368 | std::string currentShader; // 当前着色器脚本 369 | 370 | double video_brightness = 0; 371 | double video_contrast = 0; 372 | double video_saturation = 0; 373 | double video_hue = 0; 374 | double video_gamma = 0; 375 | 376 | // 低画质解码,剔除解码过程中的部分步骤,可以用来节省cpu 377 | inline static bool LOW_QUALITY = false; 378 | 379 | // 视频缓存(是否使用内存缓存视频,值为缓存的大小,单位MB) 380 | inline static int INMEMORY_CACHE = 0; 381 | 382 | // 开启 Terminal 日志 383 | inline static bool TERMINAL = false; 384 | 385 | // 硬件解码 386 | inline static bool HARDWARE_DEC = false; 387 | 388 | // 硬解方式 389 | #ifdef __SWITCH__ 390 | inline static std::string PLAYER_HWDEC_METHOD = "auto"; 391 | #elif defined(__PSV__) 392 | inline static std::string PLAYER_HWDEC_METHOD = "vita-copy"; 393 | #elif defined(PS4) 394 | inline static std::string PLAYER_HWDEC_METHOD = "no"; 395 | #else 396 | inline static std::string PLAYER_HWDEC_METHOD = "auto-safe"; 397 | #endif 398 | 399 | // 此变量为真时,加载结束后自动播放视频 400 | inline static bool AUTO_PLAY = true; 401 | 402 | // 若值大于0 则当前时间大于 CLOSE_TIME 时,自动暂停播放 403 | inline static size_t CLOSE_TIME = 0; 404 | 405 | // 触发倍速时的默认值,单位为 % 406 | inline static int VIDEO_SPEED = 200; 407 | 408 | // 默认的音量 409 | inline static int VIDEO_VOLUME = 100; 410 | 411 | // 是否镜像视频 412 | inline static bool VIDEO_MIRROR = false; 413 | 414 | // 强制的视频比例 (-1 为自动) 415 | inline static std::string VIDEO_ASPECT = "-1"; 416 | 417 | inline static double VIDEO_BRIGHTNESS = 0; 418 | inline static double VIDEO_CONTRAST = 0; 419 | inline static double VIDEO_SATURATION = 0; 420 | inline static double VIDEO_HUE = 0; 421 | inline static double VIDEO_GAMMA = 0; 422 | 423 | private: 424 | mpv_handle *mpv = nullptr; 425 | mpv_render_context *mpv_context = nullptr; 426 | brls::Rect rect = {0, 0, 1920, 1080}; 427 | #ifdef MPV_SW_RENDER 428 | const int PIXCEL_SIZE = 4; 429 | int nvg_image = 0; 430 | const char *sw_format = "rgba"; 431 | int sw_size[2] = {1920, 1080}; 432 | size_t pitch = PIXCEL_SIZE * sw_size[0]; 433 | void *pixels = nullptr; 434 | bool redraw = false; 435 | mpv_render_param mpv_params[5] = { 436 | {MPV_RENDER_PARAM_SW_SIZE, &sw_size[0]}, {MPV_RENDER_PARAM_SW_FORMAT, (void *)sw_format}, 437 | {MPV_RENDER_PARAM_SW_STRIDE, &pitch}, {MPV_RENDER_PARAM_SW_POINTER, pixels}, 438 | {MPV_RENDER_PARAM_INVALID, nullptr}, 439 | }; 440 | #elif defined(BOREALIS_USE_DEKO3D) 441 | DkFence doneFence; 442 | DkFence readyFence; 443 | mpv_deko3d_fbo mpv_fbo{ 444 | nullptr, &readyFence, &doneFence, 1280, 720, DkImageFormat_RGBA8_Unorm, 445 | }; 446 | mpv_render_param mpv_params[3] = { 447 | {MPV_RENDER_PARAM_DEKO3D_FBO, &mpv_fbo}, 448 | {MPV_RENDER_PARAM_INVALID, nullptr}, 449 | }; 450 | #elif defined(BOREALIS_USE_D3D11) 451 | mpv_render_param mpv_params[1] = { 452 | {MPV_RENDER_PARAM_INVALID, nullptr}, 453 | }; 454 | #elif defined(MPV_NO_FB) 455 | GLint default_framebuffer = 0; 456 | mpv_opengl_fbo mpv_fbo{0, 1920, 1080}; 457 | int flip_y{1}; 458 | mpv_render_param mpv_params[3] = { 459 | {MPV_RENDER_PARAM_OPENGL_FBO, &mpv_fbo}, 460 | {MPV_RENDER_PARAM_FLIP_Y, &flip_y}, 461 | {MPV_RENDER_PARAM_INVALID, nullptr}, 462 | }; 463 | #else 464 | GLint default_framebuffer = 0; 465 | GLuint media_framebuffer = 0; 466 | GLuint media_texture = 0; 467 | GLShader shader{0}; 468 | mpv_opengl_fbo mpv_fbo{0, 1920, 1080}; 469 | int flip_y{1}; 470 | bool redraw = false; 471 | mpv_render_param mpv_params[3] = { 472 | {MPV_RENDER_PARAM_OPENGL_FBO, &mpv_fbo}, 473 | {MPV_RENDER_PARAM_FLIP_Y, &flip_y}, 474 | {MPV_RENDER_PARAM_INVALID, nullptr}, 475 | }; 476 | float vertices[20] = {1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 477 | -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f}; 478 | #endif 479 | 480 | #ifdef MPV_BUNDLE_DLL 481 | HMEMORYMODULE dll; 482 | #endif 483 | 484 | // MPV 内部事件,传递内容为: 事件类型 485 | MPVEvent mpvCoreEvent; 486 | 487 | // 当前软件是否在前台的回调 488 | brls::Event::Subscription focusSubscription; 489 | 490 | /// Will be called in main thread to get events from mpv core 491 | void eventMainLoop(); 492 | 493 | void initializeVideo(); 494 | 495 | void uninitializeVideo(); 496 | 497 | void init(); 498 | 499 | void clean(); 500 | 501 | /** 502 | * 设置视频渲染区域大小 503 | * @param rect 视频区域 504 | */ 505 | void setFrameSize(brls::Rect rect); 506 | 507 | /// MPV callbacks 508 | 509 | static void on_update(void *self); 510 | 511 | static void on_wakeup(void *self); 512 | }; 513 | -------------------------------------------------------------------------------- /src/include/view/video_view.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anonymous on 2024/4/8. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "utils/gesture_helper.hpp" 10 | #include "view/mpv_core.hpp" 11 | #include "fragment/player_setting.hpp" 12 | 13 | using namespace brls::literals; 14 | 15 | #define VIDEO_CANCEL_SEEKING 0 16 | #define VIDEO_SEEK_IMMEDIATELY 0 17 | 18 | enum class OSDState { 19 | HIDDEN = 0, 20 | SHOWN = 1, 21 | ALWAYS_ON = 2, 22 | }; 23 | 24 | class VideoView : public brls::Box { 25 | public: 26 | VideoView(); 27 | ~VideoView(); 28 | 29 | inline static bool BOTTOM_BAR = true; 30 | 31 | void setUrl(const std::string& url); 32 | void setTitle(const std::string& title); 33 | 34 | void onChildFocusGained(View* directChild, View* focusedView) override; 35 | bool openPlayerSetting(); 36 | 37 | MPVCore *getCore(); 38 | 39 | View *getDefaultFocus() override; 40 | 41 | void draw(NVGcontext* vg, float x, float y, float width, float height, brls::Style style, 42 | brls::FrameContext* ctx) override; 43 | 44 | static VideoView *create(); 45 | private: 46 | MPVCore* mpvCore; 47 | 48 | void registerMPVEvent(); 49 | void setDuration(size_t duration); 50 | void setPlaybackTime(size_t playback); 51 | void setProgress(float value); 52 | void setSpeed(float speed); 53 | void togglePlay(); 54 | void refreshToggleIcon(); 55 | void showOSD(bool temp); 56 | void hideOSD(); 57 | void toggleOSD(); 58 | bool isOSDShown(); 59 | void toggleOSDLock(); 60 | void OSDLock(); 61 | void OSDUnlock(); 62 | void showLoading(); 63 | void hideLoading(); 64 | void buttonProcessing(); 65 | void onOSDStateChanged(bool state); 66 | void showCenterHint(); 67 | void hideCenterHint(); 68 | void setCenterHintIcon(const std::string& svg); 69 | void setCenterHintText(const std::string& text); 70 | void requestSeeking(int seek, int delay = 400); 71 | void requestBrightness(float brightness); 72 | void requestVolume(int volume, int delay = 0); 73 | bool isSeeking(); 74 | 75 | MPVEvent::Subscription eventSubscribeID; 76 | 77 | bool is_seeking = false; 78 | size_t seeking_iter = 0; // 请求跳转的延迟函数 handle 79 | int seeking_range = 0; // 跳转的目标进度, 跳转结束后归零 80 | int seeking_last_play_time = 0; 81 | 82 | float brightness_init = 0.0f; 83 | 84 | int volume_init = 0; 85 | size_t volume_iter = 0; // 音量UI关闭的延迟函数 handle 86 | 87 | brls::InputManager* input; 88 | 89 | time_t osdLastShowTime = 0; 90 | const time_t OSD_SHOW_TIME = 5; //默认显示五秒 91 | OSDState osd_state = OSDState::HIDDEN; 92 | bool is_osd_shown = false; 93 | bool is_osd_lock = false; 94 | bool hide_lock_button = false; 95 | 96 | std::vector videoSpeedOptions = {"4.0x", "3.0x", "2.0x", "1.75x", "1.5x", "1.25x", "1.0x", "0.75x", "0.5x", "0.25x"}; 97 | std::vector videoSpeedValues = {4, 3, 2, 1.75, 1.5, 1.25, 1, 0.75, 0.5, 0.25}; 98 | 99 | NVGcolor bottomBarColor = brls::Application::getTheme().getColor("brls/accent"); 100 | 101 | BRLS_BIND(brls::Image, osdLockIcon, "video/osd/lock/icon"); 102 | BRLS_BIND(brls::Box, osdTopBox, "video/osd/top/box"); 103 | BRLS_BIND(brls::Box, osdBottomBox, "video/osd/bottom/box"); 104 | BRLS_BIND(brls::Box, osdCenterBox, "video/osd/center/box"); 105 | BRLS_BIND(brls::Box, osdLockBox, "video/osd/lock/box"); 106 | 107 | // 用于通用的提示信息 108 | BRLS_BIND(brls::Box, osdCenterBox2, "video/osd/center/box2"); 109 | BRLS_BIND(brls::Label, centerLabel2, "video/osd/center/label2"); 110 | BRLS_BIND(brls::Image, centerIcon2, "video/osd/center/icon2"); 111 | 112 | BRLS_BIND(brls::Label, videoSpeed, "video/speed"); 113 | 114 | BRLS_BIND(brls::Label, centerLabel, "video/osd/center/label"); 115 | 116 | BRLS_BIND(brls::Label, title, "video/osd/title"); 117 | BRLS_BIND(brls::Box, speedHintBox, "video/speed/hint/box"); 118 | BRLS_BIND(brls::Label, speedHintLabel, "video/speed/hint/label"); 119 | BRLS_BIND(brls::Image, btnToggleIcon, "video/osd/toggle/icon"); 120 | 121 | BRLS_BIND(brls::Slider, osdSlider, "video/osd/bottom/progress"); 122 | BRLS_BIND(brls::Label, leftStatusLabel, "video/left/status"); 123 | BRLS_BIND(brls::Label, centerStatusLabel, "video/center/status"); 124 | BRLS_BIND(brls::Label, rightStatusLabel, "video/right/status"); 125 | 126 | BRLS_BIND(brls::Box, btnToggle, "video/osd/toggle"); 127 | BRLS_BIND(brls::Image, btnVolumeIcon, "video/osd/volume/icon"); 128 | BRLS_BIND(brls::Image, btnSettingIcon, "video/osd/setting/icon"); 129 | }; 130 | 131 | -------------------------------------------------------------------------------- /src/source/activity/main_activity.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anonymous on 2024/4/3. 3 | // 4 | 5 | #include "activity/main_activity.hpp" 6 | -------------------------------------------------------------------------------- /src/source/activity/video_activity.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anonymous on 2024/4/8. 3 | // 4 | 5 | #include "activity/video_activity.hpp" 6 | 7 | void VideoActivity::onContentAvailable() { 8 | this->video->setUrl(this->path); 9 | this->video->setTitle(this->name); 10 | } -------------------------------------------------------------------------------- /src/source/fragment/local_driver.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anonymous on 2024/4/5. 3 | // 4 | 5 | #include "utils/intent.hpp" 6 | 7 | #include "fragment/local_driver.hpp" 8 | 9 | DirectoryDataSource::DirectoryDataSource(const std::filesystem::path& path) { 10 | this->reloadData(path); 11 | } 12 | 13 | void DirectoryDataSource::reloadData(const std::filesystem::path& path) { 14 | this->items.clear(); 15 | 16 | auto iter = std::filesystem::directory_iterator(path); 17 | 18 | for (const auto& fp : iter) { 19 | if (!fp.exists() || fp.path().filename().string()[0] == '.') { 20 | continue; 21 | } 22 | 23 | auto is_directory = fp.is_directory(); 24 | 25 | if (std::find(extensions.begin(), extensions.end(), 26 | fp.path().extension().string()) != extensions.end() || is_directory) { 27 | auto item = Item { 28 | .path = fp.path(), 29 | .is_directory = is_directory, 30 | .file_size = 0, 31 | }; 32 | 33 | if (!item.is_directory) { 34 | item.file_size = fp.file_size(); 35 | } 36 | 37 | this->items.push_back(item); 38 | } 39 | } 40 | 41 | std::sort(this->items.begin(), this->items.end(), [](const Item& a, const Item& b) { 42 | return a.path.filename().string() < b.path.filename().string(); 43 | }); 44 | 45 | #ifdef __SWITCH__ 46 | if (path.parent_path().string() != "sdmc:") 47 | #else 48 | if (path.has_parent_path() && path.parent_path() != path.root_path()) 49 | #endif 50 | { 51 | this->items.insert(this->items.begin(), Item{ 52 | .is_up = true, 53 | .path = path.parent_path(), 54 | .is_directory = true, 55 | .file_size = 0, 56 | }); 57 | } 58 | #ifdef __SWITCH__ 59 | else if (path.string() != "sdmc:/") { 60 | this->items.insert(this->items.begin(), Item{ 61 | .is_up = true, 62 | .path = "sdmc:/", 63 | .is_directory = true, 64 | .file_size = 0, 65 | }); 66 | } 67 | #endif 68 | } 69 | 70 | int DirectoryDataSource::numberOfSections(brls::RecyclerFrame* recycler) { 71 | return 1; 72 | } 73 | 74 | int DirectoryDataSource::numberOfRows(brls::RecyclerFrame* recycler, int section) { 75 | return (int)this->items.size(); 76 | } 77 | 78 | brls::RecyclerCell* DirectoryDataSource::cellForRow(brls::RecyclerFrame* recycler, brls::IndexPath index) { 79 | RecyclerCell* item = (RecyclerCell*)recycler->dequeueReusableCell("Cell"); 80 | auto f = this->items[index.row]; 81 | if (f.is_directory) { 82 | item->setItem("folder", &f); 83 | } else { 84 | item->setItem("file", &f); 85 | } 86 | return item; 87 | } 88 | 89 | Item *DirectoryDataSource::getItem(int index) { 90 | if (!this->items.empty()) { 91 | return &this->items[index]; 92 | } 93 | return nullptr; 94 | } 95 | 96 | void DirectoryDataSource::didSelectRowAt(brls::RecyclerFrame* recycler, brls::IndexPath index) { 97 | auto f = this->items[index.row]; 98 | if (f.is_directory) { 99 | this->reloadData(f.path); 100 | recycler->reloadData(); 101 | } else { 102 | // open video view 103 | Intent::openVideo(f.path.filename().string(), f.path.string()); 104 | } 105 | } 106 | 107 | RecyclerCell::RecyclerCell() { 108 | this->inflateFromXMLString(recyclerCellXML); 109 | } 110 | 111 | void RecyclerCell::setItem(std::string icon, Item *item) { 112 | if (brls::Application::getThemeVariant() == brls::ThemeVariant::DARK) { 113 | this->fileicon->setImageFromRes(fmt::format("svg/ico-dark-{}.svg", icon)); 114 | } else { 115 | this->fileicon->setImageFromRes(fmt::format("svg/ico-light-{}.svg", icon)); 116 | } 117 | 118 | if (item->is_up) { 119 | this->filepath->setText(".."); 120 | } else { 121 | this->filepath->setText(item->path.filename().string()); 122 | } 123 | } 124 | 125 | RecyclerCell *RecyclerCell::create() { 126 | return new RecyclerCell(); 127 | } 128 | 129 | LocalDriver::LocalDriver() { 130 | this->inflateFromXMLRes("xml/fragment/local_driver.xml"); 131 | 132 | this->recycler->estimatedRowHeight = 70; 133 | this->recycler->registerCell("Cell", []() { return RecyclerCell::create(); }); 134 | DirectoryDataSource *dataSource = nullptr; 135 | #ifdef __SWITCH__ 136 | dataSource = new DirectoryDataSource("sdmc:/"); 137 | #else 138 | dataSource = new DirectoryDataSource(); 139 | #endif 140 | this->recycler->setDataSource(dataSource); 141 | 142 | this->recycler->registerAction("hints/back"_i18n, brls::BUTTON_B, [this, dataSource](...) { 143 | auto item = dataSource->getItem(0); 144 | brls::Logger::info("Local Driver back trigger."); 145 | if (item != nullptr && item->is_up) { 146 | dataSource->didSelectRowAt(this->recycler, brls::IndexPath(0, 0)); 147 | return true; 148 | } 149 | return false; 150 | }, true); 151 | } 152 | 153 | brls::View* LocalDriver::create() { 154 | return (brls::View*)(new LocalDriver()); 155 | } -------------------------------------------------------------------------------- /src/source/fragment/player_setting.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anonymous on 2024/4/8. 3 | // 4 | 5 | #include "fragment/player_setting.hpp" 6 | 7 | #include "utils/utils.hpp" 8 | 9 | 10 | class TrackCell : public brls::RadioCell { 11 | public: 12 | explicit TrackCell(int64_t id, std::string type): id(id), type(std::move(type)) {} 13 | int64_t getId() { 14 | return this->id; 15 | } 16 | std::string getType() { 17 | return this->type; 18 | } 19 | private: 20 | int64_t id; 21 | std::string type; 22 | }; 23 | 24 | PlayerSetting::PlayerSetting(MPVCore *mpvCore) { 25 | this->inflateFromXMLRes("xml/fragment/player_setting.xml"); 26 | 27 | this->core = mpvCore; 28 | 29 | setupTrack(); 30 | 31 | cellSleep->setText("main/setting/sleep"_i18n); 32 | this->updateSleepContent(Utils::getUnixTime()); 33 | cellSleep->registerClickAction([this](View* view) { 34 | std::vector timeList = {15, 30, 60, 90, 120}; 35 | std::string min = "main/common/minute"_i18n; 36 | std::vector optionList = {"15 " + min, "30 " + min, "60 " + min, "90 " + min, "120 " + min}; 37 | bool countdownStarted = MPVCore::CLOSE_TIME != 0 && Utils::getUnixTime() < MPVCore::CLOSE_TIME; 38 | if (countdownStarted) { 39 | // 添加关闭选项 40 | timeList.insert(timeList.begin(), -1); 41 | optionList.insert(optionList.begin(), "hints/off"_i18n); 42 | } 43 | 44 | brls::Dropdown* dropdown = new brls::Dropdown( 45 | "main/setting/sleep"_i18n, optionList, 46 | [](int selected) { 47 | 48 | }, -1, [this, timeList, countdownStarted](int selected){ 49 | brls::Logger::debug("selected {}", selected); 50 | if (selected == 0 && countdownStarted) { 51 | MPVCore::CLOSE_TIME = 0; 52 | } else { 53 | MPVCore::CLOSE_TIME = Utils::getUnixTime() + timeList[countdownStarted ? selected-1 : selected] * 60; 54 | } 55 | this->updateSleepContent(Utils::getUnixTime()); 56 | }); 57 | brls::Application::pushActivity(new brls::Activity(dropdown)); 58 | return true; 59 | }); 60 | 61 | cellProgress->init("main/setting/bottom_progress"_i18n, 62 | VideoView::BOTTOM_BAR, [this](bool s) { 63 | VideoView::BOTTOM_BAR = s; 64 | }); 65 | 66 | const float _16_9 = 16.0f/9.0f; 67 | const float _4_3 = 4.0f/3.0f; 68 | 69 | if (mpvCore->video_aspect == -1) { 70 | this->selectedVideoAspect = 0; 71 | } else if (mpvCore->video_aspect == -2) { 72 | this->selectedVideoAspect = 1; 73 | } else if (mpvCore->video_aspect == -3) { 74 | this->selectedVideoAspect = 2; 75 | } else if (mpvCore->video_aspect == _4_3) { 76 | this->selectedVideoAspect = 3; 77 | } else if (mpvCore->video_aspect == _16_9) { 78 | this->selectedVideoAspect = 4; 79 | } else { 80 | this->selectedVideoAspect = 0; 81 | } 82 | 83 | cellVideoAspect->init("main/setting/aspect/header"_i18n, { 84 | "main/setting/aspect/auto"_i18n, "main/setting/aspect/stretch"_i18n, 85 | "main/setting/aspect/crop"_i18n, "4:3", "16:9" 86 | }, this->selectedVideoAspect, [this](int selected){ 87 | this->selectedVideoAspect = selected; 88 | auto theme = brls::Application::getTheme(); 89 | cellVideoAspect->setDetailTextColor(theme["brls/list/listItem_value_color"]); 90 | 91 | if (this->selectedVideoAspect == 0) { 92 | this->core->setAspect("-1"); 93 | } else if (this->selectedVideoAspect == 1) { 94 | this->core->setAspect("-2"); 95 | } else if (this->selectedVideoAspect == 2) { 96 | this->core->setAspect("-3"); 97 | } else if (this->selectedVideoAspect == 3) { 98 | this->core->setAspect("4:3"); 99 | } else if (this->selectedVideoAspect == 4) { 100 | this->core->setAspect("16:9"); 101 | } else { 102 | this->core->setAspect("-1"); 103 | } 104 | }); 105 | 106 | cellEqualizerReset->registerClickAction([this](View* view) { 107 | cellEqualizerBrightness->slider->setProgress(0.5f); 108 | cellEqualizerContrast->slider->setProgress(0.5f); 109 | cellEqualizerSaturation->slider->setProgress(0.5f); 110 | cellEqualizerGamma->slider->setProgress(0.5f); 111 | cellEqualizerHue->slider->setProgress(0.5f); 112 | return true; 113 | }); 114 | 115 | registerHideBackground(cellEqualizerReset); 116 | 117 | setupEqualizerSetting(cellEqualizerBrightness, "main/setting/equalizer/brightness"_i18n, 118 | mpvCore->getBrightness(), [mpvCore](int value) { 119 | mpvCore->setBrightness(value); 120 | }); 121 | setupEqualizerSetting(cellEqualizerContrast, "main/setting/equalizer/contrast"_i18n, 122 | mpvCore->getContrast(), [mpvCore](int value) { 123 | mpvCore->setContrast(value); 124 | }); 125 | setupEqualizerSetting(cellEqualizerSaturation, "main/setting/equalizer/saturation"_i18n, 126 | mpvCore->getSaturation(), [mpvCore](int value) { 127 | mpvCore->setSaturation(value); 128 | }); 129 | setupEqualizerSetting(cellEqualizerGamma, "main/setting/equalizer/gamma"_i18n, 130 | mpvCore->getGamma(), [mpvCore](int value){ 131 | mpvCore->setGamma(value); 132 | }); 133 | setupEqualizerSetting(cellEqualizerHue, "main/setting/equalizer/hue"_i18n, 134 | mpvCore->getHue(), [mpvCore](int value) { 135 | mpvCore->setHue(value); 136 | }); 137 | 138 | this->registerAction("hints/cancel"_i18n, brls::BUTTON_B, [](...) { 139 | brls::Application::popActivity(); 140 | return true; 141 | }); 142 | 143 | this->registerClickAction([](...){ 144 | brls::Application::popActivity(); 145 | return true; 146 | }); 147 | this->addGestureRecognizer(new brls::TapGestureRecognizer(this)); 148 | } 149 | 150 | 151 | void PlayerSetting::setupTrack() { 152 | auto mpv = this->core; 153 | if (!mpv->isValid()) { 154 | return; 155 | } 156 | 157 | std::vector tracks = mpv->getTracks(); 158 | for (auto &track : tracks) { 159 | TrackCell *cell = nullptr; 160 | 161 | std::string lang; 162 | if (!track.lang.empty()) { 163 | lang = brls::internal::getRawStr(fmt::format("iso639/{}", track.lang)); 164 | } else { 165 | lang = "iso639/und"_i18n; 166 | } 167 | 168 | if (track.type == "audio") { 169 | cell = new TrackCell(track.id, track.type); 170 | 171 | if (trackAudioBox->getVisibility() == brls::Visibility::GONE) { 172 | trackAudioBox->setVisibility(brls::Visibility::VISIBLE); 173 | trackAudioHeader->setVisibility(brls::Visibility::VISIBLE); 174 | } 175 | 176 | trackAudioBox->addView(cell); 177 | } else if (track.type == "sub") { 178 | cell = new TrackCell(track.id, track.type); 179 | 180 | registerHideBackground(cell); 181 | 182 | if (subtitleBox->getVisibility() == brls::Visibility::GONE) { 183 | subtitleBox->setVisibility(brls::Visibility::VISIBLE); 184 | subtitleHeader->setVisibility(brls::Visibility::VISIBLE); 185 | } 186 | 187 | subtitleBox->addView(cell); 188 | } 189 | 190 | if (cell != nullptr) { 191 | if (track.title.empty()) { 192 | cell->title->setText(lang); 193 | } else { 194 | cell->title->setText(fmt::format("{} - {}", lang, track.title)); 195 | } 196 | 197 | cell->setSelected(track.selected); 198 | 199 | cell->registerClickAction([cell, mpv](...) { 200 | if (cell->getSelected()) return false; 201 | 202 | cell->setSelected(true); 203 | 204 | for (auto& child : cell->getParent()->getChildren()) { 205 | auto* v = dynamic_cast(child); 206 | if (cell == v) continue; 207 | if (v) v->setSelected(false); 208 | } 209 | 210 | if (cell->getType() == "audio") { 211 | mpv->setAudioId(cell->getId()); 212 | } else if (cell->getType() == "sub") { 213 | mpv->setSubtitleId(cell->getId()); 214 | } 215 | return true; 216 | }); 217 | } 218 | } 219 | } 220 | 221 | void PlayerSetting::setupEqualizerSetting(brls::SliderCell* cell, const std::string& title, int initValue, const std::function& callback) { 222 | if (initValue < -100) initValue = -100; 223 | if (initValue > 100) initValue = 100; 224 | cell->detail->setWidth(50); 225 | cell->title->setWidth(116); 226 | cell->title->setMarginRight(0); 227 | cell->slider->setStep(0.05f); 228 | cell->slider->setMarginRight(0); 229 | cell->slider->setPointerSize(20); 230 | cell->setDetailText(std::to_string(initValue)); 231 | cell->init(title, (initValue + 100) * 0.005f, [cell, callback](float value) { 232 | int data = (int)(value * 200 - 100); 233 | if (data < -100) data = -100; 234 | if (data > 100) data = 100; 235 | cell->detail->setText(std::to_string(data)); 236 | callback(data); 237 | }); 238 | registerHideBackground(cell->getDefaultFocus()); 239 | } 240 | 241 | void PlayerSetting::draw(NVGcontext* vg, float x, float y, float width, float height, brls::Style style, 242 | brls::FrameContext* ctx) { 243 | static size_t updateTime = 0; 244 | size_t now = Utils::getUnixTime(); 245 | if (now != updateTime) { 246 | updateTime = now; 247 | updateSleepContent(now); 248 | } 249 | Box::draw(vg, x, y, width, height, style, ctx); 250 | } 251 | 252 | void PlayerSetting::registerHideBackground(brls::View *view) { 253 | view->getFocusEvent()->subscribe([this](...) { this->setBackgroundColor(nvgRGBAf(0.0f, 0.0f, 0.0f, 0.0f)); }); 254 | 255 | view->getFocusLostEvent()->subscribe( 256 | [this](...) { this->setBackgroundColor(brls::Application::getTheme().getColor("brls/backdrop")); }); 257 | } 258 | 259 | void PlayerSetting::updateSleepContent(size_t now) { 260 | if (MPVCore::CLOSE_TIME == 0 || now > MPVCore::CLOSE_TIME) { 261 | cellSleep->setDetailTextColor(brls::Application::getTheme()["brls/text_disabled"]); 262 | cellSleep->setDetailText("hints/off"_i18n); 263 | } else { 264 | cellSleep->setDetailTextColor(brls::Application::getTheme()["brls/list/listItem_value_color"]); 265 | cellSleep->setDetailText(Utils::sec2Time(MPVCore::CLOSE_TIME - now)); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/source/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "utils/register.hpp" 4 | #include "utils/intent.hpp" 5 | #include "view/mpv_core.hpp" 6 | 7 | int main(int argc, char* argv[]) { 8 | MPVCore::HARDWARE_DEC = true; 9 | 10 | brls::Platform::APP_LOCALE_DEFAULT = brls::LOCALE_ZH_HANS; 11 | 12 | if (!brls::Application::init()) { 13 | brls::Logger::error("Unable to init application"); 14 | return EXIT_FAILURE; 15 | } 16 | 17 | brls::Logger::setLogLevel(brls::LogLevel::LOG_DEBUG); 18 | 19 | Register::initCustomView(); 20 | Register::initCustomTheme(); 21 | 22 | // only for NX 23 | brls::Application::getPlatform()->exitToHomeMode(true); 24 | brls::Application::createWindow("nxplayer"); 25 | brls::Application::getPlatform()->disableScreenDimming(false); 26 | 27 | if (brls::Application::getPlatform()->isApplicationMode()) { 28 | Intent::openMain(); 29 | } else { 30 | return EXIT_FAILURE; 31 | } 32 | 33 | while (brls::Application::mainLoop()) { 34 | }; 35 | 36 | return EXIT_SUCCESS; 37 | } 38 | -------------------------------------------------------------------------------- /src/source/utils/gesture_helper.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by fang on 2024/1/8. 3 | // 4 | 5 | #include "borealis/core/thread.hpp" 6 | #include "borealis/core/view.hpp" 7 | #include "borealis/core/application.hpp" 8 | 9 | #include "utils/gesture_helper.hpp" 10 | 11 | // Delta from touch starting point to current, when 12 | // touch will be recognized as pan movement 13 | #define MAX_DELTA_MOVEMENT 24 14 | 15 | // Time in ms to recognize long press 16 | #define LONG_TIME_MS 300 // 300ms 17 | #define LONG_TIME_US (LONG_TIME_MS * 1000) 18 | 19 | OsdGestureRecognizer::OsdGestureRecognizer(const OsdGestureEvent::Callback& respond) { 20 | tapEvent.subscribe(respond); 21 | this->startTime = 0; 22 | this->endTime = -1 - LONG_TIME_US; 23 | } 24 | 25 | /// 触摸被打断时保证触发对应的完结事件 26 | static inline OsdGestureType updateGestureType(OsdGestureType osdGestureType) { 27 | if (osdGestureType == OsdGestureType::LONG_PRESS_START) { 28 | return OsdGestureType::LONG_PRESS_CANCEL; 29 | } else if (osdGestureType == OsdGestureType::LEFT_VERTICAL_PAN_START || 30 | osdGestureType == OsdGestureType::LEFT_VERTICAL_PAN_UPDATE) { 31 | return OsdGestureType::LEFT_VERTICAL_PAN_CANCEL; 32 | } else if (osdGestureType == OsdGestureType::RIGHT_VERTICAL_PAN_START || 33 | osdGestureType == OsdGestureType::RIGHT_VERTICAL_PAN_UPDATE) { 34 | return OsdGestureType::RIGHT_VERTICAL_PAN_CANCEL; 35 | } else if (osdGestureType == OsdGestureType::HORIZONTAL_PAN_START || 36 | osdGestureType == OsdGestureType::HORIZONTAL_PAN_UPDATE) { 37 | return OsdGestureType::HORIZONTAL_PAN_CANCEL; 38 | } 39 | return osdGestureType; 40 | } 41 | 42 | brls::GestureState OsdGestureRecognizer::recognitionLoop(brls::TouchState touch, brls::MouseState mouse, 43 | brls::View* view, brls::Sound* soundToPlay) { 44 | brls::TouchPhase phase = touch.phase; 45 | brls::Point position = touch.position; 46 | 47 | if (phase == brls::TouchPhase::NONE) { 48 | position = mouse.position; 49 | phase = mouse.leftButton; 50 | } 51 | 52 | if (!enabled || phase == brls::TouchPhase::NONE) return brls::GestureState::FAILED; 53 | 54 | // If not first touch frame and state is 55 | // INTERRUPTED or FAILED, stop recognition 56 | if (phase != brls::TouchPhase::START) { 57 | if (this->state == brls::GestureState::INTERRUPTED || this->state == brls::GestureState::FAILED) { 58 | if (this->state != lastState) { 59 | // 触摸被打断时保证触发对应的完结事件 60 | this->osdGestureType = updateGestureType(this->osdGestureType); 61 | this->tapEvent.fire(getCurrentStatus()); 62 | } 63 | 64 | lastState = this->state; 65 | return this->state; 66 | } 67 | } 68 | 69 | switch (phase) { 70 | case brls::TouchPhase::START: 71 | brls::Application::giveFocus(view); 72 | this->startTime = brls::getCPUTimeUsec(); 73 | if (this->startTime - this->endTime < LONG_TIME_US) { 74 | brls::cancelDelay(iter); 75 | this->osdGestureType = OsdGestureType::DOUBLE_TAP_START; 76 | } else { 77 | this->osdGestureType = OsdGestureType::NONE; 78 | } 79 | 80 | this->state = brls::GestureState::UNSURE; 81 | this->position = position; 82 | this->tapEvent.fire(getCurrentStatus()); 83 | break; 84 | case brls::TouchPhase::STAY: { 85 | auto frame = view->getFrame(); 86 | // Check if touch is out view's bounds 87 | // if true, FAIL recognition 88 | if (!frame.pointInside(position)) { 89 | // 触摸被打断时保证触发对应的完结事件 90 | this->state = brls::GestureState::FAILED; 91 | this->osdGestureType = updateGestureType(this->osdGestureType); 92 | } else if (this->osdGestureType == OsdGestureType::NONE) { 93 | // 到目前还没有识别出具体的触摸类型 94 | this->delta = position - this->position; 95 | if (fabs(this->delta.x) > MAX_DELTA_MOVEMENT) { 96 | // 识别到水平滑动 97 | this->osdGestureType = OsdGestureType::HORIZONTAL_PAN_START; 98 | } else if (fabs(this->delta.y) > MAX_DELTA_MOVEMENT) { 99 | // 识别到垂直滑动 100 | if (position.x < frame.getMidX()) { 101 | this->osdGestureType = OsdGestureType::LEFT_VERTICAL_PAN_START; 102 | } else { 103 | this->osdGestureType = OsdGestureType::RIGHT_VERTICAL_PAN_START; 104 | } 105 | } else if (brls::getCPUTimeUsec() - this->startTime > LONG_TIME_US) { 106 | // 识别到长按 107 | this->osdGestureType = OsdGestureType::LONG_PRESS_START; 108 | } else { 109 | // 没有识别到触摸类型,不触发回调函数 110 | break; 111 | } 112 | } else if (this->osdGestureType == OsdGestureType::HORIZONTAL_PAN_START || 113 | this->osdGestureType == OsdGestureType::HORIZONTAL_PAN_UPDATE) { 114 | // 更新水平滑动的状态 115 | this->delta = position - this->position; 116 | this->deltaX = delta.x / frame.size.width; 117 | this->osdGestureType = OsdGestureType::HORIZONTAL_PAN_UPDATE; 118 | } else if (this->osdGestureType == OsdGestureType::LEFT_VERTICAL_PAN_START || 119 | this->osdGestureType == OsdGestureType::LEFT_VERTICAL_PAN_UPDATE) { 120 | // 更新左侧垂直滑动的状态 121 | this->delta = position - this->position; 122 | this->deltaY = -delta.y / frame.size.height; 123 | this->osdGestureType = OsdGestureType::LEFT_VERTICAL_PAN_UPDATE; 124 | } else if (this->osdGestureType == OsdGestureType::RIGHT_VERTICAL_PAN_START || 125 | this->osdGestureType == OsdGestureType::RIGHT_VERTICAL_PAN_UPDATE) { 126 | // 更新右侧垂直滑动的状态 127 | this->delta = position - this->position; 128 | this->deltaY = -delta.y / frame.size.height; 129 | this->osdGestureType = OsdGestureType::RIGHT_VERTICAL_PAN_UPDATE; 130 | } else { 131 | break; 132 | } 133 | this->tapEvent.fire(getCurrentStatus()); 134 | break; 135 | } 136 | 137 | case brls::TouchPhase::END: { 138 | this->endTime = brls::getCPUTimeUsec(); 139 | this->state = brls::GestureState::END; 140 | 141 | switch (this->osdGestureType) { 142 | case OsdGestureType::DOUBLE_TAP_START: 143 | this->osdGestureType = OsdGestureType::DOUBLE_TAP_END; 144 | this->tapEvent.fire(getCurrentStatus()); 145 | break; 146 | case OsdGestureType::LONG_PRESS_START: 147 | this->osdGestureType = OsdGestureType::LONG_PRESS_END; 148 | this->tapEvent.fire(getCurrentStatus()); 149 | break; 150 | case OsdGestureType::HORIZONTAL_PAN_START: 151 | case OsdGestureType::HORIZONTAL_PAN_UPDATE: 152 | // 避免拖拽结束后立刻触摸被误认为是双击事件 153 | this->endTime = 0; 154 | this->osdGestureType = OsdGestureType::HORIZONTAL_PAN_END; 155 | this->tapEvent.fire(getCurrentStatus()); 156 | break; 157 | case OsdGestureType::LEFT_VERTICAL_PAN_START: 158 | case OsdGestureType::LEFT_VERTICAL_PAN_UPDATE: 159 | this->endTime = 0; 160 | this->osdGestureType = OsdGestureType::LEFT_VERTICAL_PAN_END; 161 | this->tapEvent.fire(getCurrentStatus()); 162 | break; 163 | case OsdGestureType::RIGHT_VERTICAL_PAN_START: 164 | case OsdGestureType::RIGHT_VERTICAL_PAN_UPDATE: 165 | this->endTime = 0; 166 | this->osdGestureType = OsdGestureType::RIGHT_VERTICAL_PAN_END; 167 | this->tapEvent.fire(getCurrentStatus()); 168 | break; 169 | default: 170 | this->osdGestureType = OsdGestureType::TAP; 171 | brls::cancelDelay(iter); 172 | ASYNC_RETAIN 173 | iter = brls::delay(LONG_TIME_MS, [ASYNC_TOKEN]() { 174 | ASYNC_RELEASE 175 | // 延时触发点击事件 176 | this->tapEvent.fire(getCurrentStatus()); 177 | }); 178 | break; 179 | } 180 | break; 181 | } 182 | case brls::TouchPhase::NONE: 183 | this->state = brls::GestureState::FAILED; 184 | break; 185 | } 186 | 187 | lastState = this->state; 188 | return this->state; 189 | } 190 | 191 | OsdGestureStatus OsdGestureRecognizer::getCurrentStatus() { 192 | return OsdGestureStatus{ 193 | .osdGestureType = this->osdGestureType, 194 | .state = this->state, 195 | .position = this->position, 196 | .deltaX = this->deltaX, 197 | .deltaY = this->deltaY, 198 | }; 199 | } -------------------------------------------------------------------------------- /src/source/utils/intent.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anonymous on 2024/4/3. 3 | // 4 | 5 | #include "utils/intent.hpp" 6 | 7 | #include "activity/main_activity.hpp" 8 | #include "activity/video_activity.hpp" 9 | 10 | void Intent::openMain() { 11 | auto activity = new MainActivity(); 12 | brls::Application::pushActivity(activity); 13 | } 14 | 15 | void Intent::openVideo(std::string name, std::string url) { 16 | auto activity = new VideoActivity(name, url); 17 | brls::Application::pushActivity(activity, brls::TransitionAnimation::NONE); 18 | } 19 | -------------------------------------------------------------------------------- /src/source/utils/register.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anonymous on 2024/4/5. 3 | // 4 | 5 | #include "view/video_view.hpp" 6 | 7 | #include "utils/register.hpp" 8 | 9 | #include "fragment/local_driver.hpp" 10 | 11 | void Register::initCustomView() { 12 | brls::Application::registerXMLView("LocalDriver", LocalDriver::create); 13 | brls::Application::registerXMLView("VideoView", VideoView::create); 14 | } 15 | 16 | void Register::initCustomTheme() { 17 | brls::Theme::getLightTheme().addColor("color/grey_1", nvgRGB(245, 246, 247)); 18 | brls::Theme::getDarkTheme().addColor("color/grey_1", nvgRGB(51, 52, 53)); 19 | } 20 | -------------------------------------------------------------------------------- /src/source/utils/utils.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anonymous on 2024/4/8. 3 | // 4 | 5 | #include "utils/utils.hpp" 6 | 7 | size_t Utils::getUnixTime() { 8 | auto now = std::chrono::system_clock::now(); 9 | return std::chrono::system_clock::to_time_t(now); 10 | } 11 | 12 | std::string Utils::sec2Time(size_t t) { 13 | size_t hour = t / 3600; 14 | size_t minute = t / 60 % 60; 15 | size_t sec = t % 60; 16 | return Utils::pre0(hour, 2) + ":" + Utils::pre0(minute, 2) + ":" + Utils::pre0(sec, 2); 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/source/view/video_view.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anonymous on 2024/4/8. 3 | // 4 | 5 | #include "view/video_view.hpp" 6 | 7 | #include "fragment/player_setting.hpp" 8 | 9 | #define CHECK_OSD(shake) \ 10 | if (is_osd_lock) { \ 11 | if (isOSDShown()) { \ 12 | brls::Application::giveFocus(this->osdLockBox); \ 13 | if (shake) this->osdLockBox->shakeHighlight(brls::FocusDirection::RIGHT); \ 14 | } else { \ 15 | this->showOSD(true); \ 16 | } \ 17 | return true; \ 18 | } 19 | 20 | static int getSeekRange(int current) { 21 | current = abs(current); 22 | if (current < 60) return 5; 23 | if (current < 300) return 10; 24 | if (current < 600) return 20; 25 | if (current < 1200) return 60; 26 | return current / 15; 27 | } 28 | 29 | VideoView::VideoView() { 30 | this->inflateFromXMLRes("xml/view/video_view.xml"); 31 | 32 | this->mpvCore = &MPVCore::instance(); 33 | 34 | this->input = brls::Application::getPlatform()->getInputManager(); 35 | 36 | this->osdSlider->getInputProgressEvent()->subscribe([this](float progress) { 37 | if (seeking_iter > 0) { 38 | brls::cancelDelay(seeking_iter); 39 | seeking_iter = 0; 40 | } 41 | 42 | auto value = (float)this->mpvCore->duration * progress; 43 | int64_t sp = (int64_t)(value - this->seeking_last_play_time); 44 | if (sp >= 0 && sp < 5) { 45 | sp = 5; 46 | } else if (sp < 0 && sp > -5) { 47 | sp = -5; 48 | } 49 | 50 | if (this->seeking_last_play_time + sp < 0) { 51 | this->mpvCore->seek(0); 52 | } else if (this->seeking_last_play_time + sp > this->mpvCore->duration) { 53 | this->mpvCore->seek(this->mpvCore->duration); 54 | } else { 55 | this->mpvCore->seek(this->seeking_last_play_time + sp); 56 | } 57 | 58 | this->seeking_last_play_time = 0; 59 | 60 | if (osdCenterBox2->getVisibility() != brls::Visibility::GONE) { 61 | hideCenterHint(); 62 | } 63 | }); 64 | 65 | this->osdSlider->getProgressEvent()->subscribe([this](float progress) { 66 | if (this->isSeeking()) { 67 | auto value = (float)this->mpvCore->duration * progress; 68 | 69 | this->setPlaybackTime(value); 70 | 71 | if (this->seeking_last_play_time == 0) { 72 | this->seeking_last_play_time = this->mpvCore->playback_time; 73 | } 74 | 75 | showOSD(false); 76 | 77 | if (osdCenterBox2->getVisibility() != brls::Visibility::VISIBLE) { 78 | showCenterHint(); 79 | setCenterHintIcon("svg/arrow-left-right.svg"); 80 | } 81 | 82 | int64_t sp = (int64_t)(value - this->seeking_last_play_time); 83 | if (sp >= 0 && sp < 5) { 84 | sp = 5; 85 | } else if (sp < 0 && sp > -5) { 86 | sp = -5; 87 | } 88 | setCenterHintText(fmt::format("{:+d} s", sp)); 89 | 90 | if (seeking_iter <= 0) { 91 | ASYNC_RETAIN 92 | seeking_iter = brls::delay(300, [ASYNC_TOKEN, sp]() { 93 | ASYNC_RELEASE 94 | if (this->seeking_last_play_time + sp < 0) { 95 | this->mpvCore->seek(0); 96 | this->seeking_last_play_time = 0; 97 | } else if (this->seeking_last_play_time + sp > this->mpvCore->duration) { 98 | this->mpvCore->seek(this->mpvCore->duration); 99 | this->seeking_last_play_time = this->mpvCore->duration; 100 | } else { 101 | this->mpvCore->seek(this->seeking_last_play_time + sp); 102 | } 103 | brls::cancelDelay(seeking_iter); 104 | seeking_iter = 0; 105 | }); 106 | } 107 | } 108 | }); 109 | 110 | /// 播放/暂停 按钮 111 | this->btnToggle->addGestureRecognizer( 112 | new brls::TapGestureRecognizer(this->btnToggle, [this]() { this->togglePlay(); })); 113 | this->btnToggle->registerClickAction([this](...) { 114 | this->togglePlay(); 115 | return true; 116 | }); 117 | 118 | /// 音量按钮 119 | this->btnVolumeIcon->getParent()->registerClickAction([this](brls::View* view) { 120 | // 一直显示 OSD 121 | this->showOSD(false); 122 | auto theme = brls::Application::getTheme(); 123 | auto container = new brls::Box(); 124 | container->setHideClickAnimation(true); 125 | container->addGestureRecognizer(new brls::TapGestureRecognizer(container, [this, container]() { 126 | // 几秒后自动关闭 OSD 127 | this->showOSD(true); 128 | container->dismiss(); 129 | })); 130 | 131 | // 滑动条背景 132 | auto sliderBox = new brls::Box(); 133 | sliderBox->setAlignItems(brls::AlignItems::CENTER); 134 | sliderBox->setHeight(60); 135 | sliderBox->setCornerRadius(4); 136 | sliderBox->setBackgroundColor(theme.getColor("color/grey_1")); 137 | float sliderX = view->getX() - 100; 138 | if (sliderX < 0) sliderX = 20; 139 | if (sliderX > brls::Application::ORIGINAL_WINDOW_WIDTH - 132) 140 | sliderX = brls::Application::ORIGINAL_WINDOW_WIDTH - 132; 141 | sliderBox->setTranslationX(sliderX); 142 | sliderBox->setTranslationY(view->getY() - 70); 143 | 144 | // 滑动条 145 | auto slider = new brls::Slider(); 146 | slider->setMargins(8, 16, 8, 16); 147 | slider->setWidth(200); 148 | slider->setHeight(40); 149 | slider->setProgress((float)this->mpvCore->getVolume() / 100.0f); 150 | slider->getProgressEvent()->subscribe([this](float progress) { this->mpvCore->setVolume(progress * 100); }); 151 | sliderBox->addView(slider); 152 | container->addView(sliderBox); 153 | auto frame = new brls::AppletFrame(container); 154 | frame->setInFadeAnimation(true); 155 | frame->setHeaderVisibility(brls::Visibility::GONE); 156 | frame->setFooterVisibility(brls::Visibility::GONE); 157 | frame->setBackgroundColor(theme.getColor("brls/backdrop")); 158 | container->registerAction("hints/back"_i18n, brls::BUTTON_B, [this, container](...) { 159 | // 几秒后自动关闭 OSD 160 | this->showOSD(true); 161 | container->dismiss(); 162 | return true; 163 | }); 164 | brls::Application::pushActivity(new brls::Activity(frame)); 165 | 166 | // 手动将焦点赋给音量组件 167 | brls::sync([container]() { brls::Application::giveFocus(container); }); 168 | return true; 169 | }); 170 | this->btnVolumeIcon->getParent()->addGestureRecognizer( 171 | new brls::TapGestureRecognizer(this->btnVolumeIcon->getParent())); 172 | 173 | if (mpvCore->volume <= 0) { 174 | this->btnVolumeIcon->setImageFromRes("svg/bpx-svg-sprite-volume-off.svg"); 175 | } 176 | 177 | this->osdLockBox->registerClickAction([this](...) { 178 | this->toggleOSDLock(); 179 | return true; 180 | }); 181 | this->osdLockBox->addGestureRecognizer(new brls::TapGestureRecognizer(this->osdLockBox)); 182 | 183 | this->registerAction( 184 | "toggleOSD", brls::ControllerButton::BUTTON_Y, 185 | [this](brls::View* view) -> bool { 186 | // 拖拽进度时不要影响显示 OSD 187 | if (this->isSeeking()) return true; 188 | this->toggleOSD(); 189 | return true; 190 | }, 191 | true); 192 | 193 | this->registerAction( 194 | "lockView", brls::ControllerButton::BUTTON_X, 195 | [this](brls::View* view) -> bool { 196 | this->OSDLock(); 197 | this->showOSD(true); 198 | return true; 199 | }, 200 | true); 201 | 202 | // 暂停 203 | this->registerAction( 204 | "toggle", brls::ControllerButton::BUTTON_SPACE, 205 | [this](...) -> bool { 206 | CHECK_OSD(true); 207 | this->togglePlay(); 208 | return true; 209 | }, 210 | true); 211 | 212 | /// 组件触摸事件 213 | /// 单击控制 OSD 214 | /// 双击控制播放与暂停 215 | /// 长按加速 216 | /// 滑动调整进度 217 | /// 左右侧滑动调整音量,在支持调节背光的设备上左侧滑动调节背光亮度,右侧调节音量 218 | this->addGestureRecognizer(new OsdGestureRecognizer([this](OsdGestureStatus status) { 219 | switch (status.osdGestureType) { 220 | case OsdGestureType::TAP: 221 | this->toggleOSD(); 222 | break; 223 | case OsdGestureType::DOUBLE_TAP_END: 224 | if (is_osd_lock) { 225 | this->toggleOSD(); 226 | break; 227 | } 228 | this->togglePlay(); 229 | break; 230 | case OsdGestureType::LONG_PRESS_START: { 231 | if (is_osd_lock) break; 232 | float SPEED = MPVCore::VIDEO_SPEED == 100 ? 2.0 : MPVCore::VIDEO_SPEED * 0.01f; 233 | this->setSpeed(SPEED); 234 | // 绘制临时加速标识 235 | this->speedHintLabel->setText(fmt::format(fmt::runtime("main/player/current_speed"_i18n), SPEED)); 236 | this->speedHintBox->setVisibility(brls::Visibility::VISIBLE); 237 | break; 238 | } 239 | case OsdGestureType::LONG_PRESS_CANCEL: 240 | case OsdGestureType::LONG_PRESS_END: 241 | if (is_osd_lock) { 242 | this->toggleOSD(); 243 | break; 244 | } 245 | if (mpvCore->getSpeed() != 1.0f) { 246 | this->setSpeed(1.0f); 247 | } 248 | if (this->speedHintBox->getVisibility() != brls::Visibility::GONE) { 249 | this->speedHintBox->setVisibility(brls::Visibility::GONE); 250 | } 251 | break; 252 | case OsdGestureType::HORIZONTAL_PAN_START: 253 | if (is_osd_lock) break; 254 | this->showCenterHint(); 255 | this->setCenterHintIcon("svg/arrow-left-right.svg"); 256 | break; 257 | case OsdGestureType::HORIZONTAL_PAN_UPDATE: 258 | if (is_osd_lock) break; 259 | this->requestSeeking(fmin(120.0f, mpvCore->duration) * status.deltaX); 260 | break; 261 | case OsdGestureType::HORIZONTAL_PAN_CANCEL: 262 | if (is_osd_lock) break; 263 | // 立即取消 264 | this->requestSeeking(VIDEO_CANCEL_SEEKING, VIDEO_SEEK_IMMEDIATELY); 265 | break; 266 | case OsdGestureType::HORIZONTAL_PAN_END: 267 | if (is_osd_lock) { 268 | this->toggleOSD(); 269 | break; 270 | } 271 | // 立即跳转 272 | this->requestSeeking(fmin(120.0f, mpvCore->duration) * status.deltaX, VIDEO_SEEK_IMMEDIATELY); 273 | break; 274 | case OsdGestureType::LEFT_VERTICAL_PAN_START: 275 | if (is_osd_lock) break; 276 | if (brls::Application::getPlatform()->canSetBacklightBrightness()) { 277 | this->brightness_init = brls::Application::getPlatform()->getBacklightBrightness(); 278 | this->showCenterHint(); 279 | this->setCenterHintIcon("svg/sun-fill.svg"); 280 | break; 281 | } 282 | case OsdGestureType::RIGHT_VERTICAL_PAN_START: 283 | if (is_osd_lock) break; 284 | this->volume_init = (int)this->mpvCore->volume; 285 | this->showCenterHint(); 286 | this->setCenterHintIcon("svg/bpx-svg-sprite-volume.svg"); 287 | break; 288 | case OsdGestureType::LEFT_VERTICAL_PAN_UPDATE: 289 | if (is_osd_lock) break; 290 | if (brls::Application::getPlatform()->canSetBacklightBrightness()) { 291 | this->requestBrightness(this->brightness_init + status.deltaY); 292 | break; 293 | } 294 | case OsdGestureType::RIGHT_VERTICAL_PAN_UPDATE: 295 | if (is_osd_lock) break; 296 | this->requestVolume(this->volume_init + status.deltaY * 100); 297 | break; 298 | case OsdGestureType::LEFT_VERTICAL_PAN_CANCEL: 299 | case OsdGestureType::LEFT_VERTICAL_PAN_END: 300 | if (is_osd_lock) { 301 | this->toggleOSD(); 302 | break; 303 | } 304 | if (brls::Application::getPlatform()->canSetBacklightBrightness()) { 305 | this->hideCenterHint(); 306 | break; 307 | } 308 | case OsdGestureType::RIGHT_VERTICAL_PAN_CANCEL: 309 | case OsdGestureType::RIGHT_VERTICAL_PAN_END: 310 | if (is_osd_lock) { 311 | this->toggleOSD(); 312 | break; 313 | } 314 | this->hideCenterHint(); 315 | break; 316 | default: 317 | break; 318 | } 319 | })); 320 | 321 | this->registerAction( 322 | "volumeUp", brls::ControllerButton::BUTTON_NAV_UP, 323 | [this](brls::View* view) -> bool { 324 | CHECK_OSD(true); 325 | brls::ControllerState state{}; 326 | input->updateUnifiedControllerState(&state); 327 | if (state.buttons[brls::BUTTON_RT]) { 328 | this->requestVolume((int)MPVCore::instance().volume + 5, 400); 329 | return true; 330 | } 331 | return false; 332 | }, 333 | true, true); 334 | 335 | this->registerAction( 336 | "volumeDown", brls::ControllerButton::BUTTON_NAV_DOWN, 337 | [this](brls::View* view) -> bool { 338 | CHECK_OSD(true); 339 | brls::ControllerState state{}; 340 | input->updateUnifiedControllerState(&state); 341 | if (state.buttons[brls::BUTTON_RT]) { 342 | this->requestVolume((int)MPVCore::instance().volume - 5, 400); 343 | return true; 344 | } 345 | return false; 346 | }, 347 | true, true); 348 | 349 | this->registerAction( 350 | "cancel", brls::ControllerButton::BUTTON_B, 351 | [this](brls::View* view) -> bool { 352 | if (is_osd_lock) { 353 | this->toggleOSD(); 354 | return true; 355 | } 356 | 357 | if (isOSDShown()) { 358 | this->toggleOSD(); 359 | return true; 360 | } else { 361 | auto dialog = new brls::Dialog("main/player/exit_hint"_i18n); 362 | dialog->addButton("hints/cancel"_i18n, []() {}); 363 | dialog->addButton("hints/ok"_i18n, [this]() 364 | { 365 | brls::Application::popActivity(brls::TransitionAnimation::NONE); 366 | }); 367 | dialog->open(); 368 | } 369 | 370 | return true; 371 | }, 372 | true); 373 | 374 | this->registerAction("togglePlay",brls::ControllerButton::BUTTON_A, [this](brls::View* view) { 375 | CHECK_OSD(false); 376 | this->showOSD(true); 377 | this->togglePlay(); 378 | return true; 379 | }); 380 | 381 | this->registerAction( 382 | "\uE08F", brls::ControllerButton::BUTTON_LB, 383 | [this](brls::View* view) -> bool { 384 | CHECK_OSD(true); 385 | seeking_range -= getSeekRange(seeking_range); 386 | this->requestSeeking(seeking_range); 387 | return true; 388 | }, 389 | true, true); 390 | 391 | this->registerAction( 392 | "\uE08E", brls::ControllerButton::BUTTON_RB, 393 | [this](brls::View* view) -> bool { 394 | CHECK_OSD(true); 395 | brls::ControllerState state{}; 396 | input->updateUnifiedControllerState(&state); 397 | bool buttonY = 398 | brls::Application::isSwapInputKeys() ? state.buttons[brls::BUTTON_X] : state.buttons[brls::BUTTON_Y]; 399 | if (buttonY) { 400 | seeking_range -= getSeekRange(seeking_range); 401 | } else { 402 | seeking_range += getSeekRange(seeking_range); 403 | } 404 | this->requestSeeking(seeking_range); 405 | return true; 406 | }, 407 | true, true); 408 | 409 | this->registerAction("PLAYER_SETTING", brls::ControllerButton::BUTTON_START, 410 | [this](brls::View* view) -> bool { 411 | CHECK_OSD(true); 412 | 413 | return this->openPlayerSetting(); 414 | }); 415 | 416 | this->btnSettingIcon->getParent()->registerClickAction([this](View *view) { 417 | return this->openPlayerSetting(); 418 | }); 419 | 420 | this->videoSpeed->registerClickAction([this](View *view){ 421 | auto speed = this->mpvCore->getSpeed(); 422 | auto index = std::find(videoSpeedValues.begin(), videoSpeedValues.end(), speed); 423 | if (index >= videoSpeedValues.end()) { 424 | return true; 425 | } 426 | 427 | auto defaultOption = index - videoSpeedValues.begin(); 428 | 429 | brls::Dropdown* dropdown = new brls::Dropdown( 430 | "main/player/speed"_i18n, videoSpeedOptions, [](int selected) { 431 | }, defaultOption, [this](int selected) { 432 | this->mpvCore->setSpeed(videoSpeedValues[selected]); 433 | }); 434 | brls::Application::pushActivity(new brls::Activity(dropdown)); 435 | return true; 436 | }); 437 | this->videoSpeed->addGestureRecognizer(new brls::TapGestureRecognizer(this->btnToggle)); 438 | 439 | this->registerMPVEvent(); 440 | } 441 | 442 | VideoView::~VideoView() { 443 | mpvCore->getEvent()->unsubscribe(eventSubscribeID); 444 | mpvCore->stop(); 445 | } 446 | 447 | MPVCore *VideoView::getCore() { 448 | return this->mpvCore; 449 | } 450 | 451 | bool VideoView::openPlayerSetting() { 452 | auto setting = new PlayerSetting(this->mpvCore); 453 | 454 | setting->setInFadeAnimation(true); 455 | 456 | brls::Application::pushActivity(new brls::Activity(setting)); 457 | brls::sync([setting]() { brls::Application::giveFocus(setting); }); 458 | 459 | return true; 460 | } 461 | 462 | bool VideoView::isSeeking() { 463 | return this->is_seeking ? this->is_seeking : this->osdSlider->isProgressing(); 464 | } 465 | 466 | void VideoView::requestBrightness(float brightness) { 467 | if (brightness < 0) brightness = 0.0f; 468 | if (brightness > 1) brightness = 1.0f; 469 | brls::Application::getPlatform()->setBacklightBrightness(brightness); 470 | setCenterHintText(fmt::format("{} %", (int)(brightness * 100))); 471 | } 472 | 473 | void VideoView::requestVolume(int volume, int delay) { 474 | if (volume < 0) volume = 0; 475 | if (volume > 100) volume = 100; 476 | this->mpvCore->setVolume(volume); 477 | setCenterHintText(fmt::format("{} %", volume)); 478 | if (delay == 0) return; 479 | if (volume_iter == 0) { 480 | this->showCenterHint(); 481 | this->setCenterHintIcon("svg/bpx-svg-sprite-volume.svg"); 482 | } else { 483 | brls::cancelDelay(volume_iter); 484 | } 485 | ASYNC_RETAIN 486 | volume_iter = brls::delay(delay, [ASYNC_TOKEN]() { 487 | ASYNC_RELEASE 488 | this->hideCenterHint(); 489 | this->volume_iter = 0; 490 | }); 491 | } 492 | 493 | void VideoView::onChildFocusGained(View* directChild, View* focusedView) { 494 | Box::onChildFocusGained(directChild, focusedView); 495 | 496 | if (is_osd_lock) { 497 | brls::Application::giveFocus(this->osdLockBox); 498 | return; 499 | } 500 | 501 | if (isOSDShown()) { 502 | // 当弹幕按钮隐藏时不可获取焦点 503 | if (focusedView->getParent()->getVisibility() == brls::Visibility::GONE) { 504 | brls::Application::giveFocus(this); 505 | } 506 | static View* lastFocusedView = nullptr; 507 | 508 | // 设定自定义导航 509 | if (focusedView == this->btnSettingIcon) { 510 | this->btnSettingIcon->setCustomNavigationRoute( 511 | brls::FocusDirection::DOWN, 512 | lastFocusedView == this->btnToggle ? "video/osd/toggle" : "video/osd/lock/box"); 513 | } 514 | lastFocusedView = focusedView; 515 | return; 516 | } 517 | brls::Application::giveFocus(this); 518 | } 519 | 520 | void VideoView::showCenterHint() { osdCenterBox2->setVisibility(brls::Visibility::VISIBLE); } 521 | 522 | void VideoView::hideCenterHint() { osdCenterBox2->setVisibility(brls::Visibility::GONE); } 523 | 524 | void VideoView::setCenterHintIcon(const std::string& svg) { centerIcon2->setImageFromRes(svg); } 525 | 526 | void VideoView::setCenterHintText(const std::string& text) { centerLabel2->setText(text); } 527 | 528 | void VideoView::requestSeeking(int seek, int delay) { 529 | if (mpvCore->duration <= 0) { 530 | seeking_range = 0; 531 | is_seeking = false; 532 | return; 533 | } 534 | double progress = (this->mpvCore->playback_time + seek) / mpvCore->duration; 535 | 536 | if (progress < 0) { 537 | progress = 0; 538 | seek = (int64_t)this->mpvCore->playback_time * -1; 539 | } else if (progress > 1) { 540 | progress = 1; 541 | seek = mpvCore->duration; 542 | } 543 | 544 | showOSD(false); 545 | if (osdCenterBox2->getVisibility() != brls::Visibility::VISIBLE) { 546 | showCenterHint(); 547 | setCenterHintIcon("svg/arrow-left-right.svg"); 548 | } 549 | setCenterHintText(fmt::format("{:+d} s", seek)); 550 | osdSlider->setProgress((float)progress); 551 | leftStatusLabel->setText(Utils::sec2Time(mpvCore->duration * progress)); 552 | 553 | // 取消之前的延迟触发 554 | brls::cancelDelay(seeking_iter); 555 | if (delay <= 0) { 556 | this->hideCenterHint(); 557 | seeking_range = 0; 558 | is_seeking = false; 559 | if (seek == 0) return; 560 | mpvCore->seekRelative(seek); 561 | } else { 562 | // 延迟触发跳转进度 563 | is_seeking = true; 564 | ASYNC_RETAIN 565 | seeking_iter = brls::delay(delay, [ASYNC_TOKEN, seek]() { 566 | ASYNC_RELEASE 567 | this->hideCenterHint(); 568 | seeking_range = 0; 569 | is_seeking = false; 570 | if (seek == 0) return; 571 | mpvCore->seekRelative(seek); 572 | }); 573 | } 574 | } 575 | 576 | void VideoView::OSDLock() { 577 | osdTopBox->setVisibility(brls::Visibility::INVISIBLE); 578 | osdBottomBox->setVisibility(brls::Visibility::INVISIBLE); 579 | // 锁定时上下按键不可用 580 | osdLockBox->setCustomNavigationRoute(brls::FocusDirection::UP, "video/osd/lock/box"); 581 | osdLockBox->setCustomNavigationRoute(brls::FocusDirection::DOWN, "video/osd/lock/box"); 582 | this->osdLockIcon->setImageFromRes("svg/player-lock.svg"); 583 | is_osd_lock = true; 584 | } 585 | 586 | void VideoView::OSDUnlock() { 587 | // 手动设置上下按键的导航路线 588 | osdLockBox->setCustomNavigationRoute(brls::FocusDirection::UP, "video/osd/setting"); 589 | osdLockBox->setCustomNavigationRoute(brls::FocusDirection::DOWN, "video/osd/icon/box"); 590 | this->osdLockIcon->setImageFromRes("svg/player-unlock.svg"); 591 | is_osd_lock = false; 592 | } 593 | 594 | void VideoView::toggleOSDLock() { 595 | if (is_osd_lock) { 596 | VideoView::OSDUnlock(); 597 | } else { 598 | VideoView::OSDLock(); 599 | } 600 | this->showOSD(true); 601 | } 602 | 603 | void VideoView::togglePlay() { 604 | if (this->mpvCore->isPaused()) { 605 | this->mpvCore->resume(); 606 | } else { 607 | this->mpvCore->pause(); 608 | } 609 | } 610 | 611 | void VideoView::refreshToggleIcon() { 612 | if (this->isSeeking()) return; 613 | 614 | if (!mpvCore->isPlaying()) { 615 | btnToggleIcon->setImageFromRes("svg/bpx-svg-sprite-play.svg"); 616 | } else { 617 | btnToggleIcon->setImageFromRes("svg/bpx-svg-sprite-pause.svg"); 618 | } 619 | } 620 | 621 | void VideoView::showOSD(bool temp) { 622 | if (temp) { 623 | this->osdLastShowTime = Utils::unix_time() + VideoView::OSD_SHOW_TIME; 624 | this->osd_state = OSDState::SHOWN; 625 | } else { 626 | this->osdLastShowTime = (std::numeric_limits::max)(); 627 | this->osd_state = OSDState::ALWAYS_ON; 628 | } 629 | } 630 | 631 | void VideoView::hideOSD() { 632 | this->osdLastShowTime = 0; 633 | this->osd_state = OSDState::HIDDEN; 634 | } 635 | 636 | void VideoView::toggleOSD() { 637 | if (isOSDShown()) { 638 | this->hideOSD(); 639 | } else { 640 | this->showOSD(true); 641 | } 642 | } 643 | 644 | bool VideoView::isOSDShown() { 645 | return this->is_osd_shown; 646 | } 647 | 648 | void VideoView::showLoading() { 649 | if (osdCenterBox2->getVisibility() != brls::Visibility::VISIBLE && osdCenterBox->getVisibility() != brls::Visibility::VISIBLE) 650 | osdCenterBox->setVisibility(brls::Visibility::VISIBLE); 651 | } 652 | 653 | void VideoView::hideLoading() { 654 | if (osdCenterBox->getVisibility() != brls::Visibility::GONE) 655 | osdCenterBox->setVisibility(brls::Visibility::GONE); 656 | } 657 | 658 | void VideoView::registerMPVEvent() { 659 | eventSubscribeID = this->mpvCore->getEvent()->subscribe([this](MpvEventEnum event) { 660 | switch (event) { 661 | case MpvEventEnum::MPV_IDLE: 662 | refreshToggleIcon(); 663 | break; 664 | case MpvEventEnum::MPV_RESUME: 665 | this->showOSD(true); 666 | this->hideLoading(); 667 | break; 668 | case MpvEventEnum::MPV_PAUSE: 669 | this->showOSD(false); 670 | break; 671 | case MpvEventEnum::START_FILE: 672 | this->showOSD(false); 673 | break; 674 | case MpvEventEnum::LOADING_START: 675 | this->showLoading(); 676 | break; 677 | case MpvEventEnum::LOADING_END: 678 | this->hideLoading(); 679 | break; 680 | case MpvEventEnum::MPV_STOP: 681 | this->hideLoading(); 682 | this->showOSD(false); 683 | break; 684 | case MpvEventEnum::UPDATE_DURATION: 685 | if (!this->isSeeking()) { 686 | this->setDuration(this->mpvCore->duration); 687 | this->setProgress((float) mpvCore->playback_time / (float) this->mpvCore->duration); 688 | this->osdSlider->setStep((float) brls::Application::getFPS() / ((float) this->mpvCore->duration * 2.0f)); 689 | } 690 | break; 691 | case MpvEventEnum::UPDATE_PROGRESS: 692 | if (!this->isSeeking()) { 693 | this->setPlaybackTime(this->mpvCore->video_progress); 694 | this->setProgress((float) mpvCore->playback_time / (float) this->mpvCore->duration); 695 | this->osdSlider->setStep((float) brls::Application::getFPS() / ((float) this->mpvCore->duration * 2.0f)); 696 | } 697 | case MpvEventEnum::VIDEO_SPEED_CHANGE: 698 | if (fabs(mpvCore->video_speed - 1) < 1e-5) { 699 | this->videoSpeed->setText("main/player/speed"_i18n); 700 | } else { 701 | this->videoSpeed->setText(fmt::format("{}x", mpvCore->video_speed)); 702 | } 703 | break; 704 | case MpvEventEnum::END_OF_FILE: 705 | this->showOSD(false); 706 | case MpvEventEnum::CACHE_SPEED_CHANGE: 707 | // 仅当加载圈已经开始转起的情况显示缓存 708 | if (this->osdCenterBox->getVisibility() != brls::Visibility::GONE) { 709 | if (this->centerLabel->getVisibility() != brls::Visibility::VISIBLE) 710 | this->centerLabel->setVisibility(brls::Visibility::VISIBLE); 711 | this->centerLabel->setText(mpvCore->getCacheSpeed()); 712 | } 713 | break; 714 | case MpvEventEnum::VIDEO_MUTE: 715 | this->btnVolumeIcon->setImageFromRes("svg/bpx-svg-sprite-volume-off.svg"); 716 | break; 717 | case MpvEventEnum::VIDEO_UNMUTE: 718 | this->btnVolumeIcon->setImageFromRes("svg/bpx-svg-sprite-volume.svg"); 719 | break; 720 | case MpvEventEnum::RESET: 721 | this->setProgress(0); 722 | this->setPlaybackTime(0); 723 | this->setDuration(0); 724 | break; 725 | default: 726 | break; 727 | } 728 | }); 729 | } 730 | 731 | void VideoView::setProgress(float value) { 732 | if (this->isSeeking()) return; 733 | if (isnan(value)) return; 734 | this->osdSlider->setProgress(value); 735 | } 736 | 737 | void VideoView::setDuration(size_t duration) { 738 | this->rightStatusLabel->setText(Utils::sec2Time(duration)); 739 | } 740 | 741 | void VideoView::setPlaybackTime(size_t duration) { 742 | this->leftStatusLabel->setText(Utils::sec2Time(duration)); 743 | } 744 | 745 | void VideoView::setSpeed(float speed) { mpvCore->setSpeed(speed); } 746 | 747 | void VideoView::setUrl(const std::string& url) { 748 | this->mpvCore->setUrl(url); 749 | } 750 | 751 | void VideoView::setTitle(const std::string& title) { 752 | this->title->setText(title); 753 | } 754 | 755 | brls::View *VideoView::getDefaultFocus() { 756 | if (isOSDShown()) { 757 | return this->btnToggle; 758 | } 759 | return this; 760 | } 761 | 762 | void VideoView::buttonProcessing() { 763 | // 获取按键数据 764 | brls::ControllerState state{}; 765 | input->updateUnifiedControllerState(&state); 766 | 767 | // 当OSD显示时上下左右切换选择按钮,持续显示OSD 768 | if (isOSDShown() && (state.buttons[brls::BUTTON_NAV_RIGHT] || state.buttons[brls::BUTTON_NAV_LEFT] || 769 | state.buttons[brls::BUTTON_NAV_UP] || state.buttons[brls::BUTTON_NAV_DOWN])) { 770 | if (this->osd_state == OSDState::SHOWN) this->showOSD(true); 771 | } 772 | if (is_osd_lock) return; 773 | } 774 | 775 | void VideoView::onOSDStateChanged(bool state) { 776 | if ((!state && isChildFocused()) || (state && brls::Application::getCurrentFocus() == this)) { 777 | brls::Application::giveFocus(this); 778 | } 779 | } 780 | 781 | void VideoView::draw(NVGcontext* vg, float x, float y, float width, float height, brls::Style style, 782 | brls::FrameContext* ctx) { 783 | if (!mpvCore->isValid()) return; 784 | 785 | time_t current = Utils::unix_time(); 786 | bool drawOSD = current < this->osdLastShowTime; 787 | auto alpha = this->getAlpha(); 788 | 789 | mpvCore->draw(brls::Rect(x, y, width, height), alpha); 790 | 791 | // draw bottom bar 792 | if (BOTTOM_BAR) { 793 | bottomBarColor.a = alpha; 794 | float progress = mpvCore->playback_time / (double)mpvCore->duration; 795 | progress = progress > 1.0f ? 1.0f : progress; 796 | nvgFillColor(vg, bottomBarColor); 797 | nvgBeginPath(vg); 798 | nvgRect(vg, x, y + height - 2, width * progress, 2); 799 | nvgFill(vg); 800 | } 801 | 802 | if (drawOSD) { 803 | if (!is_osd_shown) { 804 | is_osd_shown = true; 805 | this->onOSDStateChanged(true); 806 | } 807 | 808 | if (!is_osd_lock) { 809 | if (osdTopBox->getVisibility() != brls::Visibility::VISIBLE) 810 | osdTopBox->setVisibility(brls::Visibility::VISIBLE); 811 | if (osdBottomBox->getVisibility() != brls::Visibility::VISIBLE) 812 | osdBottomBox->setVisibility(brls::Visibility::VISIBLE); 813 | osdTopBox->frame(ctx); 814 | osdBottomBox->frame(ctx); 815 | } 816 | 817 | // draw osd lock button 818 | if (osdLockBox->getVisibility() != brls::Visibility::VISIBLE) 819 | osdLockBox->setVisibility(brls::Visibility::VISIBLE); 820 | osdLockBox->frame(ctx); 821 | } else { 822 | if (is_osd_shown) { 823 | is_osd_shown = false; 824 | this->onOSDStateChanged(false); 825 | } 826 | 827 | if (osdTopBox->getVisibility() != brls::Visibility::INVISIBLE) 828 | osdTopBox->setVisibility(brls::Visibility::INVISIBLE); 829 | if (osdBottomBox->getVisibility() != brls::Visibility::INVISIBLE) 830 | osdBottomBox->setVisibility(brls::Visibility::INVISIBLE); 831 | if (osdLockBox->getVisibility() != brls::Visibility::INVISIBLE) 832 | osdLockBox->setVisibility(brls::Visibility::INVISIBLE); 833 | } 834 | 835 | // hot key 836 | this->buttonProcessing(); 837 | 838 | // draw speed 839 | if (this->speedHintBox->getVisibility() == brls::Visibility::VISIBLE) { 840 | this->speedHintBox->frame(ctx); 841 | brls::Rect frame = speedHintLabel->getFrame(); 842 | 843 | // a1-3 周期 800,范围 800 * 0.3 / 2 = 120, 0 - 120 - 0 844 | int a1 = ((brls::getCPUTimeUsec() >> 10) % 800) * 0.3; 845 | int a2 = (a1 + 40) % 240; 846 | int a3 = (a1 + 80) % 240; 847 | 848 | if (a1 > 120) a1 = 240 - a1; 849 | if (a2 > 120) a2 = 240 - a2; 850 | if (a3 > 120) a3 = 240 - a3; 851 | 852 | float tx = frame.getMinX() - 64; 853 | float ty = frame.getMinY() + 5; 854 | std::vector> data = {{0, a3 + 80}, {20, a2 + 80}, {40, a1 + 80}}; 855 | 856 | for (auto& i : data) { 857 | nvgBeginPath(vg); 858 | nvgMoveTo(vg, tx + i.first, ty); 859 | nvgLineTo(vg, tx + i.first, ty + 16); 860 | nvgLineTo(vg, tx + i.first + 16, ty + 8); 861 | nvgFillColor(vg, a(nvgRGBA(255, 255, 255, i.second))); 862 | nvgClosePath(vg); 863 | nvgFill(vg); 864 | } 865 | } 866 | 867 | osdCenterBox2->frame(ctx); 868 | } 869 | 870 | VideoView *VideoView::create() { 871 | return new VideoView(); 872 | } -------------------------------------------------------------------------------- /thirdparty/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # borealis 2 | add_subdirectory(borealis/library) --------------------------------------------------------------------------------