├── .gitignore ├── .vscode └── tasks.json ├── LICENSE ├── README.md ├── chrome-ext ├── icon.png ├── icon_128.png ├── manifest.json └── run.js ├── dist ├── config │ └── config.xml └── packages │ ├── libvlc │ └── meta │ │ └── package.xml │ ├── twitch_player │ └── meta │ │ └── package.xml │ └── twitchd │ └── meta │ └── package.xml ├── player ├── .gitignore ├── .vscode │ ├── launch.json │ └── tasks.json ├── forms │ ├── about_dialog.ui │ ├── filters_tool.ui │ ├── main_window.ui │ ├── options_dialog.ui │ ├── stream_card.ui │ ├── stream_details.ui │ ├── stream_picker.ui │ ├── video_controls.ui │ └── vlc_log_viewer.ui ├── include │ ├── api │ │ ├── oauth.hpp │ │ ├── pubsub.hpp │ │ ├── twitch.hpp │ │ └── twitchd.hpp │ ├── constants.hpp │ ├── libvlc │ │ ├── bindings.hpp │ │ ├── event_watcher.hpp │ │ ├── logger.hpp │ │ └── types.hpp │ ├── prelude │ │ ├── c_wrapper.hpp │ │ ├── http.hpp │ │ ├── promise.hpp │ │ ├── sync.hpp │ │ ├── timer.hpp │ │ └── variant.hpp │ ├── process │ │ └── daemon_control.hpp │ └── ui │ │ ├── layouts │ │ ├── flow.hpp │ │ └── splitter_grid.hpp │ │ ├── main_window.hpp │ │ ├── native │ │ ├── capabilities.hpp │ │ ├── osx.hpp │ │ ├── win32.hpp │ │ └── x11.hpp │ │ ├── overlays │ │ ├── video_controls.hpp │ │ └── video_details.hpp │ │ ├── tools │ │ ├── about_dialog.hpp │ │ ├── options_dialog.hpp │ │ ├── video_filters.hpp │ │ └── vlc_log_viewer.hpp │ │ ├── tray.hpp │ │ ├── utils │ │ └── event_notifier.hpp │ │ └── widgets │ │ ├── chat_pane.hpp │ │ ├── foreign_widget.hpp │ │ ├── stream_card.hpp │ │ ├── stream_pane.hpp │ │ ├── stream_picker.hpp │ │ ├── stream_widget.hpp │ │ └── video_widget.hpp ├── player.pro ├── resources │ ├── browse.png │ ├── clock.svg │ ├── dashboard.png │ ├── fast_forward.png │ ├── fullscreen_enter.png │ ├── fullscreen_exit.png │ ├── icon.ico │ ├── joystick.svg │ ├── kappa.png │ ├── layout_left.png │ ├── layout_right.png │ ├── player.qrc │ ├── remove.png │ ├── unzoom.png │ ├── viewers.svg │ ├── volume_off.png │ ├── volume_on.png │ └── zoom.png ├── scripts │ └── win32 │ │ ├── build.bat │ │ └── qmake.bat ├── src │ ├── api │ │ ├── oauth.cpp │ │ ├── pubsub.cpp │ │ ├── twitch.cpp │ │ └── twitchd.cpp │ ├── libvlc │ │ ├── bindings.cpp │ │ ├── event_watcher.cpp │ │ └── logger.cpp │ ├── main.cpp │ ├── process │ │ └── daemon_control.cpp │ └── ui │ │ ├── layouts │ │ ├── flow.cpp │ │ └── splitter_grid.cpp │ │ ├── main_window.cpp │ │ ├── main_window_shortcuts.cpp │ │ ├── native │ │ ├── osx.cpp │ │ ├── win32.cpp │ │ └── x11.cpp │ │ ├── overlays │ │ ├── video_controls.cpp │ │ └── video_details.cpp │ │ ├── tools │ │ ├── about_dialog.cpp │ │ ├── options_dialog.cpp │ │ ├── video_filters.cpp │ │ └── vlc_log_viewer.cpp │ │ ├── tray.cpp │ │ ├── utils │ │ └── event_notifier.cpp │ │ └── widgets │ │ ├── chat_pane.cpp │ │ ├── foreign_widget.cpp │ │ ├── stream_card.cpp │ │ ├── stream_pane.cpp │ │ ├── stream_picker.cpp │ │ ├── stream_widget.cpp │ │ └── video_widget.cpp └── vendor │ └── qtpromise-0.3.0 │ ├── .gitignore │ ├── .travis.yml │ ├── LICENSE │ ├── README.md │ ├── book.json │ ├── docs │ ├── README.md │ ├── SUMMARY.md │ ├── assets │ │ └── style.css │ └── qtpromise │ │ ├── api-reference.md │ │ ├── getting-started.md │ │ ├── helpers │ │ ├── qpromise.md │ │ └── qpromiseall.md │ │ ├── qpromise │ │ ├── all.md │ │ ├── constructor.md │ │ ├── delay.md │ │ ├── fail.md │ │ ├── finally.md │ │ ├── isfulfilled.md │ │ ├── ispending.md │ │ ├── isrejected.md │ │ ├── reject.md │ │ ├── resolve.md │ │ ├── tap.md │ │ ├── then.md │ │ ├── timeout.md │ │ └── wait.md │ │ ├── qtconcurrent.md │ │ └── thread-safety.md │ ├── include │ └── QtPromise │ ├── package │ └── features │ │ └── qtpromise.prf │ ├── qpm.json │ ├── qtpromise.pri │ ├── qtpromise.pro │ ├── src │ ├── qtpromise │ │ ├── qpromise.h │ │ ├── qpromise.inl │ │ ├── qpromise_p.h │ │ ├── qpromiseerror.h │ │ ├── qpromisefuture.h │ │ ├── qpromiseglobal.h │ │ ├── qpromisehelpers.h │ │ ├── qtpromise.pri │ │ └── qtpromise.pro │ └── src.pro │ └── tests │ ├── auto │ ├── auto.pro │ └── qtpromise │ │ ├── benchmark │ │ ├── benchmark.pro │ │ └── tst_benchmark.cpp │ │ ├── future │ │ ├── future.pro │ │ └── tst_future.cpp │ │ ├── helpers │ │ ├── helpers.pro │ │ └── tst_helpers.cpp │ │ ├── qpromise │ │ ├── all │ │ │ ├── all.pro │ │ │ └── tst_all.cpp │ │ ├── construct │ │ │ ├── construct.pro │ │ │ └── tst_construct.cpp │ │ ├── delay │ │ │ ├── delay.pro │ │ │ └── tst_delay.cpp │ │ ├── fail │ │ │ ├── fail.pro │ │ │ └── tst_fail.cpp │ │ ├── finally │ │ │ ├── finally.pro │ │ │ └── tst_finally.cpp │ │ ├── operators │ │ │ ├── operators.pro │ │ │ └── tst_operators.cpp │ │ ├── qpromise.pro │ │ ├── resolve │ │ │ ├── resolve.pro │ │ │ └── tst_resolve.cpp │ │ ├── tap │ │ │ ├── tap.pro │ │ │ └── tst_tap.cpp │ │ ├── then │ │ │ ├── then.pro │ │ │ └── tst_then.cpp │ │ └── timeout │ │ │ ├── timeout.pro │ │ │ └── tst_timeout.cpp │ │ ├── qtpromise.pri │ │ ├── qtpromise.pro │ │ ├── requirements │ │ ├── requirements.pro │ │ └── tst_requirements.cpp │ │ ├── shared │ │ └── utils.h │ │ └── thread │ │ ├── thread.pro │ │ └── tst_thread.cpp │ └── tests.pro ├── scripts └── win32 │ └── bundle-dist.bat └── twitchd ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── Cargo.lock ├── Cargo.toml ├── scripts └── win32 │ └── release.bat ├── src ├── main.rs ├── options.rs ├── prelude │ ├── http.rs │ ├── mod.rs │ ├── runtime.rs │ └── stream_ext.rs ├── server │ ├── mod.rs │ ├── service.rs │ └── state │ │ ├── index_cache.rs │ │ ├── mod.rs │ │ ├── player_pool.rs │ │ └── stream_player.rs └── twitch │ ├── api.rs │ ├── m3u8.rs │ ├── mod.rs │ ├── mpeg_ts.rs │ ├── types.rs │ └── utils.rs └── test_samples ├── m3u8 ├── faulty_playlist_01_august_2019.m3u8 ├── faulty_playlist_11_october_2019.m3u8 ├── faulty_playlist_22_may_2019.m3u8 ├── index_with_restricted_playlist.m3u8 ├── index_without_program_id.m3u8 ├── playlist_with_map.m3u8 ├── playlist_with_prefetch_segments.m3u8 ├── playlist_with_program_date_time.m3u8 ├── playlist_with_stitched_ads.m3u8 ├── simple_index.m3u8 └── simple_playlist.m3u8 └── mpeg_ts ├── encoded_160p.ts ├── encoded_480p.ts ├── encoded_720p60.ts ├── source_1080p60.ts ├── source_faulty_21_feb_2020.ts └── source_faulty_22_may_2019.ts /.gitignore: -------------------------------------------------------------------------------- 1 | dist/packages/**/data/* 2 | dist/*.exe 3 | dist/twitch-player_* 4 | .vscode/settings.json 5 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "bundle dist", 8 | "type": "shell", 9 | "windows": { 10 | "command": "${workspaceFolder}/scripts/win32/bundle-dist.bat" 11 | }, 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Guillaume Depardon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # twitch-player 2 | performance focused twitch chat and video playback 3 | 4 | ## Motivation 5 | Video playback of [Twitch](https://twitch.tv) streams inside web browsers seems to use an unusally large mount of resources, especially cpu-wise. 6 | When you use native video players like [vlc](https://www.videolan.org/vlc/index.html) or [mpc](https://mpc-hc.org/), you can observe up to ~4 times less cpu usage. 7 | 8 | Projects like [livestreamer](https://github.com/chrippa/livestreamer/releases) or [streamlink](https://github.com/streamlink/streamlink-twitch-gui) already exist but they lack an essential feature: a playback layout that includes chat. 9 | This project aims at solving this specific issue by "simply" embedding a native player and a chat window inside a shared layout so that it looks like this: 10 | ![image](https://user-images.githubusercontent.com/2079561/42701761-bb731772-86c7-11e8-804a-13ec707cb3c4.png) 11 | 12 | You get basic resizing functionalities, with an adjustable splitter between the video player and the chat, some controls to enable fullscreen/toggle chat/... 13 | You also get multistream support and a bunch of cool playback features. 14 | 15 | ## Architecture 16 | To circumvent some playback seeking limitations of vlc for live hls playlists like Twitch's and to anticipate streaming format changes, this project also includes its own hls fetcher named `twitchd`. 17 | This component can live on its own and can be tweaked to fit your specs and internet connection perfectly. It can actually yield better "latency to broadcaster" than the twitch.tv player. It can also be used as a proxy and broadcast entrypoint. 18 | 19 | The graphical interface is written to target platforms natively. It relies on `libvlc` to display the video and on some instance of `twitchd` to get the video data. 20 | This project currently does not feature a custom chat implementation and therefore relies on the native platform to embed an external window - typically a web browser - that will render chat. 21 | 22 | ## Building 23 | 24 | ### twitchd 25 | this daemon aims at being lightweight and performant, and is therefore written in rust. 26 | 27 | With a stable [rust toolchain](https://www.rustup.rs/), you can just run: 28 | ```sh 29 | cargo build --release 30 | ``` 31 | inside the `./twitchd` directory and that should be enough to build a native executable for this component. 32 | 33 | ### UI 34 | 35 | The UI aims at targetting all platforms. [Electron does not support embedding of foreign windows](https://github.com/electron/electron/issues/10547) in its current state and I couldn't find any other web based toolkit that fit my needs. I ended up picking Qt and the language of choice for that framework is C++ 36 | 37 | With a recent version of [Qt](https://www.qt.io/download) (>= 5.10), a recent version of libVLC (> 3.0.0) and a C++17 compliant compiler you should be able to build the UI by running: 38 | ```sh 39 | export LIBVLC_INCLUDE_DIR="path/to/your/libvlc/include" 40 | export LIBVLC_LIB_DIR="path/to/your/libvlc/lib" 41 | mkdir target && cd target 42 | qmake .. 43 | make release # or nmake on Windows 44 | ``` 45 | inside the `./player` directory. 46 | This has been tested on Windows with Visual Studio 2017 and also on linux with clang 6.0 47 | -------------------------------------------------------------------------------- /chrome-ext/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Globidev/twitch-player/062306685e182c422980d57fe7ba9fbdee141c37/chrome-ext/icon.png -------------------------------------------------------------------------------- /chrome-ext/icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Globidev/twitch-player/062306685e182c422980d57fe7ba9fbdee141c37/chrome-ext/icon_128.png -------------------------------------------------------------------------------- /chrome-ext/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | 4 | "name": "Open in Twitch Player", 5 | "description": "Runs Twitch web streams in Twitch Player", 6 | "version": "0.1", 7 | 8 | "permissions": [ 9 | "activeTab" 10 | ], 11 | "browser_action": { 12 | "default_icon": "icon.png" 13 | }, 14 | "background": { 15 | "scripts": ["run.js"], 16 | "persistent": false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /chrome-ext/run.js: -------------------------------------------------------------------------------- 1 | chrome.browserAction.onClicked.addListener(function(tab) { 2 | const url = new URL(tab.url), 3 | channel = url.pathname.slice(1); 4 | 5 | if (url.host == 'www.twitch.tv' && !channel.includes('/')) { 6 | chrome.tabs.executeScript({ 7 | code: `window.location="twitch-player://${channel}"` 8 | }); 9 | setTimeout(() => chrome.tabs.remove(tab.id, () => {}), 3000) 10 | } 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /dist/config/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Twitch Player 4 | 0.1.0 5 | Twitch-Player Installer 6 | Globi 7 | Twitch Player 8 | @ApplicationsDir@/TwitchPlayer 9 | 10 | 11 | http://glo.bi/twitch-player/ 12 | 1 13 | Globi repository 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /dist/packages/libvlc/meta/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | libvlc 4 | Install the libvlc dependencies (RECOMMENDED) 5 | 3.0.6 6 | 2019-02-02 7 | true 8 | Bump libvlc version 9 | 10 | -------------------------------------------------------------------------------- /dist/packages/twitch_player/meta/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Twitch Player 4 | Install the end user interface 5 | 0.9.28 6 | 2019-10-11 7 | true 8 | true 9 | Bump daemon version 10 | 11 | -------------------------------------------------------------------------------- /dist/packages/twitchd/meta/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Twitchd 4 | Install the Twitch API daemon 5 | 1.4.8 6 | 2019-10-11 7 | true 8 | true 9 | Handled new playlist format changes 10 | 11 | -------------------------------------------------------------------------------- /player/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .vscode/c_cpp_properties.json 3 | .vscode/ipch 4 | -------------------------------------------------------------------------------- /player/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "name": "(Windows) Launch", 10 | "type": "cppvsdbg", 11 | "request": "launch", 12 | "program": "${workspaceFolder}/target/debug/twitch-player.exe", 13 | "args": [ 14 | // "giantwaffle" 15 | ], 16 | "stopAtEntry": false, 17 | "cwd": "${workspaceFolder}", 18 | "environment": [], 19 | "externalConsole": true, 20 | "preLaunchTask": "dev: compile (debug)" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /player/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "dev: qmake", 8 | "group": "build", 9 | "type": "shell", 10 | "windows": { 11 | "command": "${workspaceFolder}/scripts/win32/qmake.bat" 12 | } 13 | }, 14 | { 15 | "label": "dev: compile (debug)", 16 | "group": { 17 | "kind": "build", 18 | "isDefault": true 19 | }, 20 | "type": "shell", 21 | "windows": { 22 | "command": "${workspaceFolder}/scripts/win32/build.bat", 23 | "args": ["debug"] 24 | }, 25 | }, 26 | { 27 | "label": "deploy: qmake", 28 | "group": "build", 29 | "type": "shell", 30 | "windows": { 31 | "command": "${workspaceFolder}/scripts/win32/qmake.bat", 32 | "args": ["deploy"] 33 | } 34 | }, 35 | { 36 | "label": "deploy: compile (release)", 37 | "group": "build", 38 | "type": "shell", 39 | "windows": { 40 | "command": "${workspaceFolder}/scripts/win32/build.bat", 41 | "args": ["release"] 42 | }, 43 | }, 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /player/forms/about_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | AboutDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 239 10 | 42 11 | 12 | 13 | 14 | About Twitch Player 15 | 16 | 17 | 18 | 19 | 20 | Qt::Horizontal 21 | 22 | 23 | 24 | 40 25 | 20 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 16 35 | 16 36 | 37 | 38 | 39 | 40 | 41 | 42 | :/icons/icon.ico 43 | 44 | 45 | false 46 | 47 | 48 | 49 | 50 | 51 | 52 | Twitch Player version 53 | 54 | 55 | 56 | 57 | 58 | 59 | Qt::Horizontal 60 | 61 | 62 | 63 | 40 64 | 20 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /player/include/api/oauth.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class QTcpServer; 7 | class QNetworkRequest; 8 | 9 | using QtPromise::QPromise; 10 | 11 | class OAuth: public QObject { 12 | Q_OBJECT 13 | 14 | public: 15 | OAuth(QObject * = nullptr); 16 | 17 | QPromise query_token(); 18 | 19 | signals: 20 | void token_ready(QString); 21 | 22 | private: 23 | QTcpServer *_local_server; 24 | 25 | void fetch_token(const QUrl &); 26 | void save_token_data(const QByteArray &); 27 | }; 28 | -------------------------------------------------------------------------------- /player/include/api/pubsub.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | class QWebSocket; 8 | class QTimer; 9 | 10 | struct PendingOrder { 11 | QtPromise::QPromiseResolve resolve; 12 | QtPromise::QPromiseReject reject; 13 | }; 14 | 15 | class TwitchPubSub: public QObject { 16 | Q_OBJECT 17 | 18 | public: 19 | TwitchPubSub(QObject * = nullptr); 20 | 21 | QtPromise::QPromise listen_to_channel(QString); 22 | void unlisten_to_channel(QString); 23 | 24 | signals: 25 | void channel_went_live(QString); 26 | void channel_went_offline(QString); 27 | 28 | private: 29 | void send_message(QJsonObject); 30 | void process_message(QJsonObject); 31 | 32 | QWebSocket *_ws; 33 | QTimer *_ping_timer; 34 | 35 | QMap _pending_orders; 36 | 37 | QList _order_queue; 38 | QList _orders_issued; 39 | }; 40 | -------------------------------------------------------------------------------- /player/include/api/twitch.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "prelude/http.hpp" 4 | 5 | struct ChannelData { 6 | QString name, display_name; 7 | QString title; 8 | QString logo_url; 9 | uint32_t id; 10 | }; 11 | 12 | struct StreamData { 13 | ChannelData channel; 14 | QString preview; 15 | QString current_game; 16 | uint32_t viewcount; 17 | QDateTime created_at; 18 | }; 19 | 20 | struct TwitchAPI: APIClient { 21 | using streams_response_t = response_t>; 22 | 23 | streams_response_t stream_search(QString); 24 | streams_response_t top_streams(); 25 | streams_response_t followed_streams(const QString &); 26 | 27 | using stream_response_t = response_t; 28 | stream_response_t stream(uint32_t); 29 | 30 | using channels_response_t = response_t>; 31 | channels_response_t channel_search(const QString &); 32 | }; 33 | -------------------------------------------------------------------------------- /player/include/api/twitchd.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "prelude/http.hpp" 4 | 5 | struct Resolution { 6 | uint32_t width, height; 7 | }; 8 | 9 | struct StreamInfo { 10 | Resolution resolution; 11 | uint64_t bandwidth; 12 | }; 13 | 14 | struct MediaInfo { 15 | QString name; 16 | QString group_id; 17 | }; 18 | 19 | struct PlaylistInfo { 20 | StreamInfo stream_info; 21 | MediaInfo media_info; 22 | QString url; 23 | }; 24 | 25 | struct StreamIndex { 26 | QList playlist_infos; 27 | }; 28 | 29 | struct SegmentMetadata { 30 | quint32 broadc_s; 31 | QString cmd; 32 | quint32 ingest_r, ingest_s; 33 | double stream_offset; 34 | quint64 transc_r, transc_s; 35 | }; 36 | 37 | struct TwitchdAPI: APIClient { 38 | using stream_index_response_t = response_t; 39 | stream_index_response_t stream_index(QString); 40 | 41 | using metadata_response_t = response_t; 42 | metadata_response_t metadata(QString, QString, QString); 43 | 44 | using daemon_version_response_t = response_t; 45 | daemon_version_response_t daemon_version(); 46 | 47 | using daemon_quit_response_t = response_t; 48 | daemon_quit_response_t daemon_quit(); 49 | 50 | static QString playback_url(QString, QString, QString); 51 | }; 52 | -------------------------------------------------------------------------------- /player/include/libvlc/bindings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "libvlc/types.hpp" 4 | 5 | #include "prelude/c_wrapper.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace libvlc { 12 | 13 | struct Instance: CWrapper { 14 | Instance(std::vector); 15 | 16 | using log_cb_t = std::function; 17 | 18 | void set_log_callback(log_cb_t); 19 | void unset_log_callback(); 20 | 21 | private: 22 | log_cb_t _cb; 23 | }; 24 | 25 | struct Media: CWrapper { 26 | Media(Instance &, const char *); 27 | }; 28 | 29 | struct Equalizer: CWrapper { 30 | Equalizer(); 31 | }; 32 | 33 | struct MediaPlayer: CWrapper { 34 | MediaPlayer(Instance &); 35 | 36 | void set_media(Media &); 37 | void set_renderer(void *); 38 | void play(); 39 | void stop(); 40 | void set_volume(int); 41 | void set_position(float); 42 | 43 | bool video_filters_enabled(); 44 | void enable_video_filters(bool); 45 | 46 | float get_contrast(); 47 | void set_contrast(float); 48 | float get_brightness(); 49 | void set_brightness(float); 50 | float get_hue(); 51 | void set_hue(float); 52 | float get_saturation(); 53 | void set_saturation(float); 54 | float get_gamma(); 55 | void set_gamma(float); 56 | 57 | std::vector audio_devices(); 58 | std::string get_current_device_id(); 59 | void set_audio_device(std::string); 60 | 61 | using event_cb_t = std::function; 62 | 63 | void set_event_callback(event_cb_t); 64 | 65 | private: 66 | Equalizer _equalizer; 67 | event_cb_t _cb; 68 | 69 | // For some reason, `libvlc_video_get_adjust` does not work for 70 | // libvlc_adjust_Enable. 71 | // Keeping track of the enabling manually... 72 | bool _adjust_enabled = false; 73 | }; 74 | 75 | } 76 | -------------------------------------------------------------------------------- /player/include/libvlc/event_watcher.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "libvlc/types.hpp" 6 | 7 | #include "prelude/sync.hpp" 8 | 9 | class VLCEventWatcher: public QObject { 10 | Q_OBJECT 11 | 12 | public: 13 | VLCEventWatcher(libvlc::MediaPlayer &, QObject * = nullptr); 14 | 15 | signals: 16 | void new_event(libvlc::Event); 17 | 18 | private: 19 | using EventQueue = sync::Queue; 20 | EventQueue _queue; 21 | }; 22 | -------------------------------------------------------------------------------- /player/include/libvlc/logger.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "libvlc/types.hpp" 6 | 7 | #include "prelude/sync.hpp" 8 | 9 | class VLCLogger: public QObject { 10 | Q_OBJECT 11 | 12 | public: 13 | VLCLogger(libvlc::Instance &, QObject * = nullptr); 14 | ~VLCLogger(); 15 | 16 | signals: 17 | void new_log_entry(libvlc::LogEntry); 18 | 19 | private: 20 | libvlc::Instance &_video_context; 21 | 22 | using LogEntryQueue = sync::Queue; 23 | LogEntryQueue _queue; 24 | }; 25 | -------------------------------------------------------------------------------- /player/include/libvlc/types.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | // LibVLC uses ssize_t, which is POSIX... 7 | #if defined(_MSC_VER) 8 | #include 9 | using ssize_t = SSIZE_T; 10 | #endif 11 | 12 | // libvlc Forwards 13 | struct libvlc_instance_t; 14 | struct libvlc_media_player_t; 15 | struct libvlc_media_t; 16 | struct libvlc_equalizer_t; 17 | struct libvlc_event_manager_t; 18 | struct vlc_log_t; 19 | // 20 | 21 | namespace libvlc { 22 | 23 | // Bindings forwards 24 | struct Instance; 25 | struct Media; 26 | struct Equalizer; 27 | struct MediaPlayer; 28 | // 29 | 30 | namespace events { 31 | struct Opening { }; 32 | struct Playing { }; 33 | struct TimeChanged { int64_t new_time; }; 34 | struct Buffering { float cache_percent; }; 35 | struct Stopped { }; 36 | struct EndReached { }; 37 | struct EncounteredError { }; 38 | struct Unknown { }; 39 | } 40 | 41 | using Event = std::variant< 42 | events::Opening, 43 | events::Playing, 44 | events::TimeChanged, 45 | events::Buffering, 46 | events::Stopped, 47 | events::EndReached, 48 | events::EncounteredError, 49 | events::Unknown 50 | >; 51 | 52 | enum class LogLevel { 53 | Debug, 54 | Notice, 55 | Warning, 56 | Error, 57 | Unknown 58 | }; 59 | 60 | struct LogEntry { 61 | LogLevel level; 62 | std::string text; 63 | }; 64 | 65 | struct AudioDevice { 66 | std::string id; 67 | std::string description; 68 | }; 69 | 70 | } 71 | -------------------------------------------------------------------------------- /player/include/prelude/c_wrapper.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | template 6 | using CDeleter = void (*)(T*); 7 | 8 | template 9 | using CResource = std::unique_ptr>; 10 | 11 | template 12 | struct CWrapper { 13 | CWrapper(T* resource_ptr, CDeleter deleter): 14 | resource(resource_ptr, deleter) 15 | { } 16 | 17 | auto operator &() const { return resource.get(); } 18 | bool init_success() const { return static_cast(resource); } 19 | 20 | private: 21 | using resource_t = CResource; 22 | 23 | resource_t resource; 24 | }; 25 | -------------------------------------------------------------------------------- /player/include/prelude/http.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | class APIClient { 9 | private: 10 | QNetworkAccessManager _http_client; 11 | 12 | auto send_request(const QNetworkRequest &request, const QByteArray &verb) { 13 | using QtPromise::QPromise; 14 | 15 | return QPromise([=](auto& resolve, auto& reject) { 16 | auto reply = _http_client.sendCustomRequest(request, verb); 17 | 18 | QObject::connect(reply, &QNetworkReply::finished, [=]() { 19 | auto error = reply->error(); 20 | 21 | if (error == QNetworkReply::NoError) 22 | resolve(reply->readAll()); 23 | else 24 | reject(error); 25 | 26 | reply->deleteLater(); 27 | }); 28 | 29 | }); 30 | } 31 | 32 | public: 33 | auto get(const QNetworkRequest &request) { 34 | return send_request(request, "GET"); 35 | } 36 | 37 | auto post(const QNetworkRequest &request) { 38 | return send_request(request, "POST"); 39 | } 40 | 41 | protected: 42 | template 43 | using response_t = QtPromise::QPromise; 44 | }; 45 | -------------------------------------------------------------------------------- /player/include/prelude/promise.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | struct Cancellable { 9 | void cancel() { 10 | _cancelled = true; 11 | } 12 | 13 | bool cancelled() const { 14 | return _cancelled; 15 | } 16 | 17 | private: 18 | bool _cancelled = false; 19 | }; 20 | 21 | struct CancelError: std::future_error { 22 | CancelError(): std::future_error { std::future_errc::broken_promise } { } 23 | }; 24 | 25 | template 26 | auto make_cancellable(QtPromise::QPromise promise) { 27 | auto cancel_token = std::make_shared(); 28 | 29 | auto tapped_promise = promise.tap([=](auto...) { 30 | if (cancel_token->cancelled()) 31 | throw CancelError { }; 32 | }); 33 | 34 | return std::make_pair(cancel_token, tapped_promise); 35 | } 36 | 37 | using CancelToken = std::shared_ptr; 38 | -------------------------------------------------------------------------------- /player/include/prelude/sync.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace sync { 8 | 9 | template 10 | struct Queue { 11 | using Item = T; 12 | 13 | void push(Item && item) { 14 | lock_t lock { _mutex }; 15 | 16 | _queue.push(std::move(item)); 17 | } 18 | 19 | std::optional try_pop() { 20 | lock_t lock { _mutex }; 21 | 22 | if (_queue.empty()) 23 | return std::nullopt; 24 | 25 | auto item = std::move(_queue.front()); 26 | _queue.pop(); 27 | 28 | return std::move(item); 29 | } 30 | 31 | private: 32 | using lock_t = std::unique_lock; 33 | 34 | std::mutex _mutex; 35 | std::queue _queue; 36 | }; 37 | 38 | } 39 | -------------------------------------------------------------------------------- /player/include/prelude/timer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | template 6 | void interval(QObject *parent, int ms, Slot && slot) { 7 | auto timer = new QTimer(parent); 8 | QObject::connect(timer, &QTimer::timeout, std::forward(slot)); 9 | timer->start(ms); 10 | } 11 | 12 | template 13 | void delayed(QObject *parent, int ms, Slot && slot) { 14 | auto timer = new QTimer(parent); 15 | QObject::connect(timer, &QTimer::timeout, std::forward(slot)); 16 | timer->setSingleShot(true); 17 | timer->start(ms); 18 | } 19 | -------------------------------------------------------------------------------- /player/include/prelude/variant.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | template struct Overloaded: Ts... { using Ts::operator()...; }; 6 | template Overloaded(Ts...) -> Overloaded; 7 | 8 | template 9 | constexpr auto match(Variant && variant, Cases &&... cases) { 10 | return std::visit( 11 | Overloaded { std::forward(cases)... }, 12 | std::forward(variant) 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /player/include/process/daemon_control.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace daemon_control { 6 | struct Status { 7 | bool running = false; 8 | bool managed = true; 9 | QString version; 10 | }; 11 | 12 | bool start(); 13 | bool stop(); 14 | 15 | Status status(); 16 | }; 17 | -------------------------------------------------------------------------------- /player/include/ui/layouts/flow.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Adapted from 4 | // http://doc.qt.io/qt-5/qtwidgets-layouts-flowlayout-flowlayout-h.html 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | class FlowLayout: public QLayout { 11 | public: 12 | explicit FlowLayout(QWidget * = nullptr); 13 | ~FlowLayout(); 14 | 15 | void addItem(QLayoutItem *) override; 16 | int horizontalSpacing() const; 17 | int verticalSpacing() const; 18 | Qt::Orientations expandingDirections() const override; 19 | bool hasHeightForWidth() const override; 20 | int heightForWidth(int) const override; 21 | int count() const override; 22 | QLayoutItem *itemAt(int) const override; 23 | QSize minimumSize() const override; 24 | void setGeometry(const QRect &) override; 25 | QSize sizeHint() const override; 26 | QLayoutItem *takeAt(int) override; 27 | 28 | private: 29 | int doLayout(const QRect &, bool) const; 30 | int smartSpacing(QStyle::PixelMetric) const; 31 | 32 | QList itemList; 33 | }; 34 | -------------------------------------------------------------------------------- /player/include/ui/layouts/splitter_grid.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class QSplitter; 7 | class QHBoxLayout; 8 | 9 | struct Position { 10 | int row, column; 11 | 12 | Position left(Qt::Orientation orientation = Qt::Vertical) { 13 | return orientation == Qt::Vertical 14 | ? Position { row, column - 1 } 15 | : up(); 16 | } 17 | Position right(Qt::Orientation orientation = Qt::Vertical) { 18 | return orientation == Qt::Vertical 19 | ? Position { row, column + 1 } 20 | : down(); 21 | } 22 | Position up(Qt::Orientation orientation = Qt::Vertical) { 23 | return orientation == Qt::Vertical 24 | ? Position { row - 1, column } 25 | : left(); 26 | } 27 | Position down(Qt::Orientation orientation = Qt::Vertical) { 28 | return orientation == Qt::Vertical 29 | ? Position { row + 1, column } 30 | : right(); 31 | } 32 | }; 33 | 34 | class SplitterGrid: public QWidget { 35 | public: 36 | SplitterGrid(QWidget * = nullptr); 37 | 38 | void insert_widget(Position, QWidget *); 39 | void remove_widget(QWidget *); 40 | Position widget_position(QWidget *); 41 | QWidget * closest_widget(Position); 42 | void swap(Position, Position); 43 | void rotate(); 44 | Qt::Orientation orientation() const; 45 | 46 | private: 47 | Qt::Orientation _main_orientation = Qt::Vertical; 48 | 49 | QHBoxLayout *_layout; 50 | QSplitter *_main_splitter; 51 | std::vector _inner_splitters; 52 | 53 | QSplitter * get_inner(int); 54 | }; 55 | -------------------------------------------------------------------------------- /player/include/ui/main_window.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "ui/layouts/splitter_grid.hpp" 13 | 14 | namespace Ui { 15 | class MainWindow; 16 | } 17 | 18 | namespace libvlc { 19 | struct Instance; 20 | } 21 | 22 | class StreamPane; 23 | class ChatPane; 24 | class VLCLogViewer; 25 | class QStackedWidget; 26 | class QShortcut; 27 | 28 | class TwitchPubSub; 29 | 30 | using MPane = std::variant< 31 | StreamPane *, 32 | ChatPane * 33 | >; 34 | 35 | class MainWindow : public QMainWindow { 36 | public: 37 | MainWindow(libvlc::Instance &, TwitchPubSub &, QWidget * = nullptr); 38 | ~MainWindow(); 39 | 40 | StreamPane *add_stream_pane(Position); 41 | ChatPane *add_chat_pane(Position); 42 | void remove_pane(MPane); 43 | 44 | protected: 45 | void changeEvent(QEvent *) override; 46 | void keyPressEvent(QKeyEvent *) override; 47 | 48 | void closeEvent(QCloseEvent *) override; 49 | 50 | private: 51 | std::unique_ptr _ui; 52 | 53 | libvlc::Instance &_video_context; 54 | TwitchPubSub &_pubsub; 55 | 56 | std::unique_ptr _vlc_log_viewer; 57 | 58 | std::vector _panes; 59 | SplitterGrid *_grid; 60 | QStackedWidget *_central_widget; 61 | 62 | Position _zoomed_position; 63 | 64 | std::optional focused_pane(); 65 | 66 | void move_focus(Position); 67 | void move_pane(Position, Position); 68 | 69 | void set_fullscreen(bool); 70 | bool is_zoomed(); 71 | void zoom(StreamPane *); 72 | void unzoom(); 73 | 74 | std::vector>> _shortcuts; 75 | 76 | void setup_shortcuts(); 77 | void remap_shortcuts(); 78 | 79 | QMenu *_audio_devices_menu; 80 | void setup_audio_devices(); 81 | 82 | QAction *_action_stream_zoom; 83 | QAction *_action_fullscreen; 84 | }; 85 | -------------------------------------------------------------------------------- /player/include/ui/native/capabilities.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef _WIN32 4 | #include "ui/native/win32.hpp" 5 | #elif __APPLE__ 6 | #include "ui/native/osx.hpp" 7 | #elif __linux__ 8 | #include "ui/native/x11.hpp" 9 | #else 10 | static_assert(false, "Unsupported target"); 11 | #endif 12 | 13 | template 14 | static constexpr auto reify_handle(From from) { 15 | constexpr auto from_is_pointer = std::is_pointer_v, 16 | to_is_pointer = std::is_pointer_v; 17 | 18 | if constexpr (from_is_pointer != to_is_pointer) 19 | return reinterpret_cast(from); 20 | else 21 | return static_cast(from); 22 | } 23 | 24 | static constexpr auto to_native_handle(uintptr_t qt_handle) { 25 | return reify_handle(qt_handle); 26 | } 27 | 28 | static constexpr auto from_native_handle(WindowHandle native_handle) { 29 | return reify_handle(native_handle); 30 | } -------------------------------------------------------------------------------- /player/include/ui/native/osx.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | using WindowHandle = void *; 7 | using FindWindowResult = std::set; 8 | 9 | FindWindowResult find_windows_by_title_and_pname(std::string, std::string); 10 | 11 | void toggle_window_borders(WindowHandle, bool); 12 | 13 | void toggle_always_on_top(WindowHandle, bool); 14 | 15 | void sysclose_window(WindowHandle); 16 | 17 | void redraw(WindowHandle); 18 | 19 | void set_transparent(WindowHandle); 20 | -------------------------------------------------------------------------------- /player/include/ui/native/win32.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | struct HWND__; 8 | 9 | using WindowHandle = HWND__ *; 10 | using FindWindowResult = std::set; 11 | 12 | FindWindowResult find_windows_by_title_and_pname(std::regex, std::string); 13 | 14 | void toggle_window_borders(WindowHandle, bool); 15 | void toggle_always_on_top(WindowHandle, bool); 16 | 17 | void sysclose_window(WindowHandle); 18 | 19 | void redraw(WindowHandle); 20 | 21 | void set_transparent(WindowHandle); 22 | -------------------------------------------------------------------------------- /player/include/ui/native/x11.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | using WindowHandle = unsigned long; 7 | using FindWindowResult = std::set; 8 | 9 | FindWindowResult find_windows_by_title_and_pname(std::string, std::string); 10 | 11 | void toggle_window_borders(WindowHandle, bool); 12 | 13 | void toggle_always_on_top(WindowHandle, bool); 14 | 15 | void sysclose_window(WindowHandle); 16 | 17 | void redraw(WindowHandle); 18 | 19 | void set_transparent(WindowHandle); 20 | -------------------------------------------------------------------------------- /player/include/ui/overlays/video_controls.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace Ui { 8 | class VideoControls; 9 | } 10 | 11 | class QTimer; 12 | 13 | class VideoControls: public QWidget { 14 | Q_OBJECT 15 | 16 | public: 17 | VideoControls(QWidget * = nullptr); 18 | ~VideoControls(); 19 | 20 | void set_volume(int); 21 | void set_muted(bool); 22 | 23 | void set_zoomed(bool); 24 | void set_fullscreen(bool); 25 | 26 | void appear(); 27 | 28 | void clear_qualities(); 29 | void set_qualities(QString, QStringList); 30 | 31 | void set_delay(float); 32 | 33 | protected: 34 | void mousePressEvent(QMouseEvent *) override; 35 | void paintEvent(QPaintEvent *) override; 36 | 37 | signals: 38 | void fast_forward(); 39 | void muted_changed(bool); 40 | void volume_changed(int); 41 | void quality_changed(QString); 42 | 43 | void layout_left_requested(); 44 | void layout_right_requested(); 45 | void zoom_requested(bool); 46 | void fullscreen_requested(bool); 47 | 48 | void browse_requested(); 49 | void remove_requested(); 50 | 51 | private: 52 | void set_volume_icon(); 53 | void set_zoomed_icon(); 54 | void set_fullscreen_icon(); 55 | 56 | std::unique_ptr _ui; 57 | 58 | QTimer *_appearTimer; 59 | 60 | bool _muted = false; 61 | bool _fullscreen = false; 62 | bool _zoomed = false; 63 | }; 64 | -------------------------------------------------------------------------------- /player/include/ui/overlays/video_details.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "api/twitch.hpp" 9 | 10 | namespace Ui { 11 | class StreamDetails; 12 | } 13 | 14 | class VideoDetails: public QWidget { 15 | public: 16 | VideoDetails(QWidget * = nullptr); 17 | ~VideoDetails(); 18 | 19 | void show_state(const QString &); 20 | void set_buffering(bool); 21 | void show_stream_details(); 22 | 23 | void set_channel(const QString &); 24 | 25 | void hide_stream_details(); 26 | 27 | protected: 28 | void paintEvent(QPaintEvent *) override; 29 | void mouseReleaseEvent(QMouseEvent *) override; 30 | 31 | private: 32 | QString _state_text; 33 | QTimer *_state_timer; 34 | QFont _state_text_font; 35 | bool _show_state_text = false; 36 | 37 | QImage _spinner; 38 | QTimer *_spinner_timer; 39 | int _spinner_angle = 0; 40 | bool _show_spinner = false; 41 | 42 | std::unique_ptr _stream_details_widget; 43 | std::unique_ptr _stream_details_ui; 44 | QString _channel; 45 | QTimer *_stream_details_timer; 46 | bool _show_stream_details = false; 47 | bool _has_valid_stream_details = false; 48 | 49 | QNetworkAccessManager *_http_client; 50 | 51 | void draw_state_text(); 52 | void draw_spinner(); 53 | void draw_stream_details(); 54 | 55 | void fetch_channel_details(); 56 | void fetch_channel_logo(const QString &); 57 | 58 | TwitchAPI _api; 59 | }; 60 | -------------------------------------------------------------------------------- /player/include/ui/tools/about_dialog.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace Ui { 8 | class AboutDialog; 9 | } 10 | 11 | class AboutDialog: public QDialog { 12 | public: 13 | AboutDialog(QWidget * = nullptr); 14 | ~AboutDialog(); 15 | 16 | private: 17 | std::unique_ptr _ui; 18 | }; 19 | -------------------------------------------------------------------------------- /player/include/ui/tools/options_dialog.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace Ui { 10 | class OptionsDialog; 11 | } 12 | 13 | class QKeySequenceEdit; 14 | 15 | class TwitchPubSub; 16 | 17 | class OptionsDialog: public QDialog { 18 | Q_OBJECT 19 | 20 | public: 21 | OptionsDialog(TwitchPubSub &, QWidget * = nullptr); 22 | ~OptionsDialog(); 23 | 24 | signals: 25 | void settings_changed(); 26 | 27 | private: 28 | void load_settings(); 29 | void save_settings(); 30 | 31 | void update_daemon(QString, std::function); 32 | 33 | TwitchPubSub &_pubsub; 34 | 35 | std::unique_ptr _ui; 36 | std::vector> _keybind_edits; 37 | }; 38 | -------------------------------------------------------------------------------- /player/include/ui/tools/video_filters.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace Ui { 8 | class VideoFilters; 9 | }; 10 | 11 | namespace libvlc { 12 | struct MediaPlayer; 13 | } 14 | 15 | class VideoFilters: public QWidget { 16 | public: 17 | VideoFilters(libvlc::MediaPlayer &, QWidget * = nullptr); 18 | 19 | private: 20 | std::unique_ptr _ui; 21 | }; 22 | -------------------------------------------------------------------------------- /player/include/ui/tools/vlc_log_viewer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "libvlc/bindings.hpp" 13 | 14 | namespace Ui { 15 | class VLCLogViewer; 16 | } 17 | 18 | class VLCLogger; 19 | 20 | class VLCLogItemModel: public QAbstractItemModel { 21 | public: 22 | VLCLogItemModel(QObject * = nullptr); 23 | 24 | QModelIndex index(int, int, const QModelIndex & = QModelIndex()) const override; 25 | QModelIndex parent(const QModelIndex &) const override; 26 | int rowCount(const QModelIndex & = QModelIndex()) const override; 27 | int columnCount(const QModelIndex & = QModelIndex()) const override; 28 | QVariant data(const QModelIndex &, int = Qt::DisplayRole) const override; 29 | 30 | void add_log_entry(libvlc::LogEntry); 31 | 32 | struct LogEntry { 33 | QDateTime stamp; 34 | libvlc::LogEntry data; 35 | }; 36 | std::vector entries; 37 | }; 38 | 39 | class VLCLogItemFilter: public QSortFilterProxyModel { 40 | public: 41 | VLCLogItemFilter(VLCLogItemModel &, QObject * = nullptr); 42 | 43 | bool filterAcceptsRow(int, const QModelIndex &) const override; 44 | 45 | void toggle_level(libvlc::LogLevel, bool); 46 | private: 47 | std::set _filtered_levels; 48 | VLCLogItemModel &_model; 49 | }; 50 | 51 | class VLCLogViewer: public QWidget { 52 | public: 53 | VLCLogViewer(libvlc::Instance &, QWidget * = nullptr); 54 | ~VLCLogViewer(); 55 | 56 | private: 57 | std::unique_ptr _ui; 58 | 59 | VLCLogItemModel *_item_model; 60 | VLCLogItemFilter *_filter_proxy_model; 61 | VLCLogger *_logger; 62 | std::mutex _log_mutex; 63 | }; 64 | -------------------------------------------------------------------------------- /player/include/ui/tray.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | class TwitchPubSub; 9 | 10 | class SystemTray: public QSystemTrayIcon { 11 | Q_OBJECT 12 | 13 | public: 14 | SystemTray(TwitchPubSub &); 15 | 16 | signals: 17 | void channel_open_requested(QString); 18 | 19 | private: 20 | TwitchPubSub &_pubsub; 21 | 22 | std::optional channel_to_play; 23 | 24 | QMenu _menu; 25 | }; 26 | -------------------------------------------------------------------------------- /player/include/ui/utils/event_notifier.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | class EventNotifier: public QObject { 9 | Q_OBJECT 10 | 11 | public: 12 | EventNotifier(std::initializer_list, QObject * = nullptr); 13 | 14 | signals: 15 | void new_event(const QEvent &); 16 | 17 | protected: 18 | bool eventFilter(QObject *, QEvent *) override; 19 | 20 | private: 21 | std::unordered_set _monitored_events; 22 | }; 23 | -------------------------------------------------------------------------------- /player/include/ui/widgets/chat_pane.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | class QHBoxLayout; 8 | class ForeignWidget; 9 | 10 | class ChatPane: public QWidget { 11 | Q_OBJECT 12 | 13 | public: 14 | ChatPane(QWidget * = nullptr); 15 | ~ChatPane(); 16 | 17 | private: 18 | QHBoxLayout *_layout; 19 | ForeignWidget *_chat; 20 | }; 21 | -------------------------------------------------------------------------------- /player/include/ui/widgets/foreign_widget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "ui/native/capabilities.hpp" 8 | 9 | class QHBoxLayout; 10 | 11 | class ForeignWidget: public QWidget { 12 | public: 13 | ForeignWidget(QWidget * = nullptr); 14 | ~ForeignWidget(); 15 | 16 | void grab(WindowHandle); 17 | void redraw(); 18 | 19 | private: 20 | std::optional _foreign_win_ptr; 21 | std::optional _container; 22 | QHBoxLayout *_layout; 23 | 24 | void release_window(); 25 | }; 26 | -------------------------------------------------------------------------------- /player/include/ui/widgets/stream_card.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "api/twitch.hpp" 6 | 7 | namespace Ui { 8 | class StreamCard; 9 | } 10 | 11 | class QHBoxLayout; 12 | class QLabel; 13 | 14 | class StreamCard: public QWidget { 15 | Q_OBJECT 16 | 17 | public: 18 | StreamCard(StreamData, QWidget * = nullptr); 19 | ~StreamCard(); 20 | 21 | protected: 22 | void mousePressEvent(QMouseEvent *) override; 23 | 24 | signals: 25 | void clicked(QString); 26 | 27 | private: 28 | std::unique_ptr _ui; 29 | 30 | QWidget *_uptime_widget; 31 | QHBoxLayout *_uptime_layout; 32 | QLabel *_uptime_logo; 33 | QLabel *_uptime_label; 34 | 35 | StreamData _data; 36 | }; 37 | -------------------------------------------------------------------------------- /player/include/ui/widgets/stream_pane.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | class QHBoxLayout; 8 | class StreamPicker; 9 | class StreamWidget; 10 | 11 | namespace libvlc { 12 | struct Instance; 13 | } 14 | 15 | class StreamPane: public QWidget { 16 | Q_OBJECT 17 | 18 | public: 19 | StreamPane(libvlc::Instance &, QWidget * = nullptr); 20 | ~StreamPane(); 21 | 22 | void play(QString, QString = QString()); 23 | 24 | StreamWidget *stream() const; 25 | 26 | protected: 27 | void paintEvent(QPaintEvent *) override; 28 | void focusOutEvent(QFocusEvent *) override; 29 | void focusInEvent(QFocusEvent *) override; 30 | 31 | signals: 32 | void remove_requested(); 33 | void zoom_requested(bool); 34 | void fullscreen_requested(bool); 35 | 36 | private: 37 | libvlc::Instance & _video_ctx; 38 | 39 | QHBoxLayout *_layout; 40 | std::unique_ptr _picker; 41 | std::unique_ptr _stream; 42 | 43 | void setup_picker(); 44 | void setup_stream(); 45 | }; 46 | -------------------------------------------------------------------------------- /player/include/ui/widgets/stream_picker.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "api/twitch.hpp" 8 | 9 | #include "prelude/promise.hpp" 10 | 11 | namespace Ui { 12 | class StreamPicker; 13 | } 14 | 15 | class StreamPicker: public QWidget { 16 | Q_OBJECT 17 | 18 | public: 19 | StreamPicker(QWidget * = nullptr); 20 | ~StreamPicker(); 21 | 22 | protected: 23 | void focusInEvent(QFocusEvent *) override; 24 | 25 | signals: 26 | void stream_picked(QString, QString); 27 | 28 | private: 29 | std::unique_ptr _ui; 30 | 31 | QWidget *_channels_stream_presenter; 32 | QWidget *_followed_stream_presenter; 33 | 34 | TwitchAPI _api; 35 | std::unordered_map current_queries; 36 | 37 | void fetch_streams(TwitchAPI::streams_response_t, QWidget *); 38 | void present_streams(QWidget *, QList); 39 | 40 | void channel_picked(QString); 41 | }; 42 | -------------------------------------------------------------------------------- /player/include/ui/widgets/stream_widget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class VideoWidget; 6 | class ForeignWidget; 7 | 8 | namespace libvlc { 9 | struct Instance; 10 | } 11 | 12 | class QSplitter; 13 | class QHBoxLayout; 14 | 15 | enum class ChatPosition { Left, Right }; 16 | 17 | class StreamWidget: public QWidget { 18 | public: 19 | StreamWidget(libvlc::Instance &, QWidget * = nullptr); 20 | ~StreamWidget(); 21 | 22 | void play(QString, QString); 23 | 24 | void reposition_chat(ChatPosition); 25 | void resize_chat(ChatPosition); 26 | 27 | VideoWidget *video() const; 28 | ForeignWidget *chat() const; 29 | 30 | private: 31 | QSplitter *_splitter; 32 | QHBoxLayout *_layout; 33 | VideoWidget *_video; 34 | ForeignWidget *_chat; 35 | 36 | int _chat_size, _video_size; 37 | ChatPosition _chat_position; 38 | }; 39 | -------------------------------------------------------------------------------- /player/include/ui/widgets/video_widget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "libvlc/bindings.hpp" 4 | 5 | #include "api/twitchd.hpp" 6 | 7 | #include 8 | #include 9 | 10 | class VideoControls; 11 | class VideoDetails; 12 | class VLCEventWatcher; 13 | 14 | class VideoWidget: public QWidget { 15 | public: 16 | VideoWidget(libvlc::Instance &, QWidget * = nullptr); 17 | ~VideoWidget(); 18 | 19 | void play(QString, QString); 20 | 21 | int volume() const; 22 | void set_volume(int); 23 | 24 | bool muted() const; 25 | void set_muted(bool); 26 | 27 | void fast_forward(); 28 | 29 | void hint_layout_change(); 30 | 31 | libvlc::MediaPlayer & media_player(); 32 | 33 | VideoControls & controls() const; 34 | 35 | protected: 36 | void wheelEvent(QWheelEvent *) override; 37 | void resizeEvent(QResizeEvent *) override; 38 | void showEvent(QShowEvent *) override; 39 | void mousePressEvent(QMouseEvent *) override; 40 | void mouseMoveEvent(QMouseEvent *) override; 41 | 42 | private: 43 | libvlc::Instance & _instance; 44 | libvlc::MediaPlayer _media_player; 45 | std::optional _media; 46 | 47 | VideoDetails *_details; 48 | VideoControls *_controls; 49 | 50 | VLCEventWatcher *_event_watcher; 51 | 52 | int _vol; 53 | bool _muted; 54 | 55 | QPoint _last_drag_position; 56 | 57 | TwitchdAPI _api; 58 | 59 | QString _current_channel, _current_quality, _current_meta_key; 60 | std::optional _current_metadata; 61 | 62 | QTimer *_retry_timer; 63 | 64 | void update_overlay_position(); 65 | 66 | }; 67 | -------------------------------------------------------------------------------- /player/resources/browse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Globidev/twitch-player/062306685e182c422980d57fe7ba9fbdee141c37/player/resources/browse.png -------------------------------------------------------------------------------- /player/resources/clock.svg: -------------------------------------------------------------------------------- 1 | image/svg+xml 2 | -------------------------------------------------------------------------------- /player/resources/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Globidev/twitch-player/062306685e182c422980d57fe7ba9fbdee141c37/player/resources/dashboard.png -------------------------------------------------------------------------------- /player/resources/fast_forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Globidev/twitch-player/062306685e182c422980d57fe7ba9fbdee141c37/player/resources/fast_forward.png -------------------------------------------------------------------------------- /player/resources/fullscreen_enter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Globidev/twitch-player/062306685e182c422980d57fe7ba9fbdee141c37/player/resources/fullscreen_enter.png -------------------------------------------------------------------------------- /player/resources/fullscreen_exit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Globidev/twitch-player/062306685e182c422980d57fe7ba9fbdee141c37/player/resources/fullscreen_exit.png -------------------------------------------------------------------------------- /player/resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Globidev/twitch-player/062306685e182c422980d57fe7ba9fbdee141c37/player/resources/icon.ico -------------------------------------------------------------------------------- /player/resources/joystick.svg: -------------------------------------------------------------------------------- 1 | image/svg+xml 2 | -------------------------------------------------------------------------------- /player/resources/kappa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Globidev/twitch-player/062306685e182c422980d57fe7ba9fbdee141c37/player/resources/kappa.png -------------------------------------------------------------------------------- /player/resources/layout_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Globidev/twitch-player/062306685e182c422980d57fe7ba9fbdee141c37/player/resources/layout_left.png -------------------------------------------------------------------------------- /player/resources/layout_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Globidev/twitch-player/062306685e182c422980d57fe7ba9fbdee141c37/player/resources/layout_right.png -------------------------------------------------------------------------------- /player/resources/player.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | clock.svg 4 | dashboard.png 5 | unzoom.png 6 | zoom.png 7 | fullscreen_enter.png 8 | browse.png 9 | fullscreen_exit.png 10 | layout_left.png 11 | layout_right.png 12 | remove.png 13 | joystick.svg 14 | viewers.svg 15 | fast_forward.png 16 | icon.ico 17 | volume_off.png 18 | volume_on.png 19 | 20 | 21 | kappa.png 22 | 23 | 24 | -------------------------------------------------------------------------------- /player/resources/remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Globidev/twitch-player/062306685e182c422980d57fe7ba9fbdee141c37/player/resources/remove.png -------------------------------------------------------------------------------- /player/resources/unzoom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Globidev/twitch-player/062306685e182c422980d57fe7ba9fbdee141c37/player/resources/unzoom.png -------------------------------------------------------------------------------- /player/resources/viewers.svg: -------------------------------------------------------------------------------- 1 | image/svg+xml 2 | -------------------------------------------------------------------------------- /player/resources/volume_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Globidev/twitch-player/062306685e182c422980d57fe7ba9fbdee141c37/player/resources/volume_off.png -------------------------------------------------------------------------------- /player/resources/volume_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Globidev/twitch-player/062306685e182c422980d57fe7ba9fbdee141c37/player/resources/volume_on.png -------------------------------------------------------------------------------- /player/resources/zoom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Globidev/twitch-player/062306685e182c422980d57fe7ba9fbdee141c37/player/resources/zoom.png -------------------------------------------------------------------------------- /player/scripts/win32/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cd target 3 | set CL=/MP 4 | nmake %1 5 | -------------------------------------------------------------------------------- /player/scripts/win32/qmake.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cd target 3 | if "%1" == "deploy" ( 4 | %QT_STATIC_BIN_PATH%\qmake.exe .. 5 | ) else ( 6 | qmake .. 7 | ) 8 | -------------------------------------------------------------------------------- /player/src/api/oauth.cpp: -------------------------------------------------------------------------------- 1 | #include "api/oauth.hpp" 2 | 3 | #include "constants.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | #include 21 | 22 | static auto access_code_url(const QByteArray & code) { 23 | QUrl url { "https://id.twitch.tv/oauth2/token" }; 24 | 25 | QUrlQuery url_query; 26 | url_query.addQueryItem("client_id", constants::TWITCHD_CLIENT_ID); 27 | url_query.addQueryItem("client_secret", "8mpn5c01v3k5fdi094x6275cn18du7"); 28 | url_query.addQueryItem("code", code); 29 | url_query.addQueryItem("grant_type", "authorization_code"); 30 | url_query.addQueryItem("redirect_uri", constants::OAUTH_REDIRECT_URI); 31 | url.setQuery(url_query); 32 | 33 | return url; 34 | } 35 | 36 | static auto refresh_token_url(const QString & refresh_token) { 37 | QUrl url { "https://id.twitch.tv/oauth2/token" }; 38 | 39 | QUrlQuery url_query; 40 | url_query.addQueryItem("grant_type", "refresh_token"); 41 | url_query.addQueryItem("refresh_token", refresh_token); 42 | url_query.addQueryItem("client_id", constants::TWITCHD_CLIENT_ID); 43 | url_query.addQueryItem("client_secret", "8mpn5c01v3k5fdi094x6275cn18du7"); 44 | url.setQuery(url_query); 45 | 46 | return url; 47 | } 48 | 49 | static auto authorize_url() { 50 | QUrl url { "https://id.twitch.tv/oauth2/authorize" }; 51 | 52 | QUrlQuery url_query; 53 | url_query.addQueryItem("client_id", constants::TWITCHD_CLIENT_ID); 54 | url_query.addQueryItem("redirect_uri", constants::OAUTH_REDIRECT_URI); 55 | url_query.addQueryItem("response_type", "code"); 56 | url_query.addQueryItem("scope", constants::OAUTH_SCOPES); 57 | url.setQuery(url_query); 58 | 59 | return url; 60 | } 61 | 62 | OAuth::OAuth(QObject *parent): 63 | QObject(parent), 64 | _local_server(new QTcpServer(this)) 65 | { 66 | QObject::connect(_local_server, &QTcpServer::newConnection, [=] { 67 | auto client = _local_server->nextPendingConnection(); 68 | QObject::connect(client, &QTcpSocket::readyRead, [=] { 69 | while (client->canReadLine()) { 70 | auto line = client->readLine(); 71 | auto code_param_index = line.indexOf("GET /?code="); 72 | 73 | if (code_param_index != -1) { 74 | auto code = line.mid(code_param_index + 11, 30); 75 | fetch_token(access_code_url(code)); 76 | } 77 | 78 | if (client->atEnd()) { 79 | client->write("HTTP/1.1 200 OK\r\n\r\n

OK

"); 80 | client->close(); 81 | } 82 | } 83 | }); 84 | }); 85 | 86 | _local_server->listen( 87 | QHostAddress::LocalHost, 88 | constants::OAUTH_REDIRECT_URI_PORT 89 | ); 90 | } 91 | 92 | QPromise OAuth::query_token() { 93 | QSettings settings; 94 | 95 | auto refresh_token = settings 96 | .value(constants::settings::oauth::REFRESH_TOKEN_KEY) 97 | .toString(); 98 | 99 | if (!refresh_token.isEmpty()) 100 | fetch_token(refresh_token_url(refresh_token)); 101 | else 102 | QDesktopServices::openUrl(authorize_url()); 103 | 104 | return QPromise([=](const auto & resolve, auto) { 105 | QObject::connect(this, &OAuth::token_ready, [=](auto token) { 106 | resolve(token); 107 | }); 108 | }); 109 | } 110 | 111 | void OAuth::save_token_data(const QByteArray &raw_data) { 112 | using namespace constants::settings::oauth; 113 | 114 | auto json_data = QJsonDocument::fromJson(raw_data).object(); 115 | 116 | auto access_token = json_data["access_token"].toString(); 117 | auto refresh_token = json_data["refresh_token"].toString(); 118 | 119 | QSettings settings; 120 | 121 | settings.setValue(ACCESS_TOKEN_KEY, access_token); 122 | settings.setValue(REFRESH_TOKEN_KEY, refresh_token); 123 | 124 | emit token_ready(access_token); 125 | } 126 | 127 | void OAuth::fetch_token(const QUrl &url) { 128 | auto http_client = new QNetworkAccessManager(this); 129 | auto token_reply = http_client->post(QNetworkRequest { url }, QByteArray()); 130 | 131 | QObject::connect(token_reply, &QNetworkReply::finished, [=] { 132 | auto status = token_reply 133 | ->attribute(QNetworkRequest::HttpStatusCodeAttribute) 134 | .toInt(); 135 | 136 | if (status == 200) { 137 | auto token_data = token_reply->readAll(); 138 | save_token_data(token_data); 139 | } 140 | 141 | token_reply->deleteLater(); 142 | }); 143 | } 144 | -------------------------------------------------------------------------------- /player/src/libvlc/event_watcher.cpp: -------------------------------------------------------------------------------- 1 | #include "libvlc/event_watcher.hpp" 2 | 3 | #include "libvlc/bindings.hpp" 4 | 5 | #include "prelude/timer.hpp" 6 | 7 | VLCEventWatcher::VLCEventWatcher(libvlc::MediaPlayer & mp, QObject *parent): 8 | QObject(parent) 9 | { 10 | mp.set_event_callback([this](auto event) { 11 | _queue.push(std::move(event)); 12 | }); 13 | 14 | auto poll_events = [this] { 15 | auto opt_event = _queue.try_pop(); 16 | while (opt_event) { 17 | emit new_event(std::move(*opt_event)); 18 | opt_event = _queue.try_pop(); 19 | } 20 | }; 21 | 22 | interval(this, 250, poll_events); 23 | } 24 | -------------------------------------------------------------------------------- /player/src/libvlc/logger.cpp: -------------------------------------------------------------------------------- 1 | #include "libvlc/logger.hpp" 2 | 3 | #include "libvlc/bindings.hpp" 4 | 5 | #include "prelude/timer.hpp" 6 | 7 | VLCLogger::VLCLogger(libvlc::Instance &video_context, QObject *parent): 8 | QObject(parent), 9 | _video_context(video_context) 10 | { 11 | video_context.set_log_callback([this](libvlc::LogEntry entry) { 12 | _queue.push(std::move(entry)); 13 | }); 14 | 15 | auto poll_entries = [this] { 16 | auto opt_entry = _queue.try_pop(); 17 | while (opt_entry) { 18 | emit new_log_entry(std::move(*opt_entry)); 19 | opt_entry = _queue.try_pop(); 20 | } 21 | }; 22 | 23 | interval(this, 250, poll_entries); 24 | } 25 | 26 | VLCLogger::~VLCLogger() { 27 | _video_context.unset_log_callback(); 28 | } 29 | -------------------------------------------------------------------------------- /player/src/process/daemon_control.cpp: -------------------------------------------------------------------------------- 1 | #include "process/daemon_control.hpp" 2 | 3 | #include "api/twitchd.hpp" 4 | 5 | #include "constants.hpp" 6 | 7 | #include 8 | #include 9 | 10 | struct DaemonSettings { 11 | QString host; 12 | quint16 port; 13 | 14 | QString client_id; 15 | 16 | quint32 cache_timeout_s; 17 | quint32 playlist_fetch_interval_ms; 18 | quint32 player_inactive_timeout_s; 19 | quint32 player_fetch_timeout_s; 20 | quint32 player_video_chunks_size; 21 | quint32 player_max_sink_buffer_size; 22 | }; 23 | 24 | static DaemonSettings load_settings() { 25 | using namespace constants::settings::daemon; 26 | QSettings settings; 27 | 28 | return DaemonSettings { 29 | settings.value(KEY_HOST_MANAGED, DEFAULT_HOST_MANAGED).toString(), 30 | settings.value(KEY_PORT_MANAGED, DEFAULT_PORT_MANAGED).value(), 31 | 32 | constants::TWITCHD_CLIENT_ID, 33 | 34 | settings.value(KEY_CACHE_TIMEOUT, DEFAULT_CACHE_TIMEOUT).value(), 35 | settings.value(KEY_PLAYLIST_FETCH_INTERVAL, DEFAULT_PLAYLIST_FETCH_INTERVAL).value(), 36 | settings.value(KEY_PLAYER_INACTIVE_TIMEOUT, DEFAULT_PLAYER_INACTIVE_TIMEOUT).value(), 37 | settings.value(KEY_PLAYER_FETCH_TIMEOUT, DEFAULT_PLAYER_FETCH_TIMEOUT).value(), 38 | settings.value(KEY_PLAYER_VIDEO_CHUNKS_SIZE, DEFAULT_PLAYER_VIDEO_CHUNKS_SIZE).value(), 39 | settings.value(KEY_PLAYER_MAX_SINK_BUFFER_SIZE, DEFAULT_PLAYER_MAX_SINK_BUFFER_SIZE).value(), 40 | }; 41 | } 42 | 43 | namespace daemon_control { 44 | 45 | bool start() { 46 | auto settings = load_settings(); 47 | 48 | auto arguments = QStringList() 49 | << "--client-id" << settings.client_id 50 | << "--host" << settings.host 51 | << "--port" << QString::number(settings.port) 52 | << "--index-cache-timeout" << QString("%1s").arg(settings.cache_timeout_s) 53 | << "--playlist-fetch-interval" << QString("%1ms").arg(settings.playlist_fetch_interval_ms) 54 | << "--player-inactive-timeout" << QString("%1s").arg(settings.player_inactive_timeout_s) 55 | << "--player-fetch-timeout" << QString("%1s").arg(settings.player_fetch_timeout_s) 56 | << "--player-video-chunks-size" << QString("%1").arg(settings.player_video_chunks_size) 57 | << "--player-max-sink-buffer-size" << QString("%1").arg(settings.player_max_sink_buffer_size) 58 | ; 59 | 60 | return QProcess::startDetached(constants::TWITCHD_PATH, arguments); 61 | } 62 | 63 | bool stop() { 64 | bool stopped = false; 65 | 66 | TwitchdAPI api; 67 | 68 | auto quit_response = api.daemon_quit() 69 | .tap([&] { 70 | stopped = true; 71 | }); 72 | 73 | quit_response.timeout(2000).wait(); 74 | 75 | return stopped; 76 | } 77 | 78 | Status status() { 79 | using namespace constants::settings::daemon; 80 | QSettings settings; 81 | 82 | Status status; 83 | TwitchdAPI api; 84 | 85 | status.managed = settings.value(KEY_MANAGED, DEFAULT_MANAGED).toBool(); 86 | 87 | auto version_response = api.daemon_version() 88 | .tap([&](QString version) { 89 | status.running = true; 90 | status.version = version; 91 | }); 92 | 93 | version_response.timeout(2000).wait(); 94 | 95 | return status; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /player/src/ui/layouts/flow.cpp: -------------------------------------------------------------------------------- 1 | #include "ui/layouts/flow.hpp" 2 | 3 | #include 4 | 5 | FlowLayout::FlowLayout(QWidget *parent): 6 | QLayout(parent) 7 | { 8 | setContentsMargins(-1, -1, -1, -1); 9 | } 10 | 11 | FlowLayout::~FlowLayout() { 12 | QLayoutItem *item; 13 | 14 | while ((item = takeAt(0))) 15 | delete item; 16 | } 17 | 18 | void FlowLayout::addItem(QLayoutItem *item) { 19 | itemList.append(item); 20 | } 21 | 22 | int FlowLayout::horizontalSpacing() const { 23 | return smartSpacing(QStyle::PM_LayoutHorizontalSpacing); 24 | } 25 | 26 | int FlowLayout::verticalSpacing() const { 27 | return smartSpacing(QStyle::PM_LayoutVerticalSpacing); 28 | } 29 | 30 | int FlowLayout::count() const { 31 | return itemList.size(); 32 | } 33 | 34 | QLayoutItem *FlowLayout::itemAt(int index) const { 35 | return itemList.value(index); 36 | } 37 | 38 | QLayoutItem *FlowLayout::takeAt(int index) { 39 | if (index >= 0 && index < itemList.size()) 40 | return itemList.takeAt(index); 41 | else 42 | return nullptr; 43 | } 44 | 45 | Qt::Orientations FlowLayout::expandingDirections() const { 46 | return 0; 47 | } 48 | 49 | bool FlowLayout::hasHeightForWidth() const { 50 | return true; 51 | } 52 | 53 | int FlowLayout::heightForWidth(int width) const { 54 | int height = doLayout(QRect(0, 0, width, 0), true); 55 | return height; 56 | } 57 | 58 | void FlowLayout::setGeometry(const QRect &rect) { 59 | QLayout::setGeometry(rect); 60 | doLayout(rect, false); 61 | } 62 | 63 | QSize FlowLayout::sizeHint() const { 64 | return minimumSize(); 65 | } 66 | 67 | QSize FlowLayout::minimumSize() const { 68 | QSize size; 69 | 70 | for (auto item: itemList) 71 | size = size.expandedTo(item->minimumSize()); 72 | 73 | size += QSize(2*margin(), 2*margin()); 74 | 75 | return size; 76 | } 77 | 78 | int FlowLayout::doLayout(const QRect &rect, bool testOnly) const { 79 | int left, top, right, bottom; 80 | getContentsMargins(&left, &top, &right, &bottom); 81 | QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); 82 | int x = effectiveRect.x(); 83 | int y = effectiveRect.y(); 84 | int lineHeight = 0; 85 | 86 | for (auto item: itemList) { 87 | QWidget *wid = item->widget(); 88 | int spaceX = horizontalSpacing(); 89 | if (spaceX == -1) 90 | spaceX = wid->style()->layoutSpacing( 91 | QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal); 92 | int spaceY = verticalSpacing(); 93 | if (spaceY == -1) 94 | spaceY = wid->style()->layoutSpacing( 95 | QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical); 96 | int nextX = x + item->sizeHint().width() + spaceX; 97 | if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) { 98 | x = effectiveRect.x(); 99 | y = y + lineHeight + spaceY; 100 | nextX = x + item->sizeHint().width() + spaceX; 101 | lineHeight = 0; 102 | } 103 | 104 | if (!testOnly) 105 | item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); 106 | 107 | x = nextX; 108 | lineHeight = qMax(lineHeight, item->sizeHint().height()); 109 | } 110 | return y + lineHeight - rect.y() + bottom; 111 | } 112 | 113 | int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const { 114 | QObject *parent = this->parent(); 115 | if (!parent) { 116 | return -1; 117 | } else if (parent->isWidgetType()) { 118 | QWidget *pw = static_cast(parent); 119 | return pw->style()->pixelMetric(pm, 0, pw); 120 | } else { 121 | return static_cast(parent)->spacing(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /player/src/ui/layouts/splitter_grid.cpp: -------------------------------------------------------------------------------- 1 | #include "ui/layouts/splitter_grid.hpp" 2 | 3 | #include 4 | #include 5 | 6 | static auto equal_sizes(int size) { 7 | QList sizes; 8 | for (int i = 0; i < size; ++i) 9 | sizes << 100; 10 | return sizes; 11 | } 12 | 13 | static auto invert(Qt::Orientation orientation) { 14 | return orientation == Qt::Vertical 15 | ? Qt::Horizontal 16 | : Qt::Vertical; 17 | } 18 | 19 | SplitterGrid::SplitterGrid(QWidget *parent): 20 | QWidget(parent), 21 | _layout(new QHBoxLayout(this)), 22 | _main_splitter(new QSplitter(_main_orientation, this)) 23 | { 24 | _layout->setContentsMargins(QMargins()); 25 | _layout->addWidget(_main_splitter); 26 | 27 | setLayout(_layout); 28 | } 29 | 30 | void SplitterGrid::insert_widget(Position pos, QWidget *widget) { 31 | auto inner_splitter = get_inner(pos.row); 32 | inner_splitter->insertWidget(pos.column, widget); 33 | inner_splitter->setSizes(equal_sizes(inner_splitter->count())); 34 | } 35 | 36 | void SplitterGrid::remove_widget(QWidget *widget) { 37 | auto pos = widget_position(widget); 38 | 39 | if (static_cast(pos.row) < _inner_splitters.size()) { 40 | auto inner_splitter_it = _inner_splitters.begin(); 41 | std::advance(inner_splitter_it, pos.row); 42 | if ((*inner_splitter_it)->count() == 1) { 43 | (*inner_splitter_it)->deleteLater(); 44 | _inner_splitters.erase(inner_splitter_it); 45 | } 46 | } 47 | } 48 | 49 | Position SplitterGrid::widget_position(QWidget *widget) { 50 | for (int row = 0; static_cast(row) < _inner_splitters.size(); ++row) 51 | for (int col = 0; col < _inner_splitters[row]->count(); ++col) 52 | if (_inner_splitters[row]->widget(col) == widget) 53 | return { row, col }; 54 | 55 | return { 0, 0 }; 56 | } 57 | 58 | QWidget * SplitterGrid::closest_widget(Position pos) { 59 | if (_inner_splitters.empty()) 60 | return nullptr; 61 | 62 | auto max_row = static_cast(_inner_splitters.size() - 1); 63 | auto row = std::max(std::min(pos.row, max_row), 0); 64 | auto inner_splitter = _inner_splitters[row]; 65 | auto col = std::max(std::min(pos.column, inner_splitter->count() - 1), 0); 66 | 67 | return inner_splitter->widget(col); 68 | } 69 | 70 | void SplitterGrid::swap(Position from, Position to) { 71 | auto from_w = closest_widget(from); 72 | auto to_w = closest_widget(to); 73 | if (from_w && from_w != to_w) { 74 | auto actual_from = widget_position(from_w); 75 | auto actual_to = widget_position(to_w); 76 | auto splitter_from = _inner_splitters[actual_from.row]; 77 | auto splitter_to = _inner_splitters[actual_to.row]; 78 | auto splitter_from_sizes = splitter_from->sizes(); 79 | auto splitter_to_sizes = splitter_to->sizes(); 80 | splitter_from->replaceWidget(actual_from.column, to_w); 81 | splitter_to->insertWidget(actual_to.column, from_w); 82 | splitter_from->setSizes(splitter_from_sizes); 83 | splitter_to->setSizes(splitter_to_sizes); 84 | } 85 | } 86 | 87 | void SplitterGrid::rotate() { 88 | _main_orientation = invert(_main_orientation); 89 | _main_splitter->setOrientation(_main_orientation); 90 | for (auto inner_splitter: _inner_splitters) 91 | inner_splitter->setOrientation(invert(_main_orientation)); 92 | } 93 | 94 | Qt::Orientation SplitterGrid::orientation() const { 95 | return _main_orientation; 96 | } 97 | 98 | QSplitter * SplitterGrid::get_inner(int row) { 99 | if (row < 0) { 100 | auto splitter = new QSplitter(invert(_main_orientation), this); 101 | _main_splitter->insertWidget(0, splitter); 102 | _main_splitter->setSizes(equal_sizes(_main_splitter->count())); 103 | _inner_splitters.insert(_inner_splitters.begin(), splitter); 104 | return splitter; 105 | } 106 | else if (static_cast(row) >= _inner_splitters.size()) { 107 | auto splitter = new QSplitter(invert(_main_orientation), this); 108 | _main_splitter->addWidget(splitter); 109 | _main_splitter->setSizes(equal_sizes(_main_splitter->count())); 110 | _inner_splitters.push_back(splitter); 111 | return splitter; 112 | } 113 | else { 114 | return _inner_splitters[row]; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /player/src/ui/native/osx.cpp: -------------------------------------------------------------------------------- 1 | #include "ui/native/osx.hpp" 2 | 3 | FindWindowResult find_windows_by_title_and_pname(std::string, std::string) { 4 | return { }; 5 | } 6 | 7 | void toggle_window_borders(WindowHandle, bool) { } 8 | 9 | void toggle_always_on_top(WindowHandle, bool) { } 10 | 11 | void sysclose_window(WindowHandle) { } 12 | 13 | void redraw(WindowHandle) { } 14 | 15 | void set_transparent(WindowHandle) { } 16 | -------------------------------------------------------------------------------- /player/src/ui/native/win32.cpp: -------------------------------------------------------------------------------- 1 | #include "ui/native/win32.hpp" 2 | 3 | #include 4 | #include 5 | 6 | template 7 | struct FindWindowContext { 8 | Predicate predicate; 9 | FindWindowResult matches; 10 | }; 11 | 12 | template 13 | static BOOL on_window(HWND handle, LPARAM erased_context) 14 | { 15 | auto context = reinterpret_cast *>(erased_context); 16 | 17 | if (context->predicate(handle)) 18 | context->matches.insert(handle); 19 | 20 | return TRUE; 21 | } 22 | 23 | template 24 | static FindWindowResult find_windows_by(Pred pred) { 25 | auto context = FindWindowContext { pred }; 26 | 27 | (void)EnumWindows(on_window, reinterpret_cast(&context)); 28 | 29 | return context.matches; 30 | } 31 | 32 | static std::string window_title(HWND handle) { 33 | char buff[256]; 34 | auto size = GetWindowTextA(handle, buff, sizeof buff); 35 | return { buff, static_cast(size) }; 36 | } 37 | 38 | static std::string window_process_name(HWND handle) { 39 | constexpr auto PROCESS_FLAGS = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; 40 | 41 | DWORD pid; 42 | GetWindowThreadProcessId(handle, &pid); 43 | 44 | if (auto phandle = OpenProcess(PROCESS_FLAGS, FALSE, pid); phandle) { 45 | char buff[256]; 46 | auto size = GetModuleFileNameExA(phandle, nullptr, buff, sizeof buff); 47 | return { buff, static_cast(size) }; 48 | } else { 49 | return { }; 50 | } 51 | } 52 | 53 | FindWindowResult find_windows_by_title_and_pname(std::regex title_pattern, 54 | std::string pname) 55 | { 56 | auto predicate = [&](HWND handle) { 57 | auto handle_title = window_title(handle); 58 | auto handle_pname = window_process_name(handle); 59 | 60 | auto pname_matches = handle_pname.compare(0, pname.size(), pname) == 0; 61 | auto title_matches = std::regex_search(handle_title, title_pattern); 62 | 63 | return pname_matches && title_matches; 64 | }; 65 | 66 | return find_windows_by(predicate); 67 | } 68 | 69 | void toggle_window_borders(HWND handle, bool on) { 70 | constexpr auto BORDER_FLAGS = WS_CAPTION 71 | | WS_THICKFRAME 72 | | WS_MINIMIZEBOX 73 | | WS_MAXIMIZEBOX 74 | | WS_SYSMENU 75 | ; 76 | 77 | constexpr auto REPOSITION_FLAGS = SWP_DRAWFRAME 78 | | SWP_FRAMECHANGED 79 | | SWP_NOMOVE 80 | | SWP_NOSIZE 81 | | SWP_NOZORDER 82 | | SWP_NOACTIVATE 83 | ; 84 | 85 | auto current_style = GetWindowLong(handle, GWL_STYLE); 86 | auto new_style = on 87 | ? current_style | BORDER_FLAGS 88 | : current_style & ~BORDER_FLAGS; 89 | SetWindowLong(handle, GWL_STYLE, new_style); 90 | SetWindowPos(handle, nullptr, 0, 0, 0, 0, REPOSITION_FLAGS); 91 | redraw(handle); 92 | } 93 | 94 | void toggle_always_on_top(HWND handle, bool on) { 95 | auto insert_flags = on ? HWND_TOPMOST : HWND_NOTOPMOST; 96 | SetWindowPos(handle, insert_flags, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); 97 | } 98 | 99 | void sysclose_window(HWND handle) { 100 | SendMessage(handle, WM_SYSCOMMAND, SC_CLOSE, 0); 101 | } 102 | 103 | void redraw(HWND handle) { 104 | InvalidateRect(handle, nullptr, TRUE); 105 | } 106 | 107 | void set_transparent(HWND handle) { 108 | auto styles = GetWindowLong(handle, GWL_EXSTYLE); 109 | SetWindowLong(handle, GWL_EXSTYLE, styles | WS_EX_TRANSPARENT); 110 | } 111 | -------------------------------------------------------------------------------- /player/src/ui/native/x11.cpp: -------------------------------------------------------------------------------- 1 | #include "ui/native/x11.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | FindWindowResult find_windows_by_title_and_pname(std::string title, std::string pname) { 9 | auto matches = FindWindowResult { }; 10 | 11 | auto display = XOpenDisplay(nullptr); 12 | auto root_window = XRootWindow(display, 0); 13 | 14 | auto open_set = std::deque { root_window }; 15 | 16 | while (!open_set.empty()) { 17 | auto window = open_set.front(); 18 | open_set.pop_front(); 19 | 20 | Window _root, _parent; 21 | Window *children; 22 | unsigned int child_count; 23 | XQueryTree(display, window, &_root, &_parent, &children, &child_count); 24 | 25 | for (auto i = 0u; i < child_count; ++i) { 26 | auto child = children[i]; 27 | XTextProperty property; 28 | XGetWMName(display, child, &property); 29 | if (property.value) { 30 | if (title.compare(reinterpret_cast(property.value)) == 0) 31 | matches.insert(child); 32 | } 33 | 34 | open_set.push_back(child); 35 | } 36 | XFree(children); 37 | } 38 | 39 | XCloseDisplay(display); 40 | return matches; 41 | } 42 | 43 | void toggle_window_borders(WindowHandle, bool) { 44 | // TODO 45 | } 46 | 47 | void toggle_always_on_top(WindowHandle, bool) { 48 | // TODO 49 | } 50 | 51 | void sysclose_window(WindowHandle) { 52 | // TODO 53 | } 54 | 55 | void redraw(WindowHandle) { 56 | // TODO 57 | } 58 | 59 | void set_transparent(WindowHandle) { 60 | // TODO 61 | } 62 | -------------------------------------------------------------------------------- /player/src/ui/tools/about_dialog.cpp: -------------------------------------------------------------------------------- 1 | #include "ui/tools/about_dialog.hpp" 2 | 3 | #include "ui_about_dialog.h" 4 | 5 | constexpr auto VERSION = "0.9.24"; 6 | 7 | AboutDialog::AboutDialog(QWidget *parent): 8 | QDialog(parent), 9 | _ui(std::make_unique()) 10 | { 11 | setModal(true); 12 | _ui->setupUi(this); 13 | 14 | _ui->labelVersion->setText(QString("Twitch Player version %1").arg(VERSION)); 15 | } 16 | 17 | AboutDialog::~AboutDialog() = default; 18 | -------------------------------------------------------------------------------- /player/src/ui/tools/video_filters.cpp: -------------------------------------------------------------------------------- 1 | #include "ui/tools/video_filters.hpp" 2 | #include "ui_filters_tool.h" 3 | 4 | #include "libvlc/bindings.hpp" 5 | 6 | VideoFilters::VideoFilters(libvlc::MediaPlayer &mp, QWidget *parent): 7 | QWidget(parent), 8 | _ui(std::make_unique()) 9 | { 10 | _ui->setupUi(this); 11 | setWindowFlags(Qt::Tool); 12 | 13 | auto setup_slider = [&](auto factor, auto slider, auto get, auto set) { 14 | slider->setValue((mp.*get)() * factor); 15 | auto update = [=, &mp](int value) { (mp.*set)(value / factor); }; 16 | QObject::connect(slider, &QSlider::valueChanged, update); 17 | }; 18 | 19 | _ui->videoFiltersGroupBox->setChecked(mp.video_filters_enabled()); 20 | QObject::connect(_ui->videoFiltersGroupBox, &QGroupBox::toggled, [&](bool on) { 21 | mp.enable_video_filters(on); 22 | }); 23 | 24 | using MP = libvlc::MediaPlayer; 25 | setup_slider(100.f, _ui->sliderContrast, &MP::get_contrast, &MP::set_contrast); 26 | setup_slider(100.f, _ui->sliderBrightness, &MP::get_brightness, &MP::set_brightness); 27 | setup_slider(1, _ui->sliderHue, &MP::get_hue, &MP::set_hue); 28 | setup_slider(100.f, _ui->sliderSaturation, &MP::get_saturation, &MP::set_saturation); 29 | setup_slider(100.f, _ui->sliderGamma, &MP::get_gamma, &MP::set_gamma); 30 | } 31 | -------------------------------------------------------------------------------- /player/src/ui/tray.cpp: -------------------------------------------------------------------------------- 1 | #include "ui/tray.hpp" 2 | 3 | #include "api/pubsub.hpp" 4 | 5 | #include "prelude/timer.hpp" 6 | 7 | #include "constants.hpp" 8 | 9 | #include 10 | 11 | SystemTray::SystemTray(TwitchPubSub &pubsub): 12 | QSystemTrayIcon(QIcon(":/icons/icon.ico")), 13 | _pubsub(pubsub) 14 | { 15 | QObject::connect(&pubsub, &TwitchPubSub::channel_went_live, [=](auto channel) { 16 | channel_to_play = channel; 17 | 18 | auto alert_title = QString("%1 just went live!").arg(channel); 19 | auto alert_message = QString("Click to play"); 20 | 21 | showMessage(alert_title, alert_message); 22 | 23 | delayed(this, 10'000, [=] { channel_to_play = std::nullopt; }); 24 | }); 25 | 26 | QObject::connect(this, &QSystemTrayIcon::messageClicked, [=] { 27 | if (channel_to_play) 28 | emit channel_open_requested(*channel_to_play); 29 | }); 30 | 31 | _menu.addAction("Quit", [] { qApp->quit(); }); 32 | 33 | setContextMenu(&_menu); 34 | 35 | using namespace constants::settings::notifications; 36 | 37 | QSettings settings; 38 | 39 | auto pubsub_channels = settings 40 | .value(KEY_PUBSUB_CHANNELS, DEFAULT_PUBSUB_CHANNELS) 41 | .toStringList(); 42 | 43 | for (auto channel: pubsub_channels) 44 | pubsub.listen_to_channel(channel) 45 | .fail([=](QString error) { 46 | auto alert_title = "Failed to monitor channel"; 47 | auto alert_message = QString("Error while trying to monitor %1: %2") 48 | .arg(channel) 49 | .arg(error); 50 | 51 | showMessage(alert_title, alert_message, QSystemTrayIcon::Critical); 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /player/src/ui/utils/event_notifier.cpp: -------------------------------------------------------------------------------- 1 | #include "ui/utils/event_notifier.hpp" 2 | 3 | EventNotifier::EventNotifier(std::initializer_list events, QObject *parent): 4 | QObject(parent), 5 | _monitored_events(events) 6 | { } 7 | 8 | bool EventNotifier::eventFilter(QObject *watched, QEvent *event) { 9 | if (_monitored_events.count(event->type())) 10 | emit new_event(*event); 11 | 12 | return QObject::eventFilter(watched, event); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /player/src/ui/widgets/chat_pane.cpp: -------------------------------------------------------------------------------- 1 | #include "ui/widgets/chat_pane.hpp" 2 | #include "ui/widgets/foreign_widget.hpp" 3 | #include "ui/native/capabilities.hpp" 4 | 5 | #include "constants.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | constexpr auto border_width = 1; 16 | 17 | static auto find_chat_windows(QString renderer_path) { 18 | using namespace constants::settings::chat_renderer; 19 | 20 | auto normalized_path = renderer_path.replace("/", "\\").toStdString(); 21 | 22 | QSettings settings; 23 | auto title_hint = settings 24 | .value(KEY_CHAT_RENDERER_TITLE_HINT,DEFAULT_CHAT_RENDERER_TITLE_HINT) 25 | .toString(); 26 | auto title_hint_pattern = std::regex { title_hint.toLocal8Bit().data() }; 27 | 28 | return find_windows_by_title_and_pname(title_hint_pattern, normalized_path); 29 | } 30 | 31 | static auto find_new_chat_windows(QString renderer_path, 32 | const FindWindowResult & previous_results) { 33 | auto new_results = find_chat_windows(renderer_path); 34 | 35 | FindWindowResult new_windows; 36 | std::set_difference( 37 | new_results.begin(), new_results.end(), 38 | previous_results.begin(), previous_results.end(), 39 | std::inserter(new_windows, new_windows.begin()) 40 | ); 41 | 42 | return new_windows; 43 | } 44 | 45 | ChatPane::ChatPane(QWidget *parent): 46 | QWidget(parent), 47 | _layout(new QHBoxLayout(this)), 48 | _chat(new ForeignWidget(this)) 49 | { 50 | _layout->addWidget(_chat); 51 | setLayout(_layout); 52 | 53 | auto margins = QMargins(border_width, border_width, border_width, border_width); 54 | _layout->setContentsMargins(margins); 55 | setContentsMargins(margins); 56 | 57 | using namespace constants::settings::chat_renderer; 58 | 59 | QSettings settings; 60 | 61 | auto renderer_path = settings 62 | .value(KEY_CHAT_RENDERER_PATH, DEFAULT_CHAT_RENDERER_PATH) 63 | .toString(); 64 | auto raw_renderer_args = settings 65 | .value(KEY_CHAT_RENDERER_ARGS, DEFAULT_CHAT_RENDERER_ARGS) 66 | .toStringList(); 67 | QStringList renderer_args; 68 | for (auto raw_arg: raw_renderer_args) 69 | renderer_args.append(raw_arg.replace("${channel}", "none")); 70 | auto pre_launch_windows = find_chat_windows(renderer_path); 71 | bool started = QProcess::startDetached(renderer_path, renderer_args); 72 | 73 | if (!started) 74 | QMessageBox::critical( 75 | window(), 76 | "Error starting chat renderer", 77 | QString("Failed to start the chat process: %1").arg(renderer_path) 78 | ); 79 | else { 80 | auto timer = new QTimer(this); 81 | QObject::connect(timer, &QTimer::timeout, [=] { 82 | auto chat_windows = find_new_chat_windows(renderer_path, pre_launch_windows); 83 | 84 | if (auto handle_it = chat_windows.begin(); 85 | handle_it != chat_windows.end()) 86 | { 87 | _chat->grab(*handle_it); 88 | activateWindow(); 89 | timer->deleteLater(); 90 | } 91 | }); 92 | timer->start(250); 93 | } 94 | } 95 | 96 | ChatPane::~ChatPane() = default; 97 | -------------------------------------------------------------------------------- /player/src/ui/widgets/foreign_widget.cpp: -------------------------------------------------------------------------------- 1 | #include "ui/widgets/foreign_widget.hpp" 2 | 3 | #include "ui/native/capabilities.hpp" 4 | 5 | #include 6 | #include 7 | 8 | ForeignWidget::ForeignWidget(QWidget *parent): 9 | QWidget(parent), 10 | _layout(new QHBoxLayout(this)) 11 | { 12 | setLayout(_layout); 13 | 14 | _layout->setContentsMargins(QMargins()); 15 | setContentsMargins(QMargins()); 16 | } 17 | 18 | void ForeignWidget::grab(WindowHandle handle) { 19 | release_window(); 20 | 21 | auto abstract_handle = from_native_handle(handle); 22 | if (auto win_ptr = QWindow::fromWinId(abstract_handle); win_ptr) { 23 | _foreign_win_ptr = win_ptr; 24 | if (auto container = QWidget::createWindowContainer(win_ptr); container) { 25 | _container = container; 26 | _layout->addWidget(container); 27 | // Forcing an initial redraw seems to help fixing reparenting issues 28 | redraw(); 29 | } 30 | } 31 | } 32 | 33 | ForeignWidget::~ForeignWidget() { 34 | release_window(); 35 | } 36 | 37 | void ForeignWidget::release_window() { 38 | if (_foreign_win_ptr) { 39 | auto win_ptr = *_foreign_win_ptr; 40 | auto handle = to_native_handle(win_ptr->winId()); 41 | 42 | win_ptr->setParent(nullptr); 43 | sysclose_window(handle); 44 | } 45 | } 46 | 47 | void ForeignWidget::redraw() { 48 | // On Windows, there are sometimes issues where the window won't redraw 49 | // itself when the container or its parents are reparented 50 | if (_container) 51 | ::redraw(to_native_handle((*_container)->winId())); 52 | } 53 | -------------------------------------------------------------------------------- /player/src/ui/widgets/stream_card.cpp: -------------------------------------------------------------------------------- 1 | #include "ui/widgets/stream_card.hpp" 2 | #include "ui_stream_card.h" 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | StreamCard::StreamCard(StreamData data, QWidget *parent): 10 | QWidget(parent), 11 | _ui(std::make_unique()), 12 | _uptime_widget(new QWidget(nullptr)), 13 | _uptime_layout(new QHBoxLayout(_uptime_widget)), 14 | _uptime_logo(new QLabel(this)), 15 | _uptime_label(new QLabel(this)), 16 | _data(data) 17 | { 18 | _ui->setupUi(this); 19 | 20 | _ui->channelName->setText(data.channel.display_name); 21 | _ui->title->setText(data.channel.title); 22 | _ui->gameName->setText(data.current_game); 23 | _ui->viewerCount->setText(QString("%1 viewers").arg(data.viewcount)); 24 | 25 | _uptime_widget->setParent(_ui->preview); 26 | _uptime_widget->setStyleSheet("background-color: rgba(0, 0, 0, 0.1);"); 27 | _uptime_layout->addWidget(_uptime_logo); 28 | _uptime_layout->addWidget(_uptime_label); 29 | 30 | _uptime_logo->setPixmap(QPixmap(":/icons/clock.svg")); 31 | _uptime_logo->setScaledContents(true); 32 | _uptime_logo->setMaximumSize(16, 16); 33 | 34 | auto uptime_secs = data.created_at.secsTo(QDateTime::currentDateTime()); 35 | auto uptime_hours = uptime_secs / 3600; 36 | auto uptime_minutes = (uptime_secs - uptime_hours * 3600) / 60; 37 | _uptime_label->setText(QString("%1:%2").arg(uptime_hours).arg(uptime_minutes, 2, 10, QChar('0'))); 38 | 39 | QPalette palette; 40 | palette.setColor(QPalette::WindowText, Qt::white); 41 | _uptime_label->setPalette(palette); 42 | 43 | _uptime_widget->show(); 44 | _uptime_widget->move(_ui->preview->width() - _uptime_widget->width(), 0); 45 | 46 | auto http_client = new QNetworkAccessManager(this); 47 | http_client->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); 48 | auto preview_reply = http_client->get(QNetworkRequest(QUrl(data.preview))); 49 | 50 | QObject::connect(preview_reply, &QNetworkReply::finished, [=] { 51 | auto data = preview_reply->readAll(); 52 | _ui->preview->setPixmap(QPixmap::fromImage(QImage::fromData(data))); 53 | preview_reply->deleteLater(); 54 | }); 55 | } 56 | 57 | StreamCard::~StreamCard() = default; 58 | 59 | void StreamCard::mousePressEvent(QMouseEvent *) { 60 | emit clicked(_data.channel.name); 61 | } 62 | -------------------------------------------------------------------------------- /player/src/ui/widgets/stream_pane.cpp: -------------------------------------------------------------------------------- 1 | #include "ui/widgets/stream_pane.hpp" 2 | 3 | #include "ui/widgets/stream_picker.hpp" 4 | #include "ui/widgets/stream_widget.hpp" 5 | #include "ui/widgets/video_widget.hpp" 6 | #include "ui/overlays/video_controls.hpp" 7 | #include "ui/utils/event_notifier.hpp" 8 | 9 | #include "libvlc/bindings.hpp" 10 | 11 | #include "prelude/timer.hpp" 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | constexpr auto border_width = 1; 18 | 19 | static const std::initializer_list FOCUS_INVALIDATING_EVENTS = { 20 | QEvent::MouseButtonRelease, 21 | QEvent::KeyRelease 22 | }; 23 | 24 | StreamPane::StreamPane(libvlc::Instance &video_ctx, QWidget *parent): 25 | QWidget(parent), 26 | _video_ctx(video_ctx), 27 | _layout(new QHBoxLayout(this)), 28 | _picker(std::make_unique(this)), 29 | _stream(std::make_unique(video_ctx, this)) 30 | { 31 | auto notifier = new EventNotifier(FOCUS_INVALIDATING_EVENTS, this); 32 | window()->installEventFilter(notifier); 33 | 34 | QObject::connect(notifier, &EventNotifier::new_event, [=] { 35 | repaint(); 36 | }); 37 | 38 | setup_picker(); 39 | setup_stream(); 40 | 41 | _stream->hide(); 42 | setLayout(_layout); 43 | 44 | _layout->addWidget(_picker.get()); 45 | 46 | setFocusPolicy(Qt::StrongFocus); 47 | auto margins = QMargins(border_width, border_width, border_width, border_width); 48 | _layout->setContentsMargins(margins); 49 | setContentsMargins(margins); 50 | setAutoFillBackground(true); 51 | } 52 | 53 | StreamPane::~StreamPane() = default; 54 | 55 | void StreamPane::play(QString channel, QString quality) { 56 | _picker->hide(); 57 | 58 | _layout->removeWidget(_picker.get()); 59 | _layout->addWidget(_stream.get()); 60 | 61 | _stream->setFocus(); 62 | _stream->show(); 63 | _stream->play(channel, quality); 64 | 65 | repaint(); 66 | } 67 | 68 | StreamWidget *StreamPane::stream() const { 69 | return _stream.get(); 70 | } 71 | 72 | void StreamPane::paintEvent(QPaintEvent *event) { 73 | if (isAncestorOf(qApp->focusWidget())) { 74 | QPainter painter(this); 75 | 76 | painter.setPen({ 77 | QColor(0x39, 0x2e, 0x5c), 78 | static_cast(border_width), 79 | Qt::PenStyle::DotLine 80 | }); 81 | painter.drawRect( 82 | border_width, border_width, 83 | width() - border_width * 2, 84 | height() - border_width * 2 85 | ); 86 | } 87 | 88 | QWidget::paintEvent(event); 89 | } 90 | 91 | void StreamPane::focusOutEvent(QFocusEvent *event) { 92 | QWidget::focusOutEvent(event); 93 | repaint(); 94 | } 95 | 96 | void StreamPane::focusInEvent(QFocusEvent *event) { 97 | QWidget::focusInEvent(event); 98 | if (auto item = _layout->itemAt(0); item) 99 | item->widget()->setFocus(); 100 | repaint(); 101 | } 102 | 103 | void StreamPane::setup_picker() { 104 | auto play_stream = [=](QString channel, QString quality) { 105 | play(channel, quality); 106 | }; 107 | 108 | QObject::connect( _picker.get(), &StreamPicker::stream_picked, play_stream); 109 | } 110 | 111 | void StreamPane::setup_stream() { 112 | auto on_browse = [=] { 113 | // For some unknown reasons, the foreign widget doesn't get properly 114 | // released unless we schedule the removing for later... 115 | delayed(this, 250, [=] { 116 | _layout->removeWidget(_stream.get()); 117 | 118 | _picker = std::make_unique(this); 119 | _stream = std::make_unique(_video_ctx, this); 120 | setup_picker(); 121 | setup_stream(); 122 | 123 | _layout->addWidget(_picker.get()); 124 | 125 | _picker->show(); 126 | }); 127 | }; 128 | 129 | QObject::connect( 130 | &_stream->video()->controls(), 131 | &VideoControls::browse_requested, 132 | on_browse 133 | ); 134 | 135 | QObject::connect( 136 | &_stream->video()->controls(), 137 | &VideoControls::remove_requested, 138 | [=] { emit remove_requested(); } 139 | ); 140 | QObject::connect( 141 | &_stream->video()->controls(), 142 | &VideoControls::zoom_requested, 143 | [=](bool on) { 144 | emit zoom_requested(on); 145 | _stream->video()->controls().set_zoomed(on); 146 | } 147 | ); 148 | QObject::connect( 149 | &_stream->video()->controls(), 150 | &VideoControls::fullscreen_requested, 151 | [=](bool on) { 152 | emit fullscreen_requested(on); 153 | _stream->video()->controls().set_fullscreen(on); 154 | } 155 | ); 156 | } 157 | -------------------------------------------------------------------------------- /player/src/ui/widgets/stream_picker.cpp: -------------------------------------------------------------------------------- 1 | #include "ui/widgets/stream_picker.hpp" 2 | #include "ui_stream_picker.h" 3 | 4 | #include "ui/layouts/flow.hpp" 5 | #include "ui/widgets/stream_card.hpp" 6 | 7 | #include "api/oauth.hpp" 8 | 9 | #include "constants.hpp" 10 | 11 | #include 12 | 13 | StreamPicker::StreamPicker(QWidget *parent): 14 | QWidget(parent), 15 | _ui(std::make_unique()), 16 | _channels_stream_presenter(new QWidget(this)), 17 | _followed_stream_presenter(new QWidget(this)) 18 | { 19 | _ui->setupUi(this); 20 | 21 | new FlowLayout(_channels_stream_presenter); 22 | _ui->channelsStreamArea->setWidget(_channels_stream_presenter); 23 | new FlowLayout(_followed_stream_presenter); 24 | _ui->followedStreamArea->setWidget(_followed_stream_presenter); 25 | 26 | QSettings settings; 27 | 28 | auto access_token = settings 29 | .value(constants::settings::oauth::ACCESS_TOKEN_KEY) 30 | .toString(); 31 | 32 | fetch_streams(_api.top_streams(), _channels_stream_presenter); 33 | 34 | if (!access_token.isEmpty()) 35 | fetch_streams(_api.followed_streams(access_token), _followed_stream_presenter); 36 | 37 | QObject::connect(_ui->searchBox, &QLineEdit::textChanged, [this](auto query) { 38 | if (query.isEmpty()) 39 | fetch_streams(_api.top_streams(), _channels_stream_presenter); 40 | else 41 | fetch_streams(_api.stream_search(query), _channels_stream_presenter); 42 | }); 43 | 44 | QObject::connect(_ui->searchBox, &QLineEdit::returnPressed, [this] { 45 | channel_picked(_ui->searchBox->text()); 46 | }); 47 | } 48 | 49 | StreamPicker::~StreamPicker() = default; 50 | 51 | void StreamPicker::focusInEvent(QFocusEvent *event) { 52 | QWidget::focusInEvent(event); 53 | _ui->searchBox->setFocus(); 54 | } 55 | 56 | void StreamPicker::channel_picked(QString channel) { 57 | using namespace constants::settings::streams; 58 | 59 | QSettings settings; 60 | auto quality = settings 61 | .value(KEY_LAST_QUALITY_FOR(channel)) 62 | .toString(); 63 | 64 | emit stream_picked(channel, quality); 65 | } 66 | 67 | void StreamPicker::fetch_streams(TwitchAPI::streams_response_t query, QWidget *container) { 68 | auto & current_query = current_queries[container]; 69 | 70 | if (current_query) 71 | current_query->cancel(); 72 | 73 | auto [token, cquery] = make_cancellable(query); 74 | 75 | auto present_streams_on_container = [=](QList stream_data) { 76 | present_streams(container, stream_data); 77 | }; 78 | 79 | cquery.then(present_streams_on_container); 80 | 81 | current_query = token; 82 | } 83 | 84 | void StreamPicker::present_streams(QWidget *container, QList stream_data) { 85 | auto layout = container->layout(); 86 | 87 | QLayoutItem *item; 88 | while ((item = layout->takeAt(0)) != nullptr) { 89 | item->widget()->deleteLater(); 90 | delete item; 91 | } 92 | 93 | for (auto data: stream_data) { 94 | auto stream_card = new StreamCard(data, container); 95 | layout->addWidget(stream_card); 96 | QObject::connect(stream_card, &StreamCard::clicked, [this](auto channel) { 97 | channel_picked(channel); 98 | }); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/.gitignore: -------------------------------------------------------------------------------- 1 | _book 2 | dist 3 | node_modules 4 | *.gcno 5 | *.gcda 6 | *.moc 7 | *.o 8 | *.obj 9 | *.exe 10 | *.user 11 | Makefile* 12 | moc_*.cpp 13 | moc_*.h 14 | coverage.info 15 | package-lock.json 16 | target_wrapper.bat 17 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | language: cpp 4 | compiler: gcc 5 | 6 | before_install: 7 | - sudo add-apt-repository -y ppa:beineri/opt-qt542-trusty 8 | - sudo apt-get update -qq 9 | 10 | install: 11 | - sudo apt-get install -qq qt54base 12 | - source /opt/qt54/bin/qt54-env.sh 13 | - wget http://archive.ubuntu.com/ubuntu/pool/universe/l/lcov/lcov_1.13.orig.tar.gz 14 | - tar xf lcov_1.13.orig.tar.gz 15 | - cd lcov-1.13/ 16 | - sudo make install 17 | - cd .. 18 | 19 | before_script: 20 | - qmake --version 21 | - lcov --version 22 | - gcc --version 23 | 24 | script: 25 | - qmake qtpromise.pro CONFIG+=coverage 26 | - make -j4 27 | - make check --quiet 28 | - lcov -capture --directory . --o coverage.info 29 | - lcov -e coverage.info '**/src/**/*' -o coverage.info 30 | 31 | after_success: 32 | - bash <(curl -s https://codecov.io/bash) -f coverage.info 33 | 34 | 35 | # gitbook install . 36 | # gitbook build . dist/docs 37 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Simon Brunel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/README.md: -------------------------------------------------------------------------------- 1 | Promises/A+ 2 | 3 | # QtPromise 4 | [![qpm](https://img.shields.io/github/release/simonbrunel/qtpromise.svg?style=flat-square&label=qpm&colorB=4CAF50)](https://www.qpm.io/packages/com.github.simonbrunel.qtpromise/index.html) [![Travis](https://img.shields.io/travis/simonbrunel/qtpromise/master.svg?style=flat-square)](https://travis-ci.org/simonbrunel/qtpromise) [![coverage](https://img.shields.io/codecov/c/github/simonbrunel/qtpromise.svg?style=flat-square)](https://codecov.io/gh/simonbrunel/qtpromise) 5 | 6 | [Promises/A+](https://promisesaplus.com/) implementation for [Qt/C++](https://www.qt.io/). 7 | 8 | Requires [Qt 5.4](https://www.qt.io/download/) (or later) with [C++11 support enabled](https://wiki.qt.io/How_to_use_C++11_in_your_Qt_Projects). 9 | 10 | ## Documentation 11 | 12 | * [Getting Started](https://qtpromise.netlify.com/qtpromise/getting-started) 13 | * [Thread-Safety](https://qtpromise.netlify.com/qtpromise/thread-safety) 14 | * [QtConcurrent](https://qtpromise.netlify.com/qtpromise/qtconcurrent) 15 | * [API Reference](https://qtpromise.netlify.com/qtpromise/api-reference) 16 | 17 | ## License 18 | QtPromise is available under the [MIT license](LICENSE). 19 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/book.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "QtPromise", 3 | "description": "Promises/A+ implementation for Qt/C++", 4 | "author": "Simon Brunel", 5 | "gitbook": "3.2.3", 6 | "root": "docs", 7 | "plugins": [ 8 | "-lunr", 9 | "-search", 10 | "search-plus", 11 | "anchorjs", 12 | "edit-link", 13 | "expand-active-chapter", 14 | "ga", 15 | "github" 16 | ], 17 | "pluginsConfig": { 18 | "anchorjs": { 19 | "icon": "#", 20 | "placement": "left", 21 | "visible": "always" 22 | }, 23 | "edit-link": { 24 | "base": "https://github.com/simonbrunel/qtpromise/edit/master/docs" 25 | }, 26 | "ga": { 27 | "token": "UA-113899811-1", 28 | "configuration": "auto" 29 | }, 30 | "github": { 31 | "url": "https://github.com/simonbrunel/qtpromise" 32 | }, 33 | "theme-default": { 34 | "showLevel": false, 35 | "styles": { 36 | "website": "assets/style.css" 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/README.md: -------------------------------------------------------------------------------- 1 | Promises/A+ 2 | 3 | # QtPromise 4 | [Promises/A+](https://promisesaplus.com/) implementation for [Qt/C++](https://www.qt.io/). 5 | 6 | Requires [Qt 5.4](https://www.qt.io/download/) (or later) with [C++11 support enabled](https://wiki.qt.io/How_to_use_C++11_in_your_Qt_Projects). 7 | 8 | ## QtPromise for C++ 9 | * [Getting Started](qtpromise/getting-started.md) 10 | * [Thread-Safety](qtpromise/thread-safety.md) 11 | * [QtConcurrent](qtpromise/qtconcurrent.md) 12 | * [API Reference](qtpromise/api-reference.md) 13 | 14 | ## License 15 | QtPromise is available under the [MIT license](https://github.com/simonbrunel/qtpromise/blob/master/LICENSE). 16 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | ### QtPromise for C++ 2 | * [Getting Started](qtpromise/getting-started.md) 3 | * [QtConcurrent](qtpromise/qtconcurrent.md) 4 | * [Thread-Safety](qtpromise/thread-safety.md) 5 | * [API Reference](qtpromise/api-reference.md) 6 | * [QPromise](qtpromise/qpromise/constructor.md) 7 | * [.delay](qtpromise/qpromise/delay.md) 8 | * [.fail](qtpromise/qpromise/fail.md) 9 | * [.finally](qtpromise/qpromise/finally.md) 10 | * [.isFulfilled](qtpromise/qpromise/isfulfilled.md) 11 | * [.isPending](qtpromise/qpromise/ispending.md) 12 | * [.isRejected](qtpromise/qpromise/isrejected.md) 13 | * [.tap](qtpromise/qpromise/tap.md) 14 | * [.then](qtpromise/qpromise/then.md) 15 | * [.timeout](qtpromise/qpromise/timeout.md) 16 | * [.wait](qtpromise/qpromise/wait.md) 17 | * [::all (static)](qtpromise/qpromise/all.md) 18 | * [::reject (static)](qtpromise/qpromise/reject.md) 19 | * [::resolve (static)](qtpromise/qpromise/resolve.md) 20 | * [qPromise](qtpromise/helpers/qpromise.md) 21 | * [qPromiseAll](qtpromise/helpers/qpromiseall.md) 22 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/assets/style.css: -------------------------------------------------------------------------------- 1 | a.anchorjs-link { 2 | color: rgba(65, 131, 196, 0.1); 3 | font-weight: 400; 4 | text-decoration: none; 5 | transition: color 100ms ease-out; 6 | z-index: 999; 7 | } 8 | 9 | a.anchorjs-link:hover { 10 | color: rgba(65, 131, 196, 1); 11 | } 12 | 13 | sup { 14 | font-size: 0.75em !important; 15 | } 16 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/qtpromise/api-reference.md: -------------------------------------------------------------------------------- 1 | ## QPromise 2 | 3 | ### Public Members 4 | 5 | * [`QPromise::QPromise`](qpromise/constructor.md) 6 | * [`QPromise::delay`](qpromise/delay.md) 7 | * [`QPromise::fail`](qpromise/fail.md) 8 | * [`QPromise::finally`](qpromise/finally.md) 9 | * [`QPromise::isFulfilled`](qpromise/isfulfilled.md) 10 | * [`QPromise::isPending`](qpromise/ispending.md) 11 | * [`QPromise::isRejected`](qpromise/isrejected.md) 12 | * [`QPromise::tap`](qpromise/tap.md) 13 | * [`QPromise::then`](qpromise/then.md) 14 | * [`QPromise::timeout`](qpromise/timeout.md) 15 | * [`QPromise::wait`](qpromise/wait.md) 16 | 17 | ### Public Static Members 18 | 19 | * [`[static] QPromise::all`](qpromise/all.md) 20 | * [`[static] QPromise::reject`](qpromise/reject.md) 21 | * [`[static] QPromise::resolve`](qpromise/resolve.md) 22 | 23 | ## Helpers 24 | 25 | * [`qPromise`](helpers/qpromise.md) 26 | * [`qPromiseAll`](helpers/qpromiseall.md) 27 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/qtpromise/getting-started.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | QtPromise is a [header-only](https://en.wikipedia.org/wiki/Header-only) library, simply download the [latest release](https://github.com/simonbrunel/qtpromise/releases/latest) (or [`git submodule`](https://git-scm.com/docs/git-submodule)) and include `qtpromise.pri` from your project `.pro`. 4 | 5 | ## qpm 6 | 7 | Alternatively and **only** if your project relies on [qpm](https://www.qpm.io/), you can install QtPromise as follow: 8 | 9 | ```bash 10 | qpm install com.github.simonbrunel.qtpromise 11 | ``` 12 | 13 | ## Usage 14 | 15 | The recommended way to use QtPromise is to include the single module header: 16 | 17 | ```cpp 18 | #include 19 | ``` 20 | 21 | ## Example 22 | 23 | Let's first make the code more readable by using the library namespace: 24 | 25 | ```cpp 26 | using namespace QtPromise; 27 | ``` 28 | 29 | This `download` function creates a [promise from callbacks](qpromise/constructor.md) which will be resolved when the network request is finished: 30 | 31 | ```cpp 32 | QPromise download(const QUrl& url) 33 | { 34 | return QPromise([&]( 35 | const QPromiseResolve& resolve, 36 | const QPromiseReject& reject) { 37 | 38 | QNetworkReply* reply = manager->get(QNetworkRequest(url)); 39 | QObject::connect(reply, &QNetworkReply::finished, [=]() { 40 | if (reply->error() == QNetworkReply::NoError) { 41 | resolve(reply->readAll()); 42 | } else { 43 | reject(reply->error()); 44 | } 45 | 46 | reply->deleteLater(); 47 | }); 48 | }); 49 | } 50 | ``` 51 | 52 | The following method `uncompress` data in a separate thread and returns a [promise from QFuture](qtconcurrent.md): 53 | 54 | ```cpp 55 | QPromise uncompress(const QByteArray& data) 56 | { 57 | return qPromise(QtConcurrent::run([](const QByteArray& data) { 58 | Entries entries; 59 | 60 | // {...} uncompress data and parse content. 61 | 62 | if (error) { 63 | throw MalformedException(); 64 | } 65 | 66 | return entries; 67 | }, data)); 68 | } 69 | ``` 70 | 71 | It's then easy to chain the whole asynchronous process using promises: 72 | - initiate the promise chain by downloading a specific URL, 73 | - [`then`](qpromise/then.md) *and only if download succeeded*, uncompress received data, 74 | - [`then`](qpromise/then.md) validate and process the uncompressed entries, 75 | - [`finally`](qpromise/finally.md) perform operations whatever the process succeeded or failed, 76 | - and handle specific errors using [`fail`](qpromise/fail.md). 77 | 78 | ```cpp 79 | download(url).then(&uncompress).then([](const Entries& entries) { 80 | if (entries.isEmpty()) { 81 | throw UpdateException("No entries"); 82 | } 83 | // {...} process entries 84 | }).finally([]() { 85 | // {...} cleanup 86 | }).fail([](QNetworkReply::NetworkError err) { 87 | // {...} handle network error 88 | }).fail([](const UpdateException& err) { 89 | // {...} handle update error 90 | }).fail([]() { 91 | // {...} catch all 92 | }); 93 | ``` 94 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/qtpromise/helpers/qpromise.md: -------------------------------------------------------------------------------- 1 | ## `qPromise` 2 | 3 | ``` 4 | qPromise(T value) -> QPromise 5 | ``` 6 | 7 | Similar to the [`QPromise::resolve`](../qpromise/resolve.md) static method, creates a promise resolved from a given `value` without the extra typing: 8 | 9 | ```cpp 10 | auto promise = qPromise(); // QPromise 11 | auto promise = qPromise(42); // QPromise 12 | auto promise = qPromise(QString("foo")); // QPromise 13 | ``` 14 | 15 | This method also allows to convert `QFuture` to `QPromise` delayed until the `QFuture` is finished ([read more](../qtconcurrent.md#convert)). 16 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/qtpromise/helpers/qpromiseall.md: -------------------------------------------------------------------------------- 1 | ## `qPromiseAll` 2 | 3 | ``` 4 | qPromiseAll(Sequence> promises) -> QPromise> 5 | qPromiseAll(Sequence> promises) -> QPromise 6 | ``` 7 | 8 | This method simply calls the appropriated [`QPromise::all`](../qpromise/all.md) static method based on the given `QVector` type. In some cases, this method is more convenient than the static one since it avoid some extra typing: 9 | 10 | ```cpp 11 | QVector > promises{...} 12 | 13 | auto output = qPromiseAll(promises); 14 | // eq. QPromise::all(promises) 15 | ``` 16 | 17 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/qtpromise/qpromise/all.md: -------------------------------------------------------------------------------- 1 | ## `[static] QPromise::all` 2 | 3 | ``` 4 | [static] QPromise::all(Sequence> promises) -> QPromise> 5 | ``` 6 | 7 | Returns a `QPromise>` that fulfills when **all** `promises` of (the same) type `T` have been fulfilled. The `output` value is a vector containing **all** the values of `promises`, in the same order. If any of the given `promises` fail, `output` immediately rejects with the error of the promise that rejected, whether or not the other promises are resolved. 8 | 9 | `Sequence` is any STL compatible container (eg. `QVector`, `QList`, `std::vector`, etc.) 10 | 11 | ```cpp 12 | QVector > promises{ 13 | download(QUrl("http://a...")), 14 | download(QUrl("http://b...")), 15 | download(QUrl("http://c...")) 16 | }; 17 | 18 | auto output = QPromise::all(promises); 19 | 20 | // output type: QPromise> 21 | output.then([](const QVector& res) { 22 | // {...} 23 | }); 24 | ``` 25 | 26 | See also: [`qPromiseAll`](../helpers/qpromiseall.md) 27 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/qtpromise/qpromise/constructor.md: -------------------------------------------------------------------------------- 1 | ## `QPromise::QPromise` 2 | 3 | ``` 4 | QPromise::QPromise(Function resolver) 5 | ``` 6 | 7 | Creates a new promise that will be fulfilled or rejected by the given `resolver` lambda: 8 | 9 | ```cpp 10 | QPromise promise([](const QPromiseResolve& resolve, const QPromiseReject& reject) { 11 | async_method([=](bool success, int result) { 12 | if (success) { 13 | resolve(result); 14 | } else { 15 | reject(customException()); 16 | } 17 | }); 18 | }); 19 | ``` 20 | 21 | > **Note:** `QPromise` is specialized to not contain any value, meaning that the `resolve` callback takes no argument. 22 | 23 | **C++14** 24 | 25 | ```cpp 26 | QPromise promise([](const auto& resolve, const auto& reject) { 27 | // {...} 28 | }); 29 | ``` 30 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/qtpromise/qpromise/delay.md: -------------------------------------------------------------------------------- 1 | ## `QPromise::delay` 2 | 3 | ``` 4 | QPromise::delay(int msec) -> QPromise 5 | ``` 6 | 7 | This method returns a promise that will be fulfilled with the same value as the `input` promise and after at least `msec` milliseconds. If the `input` promise is rejected, the `output` promise is immediately rejected with the same reason. 8 | 9 | ```cpp 10 | QPromise input = {...} 11 | auto output = input.delay(2000).then([](int res) { 12 | // called 2 seconds after `input` is fulfilled 13 | }); 14 | ``` 15 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/qtpromise/qpromise/fail.md: -------------------------------------------------------------------------------- 1 | ## `QPromise::fail` 2 | 3 | ``` 4 | QPromise::fail(Function onRejected) -> QPromise 5 | ``` 6 | 7 | Shorthand to `promise.then(nullptr, onRejected)`, similar to the [`catch` statement](http://en.cppreference.com/w/cpp/language/try_catch): 8 | 9 | ```cpp 10 | promise.fail([](const MyException&) { 11 | // {...} 12 | }).fail(const QException&) { 13 | // {...} 14 | }).fail(const std::exception&) { 15 | // {...} 16 | }).fail() { 17 | // {...} catch-all 18 | }); 19 | ``` 20 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/qtpromise/qpromise/finally.md: -------------------------------------------------------------------------------- 1 | ## `QPromise::finally` 2 | 3 | ``` 4 | QPromise::finally(Function handler) -> QPromise 5 | ``` 6 | 7 | This `handler` is **always** called, without any argument and whatever the `input` promise state (fulfilled or rejected). The `output` promise has the same type as the `input` one but also the same value or error. The finally `handler` **can not modify the fulfilled value** (the returned value is ignored), however, if `handler` throws, `output` is rejected with the new exception. 8 | 9 | ```cpp 10 | auto output = input.finally([]() { 11 | // {...} 12 | }); 13 | ``` 14 | 15 | If `handler` returns a promise (or QFuture), the `output` promise is delayed until the returned promise is resolved and under the same conditions: the delayed value is ignored, the error transmitted to the `output` promise. 16 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/qtpromise/qpromise/isfulfilled.md: -------------------------------------------------------------------------------- 1 | # `QPromise::isFulfilled` 2 | 3 | ``` 4 | QPromise::isFulfilled() -> bool 5 | ``` 6 | 7 | Returns `true` if the promise is fulfilled, otherwise returns `false`. 8 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/qtpromise/qpromise/ispending.md: -------------------------------------------------------------------------------- 1 | # `QPromise::isPending` 2 | 3 | ``` 4 | QPromise::isPending() -> bool 5 | ``` 6 | 7 | Returns `true` if the promise is pending (not fulfilled or rejected), otherwise returns `false`. 8 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/qtpromise/qpromise/isrejected.md: -------------------------------------------------------------------------------- 1 | # `QPromise::isRejected` 2 | 3 | ``` 4 | QPromise::isRejected() -> bool 5 | ``` 6 | 7 | Returns `true` if the promise is rejected, otherwise returns `false`. 8 | 9 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/qtpromise/qpromise/reject.md: -------------------------------------------------------------------------------- 1 | ## `[static] QPromise::reject` 2 | 3 | ``` 4 | [static] QPromise::reject(any reason) -> QPromise 5 | ``` 6 | 7 | Creates a `QPromise` that is rejected with the given `reason` of *whatever type*: 8 | 9 | ```cpp 10 | QPromise compute(const QString& type) 11 | { 12 | if (type == "foobar") { 13 | return QPromise::reject(QString("Unknown type: %1").arg(type)); 14 | } 15 | 16 | return QPromise([](const QPromiseResolve& resolve) { 17 | // {...} 18 | }); 19 | } 20 | ``` 21 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/qtpromise/qpromise/resolve.md: -------------------------------------------------------------------------------- 1 | ## `[static] QPromise::resolve` 2 | 3 | ``` 4 | [static] QPromise::resolve(T value) -> QPromise 5 | ``` 6 | 7 | Creates a `QPromise` that is fulfilled with the given `value` of type `T`: 8 | 9 | ```cpp 10 | QPromise compute(const QString& type) 11 | { 12 | if (type == "magic") { 13 | return QPromise::resolve(42); 14 | } 15 | 16 | return QPromise([](const QPromiseResolve& resolve) { 17 | // {...} 18 | }); 19 | } 20 | ``` 21 | 22 | See also: [`qPromise`](../helpers/qpromise.md) 23 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/qtpromise/qpromise/tap.md: -------------------------------------------------------------------------------- 1 | ## `QPromise::tap` 2 | 3 | ``` 4 | QPromise::tap(Function handler) -> QPromise 5 | ``` 6 | 7 | This `handler` allows to observe the value of the `input` promise, without changing the propagated value. The `output` promise will be resolved with the same value as the `input` promise (the `handler` returned value will be ignored). However, if `handler` throws, `output` is rejected with the new exception. Unlike [`finally`](finally.md), this handler is **not** called for rejections. 8 | 9 | ```cpp 10 | QPromise input = {...} 11 | auto output = input.tap([](int res) { 12 | log(res); 13 | }).then([](int res) { 14 | // {...} 15 | }); 16 | ``` 17 | 18 | If `handler` returns a promise (or QFuture), the `output` promise is delayed until the returned promise is resolved and under the same conditions: the delayed value is ignored, the error transmitted to the `output` promise. 19 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/qtpromise/qpromise/then.md: -------------------------------------------------------------------------------- 1 | ## `QPromise::then` 2 | 3 | ``` 4 | QPromise::then(Function onFulfilled, Function onRejected) -> QPromise 5 | QPromise::then(Function onFulfilled) -> QPromise 6 | ``` 7 | 8 | See [Promises/A+ `.then`](https://promisesaplus.com/#the-then-method) for details. 9 | 10 | ```cpp 11 | QPromise input = ... 12 | auto output = input.then([](int res) { 13 | // called with the 'input' result if fulfilled 14 | }, [](const ReasonType& reason) { 15 | // called with the 'input' reason if rejected 16 | // see QPromise::fail for details 17 | }); 18 | ``` 19 | 20 | > **Note**: `onRejected` handler is optional, `output` will be rejected with the same reason as `input`. 21 | 22 | > **Note**: it's recommended to use the [`fail`](fail.md) shorthand to handle errors. 23 | 24 | The type `` of the `output` promise depends on the return type of the `onFulfilled` handler: 25 | 26 | ```cpp 27 | QPromise input = {...} 28 | auto output = input.then([](int res) { 29 | return QString::number(res); // -> QPromise 30 | }); 31 | 32 | // output type: QPromise 33 | output.then([](const QString& res) { 34 | // {...} 35 | }); 36 | ``` 37 | 38 | > **Note**: only `onFulfilled` can change the promise type, `onRejected` **must** return the same type as `onFulfilled`. That also means if `onFulfilled` is `nullptr`, `onRejected` must return the same type as the `input` promise. 39 | 40 | ```cpp 41 | QPromise input = ... 42 | auto output = input.then([](int res) { 43 | return res + 4; 44 | }, [](const ReasonType& reason) { 45 | return -1; 46 | }); 47 | ``` 48 | 49 | If `onFulfilled` doesn't return any value, the `output` type is `QPromise`: 50 | 51 | ```cpp 52 | QPromise input = ... 53 | auto output = input.then([](int res) { 54 | // {...} 55 | }); 56 | 57 | // output type: QPromise 58 | output.then([]() { 59 | // `QPromise` `onFulfilled` handler has no argument 60 | }); 61 | ``` 62 | 63 | You can also decide to skip the promise result by omitting the handler argument: 64 | 65 | ```cpp 66 | QPromise input = {...} 67 | auto output = input.then([]( /* skip int result */ ) { 68 | // {...} 69 | }); 70 | ``` 71 | 72 | The `output` promise can be *rejected* by throwing an exception in either `onFulfilled` or `onRejected`: 73 | 74 | ```cpp 75 | QPromise input = {...} 76 | auto output = input.then([](int res) { 77 | if (res == -1) { 78 | throw ReasonType(); 79 | } else { 80 | return res; 81 | } 82 | }); 83 | 84 | // output.isRejected() is true 85 | ``` 86 | 87 | If an handler returns a promise (or QFuture), the `output` promise is delayed and will be resolved by the returned promise. 88 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/qtpromise/qpromise/timeout.md: -------------------------------------------------------------------------------- 1 | ## `QPromise::timeout` 2 | 3 | ``` 4 | QPromise::timeout(int msec, any error = QPromiseTimeoutException) -> QPromise 5 | ``` 6 | 7 | This method returns a promise that will be resolved with the `input` promise's fulfillment value or rejection reason. However, if the `input` promise is not fulfilled or rejected within `msec` milliseconds, the `output` promise is rejected with `error` as the reason (`QPromiseTimeoutException` by default). 8 | 9 | ```cpp 10 | QPromise input = {...} 11 | auto output = input.timeout(2000) 12 | .then([](int res) { 13 | // operation succeeded within 2 seconds 14 | }) 15 | .fail([](const QPromiseTimeoutException& e) { 16 | // operation timed out! 17 | }); 18 | ``` 19 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/qtpromise/qpromise/wait.md: -------------------------------------------------------------------------------- 1 | ## `QPromise::wait` 2 | 3 | ``` 4 | QPromise::wait() -> QPromise 5 | ``` 6 | 7 | This method holds the execution of the remaining code until the `input` promise is resolved (either fulfilled or rejected), **without** blocking the event loop of the current thread: 8 | 9 | ```cpp 10 | int result = -1; 11 | 12 | QPromise input = qPromise(QtConcurrent::run([]() { 13 | return 42; 14 | })).tap([&](int res) { 15 | result = res; 16 | }); 17 | 18 | // input.isPending() is true && result is -1 19 | 20 | input.wait(); 21 | 22 | // input.isPending() is false && result is 42 23 | ``` 24 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/qtpromise/qtconcurrent.md: -------------------------------------------------------------------------------- 1 | ## QtConcurrent 2 | 3 | QtPromise integrates with [QtConcurrent](https://doc.qt.io/qt-5/qtconcurrent-index.html) to make easy chaining QFuture with QPromise. 4 | 5 | ## Convert 6 | 7 | Converting `QFuture` to `QPromise` is done using the [`qPromise`](helpers/qpromise.md) helper: 8 | 9 | ```cpp 10 | QFuture future = QtConcurrent::run([]() { 11 | // {...} 12 | return 42; 13 | }); 14 | 15 | QPromise promise = qPromise(future); 16 | ``` 17 | 18 | or simply: 19 | 20 | ```cpp 21 | auto promise = qPromise(QtConcurrent::run([]() { 22 | // {...} 23 | })); 24 | ``` 25 | 26 | ## Chain 27 | 28 | Returning a `QFuture` in [`then`](qpromise/then.md) or [`fail`](qpromise/fail.md) automatically translate to `QPromise`: 29 | 30 | ```cpp 31 | QPromise input = ... 32 | auto output = input.then([](int res) { 33 | return QtConcurrent::run([]() { 34 | // {...} 35 | return QString("42"); 36 | }); 37 | }); 38 | 39 | // output type: QPromise 40 | output.then([](const QString& res) { 41 | // {...} 42 | }); 43 | ``` 44 | 45 | The `output` promise is resolved when the `QFuture` is [finished](https://doc.qt.io/qt-5/qfuture.html#isFinished). 46 | 47 | ## Error 48 | 49 | Exceptions thrown from a QtConcurrent thread reject the associated promise with the exception as the reason. Note that if you throw an exception that is not a subclass of `QException`, the promise with be rejected with [`QUnhandledException`](https://doc.qt.io/qt-5/qunhandledexception.html#details) (this restriction only applies to exceptions thrown from a QtConcurrent thread, [read more](https://doc.qt.io/qt-5/qexception.html#details)). 50 | 51 | ```cpp 52 | QPromise promise = ... 53 | promise.then([](int res) { 54 | return QtConcurrent::run([]() { 55 | // {...} 56 | 57 | if (!success) { 58 | throw CustomException(); 59 | } 60 | 61 | return QString("42"); 62 | }); 63 | }).fail(const CustomException& err) { 64 | // {...} 65 | }); 66 | ``` 67 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/docs/qtpromise/thread-safety.md: -------------------------------------------------------------------------------- 1 | ## Thread-Safety 2 | 3 | QPromise is thread-safe and can be copied and accessed across different threads. QPromise relies on [explicitly data sharing](https://doc.qt.io/qt-5/qexplicitlyshareddatapointer.html#details) and thus `auto p2 = p1` represents the same promise: when `p1` resolves, handlers registered on `p1` and `p2` are called, the fulfilled value being shared between both instances. 4 | 5 | > **Note:** while it's safe to access the resolved value from different threads using [`then`](qpromise/then.md), QPromise provides no guarantee about the object being pointed to. Thread-safety and reentrancy rules for that object still apply. 6 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/include/QtPromise: -------------------------------------------------------------------------------- 1 | #ifndef QTPROMISE_MODULE_H 2 | #define QTPROMISE_MODULE_H 3 | 4 | #include "../src/qtpromise/qpromise.h" 5 | #include "../src/qtpromise/qpromisefuture.h" 6 | #include "../src/qtpromise/qpromisehelpers.h" 7 | 8 | #endif // ifndef QTPROMISE_MODULE_H 9 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/package/features/qtpromise.prf: -------------------------------------------------------------------------------- 1 | include(../../qtpromise.pri) 2 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/qpm.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.github.simonbrunel.qtpromise", 3 | "description": "Promises/A+ implementation for Qt/C++", 4 | "author": { 5 | "name": "Simon Brunel", 6 | "email": "simonbrunel@users.noreply.github.com" 7 | }, 8 | "repository": { 9 | "type": "GITHUB", 10 | "url": "https://github.com/simonbrunel/qtpromise.git" 11 | }, 12 | "version": { 13 | "label": "0.3.0" 14 | }, 15 | "license": "MIT", 16 | "pri_filename": "qtpromise.pri", 17 | "webpage": "https://github.com/simonbrunel/qtpromise" 18 | } 19 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/qtpromise.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD/include $$PWD/src 2 | DEPENDPATH += $$PWD/include $$PWD/src 3 | CONFIG += c++11 4 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/qtpromise.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | SUBDIRS = \ 3 | tests 4 | 5 | _qt_creator_ { 6 | SUBDIRS += src 7 | } 8 | 9 | OTHER_FILES = \ 10 | package/features/*.prf \ 11 | include/* \ 12 | qtpromise.pri 13 | -------------------------------------------------------------------------------- /player/vendor/qtpromise-0.3.0/src/qtpromise/qpromise.h: -------------------------------------------------------------------------------- 1 | #ifndef QTPROMISE_QPROMISE_H 2 | #define QTPROMISE_QPROMISE_H 3 | 4 | // QtPromise 5 | #include "qpromise_p.h" 6 | #include "qpromiseglobal.h" 7 | 8 | // Qt 9 | #include 10 | 11 | namespace QtPromise { 12 | 13 | template 14 | class QPromiseBase 15 | { 16 | public: 17 | using Type = T; 18 | 19 | template ::count == 1, int>::type = 0> 20 | inline QPromiseBase(F resolver); 21 | 22 | template ::count != 1, int>::type = 0> 23 | inline QPromiseBase(F resolver); 24 | 25 | QPromiseBase(const QPromiseBase& other): m_d(other.m_d) {} 26 | QPromiseBase(const QPromise& other): m_d(other.m_d) {} 27 | QPromiseBase(QPromiseBase&& other) Q_DECL_NOEXCEPT { swap(other); } 28 | 29 | virtual ~QPromiseBase() { } 30 | 31 | QPromiseBase& operator=(const QPromiseBase& other) { m_d = other.m_d; return *this;} 32 | QPromiseBase& operator=(QPromiseBase&& other) Q_DECL_NOEXCEPT 33 | { QPromiseBase(std::move(other)).swap(*this); return *this; } 34 | 35 | bool operator==(const QPromiseBase& other) const { return (m_d == other.m_d); } 36 | bool operator!=(const QPromiseBase& other) const { return (m_d != other.m_d); } 37 | 38 | void swap(QPromiseBase& other) Q_DECL_NOEXCEPT { qSwap(m_d, other.m_d); } 39 | 40 | bool isFulfilled() const { return m_d->isFulfilled(); } 41 | bool isRejected() const { return m_d->isRejected(); } 42 | bool isPending() const { return m_d->isPending(); } 43 | 44 | template 45 | inline typename QtPromisePrivate::PromiseHandler::Promise 46 | then(const TFulfilled& fulfilled, const TRejected& rejected) const; 47 | 48 | template 49 | inline typename QtPromisePrivate::PromiseHandler::Promise 50 | then(TFulfilled&& fulfilled) const; 51 | 52 | template 53 | inline typename QtPromisePrivate::PromiseHandler::Promise 54 | fail(TRejected&& rejected) const; 55 | 56 | template 57 | inline QPromise finally(THandler handler) const; 58 | 59 | template 60 | inline QPromise tap(THandler handler) const; 61 | 62 | template 63 | inline QPromise timeout(int msec, E&& error = E()) const; 64 | 65 | inline QPromise delay(int msec) const; 66 | inline QPromise wait() const; 67 | 68 | public: // STATIC 69 | template 70 | inline static QPromise reject(E&& error); 71 | 72 | protected: 73 | friend struct QtPromisePrivate::PromiseFulfill >; 74 | friend class QPromiseResolve; 75 | friend class QPromiseReject; 76 | 77 | QExplicitlySharedDataPointer > m_d; 78 | }; 79 | 80 | template 81 | class QPromise : public QPromiseBase 82 | { 83 | public: 84 | template 85 | QPromise(F&& resolver): QPromiseBase(std::forward(resolver)) { } 86 | 87 | public: // STATIC 88 | template