├── .gitignore ├── QmlFireworks ├── README.md ├── QmlFireworks_zh_CN.qm ├── resource │ ├── background.png │ └── res.qrc ├── src │ ├── src.pri │ ├── qml │ │ ├── qml.qrc │ │ └── main.qml │ └── main.cpp ├── QmlFireworks_zh_CN.ts └── QmlFireworks.pro ├── QmlDemo ├── QmlDemo_zh_CN.qm ├── src │ ├── src.pri │ └── customItem │ │ ├── customItem.pri │ │ ├── clickwaveeffect.h │ │ └── clickwaveeffect.cpp ├── qml.qrc ├── res.qrc ├── README.md ├── QmlDemo.pro ├── main.cpp ├── QmlDemo_zh_CN.ts └── main.qml ├── VideoPlayer ├── resource │ ├── back.png │ ├── file.png │ ├── play.png │ ├── stop.png │ ├── pause.png │ ├── volume.png │ ├── goahead.png │ ├── settings.png │ ├── full_screen.png │ ├── non-volume.png │ ├── exit_full_screen.png │ └── res.qrc ├── screenshot │ └── run.png ├── VideoPlayer_zh_CN.qm ├── shaders │ ├── shaders.qrc │ ├── vertex.vsh │ └── fragment.fsh ├── src │ ├── src.pri │ ├── util │ │ ├── util.pri │ │ ├── keyboardcontrollor.h │ │ └── keyboardcontrollor.cpp │ ├── player │ │ ├── player.pri │ │ ├── config.h │ │ ├── ffmpeg.h │ │ ├── audiooutput.h │ │ ├── videoplayer_p.h │ │ ├── videorenderer.h │ │ ├── videoplayer_p.cpp │ │ ├── audiooutput.cpp │ │ ├── videoplayer.h │ │ ├── ffmpegdecoder.h │ │ └── videoplayer.cpp │ └── main.cpp ├── qml │ ├── qml.qrc │ ├── PlaySlider.qml │ └── Toast.qml ├── README.md ├── VideoPlayer.pro └── VideoPlayer_zh_CN.ts ├── CustomWidgetDemos ├── CustomWidgetDemos_zh_CN.qm ├── resource │ └── image │ │ └── close.png ├── screenshot │ ├── CustomWidgetDemos_1.png │ └── CustomWidgetDemos_2.png ├── res.qrc ├── src │ ├── src.pri │ ├── main.cpp │ ├── customWidgets │ │ ├── customWidgets.pri │ │ ├── countdownbutton.h │ │ ├── countdownbutton.cpp │ │ ├── progressdial.h │ │ ├── progressdial.cpp │ │ ├── aligniconbutton.cpp │ │ ├── progressbutton.cpp │ │ ├── notifymanager.h │ │ ├── toast.h │ │ ├── rotatestackedwidget.h │ │ ├── notifywidget.h │ │ ├── progressbutton.h │ │ ├── aligniconbutton.h │ │ ├── translationstackedwidget.h │ │ ├── notifymanager.cpp │ │ ├── toast.cpp │ │ ├── rotatestackedwidget.cpp │ │ ├── notifywidget.cpp │ │ └── translationstackedwidget.cpp │ ├── mainwidget.h │ └── mainwidget.cpp ├── CustomWidgetDemos.pro ├── README.md └── CustomWidgetDemos_zh_CN.ts ├── MultithreadedDownloader ├── MultithreadedDownloader_zh_CN.qm ├── image │ ├── MultithreadedDownloader_1.png │ └── MultithreadedDownloader_2.png ├── res.qrc ├── src │ ├── customWidgets │ │ ├── customWidgets.pri │ │ ├── toast.h │ │ ├── translationstackedwidget.h │ │ ├── toast.cpp │ │ └── translationstackedwidget.cpp │ ├── src.pri │ ├── downloader │ │ ├── downloader.pri │ │ ├── util.h │ │ ├── multithreadeddownloaderwriter.cpp │ │ ├── abstractmission.h │ │ ├── multithreadeddownloaderwriter.h │ │ ├── downloadmission.h │ │ ├── downloadmission.cpp │ │ ├── multithreadeddownloader.h │ │ └── multithreadeddownloader.cpp │ ├── mainwidget.h │ ├── main.cpp │ ├── mainwidget.cpp │ └── mainwidget.ui ├── MultithreadedDownloader.pro ├── README.md └── MultithreadedDownloader_zh_CN.ts ├── QtDemos.pro ├── LICENSE ├── README.CN.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.pro.user* -------------------------------------------------------------------------------- /QmlFireworks/README.md: -------------------------------------------------------------------------------- 1 | # QmlFireworks 2 | * 一个基于 `QML Particle System` 的烟花 Demo。 -------------------------------------------------------------------------------- /QmlDemo/QmlDemo_zh_CN.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/QmlDemo/QmlDemo_zh_CN.qm -------------------------------------------------------------------------------- /VideoPlayer/resource/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/VideoPlayer/resource/back.png -------------------------------------------------------------------------------- /VideoPlayer/resource/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/VideoPlayer/resource/file.png -------------------------------------------------------------------------------- /VideoPlayer/resource/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/VideoPlayer/resource/play.png -------------------------------------------------------------------------------- /VideoPlayer/resource/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/VideoPlayer/resource/stop.png -------------------------------------------------------------------------------- /VideoPlayer/resource/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/VideoPlayer/resource/pause.png -------------------------------------------------------------------------------- /VideoPlayer/resource/volume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/VideoPlayer/resource/volume.png -------------------------------------------------------------------------------- /VideoPlayer/screenshot/run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/VideoPlayer/screenshot/run.png -------------------------------------------------------------------------------- /QmlFireworks/QmlFireworks_zh_CN.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/QmlFireworks/QmlFireworks_zh_CN.qm -------------------------------------------------------------------------------- /VideoPlayer/VideoPlayer_zh_CN.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/VideoPlayer/VideoPlayer_zh_CN.qm -------------------------------------------------------------------------------- /VideoPlayer/resource/goahead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/VideoPlayer/resource/goahead.png -------------------------------------------------------------------------------- /VideoPlayer/resource/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/VideoPlayer/resource/settings.png -------------------------------------------------------------------------------- /QmlDemo/src/src.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | DEPENDPATH += $$PWD 3 | 4 | include($$PWD/customItem/customItem.pri) 5 | -------------------------------------------------------------------------------- /QmlFireworks/resource/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/QmlFireworks/resource/background.png -------------------------------------------------------------------------------- /VideoPlayer/resource/full_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/VideoPlayer/resource/full_screen.png -------------------------------------------------------------------------------- /VideoPlayer/resource/non-volume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/VideoPlayer/resource/non-volume.png -------------------------------------------------------------------------------- /QmlDemo/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | 5 | 6 | -------------------------------------------------------------------------------- /QmlFireworks/src/src.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | DEPENDPATH += $$PWD 3 | 4 | SOURCES += \ 5 | $$PWD/main.cpp 6 | -------------------------------------------------------------------------------- /VideoPlayer/resource/exit_full_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/VideoPlayer/resource/exit_full_screen.png -------------------------------------------------------------------------------- /CustomWidgetDemos/CustomWidgetDemos_zh_CN.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/CustomWidgetDemos/CustomWidgetDemos_zh_CN.qm -------------------------------------------------------------------------------- /CustomWidgetDemos/resource/image/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/CustomWidgetDemos/resource/image/close.png -------------------------------------------------------------------------------- /QmlFireworks/src/qml/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | 5 | 6 | -------------------------------------------------------------------------------- /QmlDemo/res.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | QmlDemo_zh_CN.qm 4 | 5 | 6 | -------------------------------------------------------------------------------- /QmlFireworks/resource/res.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | background.png 4 | 5 | 6 | -------------------------------------------------------------------------------- /CustomWidgetDemos/screenshot/CustomWidgetDemos_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/CustomWidgetDemos/screenshot/CustomWidgetDemos_1.png -------------------------------------------------------------------------------- /CustomWidgetDemos/screenshot/CustomWidgetDemos_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/CustomWidgetDemos/screenshot/CustomWidgetDemos_2.png -------------------------------------------------------------------------------- /MultithreadedDownloader/MultithreadedDownloader_zh_CN.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/MultithreadedDownloader/MultithreadedDownloader_zh_CN.qm -------------------------------------------------------------------------------- /MultithreadedDownloader/image/MultithreadedDownloader_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/MultithreadedDownloader/image/MultithreadedDownloader_1.png -------------------------------------------------------------------------------- /MultithreadedDownloader/image/MultithreadedDownloader_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/QtDemos/HEAD/MultithreadedDownloader/image/MultithreadedDownloader_2.png -------------------------------------------------------------------------------- /MultithreadedDownloader/res.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | MultithreadedDownloader_zh_CN.qm 4 | 5 | 6 | -------------------------------------------------------------------------------- /QtDemos.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += \ 4 | CustomWidgetDemos \ 5 | MultithreadedDownloader \ 6 | QmlDemo \ 7 | QmlFireworks \ 8 | VideoPlayer 9 | -------------------------------------------------------------------------------- /VideoPlayer/shaders/shaders.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | fragment.fsh 4 | vertex.vsh 5 | 6 | 7 | -------------------------------------------------------------------------------- /QmlDemo/src/customItem/customItem.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | DEPENDPATH += $$PWD 3 | 4 | HEADERS += \ 5 | $$PWD/clickwaveeffect.h 6 | 7 | SOURCES += \ 8 | $$PWD/clickwaveeffect.cpp 9 | -------------------------------------------------------------------------------- /VideoPlayer/src/src.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | DEPENDPATH += $$PWD 3 | 4 | SOURCES += \ 5 | $$PWD/main.cpp 6 | 7 | include($$PWD/player/player.pri) 8 | include($$PWD/util/util.pri) 9 | -------------------------------------------------------------------------------- /VideoPlayer/src/util/util.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | DEPENDPATH += $$PWD 3 | 4 | HEADERS += \ 5 | $$PWD/keyboardcontrollor.h 6 | 7 | SOURCES += \ 8 | $$PWD/keyboardcontrollor.cpp 9 | -------------------------------------------------------------------------------- /VideoPlayer/qml/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | PlaySlider.qml 5 | Toast.qml 6 | 7 | 8 | -------------------------------------------------------------------------------- /CustomWidgetDemos/res.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | CustomWidgetDemos_zh_CN.qm 4 | 5 | 6 | resource/image/close.png 7 | 8 | 9 | -------------------------------------------------------------------------------- /VideoPlayer/shaders/vertex.vsh: -------------------------------------------------------------------------------- 1 | #version 440 2 | layout (location = 0) in vec2 vertex; 3 | layout (location = 1) in vec2 texCoord; 4 | 5 | out vec2 v_texCoord; 6 | void main(void) 7 | { 8 | gl_Position = vec4(vertex, 0, 1.0f); 9 | v_texCoord = texCoord; 10 | } 11 | -------------------------------------------------------------------------------- /MultithreadedDownloader/src/customWidgets/customWidgets.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | DEPENDPATH += $$PWD 3 | 4 | HEADERS += \ 5 | $$PWD/toast.h \ 6 | $$PWD/translationstackedwidget.h 7 | 8 | SOURCES += \ 9 | $$PWD/toast.cpp \ 10 | $$PWD/translationstackedwidget.cpp 11 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/src.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | DEPENDPATH += $$PWD 3 | 4 | SOURCES += \ 5 | $$PWD/main.cpp \ 6 | $$PWD/mainwidget.cpp 7 | 8 | HEADERS += \ 9 | $$PWD/mainwidget.h 10 | 11 | FORMS += \ 12 | $$PWD/mainwidget.ui 13 | 14 | include($$PWD/customWidgets/customWidgets.pri) 15 | -------------------------------------------------------------------------------- /MultithreadedDownloader/src/src.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | DEPENDPATH += $$PWD 3 | 4 | SOURCES += \ 5 | $$PWD/main.cpp \ 6 | $$PWD/mainwidget.cpp 7 | 8 | HEADERS += \ 9 | $$PWD/mainwidget.h 10 | 11 | FORMS += \ 12 | $$PWD/mainwidget.ui 13 | 14 | include($$PWD/downloader/downloader.pri) 15 | include($$PWD/customWidgets/customWidgets.pri) 16 | -------------------------------------------------------------------------------- /QmlFireworks/QmlFireworks_zh_CN.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | main 6 | 7 | 8 | Fireworks 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /MultithreadedDownloader/src/downloader/downloader.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | DEPENDPATH += $$PWD 3 | 4 | HEADERS += \ 5 | $$PWD/abstractmission.h \ 6 | $$PWD/downloadmission.h \ 7 | $$PWD/multithreadeddownloader.h \ 8 | $$PWD/multithreadeddownloaderwriter.h \ 9 | $$PWD/util.h 10 | 11 | SOURCES += \ 12 | $$PWD/downloadmission.cpp \ 13 | $$PWD/multithreadeddownloader.cpp \ 14 | $$PWD/multithreadeddownloaderwriter.cpp 15 | -------------------------------------------------------------------------------- /VideoPlayer/src/player/player.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | DEPENDPATH += $$PWD 3 | 4 | HEADERS += \ 5 | $$PWD/audiooutput.h \ 6 | $$PWD/config.h \ 7 | $$PWD/ffmpeg.h \ 8 | $$PWD/ffmpegdecoder.h \ 9 | $$PWD/videoplayer.h \ 10 | $$PWD/videoplayer_p.h \ 11 | $$PWD/videorenderer.h 12 | 13 | SOURCES += \ 14 | $$PWD/audiooutput.cpp \ 15 | $$PWD/ffmpegdecoder.cpp \ 16 | $$PWD/videoplayer.cpp \ 17 | $$PWD/videoplayer_p.cpp \ 18 | $$PWD/videorenderer.cpp 19 | -------------------------------------------------------------------------------- /VideoPlayer/README.md: -------------------------------------------------------------------------------- 1 | # Video Player 2 | * A video player based on `Qt`, using `FFmpeg` for decoding and `OpenGL` for rendering. 3 | 4 | * Screenshot 5 | ![image](./screenshot/run.png) 6 | 7 | ## Featrues 8 | - [x] Play audio and video. 9 | - [x] Play progress control. 10 | - [x] Play volume control. 11 | - [x] Subtitle support. 12 | - [x] `.ass` subtitle support. 13 | - [x] `Bitmap` subtitle support. 14 | - [x] Subtitle track select. 15 | - [x] Audio track select. 16 | - [x] Play internet stream 17 | -------------------------------------------------------------------------------- /VideoPlayer/resource/res.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | file.png 4 | play.png 5 | pause.png 6 | back.png 7 | goahead.png 8 | full_screen.png 9 | exit_full_screen.png 10 | stop.png 11 | volume.png 12 | non-volume.png 13 | settings.png 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief Demo 3 | * @anchor Ho229 4 | * @date 2020/12/12 5 | */ 6 | 7 | #include "mainwidget.h" 8 | 9 | #include 10 | #include 11 | 12 | int main(int argc, char *argv[]) 13 | { 14 | QApplication a(argc, argv); 15 | 16 | #ifdef Q_OS_WIN 17 | a.setFont(QFont("Microsoft YaHei", 9)); 18 | #endif 19 | 20 | QTextCodec::setCodecForLocale( // Set codec to UTF-8 21 | QTextCodec::codecForName("UTF-8")); 22 | 23 | MainWidget w; 24 | w.show(); 25 | return a.exec(); 26 | } 27 | -------------------------------------------------------------------------------- /VideoPlayer/src/player/config.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief Static Configurations 3 | * @anchor Ho 229 4 | * @date 2023/4/21 5 | */ 6 | 7 | #ifndef CONFIG_H 8 | #define CONFIG_H 9 | 10 | #define VIDEO_CACHE_SIZE 256 11 | #define AUDIO_CACHE_SIZE 256 12 | #define SUBTITLE_CACHE_SIZE 64 13 | 14 | // FFmpegDecoder::decode() will be called asynchronously 15 | // when the time difference between the current frame and the last cached frame 16 | // is less than MIN_DECODED_DURATION, in seconds 17 | #define MIN_DECODED_DURATION 0.25 18 | 19 | #define MAX_DECODED_DURATION MIN_DECODED_DURATION * 2 20 | 21 | #endif // CONFIG_H 22 | -------------------------------------------------------------------------------- /VideoPlayer/src/util/keyboardcontrollor.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief keyboard Controllor 3 | * @author Ho 229 4 | */ 5 | 6 | #ifndef KEYBOARDCONTROLLOR_H 7 | #define KEYBOARDCONTROLLOR_H 8 | 9 | #include 10 | 11 | class KeyboardControllor : public QObject 12 | { 13 | Q_OBJECT 14 | public: 15 | explicit KeyboardControllor(QObject *parent = nullptr); 16 | 17 | signals: 18 | void play(); 19 | void goahead(); 20 | void back(); 21 | void escape(); 22 | 23 | private: 24 | bool eventFilter(QObject *obj, QEvent *event) Q_DECL_OVERRIDE; 25 | }; 26 | 27 | #endif // KEYBOARDCONTROLLOR_H 28 | -------------------------------------------------------------------------------- /QmlFireworks/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 7 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 8 | #endif 9 | 10 | QGuiApplication app(argc, argv); 11 | 12 | QQmlApplicationEngine engine; 13 | const QUrl url(QStringLiteral("qrc:/main.qml")); 14 | QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, 15 | &app, [url](QObject *obj, const QUrl &objUrl) { 16 | if (!obj && url == objUrl) 17 | QCoreApplication::exit(-1); 18 | }, Qt::QueuedConnection); 19 | engine.load(url); 20 | 21 | return app.exec(); 22 | } 23 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/customWidgets/customWidgets.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | DEPENDPATH += $$PWD 3 | 4 | HEADERS += \ 5 | $$PWD/aligniconbutton.h \ 6 | $$PWD/countdownbutton.h \ 7 | $$PWD/notifymanager.h \ 8 | $$PWD/notifywidget.h \ 9 | $$PWD/progressbutton.h \ 10 | $$PWD/progressdial.h \ 11 | $$PWD/rotatestackedwidget.h \ 12 | $$PWD/toast.h \ 13 | $$PWD/translationstackedwidget.h 14 | 15 | SOURCES += \ 16 | $$PWD/aligniconbutton.cpp \ 17 | $$PWD/countdownbutton.cpp \ 18 | $$PWD/notifymanager.cpp \ 19 | $$PWD/notifywidget.cpp \ 20 | $$PWD/progressbutton.cpp \ 21 | $$PWD/progressdial.cpp \ 22 | $$PWD/rotatestackedwidget.cpp \ 23 | $$PWD/toast.cpp \ 24 | $$PWD/translationstackedwidget.cpp 25 | -------------------------------------------------------------------------------- /VideoPlayer/src/player/ffmpeg.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief FFmpeg Library 3 | * @anchor Ho 229 4 | * @date 2021/4/10 5 | */ 6 | 7 | #ifndef FFMPEG_H 8 | #define FFMPEG_H 9 | 10 | extern "C" 11 | { 12 | #ifdef __cplusplus 13 | # define __STDC_CONSTANT_MACROS 14 | # ifdef _STDINT_H 15 | # undef _STDINT_H 16 | # endif 17 | # include 18 | #endif 19 | 20 | #ifndef INT64_C 21 | #define INT64_C(c) (c ## LL) 22 | #define UINT64_C(c) (c ## ULL) 23 | #endif 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | } 34 | 35 | #endif // FFMPEG_H 36 | -------------------------------------------------------------------------------- /QmlDemo/README.md: -------------------------------------------------------------------------------- 1 | # QmlDemo 2 | * Just a QML Demo. 3 | ## Items 4 | * [ClickWaveEffect](./src/customItem/clickwaveeffect.h) 5 | * Add [ClickWaveEffect](./src/customItem/clickwaveeffect.h) class to your project. 6 | * Example 7 | ```cpp 8 | // main.cpp 9 | ... 10 | #include "clickwaveeffect" 11 | 12 | int main(int argc, char*[] argv) 13 | { 14 | ... 15 | qmlRegisterType("com.MyItems.Effect", 1, 0, "ClickWaveEffect"); 16 | ... 17 | } 18 | ``` 19 | ```qml 20 | // main.qml 21 | import QtQuick 2.12 22 | import com.MyItems.Effect 1.0 23 | 24 | Rectangle { 25 | id: root 26 | 27 | ClickWaveEffect { 28 | target: root 29 | 30 | anchors.fill: parent 31 | } 32 | } 33 | ``` -------------------------------------------------------------------------------- /QmlDemo/QmlDemo.pro: -------------------------------------------------------------------------------- 1 | QT += quick 2 | 3 | CONFIG += c++11 4 | 5 | # You can make your code fail to compile if it uses deprecated APIs. 6 | # In order to do so, uncomment the following line. 7 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 8 | 9 | SOURCES += \ 10 | main.cpp 11 | 12 | RESOURCES += qml.qrc \ 13 | res.qrc 14 | 15 | TRANSLATIONS += \ 16 | QmlDemo_zh_CN.ts 17 | 18 | include($$_PRO_FILE_PWD_/src/src.pri) 19 | 20 | # Additional import path used to resolve QML modules in Qt Creator's code model 21 | QML_IMPORT_PATH = 22 | 23 | # Additional import path used to resolve QML modules just for Qt Quick Designer 24 | QML_DESIGNER_IMPORT_PATH = 25 | 26 | # Default rules for deployment. 27 | qnx: target.path = /tmp/$${TARGET}/bin 28 | else: unix:!android: target.path = /opt/$${TARGET}/bin 29 | !isEmpty(target.path): INSTALLS += target 30 | -------------------------------------------------------------------------------- /QmlFireworks/QmlFireworks.pro: -------------------------------------------------------------------------------- 1 | QT += quick 2 | 3 | CONFIG += c++11 4 | 5 | # You can make your code fail to compile if it uses deprecated APIs. 6 | # In order to do so, uncomment the following line. 7 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 8 | 9 | include($$_PRO_FILE_PWD_/src/src.pri) 10 | 11 | RESOURCES += $$_PRO_FILE_PWD_/src/qml/qml.qrc \ 12 | $$_PRO_FILE_PWD_/resource/res.qrc 13 | 14 | TRANSLATIONS += \ 15 | QmlFireworks_zh_CN.ts 16 | 17 | # Additional import path used to resolve QML modules in Qt Creator's code model 18 | QML_IMPORT_PATH = 19 | 20 | # Additional import path used to resolve QML modules just for Qt Quick Designer 21 | QML_DESIGNER_IMPORT_PATH = 22 | 23 | # Default rules for deployment. 24 | qnx: target.path = /tmp/$${TARGET}/bin 25 | else: unix:!android: target.path = /opt/$${TARGET}/bin 26 | !isEmpty(target.path): INSTALLS += target 27 | -------------------------------------------------------------------------------- /VideoPlayer/src/util/keyboardcontrollor.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief keyboard Controllor 3 | * @author Ho 229 4 | */ 5 | 6 | #include "keyboardcontrollor.h" 7 | 8 | #include 9 | 10 | KeyboardControllor::KeyboardControllor(QObject *parent) : QObject(parent) 11 | { 12 | 13 | } 14 | 15 | bool KeyboardControllor::eventFilter(QObject *obj, QEvent *event) 16 | { 17 | if(event->type() == QEvent::KeyPress) 18 | { 19 | QKeyEvent *keyEvent = static_cast(event); 20 | 21 | switch(keyEvent->key()) 22 | { 23 | case Qt::Key_Space: 24 | emit play(); 25 | break; 26 | case Qt::Key_Left: 27 | emit back(); 28 | break; 29 | case Qt::Key_Right: 30 | emit goahead(); 31 | break; 32 | case Qt::Key_Escape: 33 | emit escape(); 34 | break; 35 | } 36 | } 37 | 38 | return QObject::eventFilter(obj, event); 39 | } 40 | -------------------------------------------------------------------------------- /MultithreadedDownloader/src/downloader/util.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief Until 3 | * @anchor NiceBlueChai 4 | * @date 2020/07/16 5 | */ 6 | 7 | #ifndef UTIL_H 8 | #define UTIL_H 9 | 10 | #include 11 | 12 | namespace Until { 13 | 14 | /** 15 | * @brief readableFileSize 16 | * @param value 17 | * @param precision 18 | * @return readable size 19 | */ 20 | QString readableFileSize(const qint64 value, int precision = 2) 21 | { 22 | qint64 kbSize = value / 1024; 23 | if (kbSize > 1024) 24 | { 25 | qreal mbRet = static_cast(kbSize) / 1024.0; 26 | 27 | if (mbRet - 1024.0 > 0.000001) 28 | { 29 | qreal gbRet = mbRet / 1024.0; 30 | return QString::number(gbRet, 'f', precision) + "GB"; 31 | } 32 | else 33 | return QString::number(mbRet, 'f', precision) + "MB"; 34 | } 35 | else 36 | return QString::number(kbSize) + "KB"; 37 | } 38 | } 39 | 40 | #endif // UTIL_H 41 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/customWidgets/countdownbutton.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Countdown Button 3 | * @brief 倒计时 Button 4 | * @anchor Ho229<2189684957@qq.com> 5 | * @date 2021/2/1 6 | */ 7 | 8 | #ifndef COUNTDOWNBUTTON_H 9 | #define COUNTDOWNBUTTON_H 10 | 11 | #include "progressbutton.h" 12 | 13 | class QPropertyAnimation; 14 | 15 | class CountdownButton : public ProgressButton 16 | { 17 | Q_OBJECT 18 | 19 | Q_PROPERTY(int countdown READ countdown WRITE setCountdown) 20 | 21 | public: 22 | explicit CountdownButton(QWidget *parent = nullptr); 23 | virtual ~CountdownButton() Q_DECL_OVERRIDE; 24 | 25 | /** 26 | * @brief 设置倒计时时长 (毫秒) 27 | */ 28 | void setCountdown(int ms); 29 | int countdown() const; 30 | 31 | /** 32 | * @brief 启动倒计时 33 | */ 34 | void conutdownCilk(); 35 | 36 | void conutdownCilk(int ms) 37 | { 38 | this->setCountdown(ms); 39 | this->conutdownCilk(); 40 | } 41 | 42 | private: 43 | QPropertyAnimation *m_animation = nullptr; 44 | 45 | }; 46 | 47 | #endif // COUNTDOWNBUTTON_H 48 | -------------------------------------------------------------------------------- /VideoPlayer/qml/PlaySlider.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 2.12 3 | 4 | Slider { 5 | id: slider 6 | 7 | background: Rectangle { 8 | x: slider.leftPadding 9 | y: slider.topPadding + slider.availableHeight / 2 - height / 2 10 | 11 | implicitWidth: 200 12 | implicitHeight: 5 13 | 14 | width: slider.availableWidth 15 | height: implicitHeight 16 | 17 | radius: 3 18 | color: "#bdbebf" 19 | 20 | Rectangle { 21 | width: slider.visualPosition * parent.width 22 | height: parent.height 23 | color: "#3F85FF" 24 | radius: 2 25 | } 26 | } 27 | 28 | handle: Rectangle { 29 | x: slider.leftPadding + slider.visualPosition * (slider.availableWidth - width) 30 | y: slider.topPadding + slider.availableHeight / 2 - height / 2 31 | implicitWidth: 18 32 | implicitHeight: 18 33 | radius: 13 34 | color: slider.pressed ? "#f0f0f0" : "#f6f6f6" 35 | border.color: "#bdbebf" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ho 229 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 | -------------------------------------------------------------------------------- /VideoPlayer/shaders/fragment.fsh: -------------------------------------------------------------------------------- 1 | #version 440 2 | in vec2 v_texCoord; 3 | out vec4 fragColor; 4 | 5 | layout (location = 0) uniform sampler2D texY; 6 | layout (location = 1) uniform sampler2D texU; 7 | layout (location = 2) uniform sampler2D texV; 8 | layout (location = 3) uniform sampler2D texSubtitle; 9 | 10 | layout (location = 4) uniform mat3 colorConversion; 11 | layout (location = 5) uniform bool is10Bit; 12 | 13 | void main(void) 14 | { 15 | vec3 yuv; 16 | yuv.x = texture(texY, v_texCoord).x; 17 | yuv.y = texture(texU, v_texCoord).x; 18 | yuv.z = texture(texV, v_texCoord).x; 19 | 20 | if(is10Bit) 21 | { 22 | vec3 yuv_h; 23 | yuv_h.x = texture(texY, v_texCoord).a; 24 | yuv_h.y = texture(texU, v_texCoord).a; 25 | yuv_h.z = texture(texV, v_texCoord).a; 26 | yuv = (yuv * 255.0 + yuv_h * 255.0 * 256.0) / 1023.0; 27 | } 28 | 29 | yuv -= vec3(16. / 255., 128. / 255., 128. / 255.); 30 | 31 | vec3 rgb = colorConversion * yuv; 32 | vec4 subtitle = texture(texSubtitle, v_texCoord); 33 | fragColor = mix(vec4(rgb, 1), subtitle, subtitle.a); 34 | } 35 | -------------------------------------------------------------------------------- /MultithreadedDownloader/MultithreadedDownloader.pro: -------------------------------------------------------------------------------- 1 | QT += core gui network 2 | 3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 4 | 5 | CONFIG += c++11 6 | 7 | # The following define makes your compiler emit warnings if you use 8 | # any Qt feature that has been marked deprecated (the exact warnings 9 | # depend on your compiler). Please consult the documentation of the 10 | # deprecated API in order to know how to port your code away from it. 11 | DEFINES += QT_DEPRECATED_WARNINGS 12 | 13 | # You can also make your code fail to compile if it uses deprecated APIs. 14 | # In order to do so, uncomment the following line. 15 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 16 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 17 | 18 | include($$_PRO_FILE_PWD_/src/src.pri) 19 | 20 | TRANSLATIONS += \ 21 | MultithreadedDownloader_zh_CN.ts 22 | 23 | # Default rules for deployment. 24 | qnx: target.path = /tmp/$${TARGET}/bin 25 | else: unix:!android: target.path = /opt/$${TARGET}/bin 26 | !isEmpty(target.path): INSTALLS += target 27 | 28 | RESOURCES += \ 29 | res.qrc 30 | -------------------------------------------------------------------------------- /CustomWidgetDemos/CustomWidgetDemos.pro: -------------------------------------------------------------------------------- 1 | QT += core gui 2 | 3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 4 | 5 | CONFIG += c++11 6 | 7 | # The following define makes your compiler emit warnings if you use 8 | # any Qt feature that has been marked deprecated (the exact warnings 9 | # depend on your compiler). Please consult the documentation of the 10 | # deprecated API in order to know how to port your code away from it. 11 | DEFINES += QT_DEPRECATED_WARNINGS 12 | 13 | # You can also make your code fail to compile if it uses deprecated APIs. 14 | # In order to do so, uncomment the following line. 15 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 16 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 17 | 18 | include($$_PRO_FILE_PWD_/src/src.pri) 19 | 20 | # Default rules for deployment. 21 | qnx: target.path = /tmp/$${TARGET}/bin 22 | else: unix:!android: target.path = /opt/$${TARGET}/bin 23 | !isEmpty(target.path): INSTALLS += target 24 | 25 | TRANSLATIONS += \ 26 | CustomWidgetDemos_zh_CN.ts 27 | 28 | RESOURCES += \ 29 | res.qrc 30 | 31 | DISTFILES += 32 | 33 | HEADERS += 34 | 35 | SOURCES += 36 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/customWidgets/countdownbutton.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Countdown Button 3 | * @brief 倒计时 Button 4 | * @anchor Ho229<2189684957@qq.com> 5 | * @date 2021/2/1 6 | */ 7 | 8 | #include "countdownbutton.h" 9 | 10 | #include 11 | 12 | CountdownButton::CountdownButton(QWidget *parent) : 13 | ProgressButton(parent), 14 | m_animation(new QPropertyAnimation(this, "value")) 15 | { 16 | m_animation->setDuration(2000); 17 | m_animation->setStartValue(this->minimun()); 18 | m_animation->setEndValue(this->maximun()); 19 | QObject::connect(m_animation, &QPropertyAnimation::finished, this, 20 | [this]{ 21 | this->setValue(0); 22 | this->click(); 23 | }); 24 | } 25 | 26 | CountdownButton::~CountdownButton() 27 | { 28 | 29 | } 30 | 31 | void CountdownButton::setCountdown(int ms) 32 | { 33 | m_animation->setDuration(ms); 34 | } 35 | 36 | int CountdownButton::countdown() const 37 | { 38 | return m_animation->duration(); 39 | } 40 | 41 | void CountdownButton::conutdownCilk() 42 | { 43 | if(m_animation->state() == QPropertyAnimation::Running) 44 | m_animation->stop(); 45 | 46 | m_animation->start(); 47 | } 48 | -------------------------------------------------------------------------------- /QmlDemo/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "clickwaveeffect.h" 7 | 8 | int main(int argc, char *argv[]) 9 | { 10 | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 11 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 12 | #endif 13 | 14 | QGuiApplication app(argc, argv); 15 | 16 | #ifdef Q_OS_WIN 17 | QGuiApplication::setFont(QFont("Microsoft YaHei", 12)); 18 | #endif 19 | 20 | QTranslator tr_CN; 21 | if(QLocale::system().language() == QLocale::Chinese) 22 | { 23 | tr_CN.load(":/translate/QmlDemo_zh_CN.qm"); 24 | app.installTranslator(&tr_CN); 25 | } 26 | 27 | qmlRegisterType("com.MyItems.Effect", 1, 0, "ClickWaveEffect"); 28 | 29 | QQmlApplicationEngine engine; 30 | const QUrl url(QStringLiteral("qrc:/main.qml")); 31 | QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, 32 | &app, [url](QObject *obj, const QUrl &objUrl) { 33 | if (!obj && url == objUrl) 34 | QCoreApplication::exit(-1); 35 | }, Qt::QueuedConnection); 36 | engine.load(url); 37 | 38 | return app.exec(); 39 | } 40 | -------------------------------------------------------------------------------- /README.CN.md: -------------------------------------------------------------------------------- 1 | # QtDemos 2 | 3 | [English](./README.md) | 简体中文 4 | 5 | * 这是一个关于 `Qt5` 的项目实践,未来会添加一些控件。欢迎大家留言评论,参考学习和测试。 6 | 7 | | 名称 | 描述 | 8 | | ---- | ---------------- | 9 | | [CustomWidgetDemos](./CustomWidgetDemos) | 自定义动画控件和使用示例 | 10 | | [MultithreadedDownloader](./MultithreadedDownloader) | 多线程下载器 | 11 | | [QmlDemo](./QmlDemo) | QML 学习项目 | 12 | | [QmlFireworks](./QmlFireworks) | QML 烟花 (粒子系统) Demo | 13 | | [VideoPlayer](./VideoPlayer) | 视频播放器 (使用了 `FFmpeg` 和 `OpenGL`) | 14 | 15 | ## 开发环境 16 | 17 | * 工具集 : `Qt 5.15.2` 18 | * 编译器 : `Microsoft Visual C++ 2019` , `GCC 10` 19 | 20 | ## 如何编译 21 | 22 | * 需要 `Qt5` 和 `FFmpeg` 环境。 23 | 24 | * 安装 Qt5。 25 | 26 | * 安装 FFmpeg。 27 | * Windows 28 | 29 | 下载 [FFmpeg(shared libraries)](https://github.com/BtbN/FFmpeg-Builds/releases),将库路径添加到系统环境变量 `FFMPEG_PATH`。 30 | 31 | * Linux 32 | 33 | ```shell 34 | sudo pacman -S ffmpeg # Arch 35 | sudo apt install ffmpeg libavfilter-dev # Debian 36 | ``` 37 | 38 | * 克隆仓库到本地并编译项目。 39 | 40 | ```shell 41 | git clone https://github.com/ho-229/QtDemos.git 42 | # or https://gitee.com/ho229/QtDemos.git 43 | cd QtDemos 44 | mkdir build 45 | cd build 46 | qmake .. 47 | make -j 48 | ``` 49 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/customWidgets/progressdial.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Progress Dial 3 | * @brief 进度圆盘按钮 4 | * @anchor Ho229 5 | * @date 2021/2/24 6 | */ 7 | 8 | #ifndef PROGRESSDIAL_H 9 | #define PROGRESSDIAL_H 10 | 11 | #include 12 | #include 13 | 14 | #define TransAngle(x) (x * 16) 15 | 16 | class ProgressDial : public QDial 17 | { 18 | Q_OBJECT 19 | public: 20 | explicit ProgressDial(QWidget *parent = nullptr); 21 | virtual ~ProgressDial() Q_DECL_OVERRIDE; 22 | 23 | /** 24 | * @brief 设置内圈颜色 25 | * @param color 26 | */ 27 | void setProgressColor(const QColor color){ m_pen.setColor(color); } 28 | 29 | /** 30 | * @return 内圈颜色 31 | */ 32 | QColor progressColor() const { return m_pen.color(); } 33 | 34 | /** 35 | * @brief 设置内圈宽度 36 | * @param width 37 | */ 38 | void setProgressWidth(qreal width){ m_pen.setWidthF(width); } 39 | 40 | /** 41 | * @return 内圈宽度 42 | */ 43 | qreal progressWidth() const { return m_pen.widthF(); } 44 | 45 | protected: 46 | virtual void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; 47 | 48 | private: 49 | QPen m_pen; 50 | 51 | inline void drawProgress(int start, int angle); 52 | 53 | }; 54 | 55 | #endif // PROGRESSDIAL_H 56 | -------------------------------------------------------------------------------- /VideoPlayer/src/player/audiooutput.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief Audio Output 3 | * @anchor Ho 229 4 | * @date 2021/5/1 5 | */ 6 | 7 | #ifndef AUDIOOUTPUT_H 8 | #define AUDIOOUTPUT_H 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | class QAudioOutput; 16 | class AudioDevice; 17 | 18 | class AudioOutput : public QObject 19 | { 20 | Q_OBJECT 21 | public: 22 | using Callback = std::function; 23 | 24 | explicit AudioOutput(const Callback &callback, QObject *parent = nullptr); 25 | ~AudioOutput() Q_DECL_OVERRIDE; 26 | 27 | /** 28 | * @brief Update audio output when the audio format changed 29 | */ 30 | void updateAudioOutput(const QAudioFormat &format); 31 | 32 | void setVolume(qreal volume); 33 | qreal volume() const; 34 | 35 | void play(); 36 | void pause(); 37 | void stop(); 38 | 39 | /** 40 | * @brief Reset the buffer of audio output 41 | */ 42 | void reset(); 43 | 44 | bool isLowDataLeft() const; 45 | qreal bufferDuration() const; 46 | 47 | private: 48 | QAudioOutput *m_output = nullptr; 49 | AudioDevice *m_audioDevice = nullptr; 50 | qreal m_bufferDuration = 0; 51 | }; 52 | 53 | #endif // AUDIOOUTPUT_H 54 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/mainwidget.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief Demo 3 | * @anchor Ho229 4 | * @date 2020/12/12 5 | */ 6 | 7 | #ifndef MAINWIDGET_H 8 | #define MAINWIDGET_H 9 | 10 | #include 11 | 12 | #include "customWidgets/toast.h" 13 | #include "customWidgets/notifymanager.h" 14 | 15 | class QTranslator; 16 | class QButtonGroup; 17 | class QAbstractButton; 18 | 19 | QT_BEGIN_NAMESPACE 20 | namespace Ui { class MainWidget; } 21 | QT_END_NAMESPACE 22 | 23 | class MainWidget : public QWidget 24 | { 25 | Q_OBJECT 26 | 27 | public: 28 | MainWidget(QWidget *parent = nullptr); 29 | ~MainWidget() Q_DECL_OVERRIDE; 30 | 31 | private slots: 32 | void on_toastBtn_clicked(); 33 | 34 | void on_buttonClicked(int id); 35 | 36 | void on_countdownStartBtn_clicked(); 37 | 38 | void on_countdownBtn_clicked(); 39 | 40 | void on_notifyBtn_clicked(); 41 | 42 | private: 43 | Ui::MainWidget *ui; 44 | 45 | Toast *m_toast = nullptr; 46 | 47 | QButtonGroup *m_buttonGroup_1 = nullptr; 48 | QButtonGroup *m_buttonGroup_2 = nullptr; 49 | QButtonGroup *m_langGroup = nullptr; 50 | 51 | NotifyManager *m_notifyManager = nullptr; 52 | 53 | QTranslator *m_trans = nullptr; 54 | 55 | inline void initUI(); 56 | }; 57 | #endif // MAINWIDGET_H 58 | -------------------------------------------------------------------------------- /MultithreadedDownloader/src/mainwidget.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief MainWidget 3 | * @anchor Ho229<2189684957@qq.com> 4 | * @date 2021/2/1 5 | */ 6 | 7 | #ifndef MAINWIDGET_H 8 | #define MAINWIDGET_H 9 | 10 | #include "multithreadeddownloader.h" 11 | 12 | #include "toast.h" 13 | 14 | #include 15 | 16 | QT_BEGIN_NAMESPACE 17 | namespace Ui { class MainWidget; } 18 | QT_END_NAMESPACE 19 | 20 | class MainWidget : public QWidget 21 | { 22 | Q_OBJECT 23 | 24 | public: 25 | MainWidget(QWidget *parent = nullptr); 26 | ~MainWidget() Q_DECL_OVERRIDE; 27 | 28 | void initThreadNumber(int num) 29 | { 30 | if(num > 0) 31 | m_downloader->setThreadCount(num); 32 | } 33 | 34 | void initDownloadUrl(const QString& url); 35 | 36 | private slots: 37 | void on_downloadBtn_clicked(); 38 | 39 | void on_startBtn_clicked(); 40 | 41 | void on_pauseBtn_clicked(); 42 | 43 | void on_stopBtn_clicked(); 44 | 45 | private: 46 | Ui::MainWidget *ui; 47 | 48 | MultithreadedDownloader* m_downloader = nullptr; 49 | 50 | Toast *m_toast = nullptr; 51 | 52 | qint64 m_old_progressed_bytes; 53 | 54 | qint64 m_oldProgressedBytes; 55 | 56 | inline void initUI(); 57 | inline void initSignalSlots(); 58 | }; 59 | #endif // MAINWIDGET_H 60 | -------------------------------------------------------------------------------- /VideoPlayer/qml/Toast.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | 3 | Rectangle { 4 | id: myToast 5 | 6 | visible: false 7 | 8 | property bool closeAvailable: true 9 | 10 | function toast() { 11 | if(showAnimation.running) 12 | return; 13 | else if(pauseTimer.running) 14 | { 15 | pauseTimer.restart(); 16 | return; 17 | } 18 | else if(hideAnimation.running) 19 | hideAnimation.stop(); 20 | 21 | opacity = 0; 22 | visible = true; 23 | showAnimation.start(); 24 | } 25 | 26 | OpacityAnimator { 27 | id: showAnimation 28 | 29 | target: myToast; 30 | from: 0 31 | to: 1 32 | duration: 300 33 | 34 | onFinished: pauseTimer.start(); 35 | } 36 | 37 | OpacityAnimator { 38 | id: hideAnimation 39 | 40 | target: myToast 41 | from: 1 42 | to: 0 43 | duration: 300 44 | 45 | onFinished: visible = false 46 | } 47 | 48 | Timer { 49 | id: pauseTimer 50 | 51 | interval: 3000 52 | 53 | onTriggered: { 54 | if(!closeAvailable) 55 | pauseTimer.restart(); 56 | else 57 | hideAnimation.start() 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /MultithreadedDownloader/src/downloader/multithreadeddownloaderwriter.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief MultithreadedDownloaderWriter 3 | * @anchor Ho229<2189684957@qq.com> 4 | * @date 2021/2/1 5 | */ 6 | 7 | #include "multithreadeddownloaderwriter.h" 8 | 9 | MultithreadedDownloaderWriter::MultithreadedDownloaderWriter(QObject *parent) 10 | : QThread(parent) 11 | { 12 | QDir::setCurrent("./"); 13 | } 14 | 15 | MultithreadedDownloaderWriter::~MultithreadedDownloaderWriter() 16 | { 17 | if(m_downloadFile.isOpen()) 18 | m_downloadFile.close(); 19 | } 20 | 21 | void MultithreadedDownloaderWriter::write(const QByteArray& data, const qint64 seek) 22 | { 23 | WriteMisson newMisson = { data, seek }; 24 | 25 | m_mutex.lock(); 26 | m_writeList.push_back(newMisson); 27 | m_mutex.unlock(); 28 | 29 | if(!this->isRunning()) // Stopped 30 | this->start(); 31 | } 32 | 33 | void MultithreadedDownloaderWriter::run() 34 | { 35 | do 36 | { 37 | m_downloadFile.seek(m_writeList.first().second); 38 | m_downloadFile.write(m_writeList.first().first); 39 | 40 | m_mutex.lock(); 41 | m_writeList.pop_front(); 42 | m_mutex.unlock(); 43 | 44 | if(m_writeList.isEmpty()) // Hold on !!! 45 | this->msleep(200); 46 | } 47 | while(!m_writeList.isEmpty()); 48 | } 49 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/customWidgets/progressdial.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Progress Dial 3 | * @brief 进度圆盘按钮 4 | * @anchor Ho229 5 | * @date 2021/2/24 6 | */ 7 | 8 | #include "progressdial.h" 9 | 10 | #include 11 | 12 | ProgressDial::ProgressDial(QWidget *parent) : QDial(parent) 13 | { 14 | // init Pen 15 | m_pen.setWidth(5); 16 | m_pen.setColor(QColor(55, 120, 249)); 17 | } 18 | 19 | ProgressDial::~ProgressDial() 20 | { 21 | 22 | } 23 | 24 | void ProgressDial::paintEvent(QPaintEvent *event) 25 | { 26 | QDial::paintEvent(event); 27 | 28 | if(this->wrapping()) 29 | this->drawProgress(-90, 360); 30 | else 31 | this->drawProgress(-120, 290); 32 | } 33 | 34 | void ProgressDial::drawProgress(int start, int angle) 35 | { 36 | int drawAngle = static_cast( 37 | static_cast(this->value()) / (this->maximum() - this->minimum()) * angle); 38 | if(drawAngle <= 0) 39 | return; 40 | 41 | QPainter painter(this); 42 | painter.setRenderHint(QPainter::Antialiasing); 43 | painter.setPen(m_pen); 44 | 45 | QRect rect; 46 | int min = qMin(this->width(), this->height()) - m_pen.width() * 2; 47 | min -= min / 6; 48 | rect.setSize(QSize(min, min)); 49 | rect.moveCenter(this->rect().center()); 50 | 51 | painter.drawArc(rect, TransAngle(start), TransAngle(start - drawAngle)); 52 | } 53 | -------------------------------------------------------------------------------- /VideoPlayer/VideoPlayer.pro: -------------------------------------------------------------------------------- 1 | QT += quick multimedia 2 | 3 | CONFIG += c++11 4 | 5 | # You can make your code fail to compile if it uses deprecated APIs. 6 | # In order to do so, uncomment the following line. 7 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 8 | 9 | RESOURCES += $$_PRO_FILE_PWD_/qml/qml.qrc \ 10 | resource/res.qrc \ 11 | shaders/shaders.qrc 12 | 13 | TRANSLATIONS += \ 14 | VideoPlayer_zh_CN.ts 15 | 16 | include($$_PRO_FILE_PWD_/src/src.pri) 17 | 18 | # Additional import path used to resolve QML modules in Qt Creator's code model 19 | QML_IMPORT_PATH = 20 | 21 | # Additional import path used to resolve QML modules just for Qt Quick Designer 22 | QML_DESIGNER_IMPORT_PATH = 23 | 24 | # Default rules for deployment. 25 | qnx: target.path = /tmp/$${TARGET}/bin 26 | else: unix:!android: target.path = /opt/$${TARGET}/bin 27 | !isEmpty(target.path): INSTALLS += target 28 | 29 | win32 { 30 | # FFmpeg 31 | LIBS += -L$$(FFMPEG_PATH)/lib/ -lavutil -lavcodec -lavformat -lavfilter -lswresample -lswscale 32 | INCLUDEPATH += $$(FFMPEG_PATH)/include 33 | DEPENDPATH += $$(FFMPEG_PATH)/include 34 | } 35 | 36 | unix { 37 | # FFmpeg 38 | LIBS += -L/usr/lib64/ -lavutil -lavcodec -lavformat -lavfilter -lswresample -lswscale 39 | INCLUDEPATH += /usr/include/ffmpeg 40 | DEPENDPATH += /usr/include/ffmpeg 41 | } 42 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/customWidgets/aligniconbutton.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief 左右 icon 对齐 Push Button 3 | * @anchor Ho229<2189684957@qq.com> 4 | * @date 2021/2/1 5 | */ 6 | 7 | #include "aligniconbutton.h" 8 | 9 | #include 10 | 11 | AlignIconButton::AlignIconButton(QWidget *parent) : QPushButton(parent) 12 | { 13 | 14 | } 15 | 16 | AlignIconButton::~AlignIconButton() 17 | { 18 | 19 | } 20 | 21 | void AlignIconButton::paintEvent(QPaintEvent *event) 22 | { 23 | QPushButton::paintEvent(event); 24 | 25 | QPainter painter(this); 26 | this->drawIcon(&painter); 27 | } 28 | 29 | void AlignIconButton::drawIcon(QPainter *painter) 30 | { 31 | QRect rect; 32 | rect.setSize(QSize(this->width() - m_sideMargin * 2, 33 | this->height() - m_topBottomMargin * 2)); 34 | rect.moveCenter(this->rect().center()); 35 | 36 | if(!m_leftIcon.isNull()) 37 | { 38 | m_leftIcon.paint(painter, rect, Qt::AlignLeft, 39 | this->isEnabled() ? QIcon::Normal : QIcon::Disabled, 40 | this->isChecked() ? QIcon::On : QIcon::Off); 41 | } 42 | 43 | if(!m_rightIcon.isNull()) 44 | { 45 | m_rightIcon.paint(painter, rect, Qt::AlignRight, 46 | this->isEnabled() ? QIcon::Normal : QIcon::Disabled, 47 | this->isChecked() ? QIcon::On : QIcon::Off); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/customWidgets/progressbutton.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Progress Button 3 | * @brief 进度条按钮 4 | * @anchor Ho229 5 | * @date 2021/2/22 6 | */ 7 | 8 | #include "progressbutton.h" 9 | 10 | #include 11 | 12 | ProgressButton::ProgressButton(QWidget *parent, const QString &style) : 13 | QPushButton(parent) 14 | { 15 | m_pen.setCapStyle(Qt::RoundCap); 16 | m_pen.setWidth(3); 17 | m_pen.setColor(QColor(69, 198, 214)); 18 | 19 | this->setStyleSheet(style); 20 | } 21 | 22 | ProgressButton::~ProgressButton() 23 | { 24 | 25 | } 26 | 27 | void ProgressButton::setValue(int value) 28 | { 29 | value = qMin(value, m_maximun); 30 | 31 | if(value == m_value) // No changed 32 | return; 33 | 34 | m_value = value; 35 | emit valueChanged(value); 36 | this->repaint(); 37 | } 38 | 39 | void ProgressButton::paintEvent(QPaintEvent *event) 40 | { 41 | QPushButton::paintEvent(event); 42 | 43 | int drawAngle = static_cast( 44 | static_cast(m_value) / (m_maximun - m_minimun) * 360); 45 | if(drawAngle <= 0) 46 | return; 47 | 48 | QPainter painter(this); 49 | painter.setRenderHint(QPainter::Antialiasing); 50 | painter.setPen(m_pen); 51 | 52 | QRect rect; 53 | int min = qMin(this->width(), this->height()) - m_pen.width() * 2; 54 | rect.setSize(QSize(min, min)); 55 | rect.moveCenter(this->rect().center()); 56 | 57 | painter.drawArc(rect, TransAngle(90), TransAngle(90 - drawAngle)); 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QtDemos 2 | 3 | English | [简体中文](./README.CN.md) 4 | 5 | * This is a small project about `Qt5`. More controls will be added in the future. Welcome to try it out and leave your comments. 6 | 7 | | Name | Description | 8 | | ---- | ---------------- | 9 | | [CustomWidgetDemos](./CustomWidgetDemos) | Custom animation controls and examples | 10 | | [MultithreadedDownloader](./MultithreadedDownloader) | Multi-threaded Downloader | 11 | | [QmlDemo](./QmlDemo) | QML Learning project | 12 | | [QmlFireworks](./QmlFireworks) | QML Fireworks (Particle System) Demo | 13 | | [VideoPlayer](./VideoPlayer) | Video Player (Use `FFmpeg` and `OpenGL`) | 14 | 15 | ## Development Environment 16 | 17 | * Tool Kit : `Qt 5.15.2`. 18 | * Complier : `Microsoft Visual C++ 2019` , `GCC 10`. 19 | 20 | ## Build 21 | 22 | * Require `Qt5` and `FFmpeg`. 23 | 24 | * Install Qt5. 25 | 26 | * Install FFmpeg. 27 | * Windows 28 | 29 | Download [FFmpeg(shared libraries)](https://github.com/BtbN/FFmpeg-Builds/releases) and add the path to your system environment variable `FFMPEG_PATH`. 30 | 31 | * Linux 32 | 33 | ```shell 34 | sudo pacman -S ffmpeg # Arch 35 | sudo apt install ffmpeg libavfilter-dev # Debian 36 | ``` 37 | 38 | * Clone this repository and build from source. 39 | 40 | ```shell 41 | git clone https://github.com/ho-229/QtDemos.git 42 | # or https://gitee.com/ho229/QtDemos.git 43 | cd QtDemos 44 | mkdir build 45 | cd build 46 | qmake .. 47 | make -j 48 | ``` 49 | -------------------------------------------------------------------------------- /MultithreadedDownloader/src/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @anchor Ho229<2189684957@qq.com> 3 | * @date 2021/2/1 4 | */ 5 | 6 | #include "mainwidget.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | QApplication a(argc, argv); 16 | 17 | #ifdef Q_OS_WIN 18 | a.setFont(QFont("Microsoft YaHei", 9)); 19 | #endif 20 | 21 | QTranslator tr_CN; 22 | if(QLocale::system().language() == QLocale::Chinese) 23 | { 24 | tr_CN.load(":/translations/MultithreadedDownloader_zh_CN.qm"); 25 | a.installTranslator(&tr_CN); 26 | } 27 | 28 | a.setApplicationName("Multithreaded Downloader"); 29 | 30 | QCommandLineOption threadNumOpt({"t", "thread-number"}, 31 | "Set the thread number for download.", 32 | "number", 33 | "0"); 34 | 35 | QCommandLineOption urlOpt({"u", "url"}, 36 | "The URL to download.", 37 | "URL"); 38 | 39 | QCommandLineParser parser; 40 | parser.setApplicationDescription("Qt Multithreaded Downloader Example."); 41 | parser.addHelpOption(); 42 | parser.addOption(urlOpt); 43 | parser.addOption(threadNumOpt); 44 | parser.process(a); 45 | 46 | MainWidget w; 47 | w.initThreadNumber(parser.value(threadNumOpt).toInt()); 48 | w.show(); 49 | w.initDownloadUrl(parser.value(urlOpt)); 50 | return a.exec(); 51 | } 52 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/customWidgets/notifymanager.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Notify Manager 3 | * @brief 右下角提示窗管理器 4 | * @anchor Ho229<2189684957@qq.com> 5 | * @date 2021/2/1 6 | */ 7 | 8 | #ifndef NOTIFYMANAGER_H 9 | #define NOTIFYMANAGER_H 10 | 11 | #include 12 | #include 13 | 14 | #include "notifywidget.h" 15 | 16 | typedef QPair // Show Time 18 | NotifyItem; 19 | 20 | class NotifyManager Q_DECL_FINAL : public QObject 21 | { 22 | Q_OBJECT 23 | public: 24 | explicit NotifyManager(QObject *parent = nullptr); 25 | ~NotifyManager() Q_DECL_OVERRIDE; 26 | 27 | /** 28 | * @brief 设置最大弹窗数 29 | */ 30 | void setMaximum(int value){ m_maximum = qMax(1, value); } 31 | int maximum() const { return m_maximum; } 32 | 33 | /** 34 | * @return 当前弹窗数 35 | */ 36 | int showCount() const { return m_showCount; } 37 | 38 | public slots: 39 | /** 40 | * @brief 弹出提示窗 41 | * @param parent 父对象 42 | * @param title 提示窗标题 43 | * @param message 提示消息 44 | * @param showTime 展示时间 ( 为 -1 时永久展示 ) 45 | */ 46 | NotifyWidget* notify(QWidget *parent, const QString& title, const QString& message, 47 | const QIcon& icon = QIcon(), int showTime = 5000); 48 | 49 | signals: 50 | void windowClosed(); 51 | 52 | private slots: 53 | void onNotifyClosed(); 54 | 55 | private: 56 | QList m_list; 57 | int m_maximum = 5; 58 | int m_showCount = 0; 59 | 60 | const QSize m_desktopSize; 61 | 62 | void updateNotifys(); 63 | 64 | }; 65 | 66 | #endif // NOTIFYMANAGER_H 67 | -------------------------------------------------------------------------------- /MultithreadedDownloader/src/downloader/abstractmission.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief AbstractMission 3 | * @anchor Ho229<2189684957@qq.com> 4 | * @date 2021/2/1 5 | */ 6 | 7 | #ifndef ABSTRACTMISSION_H 8 | #define ABSTRACTMISSION_H 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | class AbstractMission : public QObject 15 | { 16 | Q_OBJECT 17 | public: 18 | enum State 19 | { 20 | Running, 21 | Paused, 22 | Stopped 23 | }; 24 | 25 | virtual ~AbstractMission() Q_DECL_OVERRIDE = default; 26 | 27 | template 28 | void setUrl(const T& url) 29 | { 30 | m_url = url; 31 | if(!m_url.isValid()) 32 | qWarning("AbstractMission : url is not valid."); 33 | } 34 | 35 | QUrl url() const { return m_url; } 36 | 37 | bool isFinished() const { return m_isFinished; } 38 | 39 | virtual void start() = 0; 40 | virtual void pause() = 0; 41 | virtual void stop() = 0; 42 | 43 | State state() const { return m_state; } 44 | 45 | protected: 46 | explicit AbstractMission(QObject* parent = nullptr) : QObject(parent) {} 47 | 48 | inline void updateState(const State state) 49 | { 50 | m_state = state; 51 | emit stateChanged(state); 52 | } 53 | 54 | inline void setFinished(const bool isFinished = true) 55 | { 56 | m_isFinished = isFinished; 57 | 58 | if(isFinished) 59 | emit finished(); 60 | } 61 | 62 | QUrl m_url; 63 | State m_state = Stopped; 64 | bool m_isFinished = false; 65 | 66 | signals: 67 | void finished(); 68 | void stateChanged(const AbstractMission::State state); 69 | }; 70 | 71 | #endif // ABSTRACTMISSION_H 72 | -------------------------------------------------------------------------------- /VideoPlayer/VideoPlayer_zh_CN.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | main 6 | 7 | 8 | Video Player 9 | 10 | 11 | 12 | 13 | Volume: 14 | 15 | 16 | 17 | 18 | Settings 19 | 20 | 21 | 22 | 23 | Audio Track 24 | 25 | 26 | 27 | 28 | <font color='white'>Open file<br>or drop here</font> 29 | 30 | 31 | 32 | 33 | 34 | <font color='white'>Open file<br>Or drop here</font> 35 | 36 | 37 | 38 | 39 | <font color='white'>Release to open file</font> 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /MultithreadedDownloader/src/downloader/multithreadeddownloaderwriter.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief MultithreadedDownloaderWriter 3 | * @anchor Ho229<2189684957@qq.com> 4 | * @date 2021/2/1 5 | */ 6 | 7 | #ifndef MULTITHREADEDDOWNLOADERWRITER_H 8 | #define MULTITHREADEDDOWNLOADERWRITER_H 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | typedef QPair WriteMisson; 16 | 17 | class MultithreadedDownloaderWriter Q_DECL_FINAL : public QThread 18 | { 19 | Q_OBJECT 20 | public: 21 | explicit MultithreadedDownloaderWriter(QObject *parent = nullptr); 22 | ~MultithreadedDownloaderWriter() Q_DECL_OVERRIDE; 23 | 24 | /** 25 | * @brief 打开文件 26 | * @return 操作是否成功 27 | */ 28 | bool open() 29 | { return m_downloadFile.open(QFile::WriteOnly) && m_downloadFile.resize(m_fileSize); } 30 | 31 | /** 32 | * @brief 关闭文件 33 | */ 34 | void close() 35 | { m_downloadFile.close(); } 36 | 37 | /** 38 | * @brief 设置下载文件名 39 | * @param name 下载文件名 40 | */ 41 | void setFileName(const QString& name){ m_downloadFile.setFileName(name); } 42 | QString fileName() const { return m_downloadFile.fileName(); } 43 | 44 | /** 45 | * @brief 设置下载文件大小 46 | * @param size 下载文件大小 byte 47 | */ 48 | void setSize(const qint64 size){ m_fileSize = size; } 49 | qint64 size() const { return m_fileSize; } 50 | 51 | /** 52 | * @brief 添加写任务 53 | * @param data 写入数据 54 | * @param seek 写入偏移量 55 | */ 56 | void write(const QByteArray& data, const qint64 seek); 57 | 58 | private: 59 | void run() Q_DECL_OVERRIDE; 60 | 61 | qint64 m_fileSize = 0; 62 | QFile m_downloadFile; 63 | 64 | mutable QMutex m_mutex; 65 | QList m_writeList; 66 | }; 67 | 68 | #endif // MULTITHREADEDDOWNLOADERWRITER_H 69 | -------------------------------------------------------------------------------- /VideoPlayer/src/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief Main 3 | * @anchor Ho 229 4 | * @date 2021/4/10 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "videoplayer.h" 12 | #include "keyboardcontrollor.h" 13 | 14 | int main(int argc, char *argv[]) 15 | { 16 | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 17 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 18 | #endif 19 | 20 | QGuiApplication app(argc, argv); 21 | 22 | #ifdef Q_OS_WIN 23 | app.setFont(QFont("Microsoft YaHei", 9)); 24 | #endif 25 | 26 | app.setOrganizationName("Ho229"); 27 | app.setOrganizationDomain("https://github.com/ho229v3666/"); 28 | 29 | qmlRegisterType("com.multimedia.videoplayer", 1, 0, "VideoPlayer"); 30 | 31 | KeyboardControllor qmlKey; 32 | QQmlApplicationEngine engine; 33 | const QUrl url(QStringLiteral("qrc:/main.qml")); 34 | QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, 35 | &app, [url](QObject *obj, const QUrl &objUrl) { 36 | if (!obj && url == objUrl) 37 | QCoreApplication::exit(-1); 38 | }, Qt::QueuedConnection); 39 | 40 | engine.load(url); 41 | 42 | engine.rootContext()->setContextProperty("qmlKey", &qmlKey); 43 | 44 | const QList objList = engine.rootObjects(); 45 | objList.first()->installEventFilter(&qmlKey); 46 | 47 | QObject::connect(&qmlKey, SIGNAL(play()), objList.first(), 48 | SLOT(onPlay())); 49 | QObject::connect(&qmlKey, SIGNAL(back()), objList.first(), 50 | SLOT(onBack())); 51 | QObject::connect(&qmlKey, SIGNAL(goahead()), objList.first(), 52 | SLOT(onGoahead())); 53 | QObject::connect(&qmlKey, SIGNAL(escape()), objList.first(), 54 | SLOT(onEscape())); 55 | 56 | return app.exec(); 57 | } 58 | -------------------------------------------------------------------------------- /VideoPlayer/src/player/videoplayer_p.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief Video Player Private 3 | * @anchor Ho 229 4 | * @date 2021/4/24 5 | */ 6 | 7 | #ifndef VIDEOPLAYERPRIVATE_H 8 | #define VIDEOPLAYERPRIVATE_H 9 | 10 | #include "videoplayer.h" 11 | 12 | #include 13 | 14 | struct AVFrame; 15 | 16 | class AudioOutput; 17 | class FFmpegDecoder; 18 | class VideoRenderer; 19 | 20 | class Clock 21 | { 22 | qreal m_time = 0; 23 | QElapsedTimer m_updateClock; 24 | public: 25 | explicit Clock() = default; 26 | 27 | qreal time() const { return m_time + qreal(m_updateClock.elapsed()) / 1000; } 28 | bool isValid() const { return m_updateClock.isValid(); } 29 | 30 | void update(qreal time) 31 | { 32 | m_time = time; 33 | m_updateClock.start(); 34 | } 35 | 36 | void pause() 37 | { 38 | if(m_updateClock.isValid()) 39 | m_time += qreal(m_updateClock.elapsed()) / 1000; 40 | } 41 | void resume() { m_updateClock.start(); } 42 | 43 | void invalidate() { m_updateClock.invalidate(); } 44 | }; 45 | 46 | class VideoPlayerPrivate 47 | { 48 | public: 49 | VideoPlayerPrivate(VideoPlayer *parent) : q_ptr(parent) {} 50 | 51 | FFmpegDecoder *decoder = nullptr; 52 | 53 | AudioOutput *audioOutput = nullptr; 54 | VideoRenderer *videoRenderer = nullptr; 55 | 56 | VideoPlayer::State state = VideoPlayer::Stopped; 57 | 58 | int position = 0; 59 | 60 | int interval = 0; 61 | int timerId = -1; 62 | 63 | AVFrame *audioFrame = nullptr; 64 | qint64 audioFramePos = 0; 65 | 66 | void restartAudioOutput(); 67 | 68 | Clock videoClock; 69 | Clock audioClock; 70 | 71 | qint64 updateAudioData(char *data, qint64 maxlen); 72 | void updateVideoFrame(); 73 | void updateSubtitleFrame(); 74 | 75 | private: 76 | inline void updateTimer(int newInterval); 77 | 78 | VideoPlayer *const q_ptr; 79 | Q_DECLARE_PUBLIC(VideoPlayer) 80 | }; 81 | 82 | #endif // VIDEOPLAYERPRIVATE_H 83 | -------------------------------------------------------------------------------- /MultithreadedDownloader/README.md: -------------------------------------------------------------------------------- 1 | # MultithreadedDownloader 2 | > *我想凭时间的有效利用去弥补匆匆流逝的光阴。* 3 | > *—— 蒙田《蒙田随笔》* 4 | 5 | ![image](./image/MultithreadedDownloader_1.png) 6 | ![image](./image/MultithreadedDownloader_2.png) 7 | 8 | ------ 9 | ### 使用方法 10 | * Qt Multithreaded Downloader Example 11 | ``` 12 | Usage: MultithreadedDownloader [options] 13 | Qt Multithreaded Downloader Example. 14 | 15 | Options: 16 | -?, -h, --help Displays help on commandline options. 17 | --help-all Displays help including Qt specific options. 18 | -u, --url The URL to download. 19 | -t, --thread-number Set the thread number for download. 20 | ``` 21 | * Class MultithreadedDownloader 22 | * Add [src/downloader](./src/downloader) directory to your project. 23 | * `#include "multithreadeddownloader.h"` 24 | ```cpp 25 | MultithreadedDownloader *downloader = new MultithreadedDownloader(this); 26 | downloader->setUrl("https://mirrors.tuna.tsinghua.edu.cn/qt/official_releases/qt/5.15/5.15.2/submodules/qtbase-everywhere-src-5.15.2.zip"); 27 | 28 | if(!downloader->load()) 29 | return; // Failed to get resource. 30 | 31 | QDir::setCurrent("./"); // Set download path 32 | downloader->start(); // Start download 33 | ``` 34 | ------ 35 | ### 原理介绍 36 | * 这是一个基于 `Qt5` 的多线程下载器。 37 | * `Class AbstractMission` 是一个任务抽象类。 38 | * `Class DownloadMission` 继承于 `AbstractMission` ,是一个下载任务类,它提供 `setRange()` 以用于分段下载,还实现了 `start()`,`pause()`,`stop()` 等任务控制函数。它的内部维护着一个 `QNetworkReply` 对象,当 `QNetworkReply::readyRead` 信号发射时,它会读出 reply 的缓冲区数据并调用 `MultithreadedDownloaderWriter::write()` 将一个写请求添加到处理队列。 39 | * `Class MultithreadedDownloader` 继承于 `AbstractMission` ,管理着多个 `DownloadMission` 以实现文件分段并发下载,它也实现了 `start()`,`pause()`,`stop()` 等函数以控制多个 `DownloadMission`。分段下载(并发)数默认为 `QThread::idealThreadCount()` (CPU核心数)。它还管理着一个 `MultithreadedDownloaderWriter`。 40 | * `Class MultithreadedDownloaderWriter` 继承于 `QThread` ,它负责文件写入,同时管理着写线程和下载的文件。 41 | -------- 42 | *大概就这么多吧,不知道有没有人看,qwq。* -------------------------------------------------------------------------------- /MultithreadedDownloader/src/customWidgets/toast.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Toast Widget 3 | * @brief 自动消失提示框 4 | * @anchor Ho229 5 | * @date 2020/12/12 6 | */ 7 | 8 | #ifndef TOAST_H 9 | #define TOAST_H 10 | 11 | #include 12 | 13 | class QTimer; 14 | class QHBoxLayout; 15 | class QPropertyAnimation; 16 | class QSequentialAnimationGroup; 17 | 18 | #define DEFULT_TOAST_STYLE "\ 19 | QLabel{\ 20 | color:#FFFFFF;\ 21 | font:15px;\ 22 | font-weight:500;\ 23 | background-color:rgba(0,0,0,150);\ 24 | padding:3px;\ 25 | border-radius:9;\ 26 | }"\ 27 | 28 | class Toast : public QWidget 29 | { 30 | Q_OBJECT 31 | public: 32 | 33 | /** 34 | * @brief Toast 35 | * @param parent 父对象 36 | * @param horizontalMargin 水平方向的边界 37 | * @param verticalMargin 竖直方向上的边界 38 | * @param maxmaximumWidth 最大宽度 39 | * @param wordWrap 启用自动换行 40 | * @param waitMsece 等待时间 41 | * @param style 提示框样式表:注意字体大小和宽高效果要配合好 42 | */ 43 | explicit Toast(QWidget *parent = nullptr, int horizontalMargin = 12, int verticalMargin = 12, 44 | int maxmaximumWidth = 1400, bool wordWrap = false, int waitMsecs = 1200, 45 | const QString &style = DEFULT_TOAST_STYLE); 46 | ~Toast(); 47 | 48 | /** 49 | * @brief 设置提示文字 50 | * @param text 提示文字 51 | */ 52 | void setText(const QString& text); 53 | 54 | /** 55 | * @brief 弹出提示 56 | * @warning 此函数不会重新调整弹出位置 57 | */ 58 | void toast(); 59 | 60 | /** 61 | * @brief 弹出提示 62 | * @param text 提示文字 63 | */ 64 | void toast(const QString& text) 65 | { 66 | this->setText(text); 67 | this->toast(); 68 | } 69 | 70 | private: 71 | QLabel *m_messageLabel = nullptr; 72 | QHBoxLayout *m_layout = nullptr; 73 | 74 | QSequentialAnimationGroup *m_animation = nullptr; 75 | QPropertyAnimation *m_posAnimation = nullptr; // 弹出动画 76 | QPropertyAnimation *m_opacityAnimation = nullptr; // 消失动画 77 | 78 | }; 79 | 80 | #endif // TOAST_H 81 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/customWidgets/toast.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Toast Widget 3 | * @brief 自动消失提示框 4 | * @anchor Ho229 5 | * @date 2020/12/12 6 | */ 7 | 8 | #ifndef TOAST_H 9 | #define TOAST_H 10 | 11 | #include 12 | 13 | class QTimer; 14 | class QHBoxLayout; 15 | class QPropertyAnimation; 16 | class QSequentialAnimationGroup; 17 | 18 | #define DEFULT_TOAST_STYLE "\ 19 | QLabel\ 20 | {\ 21 | color:#FFFFFF;\ 22 | font:15px;\ 23 | font-weight:500;\ 24 | background-color:rgba(0,0,0,150);\ 25 | padding:3px;\ 26 | border-radius:9;\ 27 | }" 28 | 29 | class Toast Q_DECL_FINAL : public QWidget 30 | { 31 | Q_OBJECT 32 | public: 33 | 34 | /** 35 | * @brief Toast 36 | * @param parent 父对象 37 | * @param horizontalMargin 水平方向的边界 38 | * @param verticalMargin 竖直方向上的边界 39 | * @param maxmaximumWidth 最大宽度 40 | * @param wordWrap 启用自动换行 41 | * @param waitMsece 等待时间 42 | * @param style 提示框样式表:注意字体大小和宽高效果要配合好 43 | */ 44 | explicit Toast(QWidget *parent = nullptr, int horizontalMargin = 12, int verticalMargin = 12, 45 | int maxmaximumWidth = 1400, bool wordWrap = false, int waitMsecs = 1200, 46 | const QString &style = DEFULT_TOAST_STYLE); 47 | ~Toast() Q_DECL_OVERRIDE; 48 | 49 | /** 50 | * @brief 设置提示文字 51 | * @param text 提示文字 52 | */ 53 | void setText(const QString& text); 54 | 55 | /** 56 | * @brief 弹出提示 57 | * @warning 此函数不会重新调整弹出位置 58 | */ 59 | void toast(); 60 | 61 | /** 62 | * @brief 弹出提示 63 | * @param text 提示文字 64 | */ 65 | void toast(const QString& text) 66 | { 67 | this->setText(text); 68 | this->toast(); 69 | } 70 | 71 | private: 72 | QLabel *m_messageLabel = nullptr; 73 | QHBoxLayout *m_layout = nullptr; 74 | 75 | QSequentialAnimationGroup *m_animation = nullptr; 76 | QPropertyAnimation *m_posAnimation = nullptr; // 弹出动画 77 | QPropertyAnimation *m_opacityAnimation = nullptr; // 消失动画 78 | 79 | }; 80 | 81 | #endif // TOAST_H 82 | -------------------------------------------------------------------------------- /QmlDemo/QmlDemo_zh_CN.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | main 6 | 7 | 8 | 9 | Hello World 10 | 你好,世界 11 | 12 | 13 | 14 | Font Style 15 | 字体样式 16 | 17 | 18 | 19 | Underline 20 | 下划线 21 | 22 | 23 | 24 | Italic 25 | 斜体 26 | 27 | 28 | 29 | Bold 30 | 粗体 31 | 32 | 33 | 34 | Color 35 | 颜色 36 | 37 | 38 | 39 | Black 40 | 黑色 41 | 42 | 43 | 44 | Red 45 | 红色 46 | 47 | 48 | 49 | Blud 50 | 蓝色 51 | 52 | 53 | 54 | Push Button 55 | 56 | 57 | 58 | 59 | Close 60 | 关闭 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /VideoPlayer/src/player/videorenderer.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief Video Renderer 3 | * @anchor Ho 229 4 | * @date 2021/4/14 5 | */ 6 | 7 | #ifndef VIDEORENDERER_H 8 | #define VIDEORENDERER_H 9 | 10 | #include "ffmpegdecoder.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | struct AVFrame; 21 | 22 | class QOpenGLTexture; 23 | class VideoPlayerPrivate; 24 | 25 | class VideoRenderer : public QQuickFramebufferObject::Renderer, 26 | protected QOpenGLFunctions_4_4_Core 27 | { 28 | public: 29 | VideoRenderer(); 30 | ~VideoRenderer() Q_DECL_OVERRIDE; 31 | 32 | void render() Q_DECL_OVERRIDE; 33 | 34 | QOpenGLFramebufferObject *createFramebufferObject( 35 | const QSize &size) Q_DECL_OVERRIDE; 36 | 37 | void synchronize(QQuickFramebufferObject *) Q_DECL_OVERRIDE; 38 | 39 | void updateVideoFrame(AVFrame *frame); 40 | void updateSubtitleFrame(SubtitleFrame *frame); 41 | 42 | private: 43 | QOpenGLTexture *m_texture[4] = { nullptr }; // [0]: Y, [1]: U, [2]: V, [3]: Subtitle 44 | 45 | QOpenGLBuffer m_vbo; 46 | QOpenGLVertexArrayObject m_vao; 47 | 48 | QOpenGLShaderProgram m_program; 49 | 50 | QSize m_size, m_videoSize; 51 | QRect m_viewRect; 52 | QOpenGLTexture::PixelFormat m_pixelFormat; 53 | 54 | quint8 m_flags; 55 | 56 | AVFrame *m_frame = nullptr; 57 | SubtitleFrame *m_subtitle = nullptr; 58 | QScopedArrayPointer m_dummySubtitle; 59 | 60 | bool m_textureAlloced = false; 61 | 62 | void updateVideoTextureData(); 63 | void updateSubtitleTextureData(); 64 | 65 | void resize(); 66 | 67 | void initializeProgram(); 68 | 69 | void setupTexture(); 70 | void allocateTexture(const QSize sizes[3]); 71 | void updateSubtitleTexture(const QSize &size); 72 | 73 | void destoryTexture(); 74 | }; 75 | 76 | #endif // VIDEORENDERER_H 77 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/customWidgets/rotatestackedwidget.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Animation Stacked Widget 3 | * @brief 翻转动画 Stacked Widget 4 | * @anchor Ho229 5 | * @date 2020/12/12 6 | */ 7 | 8 | #ifndef ROTATESTACKEDWIDGET_H 9 | #define ROTATESTACKEDWIDGET_H 10 | 11 | #include 12 | #include 13 | 14 | class QVariantAnimation; 15 | 16 | class RotateStackedWidget Q_DECL_FINAL : public QStackedWidget 17 | { 18 | Q_OBJECT 19 | 20 | Q_PROPERTY(int animationDuration READ animationDuration WRITE setAnimationDuration) 21 | Q_PROPERTY(QEasingCurve animationEasingCurve READ animationEasingCurve WRITE setAnimationEasingCurve) 22 | 23 | public: 24 | 25 | /** 26 | * @brief RotateStackedWidget 27 | * @param parent 父对象 28 | */ 29 | explicit RotateStackedWidget(QWidget *parent = nullptr); 30 | ~RotateStackedWidget() Q_DECL_OVERRIDE; 31 | 32 | /** 33 | * @brief 翻转到 index 34 | * @param index 目标 index 35 | * @param exec 是否启用局部事件循环 36 | */ 37 | void rotate(int index, bool exec = true); 38 | 39 | /** 40 | * @brief 设置动画持续时间 41 | * @param duration 持续时间(msecs) 42 | */ 43 | void setAnimationDuration(int duration); 44 | 45 | /** 46 | * @return 动画持续时间 47 | */ 48 | int animationDuration(); 49 | 50 | /** 51 | * @brief 设置动画缓和曲线 52 | * @param easing 动画缓和曲线 53 | */ 54 | void setAnimationEasingCurve(const QEasingCurve& easing); 55 | /** 56 | * @return 动画缓和曲线 57 | */ 58 | QEasingCurve animationEasingCurve(); 59 | 60 | private slots: 61 | void on_finished(); 62 | 63 | private: 64 | QVariantAnimation *m_animation = nullptr; 65 | 66 | int m_nextIndex; 67 | qreal m_rotateValue = 0; 68 | 69 | QPixmap m_currentPixmap; 70 | QPixmap m_nextPixmap; 71 | 72 | QWidget *m_currentWidget = nullptr; 73 | QWidget *m_nextWidget = nullptr; 74 | 75 | inline void updateFrame(); 76 | 77 | void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; 78 | void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE; 79 | }; 80 | 81 | #endif // ROTATESTACKEDWIDGET_H 82 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/customWidgets/notifywidget.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Notify Widget 3 | * @brief 右下角提示窗 4 | * @anchor Ho229<2189684957@qq.com> 5 | * @date 2021/2/1 6 | */ 7 | 8 | #ifndef NOTIFYWIDGET_H 9 | #define NOTIFYWIDGET_H 10 | 11 | #include 12 | #include 13 | 14 | class QLabel; 15 | class QHBoxLayout; 16 | class QVBoxLayout; 17 | class QSpacerItem; 18 | class CountdownButton; 19 | class QPropertyAnimation; 20 | 21 | #define DEFULT_NOTIFY_STYLE "\ 22 | NotifyWidget\ 23 | {\ 24 | background:rgb(46, 47, 48);\ 25 | border-style:none;\ 26 | }\ 27 | QLabel\ 28 | {\ 29 | color:white;\ 30 | }" 31 | 32 | class NotifyWidget Q_DECL_FINAL : public QWidget 33 | { 34 | Q_OBJECT 35 | public: 36 | explicit NotifyWidget(QWidget *parent = nullptr, const QString& title = "", 37 | const QString& message = "", const QIcon& icon = QIcon(), 38 | const QString& style = DEFULT_NOTIFY_STYLE); 39 | ~NotifyWidget() Q_DECL_OVERRIDE; 40 | 41 | void setCloseCountdown(int ms); 42 | int closeCountdown() const; 43 | 44 | void animatMove(int x, int y); 45 | 46 | bool isClosing() const { return m_isClosing; } 47 | 48 | QIcon icon() const { return m_icon; } 49 | 50 | /** 51 | * @return 剩余时间 52 | */ 53 | int leftTime() const; 54 | 55 | signals: 56 | void closed(); 57 | void clicked(); 58 | 59 | private slots: 60 | void closeAnimation(); 61 | 62 | private: 63 | QLabel *m_iconLabel = nullptr; 64 | QLabel *m_titleLabel = nullptr; 65 | QLabel *m_messageLabel = nullptr; 66 | 67 | QHBoxLayout *m_hLayout = nullptr; 68 | QHBoxLayout *m_mLayout = nullptr; 69 | QVBoxLayout *m_vLayout = nullptr; 70 | QSpacerItem *m_hSpacer = nullptr; 71 | 72 | QIcon m_icon; 73 | 74 | CountdownButton *m_closeButton = nullptr; 75 | 76 | QPropertyAnimation *m_animation = nullptr; 77 | 78 | bool m_isClosing = false; 79 | 80 | void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; 81 | void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE; 82 | void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE; 83 | }; 84 | 85 | #endif // NOTIFYWIDGET_H 86 | -------------------------------------------------------------------------------- /QmlDemo/src/customItem/clickwaveeffect.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Click Wave Effect 3 | * @anchor Ho 229 4 | * @date 2021/7/18 5 | */ 6 | 7 | #ifndef CLICKWAVEEFFECT_H 8 | #define CLICKWAVEEFFECT_H 9 | 10 | #include 11 | 12 | class QVariantAnimation; 13 | class QSequentialAnimationGroup; 14 | 15 | class ClickWaveEffect : public QQuickPaintedItem 16 | { 17 | Q_OBJECT 18 | 19 | Q_PROPERTY(QQuickItem* target READ target WRITE setTarget NOTIFY targetChanged) 20 | Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) 21 | Q_PROPERTY(int waveDuration READ waveDuration WRITE setWaveDuration NOTIFY waveDurationChanged) 22 | Q_PROPERTY(int maxRadius READ maxRadius WRITE setMaxRadius NOTIFY maxRadiusChanged) 23 | 24 | public: 25 | explicit ClickWaveEffect(QQuickItem *parent = nullptr); 26 | virtual ~ClickWaveEffect() Q_DECL_OVERRIDE; 27 | 28 | void setTarget(QQuickItem *target); 29 | QQuickItem* target() const { return m_target; } 30 | 31 | void setColor(const QColor& color); 32 | QColor color() const { return m_color; } 33 | 34 | void setWaveDuration(int duration); 35 | int waveDuration() const; 36 | 37 | void setMaxRadius(int radius); 38 | int maxRadius() const { return m_maxRadius; } 39 | 40 | signals: 41 | void waveDurationChanged(int duration); 42 | void targetChanged(QQuickItem *target); 43 | void maxRadiusChanged(int radius); 44 | void colorChanged(QColor color); 45 | void finished(); 46 | 47 | protected: 48 | virtual void paint(QPainter *painter) Q_DECL_OVERRIDE; 49 | virtual bool eventFilter(QObject *obj, QEvent *event) Q_DECL_OVERRIDE; 50 | 51 | private slots: 52 | void on_finished(); 53 | 54 | private: 55 | int m_radius = 0; 56 | int m_maxRadius = -1; 57 | bool m_isPressed = false; 58 | 59 | QQuickItem *m_target = nullptr; 60 | 61 | QPoint m_pos; 62 | QColor m_color; 63 | QColor m_currentColor; 64 | 65 | QSequentialAnimationGroup *m_animation = nullptr; 66 | QVariantAnimation *m_alphaAnimation = nullptr; 67 | QVariantAnimation *m_radiusAnimation = nullptr; 68 | 69 | static int maxRadius(const QPoint& pos, const QSize &size); 70 | }; 71 | 72 | #endif // CLICKWAVEEFFECT_H 73 | -------------------------------------------------------------------------------- /MultithreadedDownloader/src/downloader/downloadmission.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief DownloadMission 3 | * @anchor Ho229<2189684957@qq.com> 4 | * @date 2021/2/1 5 | */ 6 | 7 | #ifndef DOWNLOADMISSION_H 8 | #define DOWNLOADMISSION_H 9 | 10 | #include 11 | 12 | #include "abstractmission.h" 13 | #include "multithreadeddownloaderwriter.h" 14 | 15 | class QNetworkAccessManager; 16 | 17 | class DownloadMission : public AbstractMission 18 | { 19 | Q_OBJECT 20 | public: 21 | explicit DownloadMission(QObject *parent); 22 | ~DownloadMission() Q_DECL_OVERRIDE; 23 | 24 | /** 25 | * @brief 设置下载范围 26 | * @param start 下载起始位置 27 | * @param end 下载结束位置 28 | */ 29 | void setRange(qint64 start, qint64 end) 30 | { 31 | if(m_state == Stopped) 32 | { 33 | m_start = start; 34 | m_end = end; 35 | m_totalSize = end - start; 36 | } 37 | } 38 | 39 | void setManager(QNetworkAccessManager *manager){ m_manager = manager; } 40 | QNetworkAccessManager* manager() const { return m_manager; } 41 | 42 | void setWriter(MultithreadedDownloaderWriter *writer){ m_writer = writer; } 43 | MultithreadedDownloaderWriter* writer() const { return m_writer; } 44 | 45 | /** 46 | * @return 下载大小 47 | */ 48 | qint64 downloadedSize() const { return m_downloadedSize; } 49 | 50 | QString replyErrorString() const 51 | { return m_reply == nullptr ? QString() : m_reply->errorString(); } 52 | 53 | void start() Q_DECL_OVERRIDE; 54 | void pause() Q_DECL_OVERRIDE; 55 | void stop() Q_DECL_OVERRIDE; 56 | 57 | signals: 58 | void replyError(QNetworkReply::NetworkError err); 59 | 60 | private slots: 61 | void on_finished(); 62 | void writeData(); 63 | 64 | private: 65 | MultithreadedDownloaderWriter* m_writer = nullptr; 66 | QNetworkAccessManager* m_manager = nullptr; 67 | QNetworkReply* m_reply = nullptr; 68 | 69 | qint64 m_start = -1; 70 | qint64 m_end = -1; 71 | 72 | qint64 m_totalSize = 0; 73 | qint64 m_downloadedSize = 0; 74 | 75 | inline void reset(); // 重置状态 76 | inline void destoryReply(); 77 | }; 78 | 79 | #endif // DOWNLOADMISSION_H 80 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/customWidgets/progressbutton.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Progress Button 3 | * @brief 进度条按钮 4 | * @anchor Ho229 5 | * @date 2021/2/22 6 | */ 7 | 8 | #ifndef PROGRESSBUTTON_H 9 | #define PROGRESSBUTTON_H 10 | 11 | #include 12 | #include 13 | 14 | #define TransAngle(x) (x * 16) 15 | 16 | #define DEFULT_BUTTON_STYLE "\ 17 | QPushButton\ 18 | {\ 19 | background:transparent;\ 20 | border-style:none;\ 21 | }\ 22 | QPushButton:hover\ 23 | {\ 24 | background:transparent;\ 25 | border-radius:7px;\ 26 | border:1px solid rgb(119,119,119);\ 27 | }\ 28 | QPushButton:pressed\ 29 | {\ 30 | background:rgba(100, 234, 255, 200);\ 31 | }" 32 | 33 | class ProgressButton : public QPushButton 34 | { 35 | Q_OBJECT 36 | 37 | Q_PROPERTY(int value READ value WRITE setValue) 38 | Q_PROPERTY(int maximun READ maximun WRITE setMaximun) 39 | Q_PROPERTY(int minimun READ minimun WRITE setMinimun) 40 | 41 | Q_PROPERTY(int progressWidth READ progressWidth WRITE setProgressWidth) 42 | Q_PROPERTY(QColor progressColor READ progressColor WRITE setProgressColor) 43 | 44 | public: 45 | explicit ProgressButton(QWidget *parent = nullptr, 46 | const QString &style = DEFULT_BUTTON_STYLE); 47 | virtual ~ProgressButton() Q_DECL_OVERRIDE; 48 | 49 | void setMaximun(const int value){ m_maximun = qMax(value, m_minimun); } 50 | void setMinimun(const int value){ m_minimun = qMin(value, m_maximun); } 51 | void setValue(int value); 52 | 53 | void setProgressWidth(const int width){ m_pen.setWidth(width); } 54 | void setProgressColor(const QColor& color){ m_pen.setColor(color); } 55 | 56 | int maximun() const { return m_maximun; } 57 | int minimun() const { return m_minimun; } 58 | int value() const { return m_value; } 59 | 60 | int progressWidth() const { return m_pen.width(); } 61 | QColor progressColor() const { return m_pen.color(); } 62 | 63 | signals: 64 | void valueChanged(int value); 65 | 66 | protected: 67 | virtual void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; 68 | 69 | private: 70 | int m_maximun = 100; 71 | int m_minimun = 0; 72 | int m_value = 0; 73 | 74 | QPen m_pen; 75 | }; 76 | 77 | #endif // PROGRESSBUTTON_H 78 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/customWidgets/aligniconbutton.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief 左右 icon 对齐 Push Button 3 | * @anchor Ho229<2189684957@qq.com> 4 | * @date 2021/2/1 5 | */ 6 | 7 | #ifndef ALIGNICONBUTTON_H 8 | #define ALIGNICONBUTTON_H 9 | 10 | #include 11 | 12 | class AlignIconButton : public QPushButton 13 | { 14 | Q_OBJECT 15 | 16 | Q_PROPERTY(QIcon leftIcon READ leftIcon WRITE setLeftIcon) 17 | Q_PROPERTY(QIcon rightIcon READ rightIcon WRITE setRightIcon) 18 | Q_PROPERTY(int sideMargin READ sideMargin WRITE setSideMargin) 19 | Q_PROPERTY(int topBottomMargin READ topBottomMargin WRITE setTopBottomMargin) 20 | 21 | public: 22 | explicit AlignIconButton(QWidget *parent = nullptr); 23 | virtual ~AlignIconButton() Q_DECL_OVERRIDE; 24 | 25 | /** 26 | * @brief 设置左对齐 icon 27 | */ 28 | void setLeftIcon(QIcon icon) 29 | { 30 | m_leftIcon = icon; 31 | this->repaint(); 32 | } 33 | 34 | /** 35 | * @return 左对齐 icon 36 | */ 37 | QIcon leftIcon() const { return m_leftIcon; } 38 | 39 | /** 40 | * @brief 设置右对齐 icon 41 | */ 42 | void setRightIcon(QIcon icon) 43 | { 44 | m_rightIcon = icon; 45 | this->repaint(); 46 | } 47 | 48 | /** 49 | * @return 右对齐 icon 50 | */ 51 | QIcon rightIcon() const { return m_rightIcon; } 52 | 53 | /** 54 | * @brief 设置左右边距 55 | * @warning 效果将在下次 repaint() 生效 56 | */ 57 | void setSideMargin(int margin){ m_sideMargin = margin; } 58 | 59 | /** 60 | * @return 左右边距 61 | */ 62 | int sideMargin() const { return m_sideMargin; } 63 | 64 | /** 65 | * @brief 设置上下边距 66 | * @warning 效果将在下次 repaint() 生效 67 | */ 68 | void setTopBottomMargin(int margin){ m_topBottomMargin = margin; } 69 | 70 | /** 71 | * @return 上下边距 72 | */ 73 | int topBottomMargin() const { return m_topBottomMargin; } 74 | 75 | protected: 76 | virtual void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; 77 | 78 | private: 79 | QIcon m_leftIcon; 80 | QIcon m_rightIcon; 81 | 82 | int m_sideMargin = 5; 83 | int m_topBottomMargin = 5; 84 | 85 | inline void drawIcon(QPainter *painter); 86 | }; 87 | 88 | #endif // ALIGNICONBUTTON_H 89 | -------------------------------------------------------------------------------- /MultithreadedDownloader/src/customWidgets/translationstackedwidget.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Animation Stacked Widget 3 | * @brief 平移动画 Stacked Widget 4 | * @anchor Ho229 5 | * @date 2020/12/12 6 | */ 7 | 8 | #ifndef TRANSLATIONSTACKEDWIDGET_H 9 | #define TRANSLATIONSTACKEDWIDGET_H 10 | 11 | #include 12 | #include 13 | 14 | class QVariantAnimation; 15 | 16 | class TranslationStackedWidget : public QStackedWidget 17 | { 18 | Q_OBJECT 19 | 20 | Q_PROPERTY(int animationDuration READ animationDuration WRITE setAnimationDuration) 21 | Q_PROPERTY(QEasingCurve animationEasingCurve READ animationEasingCurve WRITE setAnimationEasingCurve) 22 | 23 | public: 24 | /** 25 | * @brief TranslationStackedWidget 26 | * @param parent 父对象 27 | */ 28 | explicit TranslationStackedWidget(QWidget *parent = nullptr); 29 | ~TranslationStackedWidget() Q_DECL_OVERRIDE; 30 | 31 | /** 32 | * @brief 平移到 index 33 | * @param index 目标 index 34 | * @param exec 是否启用局部事件循环 35 | */ 36 | void moveToIndex(int index, bool exec = true); 37 | 38 | /** 39 | * @brief 设置动画持续时间 40 | * @param duration 持续时间(msecs) 41 | */ 42 | void setAnimationDuration(int duration); 43 | 44 | /** 45 | * @return 动画持续时间 46 | */ 47 | int animationDuration(); 48 | 49 | /** 50 | * @brief 设置动画缓和曲线 51 | * @param easing 动画缓和曲线 52 | */ 53 | void setAnimationEasingCurve(const QEasingCurve& easing); 54 | /** 55 | * @return 动画缓和曲线 56 | */ 57 | QEasingCurve animationEasingCurve(); 58 | 59 | private slots: 60 | void on_finished(); 61 | 62 | private: 63 | QVariantAnimation *m_animation = nullptr; 64 | 65 | QPixmap m_animationPixmap; 66 | 67 | QWidget *m_currentWidget = nullptr; 68 | QWidget *m_nextWidget = nullptr; 69 | 70 | int m_animationX = 0; 71 | int m_nextIndex; 72 | 73 | static QPixmap mergePixmap(const QPixmap& leftPixmap, const QPixmap& rightPixmap); 74 | 75 | inline void updateFrame(); 76 | inline void updateAnimation(); 77 | 78 | void paintEvent(QPaintEvent* event) Q_DECL_OVERRIDE; 79 | void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE; 80 | 81 | }; 82 | 83 | #endif // TRANSLATIONSTACKEDWIDGET_H 84 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/customWidgets/translationstackedwidget.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Animation Stacked Widget 3 | * @brief 平移动画 Stacked Widget 4 | * @anchor Ho229 5 | * @date 2020/12/12 6 | */ 7 | 8 | #ifndef TRANSLATIONSTACKEDWIDGET_H 9 | #define TRANSLATIONSTACKEDWIDGET_H 10 | 11 | #include 12 | #include 13 | 14 | class QVariantAnimation; 15 | 16 | class TranslationStackedWidget Q_DECL_FINAL : public QStackedWidget 17 | { 18 | Q_OBJECT 19 | 20 | Q_PROPERTY(int animationDuration READ animationDuration WRITE setAnimationDuration) 21 | Q_PROPERTY(QEasingCurve animationEasingCurve READ animationEasingCurve WRITE setAnimationEasingCurve) 22 | 23 | public: 24 | /** 25 | * @brief TranslationStackedWidget 26 | * @param parent 父对象 27 | */ 28 | explicit TranslationStackedWidget(QWidget *parent = nullptr); 29 | ~TranslationStackedWidget() Q_DECL_OVERRIDE; 30 | 31 | /** 32 | * @brief 平移到 index 33 | * @param index 目标 index 34 | * @param exec 是否启用局部事件循环 35 | */ 36 | void moveToIndex(int index, bool exec = true); 37 | 38 | /** 39 | * @brief 设置动画持续时间 40 | * @param duration 持续时间(msecs) 41 | */ 42 | void setAnimationDuration(int duration); 43 | 44 | /** 45 | * @return 动画持续时间 46 | */ 47 | int animationDuration(); 48 | 49 | /** 50 | * @brief 设置动画缓和曲线 51 | * @param easing 动画缓和曲线 52 | */ 53 | void setAnimationEasingCurve(const QEasingCurve& easing); 54 | /** 55 | * @return 动画缓和曲线 56 | */ 57 | QEasingCurve animationEasingCurve(); 58 | 59 | private slots: 60 | void on_finished(); 61 | 62 | private: 63 | QVariantAnimation *m_animation = nullptr; 64 | 65 | QPixmap m_animationPixmap; 66 | 67 | QWidget *m_currentWidget = nullptr; 68 | QWidget *m_nextWidget = nullptr; 69 | 70 | int m_animationX = 0; 71 | int m_nextIndex; 72 | 73 | static QPixmap mergePixmap(const QPixmap& leftPixmap, const QPixmap& rightPixmap); 74 | 75 | inline void updateFrame(); 76 | inline void updateAnimation(); 77 | 78 | void paintEvent(QPaintEvent* event) Q_DECL_OVERRIDE; 79 | void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE; 80 | 81 | }; 82 | 83 | #endif // TRANSLATIONSTACKEDWIDGET_H 84 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/customWidgets/notifymanager.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Notify Manager 3 | * @brief 右下角提示窗管理器 4 | * @anchor Ho229<2189684957@qq.com> 5 | * @date 2021/2/1 6 | */ 7 | 8 | #include "notifymanager.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | NotifyManager::NotifyManager(QObject *parent) : 16 | QObject(parent), 17 | m_desktopSize(QGuiApplication::primaryScreen()->size()) 18 | { 19 | 20 | } 21 | 22 | NotifyManager::~NotifyManager() 23 | { 24 | 25 | } 26 | 27 | NotifyWidget* NotifyManager::notify(QWidget *parent, const QString& title, 28 | const QString& message, const QIcon& icon, int showTime) 29 | { 30 | NotifyWidget *newNotofy = new NotifyWidget(parent, title, message, icon); 31 | QObject::connect(newNotofy, &NotifyWidget::closed, this, 32 | &NotifyManager::onNotifyClosed); 33 | m_list.push_back({newNotofy, showTime}); 34 | 35 | this->updateNotifys(); 36 | 37 | return newNotofy; 38 | } 39 | 40 | void NotifyManager::onNotifyClosed() 41 | { 42 | NotifyWidget *closedWidget = static_cast(this->sender()); 43 | 44 | const QList& constlist = m_list; 45 | for(const NotifyItem& item : constlist) 46 | { 47 | if(item.first == closedWidget) 48 | { 49 | m_list.removeOne(item); 50 | break; 51 | } 52 | } 53 | this->updateNotifys(); 54 | emit windowClosed(); 55 | } 56 | 57 | void NotifyManager::updateNotifys() 58 | { 59 | if(m_list.isEmpty()) 60 | return; 61 | 62 | m_showCount = 0; 63 | for(int i = 0; i < m_list.size() && i < m_maximum; i++) 64 | { 65 | NotifyItem item = m_list.at(i); 66 | 67 | if(item.first->isHidden()) 68 | { 69 | item.first->move(QPoint(m_desktopSize.width(), 70 | m_desktopSize.height() - (i + 1) * 170)); 71 | if(item.second > 0) 72 | item.first->setCloseCountdown(item.second); 73 | 74 | item.first->show(); 75 | } 76 | else 77 | if(item.first->isClosing()) 78 | item.first->animatMove(m_desktopSize.width(), 79 | m_desktopSize.height() - (i + 1) * 170); 80 | else 81 | item.first->animatMove(m_desktopSize.width() - item.first->width(), 82 | m_desktopSize.height() - (i + 1) * 170); 83 | m_showCount++; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /VideoPlayer/src/player/videoplayer_p.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief Video Player Private 3 | * @anchor Ho 229 4 | * @date 2023/4/21 5 | */ 6 | 7 | #include "videoplayer_p.h" 8 | 9 | #include "audiooutput.h" 10 | #include "ffmpegdecoder.h" 11 | #include "videorenderer.h" 12 | 13 | void VideoPlayerPrivate::updateTimer(int newInterval) 14 | { 15 | Q_Q(VideoPlayer); 16 | 17 | interval = newInterval; 18 | q->killTimer(timerId); 19 | q->startTimer(interval, Qt::PreciseTimer); 20 | } 21 | 22 | void VideoPlayerPrivate::restartAudioOutput() 23 | { 24 | audioOutput->updateAudioOutput(decoder->audioFormat()); 25 | 26 | if(state == VideoPlayer::Playing) 27 | audioOutput->play(); 28 | } 29 | 30 | qint64 VideoPlayerPrivate::updateAudioData(char *data, qint64 maxlen) 31 | { 32 | if(!data) 33 | return 0; 34 | 35 | qint64 free = maxlen; 36 | char *dest = data; 37 | 38 | while(free) 39 | { 40 | if(!audioFrame) 41 | { 42 | if(!(audioFrame = decoder->takeAudioFrame())) 43 | break; 44 | } 45 | 46 | if(!audioClock.isValid() || (audioOutput->isLowDataLeft() && dest == data)) 47 | audioClock.update(FFmpegDecoder::framePts(audioFrame)); 48 | 49 | const auto size = qMin(qint64(audioFrame->linesize[0]) - audioFramePos, free); 50 | 51 | memcpy(dest, audioFrame->data[0] + audioFramePos, size); 52 | dest += size; 53 | free -= size; 54 | audioFramePos += size; 55 | 56 | if(audioFramePos >= audioFrame->linesize[0]) 57 | { 58 | audioFramePos = 0; 59 | av_frame_free(&audioFrame); 60 | } 61 | } 62 | 63 | return maxlen - free; 64 | } 65 | 66 | void VideoPlayerPrivate::updateVideoFrame() 67 | { 68 | AVFrame *frame = nullptr; 69 | while((frame = decoder->takeVideoFrame())) 70 | { 71 | const qreal pts = FFmpegDecoder::framePts(frame); 72 | if(!qFuzzyCompare(pts, -1)) 73 | { 74 | videoClock.update(pts); 75 | auto nextInterval = FFmpegDecoder::frameDuration(frame); 76 | 77 | if(audioClock.isValid()) 78 | nextInterval -= audioClock.time() - videoClock.time()/* - audioOutput->bufferDuration()*/; 79 | nextInterval *= 1000; 80 | 81 | if(nextInterval < 1) 82 | { 83 | av_frame_free(&frame); 84 | continue; 85 | } 86 | 87 | if(interval != nextInterval) 88 | this->updateTimer(nextInterval); 89 | } 90 | 91 | videoRenderer->updateVideoFrame(frame); 92 | break; 93 | } 94 | } 95 | 96 | void VideoPlayerPrivate::updateSubtitleFrame() 97 | { 98 | SubtitleFrame *frame = nullptr; 99 | if((frame = decoder->takeSubtitleFrame(videoClock.time()))) 100 | videoRenderer->updateSubtitleFrame(frame); 101 | } 102 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/customWidgets/toast.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Toast Widget 3 | * @brief 自动消失提示框 4 | * @anchor Ho229 5 | * @date 2020/12/12 6 | */ 7 | 8 | #include "toast.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | Toast::Toast(QWidget *parent, int horizontalMargin, int verticalMargin, 17 | int maximumWidth, bool wordWrap, int waitMsecs, const QString &style) : 18 | QWidget(parent), 19 | m_messageLabel(new QLabel(this)), 20 | m_layout(new QHBoxLayout(this)), 21 | m_animation(new QSequentialAnimationGroup(this)), 22 | m_posAnimation(new QPropertyAnimation(this, "pos")), 23 | m_opacityAnimation(new QPropertyAnimation(this, "windowOpacity")) 24 | { 25 | this->setAttribute(Qt::WA_TransparentForMouseEvents); 26 | this->setAttribute(Qt::WA_TranslucentBackground); 27 | this->setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip | Qt::CustomizeWindowHint); 28 | 29 | m_layout->addWidget(m_messageLabel); 30 | m_layout->setContentsMargins(0, 0, 0, 0); 31 | 32 | m_messageLabel->setStyleSheet(style); 33 | m_messageLabel->setContentsMargins(horizontalMargin, verticalMargin, 34 | horizontalMargin, verticalMargin); 35 | m_messageLabel->setAlignment(Qt::AlignCenter); 36 | m_messageLabel->setMaximumWidth(maximumWidth); 37 | m_messageLabel->setWordWrap(wordWrap); 38 | 39 | /* 显示动画 */ 40 | m_posAnimation->setDuration(300); 41 | m_posAnimation->setEasingCurve(QEasingCurve::OutCubic); 42 | 43 | /* 消失动画 */ 44 | m_opacityAnimation->setDuration(250); 45 | m_opacityAnimation->setStartValue(1); 46 | m_opacityAnimation->setEndValue(0); 47 | 48 | m_animation->addAnimation(m_posAnimation); 49 | m_animation->addPause(waitMsecs); 50 | m_animation->addAnimation(m_opacityAnimation); 51 | 52 | connect(m_animation, &QSequentialAnimationGroup::finished, this, &Toast::close); 53 | } 54 | 55 | Toast::~Toast() 56 | { 57 | 58 | } 59 | 60 | void Toast::setText(const QString &text) 61 | { 62 | // 自适应大小 63 | m_messageLabel->setText(text); 64 | m_messageLabel->adjustSize(); 65 | this->adjustSize(); 66 | 67 | // 计算动画起止坐标 68 | QRect rect = this->rect(); 69 | QRect parentGeometry = this->parentWidget() == nullptr ? 70 | QGuiApplication::primaryScreen()->geometry() : this->parentWidget()->frameGeometry(); 71 | 72 | rect.moveCenter(parentGeometry.center()); 73 | rect.translate(0, parentGeometry.height() / 4); 74 | 75 | m_posAnimation->setEndValue(rect.topLeft()); 76 | 77 | rect.translate(0, parentGeometry.height() / 4 - this->height() / 2); 78 | 79 | m_posAnimation->setStartValue(rect.topLeft()); 80 | } 81 | 82 | void Toast::toast() 83 | { 84 | if(m_animation->state() == QSequentialAnimationGroup::Running) 85 | m_animation->stop(); 86 | 87 | this->setWindowOpacity(1); // 透明度复位 88 | this->show(); 89 | m_animation->start(); 90 | } 91 | -------------------------------------------------------------------------------- /MultithreadedDownloader/src/customWidgets/toast.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Toast Widget 3 | * @brief 自动消失提示框 4 | * @anchor Ho229 5 | * @date 2020/12/12 6 | */ 7 | 8 | #include "toast.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | Toast::Toast(QWidget *parent, int horizontalMargin, int verticalMargin, 17 | int maximumWidth, bool wordWrap, int waitMsecs, const QString &style) : 18 | QWidget(parent), 19 | m_messageLabel(new QLabel(this)), 20 | m_layout(new QHBoxLayout(this)), 21 | m_animation(new QSequentialAnimationGroup(this)), 22 | m_posAnimation(new QPropertyAnimation(this, "pos")), 23 | m_opacityAnimation(new QPropertyAnimation(this, "windowOpacity")) 24 | { 25 | this->setAttribute(Qt::WA_TransparentForMouseEvents); 26 | this->setAttribute(Qt::WA_TranslucentBackground); 27 | this->setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::CustomizeWindowHint); 28 | 29 | m_layout->addWidget(m_messageLabel); 30 | m_layout->setContentsMargins(0, 0, 0, 0); 31 | 32 | m_messageLabel->setStyleSheet(style); 33 | m_messageLabel->setContentsMargins(horizontalMargin, verticalMargin, 34 | horizontalMargin, verticalMargin); 35 | m_messageLabel->setAlignment(Qt::AlignCenter); 36 | m_messageLabel->setMaximumWidth(maximumWidth); 37 | m_messageLabel->setWordWrap(wordWrap); 38 | 39 | /* 显示动画 */ 40 | m_posAnimation->setDuration(300); 41 | m_posAnimation->setEasingCurve(QEasingCurve::OutCubic); 42 | 43 | /* 消失动画 */ 44 | m_opacityAnimation->setDuration(250); 45 | m_opacityAnimation->setStartValue(1); 46 | m_opacityAnimation->setEndValue(0); 47 | 48 | m_animation->addAnimation(m_posAnimation); 49 | m_animation->addPause(waitMsecs); 50 | m_animation->addAnimation(m_opacityAnimation); 51 | 52 | connect(m_animation, &QSequentialAnimationGroup::finished, this, &Toast::close); 53 | } 54 | 55 | Toast::~Toast() 56 | { 57 | 58 | } 59 | 60 | void Toast::setText(const QString &text) 61 | { 62 | // 自适应大小 63 | m_messageLabel->setText(text); 64 | m_messageLabel->adjustSize(); 65 | this->adjustSize(); 66 | 67 | // 计算动画起止坐标 68 | QRect rect = this->rect(); 69 | QRect parentGeometry = this->parentWidget() == nullptr ? 70 | QGuiApplication::primaryScreen()->geometry() : this->parentWidget()->frameGeometry(); 71 | 72 | rect.moveCenter(parentGeometry.center()); 73 | rect.translate(0, parentGeometry.height() / 4); 74 | 75 | m_posAnimation->setEndValue(rect.topLeft()); 76 | 77 | rect.translate(0, parentGeometry.height() / 4 - this->height() / 2); 78 | 79 | m_posAnimation->setStartValue(rect.topLeft()); 80 | } 81 | 82 | void Toast::toast() 83 | { 84 | if(m_animation->state() == QSequentialAnimationGroup::Running) 85 | m_animation->stop(); 86 | 87 | this->setWindowOpacity(1); // 透明度复位 88 | this->show(); 89 | m_animation->start(); 90 | } 91 | -------------------------------------------------------------------------------- /MultithreadedDownloader/src/downloader/downloadmission.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief DownloadMission 3 | * @anchor Ho229<2189684957@qq.com> 4 | * @date 2021/2/1 5 | */ 6 | 7 | #include "downloadmission.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | DownloadMission::DownloadMission(QObject *parent) 15 | : AbstractMission(parent) 16 | { 17 | 18 | } 19 | 20 | DownloadMission::~DownloadMission() 21 | { 22 | if(m_state == Running) 23 | this->DownloadMission::stop(); 24 | } 25 | 26 | void DownloadMission::start() 27 | { 28 | if(m_state == Running) 29 | return; 30 | 31 | QNetworkRequest request(m_url); 32 | 33 | if(m_start >= 0 && m_end > 0) 34 | request.setRawHeader("Range", QString("bytes=%1-%2") 35 | .arg(m_start + m_downloadedSize).arg(m_end).toLatin1()); 36 | request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); 37 | 38 | m_reply = m_manager->get(request); 39 | 40 | QObject::connect(m_reply, &QNetworkReply::finished, this, 41 | &DownloadMission::on_finished); 42 | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) 43 | QObject::connect(m_reply, &QNetworkReply::errorOccurred, this, 44 | &DownloadMission::replyError); 45 | #else 46 | QObject::connect(m_reply, QOverload::of(&QNetworkReply::error), this, 47 | &DownloadMission::replyError); 48 | #endif 49 | QObject::connect(m_reply, &QNetworkReply::readyRead, this, 50 | &DownloadMission::writeData); 51 | 52 | this->setFinished(false); 53 | this->updateState(Running); 54 | } 55 | 56 | void DownloadMission::pause() 57 | { 58 | if(m_state == Running) 59 | { 60 | this->updateState(Paused); 61 | 62 | m_reply->abort(); 63 | this->destoryReply(); 64 | } 65 | } 66 | 67 | void DownloadMission::stop() 68 | { 69 | if(m_state != Stopped) 70 | { 71 | this->updateState(Stopped); 72 | 73 | if(m_state == Running) 74 | { 75 | m_reply->abort(); 76 | this->destoryReply(); 77 | } 78 | 79 | this->reset(); 80 | } 81 | } 82 | 83 | void DownloadMission::on_finished() 84 | { 85 | if(m_state == Running) 86 | { 87 | this->updateState(Stopped); 88 | this->setFinished(); 89 | } 90 | } 91 | 92 | void DownloadMission::writeData() 93 | { 94 | QByteArray data = m_reply->readAll(); 95 | m_writer->write(data, m_start + m_downloadedSize); 96 | m_downloadedSize += data.size(); 97 | } 98 | 99 | void DownloadMission::reset() 100 | { 101 | m_start = -1; 102 | m_end = -1; 103 | 104 | m_totalSize = 0; 105 | m_downloadedSize = 0; 106 | } 107 | 108 | void DownloadMission::destoryReply() 109 | { 110 | m_reply->deleteLater(); 111 | m_reply = nullptr; 112 | } 113 | -------------------------------------------------------------------------------- /CustomWidgetDemos/README.md: -------------------------------------------------------------------------------- 1 | # CustomWidgetDemos 2 | ![image](./screenshot/CustomWidgetDemos_1.png) 3 | ![image](./screenshot/CustomWidgetDemos_2.png) 4 | 5 | ------- 6 | ## Class AlignIconButton 7 | | File | 8 | | ---- | 9 | | [aligniconbutton.h](./src/customWidgets/aligniconbutton.h) | 10 | | [aligniconbutton.cpp](./src/customWidgets/aligniconbutton.cpp) | 11 | * 左右 icon 对齐 Push Button 12 | * Example 13 | ```cpp 14 | AlignIconButton *pushButton = new AlignIconButton(this); 15 | pushButton->setLeftIcon(leftIcon); 16 | pushButton->setRightIcon(rightIcon); 17 | pushButton->show(); 18 | ``` 19 | ----- 20 | ## Class NotifyWidget & NotifyManager 21 | | File | 22 | | ---- | 23 | | [notifywidget.h](./src/customWidgets/notifywidget.h) | 24 | | [notifywidget.cpp](./src/customWidgets/notifywidget.cpp) | 25 | | [notifymanager.h](./src/customWidgets/notifymanager.h) | 26 | | [notifymanager.cpp](./src/customWidgets/notifymanager.cpp) | 27 | * 桌面右下角弹窗 28 | * 注意: 29 | * `NotifyWidget` 是一次性的,关闭窗口时将被销毁。 30 | * Example 31 | ```cpp 32 | NotifyManager *manager = new NotifyManager(this); 33 | manager->notify(this, "Hello", "Hello World.\nHow are you today."); 34 | ``` 35 | ----- 36 | ## Class ProgressButton 37 | | File | 38 | | ---- | 39 | | [progressbutton.h](./src/customWidgets/progressbutton.h) | 40 | | [progressbutton.cpp](./src/customWidgets/progressbutton.cpp) | 41 | * 进度条按钮 42 | * 提供了类似 `QProgressBar` 的API 43 | * Example 44 | ```cpp 45 | ProgressButton *button = new ProgressButton(this); 46 | button->setValue(50); 47 | button->show(); 48 | ``` 49 | ----- 50 | ## Class RotateStackedWidget 51 | | File | 52 | | ---- | 53 | | [rotatestackedwidget.h](./src/customWidgets/rotatestackedwidget.h) | 54 | | [rotatestackedwidget.cpp](./src/customWidgets/rotatestackedwidget.cpp) | 55 | 56 | * 带有翻转动画的 Stacked Widget 57 | * Example 58 | ```cpp 59 | RotateStackedWidget *stackedWidget = new RotateStackedWidget(this); 60 | stackedWidget->addWidget(widget_1); 61 | stackedWidget->addWidget(widget_2); 62 | stackedWidget->setCurrentIndex(0); 63 | stackedWidget->rotate(1); // 页面翻转 64 | ``` 65 | ----- 66 | ## Class Toast 67 | | File | 68 | | ---- | 69 | | [toast.h](./src/customWidgets/toast.h) | 70 | | [toast.cpp](./src/customWidgets/toast.cpp) | 71 | 72 | * Toast 提示窗 73 | 74 | 注意: 75 | * 1.当 `parent == nullptr` 时,Toast会出现在活动桌面水平居中垂直 3/4 的地方,`parent != nullptr` 时则Toast会出现在父窗口水平居中垂直 3/4 的地方。 76 | * 2.当 Toast 正在显示消息时,再次调用 `Toast::toast()` 将显示新消息。 77 | * Example 78 | ```cpp 79 | Toast *toast = new Toast(this); 80 | toast->toast("Hello"); 81 | ``` 82 | ----- 83 | ## Class TranslationStackedWidget 84 | | File | 85 | | ---- | 86 | | [translationstackedwidget.h](./src/customWidgets/translationstackedwidget.h) | 87 | | [translationstackedwidget.cpp](./src/customWidgets/translationstackedwidget.cpp) | 88 | 89 | * 具有平移动画的 Stacked Widget 90 | * Example 91 | ```cpp 92 | TranslationStackedWidget *stackedWidget = new TranslationStackedWidget(this); 93 | stackedWidget->addWidget(widget_1); 94 | stackedWidget->addWidget(widget_2); 95 | stackedWidget->setCurrentIndex(0); 96 | stackedWidget->moveToIndex(1); // 页面平移 97 | ``` -------------------------------------------------------------------------------- /VideoPlayer/src/player/audiooutput.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief Audio Output 3 | * @anchor Ho 229 4 | * @date 2021/5/1 5 | */ 6 | 7 | #include "audiooutput.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | class AudioDevice final : public QIODevice 14 | { 15 | const AudioOutput::Callback m_callback; 16 | public: 17 | AudioDevice(const AudioOutput::Callback &callback, QObject *parent = nullptr) 18 | : QIODevice(parent) 19 | , m_callback(callback) 20 | { this->setOpenMode(QIODevice::ReadOnly); } 21 | 22 | qint64 readData(char *data, qint64 maxlen) override 23 | { 24 | if(!maxlen) 25 | return 0; 26 | 27 | return m_callback(data, maxlen); 28 | } 29 | 30 | qint64 writeData(const char *, qint64) override { return 0; } 31 | }; 32 | 33 | AudioOutput::AudioOutput(const Callback &callback, QObject *parent) : 34 | QObject(parent) 35 | { 36 | m_audioDevice = new AudioDevice(callback, this); 37 | } 38 | 39 | AudioOutput::~AudioOutput() 40 | { 41 | this->stop(); 42 | } 43 | 44 | void AudioOutput::updateAudioOutput(const QAudioFormat &format) 45 | { 46 | if(m_output) 47 | this->stop(); 48 | 49 | if(!format.isValid()) 50 | return; 51 | 52 | m_output = new QAudioOutput(format, this); 53 | 54 | // restart when encounter unexpected idle 55 | QObject::connect(m_output, &QAudioOutput::stateChanged, this, 56 | [this](QAudio::State s) { 57 | if(s == QAudio::IdleState) 58 | m_output->start(m_audioDevice); 59 | }); 60 | } 61 | 62 | void AudioOutput::setVolume(qreal volume) 63 | { 64 | if (m_output) 65 | m_output->setVolume(volume); 66 | } 67 | 68 | qreal AudioOutput::volume() const 69 | { 70 | return m_output ? m_output->volume() : 0.; 71 | } 72 | 73 | void AudioOutput::play() 74 | { 75 | if (!m_output) 76 | return; 77 | 78 | m_output->start(m_audioDevice); 79 | m_bufferDuration = qreal(m_output->format().durationForBytes(m_output->bufferSize())) / 1000000; 80 | 81 | if (m_output->error() != QAudio::NoError) 82 | qCritical() << __FUNCTION__ << ":" << m_output->error(); 83 | } 84 | 85 | void AudioOutput::pause() 86 | { 87 | if (m_output) 88 | m_output->reset(); 89 | } 90 | 91 | void AudioOutput::stop() 92 | { 93 | if (!m_output) 94 | return; 95 | 96 | m_output->reset(); 97 | m_output->deleteLater(); 98 | m_output = nullptr; 99 | } 100 | 101 | void AudioOutput::reset() 102 | { 103 | if (!m_output) 104 | return; 105 | 106 | const auto previousState = m_output->state(); 107 | m_output->reset(); 108 | 109 | if (previousState == QAudio::ActiveState) 110 | m_output->start(m_audioDevice); 111 | } 112 | 113 | bool AudioOutput::isLowDataLeft() const 114 | { 115 | if(!m_output) 116 | return false; 117 | 118 | return m_output->bytesFree() > m_output->bufferSize() * 0.75; 119 | } 120 | 121 | qreal AudioOutput::bufferDuration() const 122 | { 123 | return m_bufferDuration; 124 | } 125 | -------------------------------------------------------------------------------- /MultithreadedDownloader/src/downloader/multithreadeddownloader.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief MultithreadedDownloader 3 | * @anchor Ho229<2189684957@qq.com> 4 | * @date 2021/2/1 5 | */ 6 | 7 | #ifndef MULTITHREADEDDOWNLOADER_H 8 | #define MULTITHREADEDDOWNLOADER_H 9 | 10 | #include 11 | 12 | #include "downloadmission.h" 13 | #include "multithreadeddownloaderwriter.h" 14 | 15 | class QNetworkAccessManager; 16 | 17 | class MultithreadedDownloader : public AbstractMission 18 | { 19 | Q_OBJECT 20 | public: 21 | enum Error 22 | { 23 | OpenFileFailed, 24 | DownloadFailed 25 | }; 26 | 27 | explicit MultithreadedDownloader(QObject *parent = nullptr); 28 | ~MultithreadedDownloader() Q_DECL_OVERRIDE; 29 | 30 | /** 31 | * @brief 设置下载线程数 32 | * @param num 下载线程数 33 | */ 34 | void setThreadCount(int num){ m_threadCount = num; } 35 | int threadCount() const { return m_threadCount; } 36 | 37 | /** 38 | * @brief 初始化下载 39 | * @note 获取下载文件大小,文件名 40 | * @return 是否成功 41 | */ 42 | bool load(); 43 | 44 | /** 45 | * @brief 设置下载文件名 46 | * @param name 下载文件名 47 | */ 48 | void setFileName(const QString& name){ m_writer->setFileName(name); } 49 | QString fileName() const { return m_writer->fileName(); } 50 | 51 | /** 52 | * @return 下载文件大小 53 | */ 54 | qint64 downloadSize() const { return m_writer->size(); } 55 | 56 | /** 57 | * @return 目标服务器是否支持 Range Header 58 | * @note 如果不支持 Range,多线程下载和暂停将不可用 59 | */ 60 | bool isRangeSupport() const { return m_isRangeSupport; } 61 | 62 | /** 63 | * @brief The interval at downloadProgress() will emit. 64 | * @default 500 65 | */ 66 | void setNotifyInterval(int interval) { m_notifyInterval = interval; } 67 | int notifyInterval() const { return m_notifyInterval; } 68 | 69 | QNetworkReply::NetworkError networkError() const { return m_networkError; } 70 | QString networkErrorString() const { return m_networkErrorString; } 71 | 72 | void start() Q_DECL_OVERRIDE; 73 | void pause() Q_DECL_OVERRIDE; 74 | void stop() Q_DECL_OVERRIDE; 75 | 76 | signals: 77 | void error(const MultithreadedDownloader::Error err); 78 | void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); 79 | 80 | private slots: 81 | void errorHanding(QNetworkReply::NetworkError err); // 错误处理 82 | void on_finished(); 83 | 84 | private: 85 | QNetworkAccessManager* m_manager = nullptr; 86 | MultithreadedDownloaderWriter *m_writer = nullptr; 87 | 88 | int m_threadCount = 0; 89 | int m_finishedCount = 0; 90 | 91 | int m_timerId = 0; 92 | 93 | int m_notifyInterval = 500; 94 | 95 | bool m_isRangeSupport = false; 96 | 97 | QList m_missions; 98 | 99 | QNetworkReply::NetworkError m_networkError = QNetworkReply::NoError; 100 | QString m_networkErrorString; 101 | 102 | inline DownloadMission* createMission(qint64 start, qint64 end); 103 | inline void destoryMissions(); 104 | inline void updateProgress(); 105 | inline void reset(); 106 | 107 | void timerEvent(QTimerEvent* event) Q_DECL_OVERRIDE; // 更新进度 108 | }; 109 | 110 | #endif // MULTITHREADEDDOWNLOADER_H 111 | -------------------------------------------------------------------------------- /VideoPlayer/src/player/videoplayer.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief Video Player 3 | * @anchor Ho 229 4 | * @date 2021/4/14 5 | */ 6 | 7 | #ifndef VIDEOPLAYER_H 8 | #define VIDEOPLAYER_H 9 | 10 | #include 11 | 12 | class VideoPlayerPrivate; 13 | 14 | class VideoPlayer : public QQuickFramebufferObject 15 | { 16 | Q_OBJECT 17 | 18 | Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) 19 | Q_PROPERTY(qreal volume READ volume WRITE setVolume NOTIFY volumeChanged) 20 | 21 | Q_PROPERTY(int activeVideoTrack READ activeVideoTrack WRITE setActiveVideoTrack NOTIFY activeVideoTrackChanged) 22 | Q_PROPERTY(int activeAudioTrack READ activeAudioTrack WRITE setActiveAudioTrack NOTIFY activeAudioTrackChanged) 23 | Q_PROPERTY(int activeSubtitleTrack READ activeSubtitleTrack WRITE setActiveSubtitleTrack NOTIFY activeSubtitleTrackChanged) 24 | 25 | // Read only property 26 | Q_PROPERTY(int position READ position NOTIFY positionChanged) 27 | 28 | Q_PROPERTY(QString errorString READ errorString NOTIFY errorOccurred) 29 | Q_PROPERTY(State playbackState READ playbackState NOTIFY playbackStateChanged) 30 | 31 | Q_PROPERTY(int duration READ duration NOTIFY loaded) 32 | 33 | Q_PROPERTY(bool hasVideo READ hasVideo NOTIFY loaded) 34 | Q_PROPERTY(bool hasAudio READ hasAudio NOTIFY loaded) 35 | Q_PROPERTY(bool hasSubtitle READ hasSubtitle NOTIFY loaded) 36 | 37 | Q_PROPERTY(bool seekable READ seekable NOTIFY loaded) 38 | 39 | Q_PROPERTY(int videoTrackCount READ videoTrackCount NOTIFY loaded) 40 | Q_PROPERTY(int audioTrackCount READ audioTrackCount NOTIFY loaded) 41 | Q_PROPERTY(int subtitleTrackCount READ subtitleTrackCount NOTIFY loaded) 42 | 43 | public: 44 | enum State 45 | { 46 | Playing, 47 | Paused, 48 | Stopped 49 | }; 50 | Q_ENUM(State) 51 | 52 | VideoPlayer(QQuickItem *parent = nullptr); 53 | virtual ~VideoPlayer() Q_DECL_OVERRIDE; 54 | 55 | Renderer *createRenderer() const Q_DECL_OVERRIDE; 56 | 57 | void setSource(const QUrl& source); 58 | QUrl source() const; 59 | 60 | State playbackState() const; 61 | 62 | void setVolume(qreal volume); 63 | qreal volume() const; 64 | 65 | void setActiveVideoTrack(int index); 66 | int activeVideoTrack() const; 67 | 68 | void setActiveAudioTrack(int index); 69 | int activeAudioTrack() const; 70 | 71 | void setActiveSubtitleTrack(int index); 72 | int activeSubtitleTrack() const; 73 | 74 | int videoTrackCount() const; 75 | int audioTrackCount() const; 76 | int subtitleTrackCount() const; 77 | 78 | /** 79 | * @return duration of the media in seconds. 80 | */ 81 | int duration() const; 82 | int position() const; 83 | 84 | bool hasVideo() const; 85 | bool hasAudio() const; 86 | bool hasSubtitle() const; 87 | 88 | bool seekable() const; 89 | 90 | QString errorString() const; 91 | 92 | Q_INVOKABLE void play(); 93 | Q_INVOKABLE void pause(); 94 | Q_INVOKABLE void stop(); 95 | 96 | Q_INVOKABLE void seek(int position); 97 | 98 | signals: 99 | void errorOccurred(QString); 100 | 101 | void loaded(); 102 | void sourceChanged(QUrl); 103 | void playbackStateChanged(VideoPlayer::State); 104 | void volumeChanged(qreal); 105 | void positionChanged(int); 106 | 107 | void activeVideoTrackChanged(int); 108 | void activeAudioTrackChanged(int); 109 | void activeSubtitleTrackChanged(int); 110 | 111 | private: 112 | VideoPlayerPrivate *const d_ptr; 113 | Q_DECLARE_PRIVATE(VideoPlayer) 114 | 115 | void timerEvent(QTimerEvent *) Q_DECL_OVERRIDE; 116 | }; 117 | 118 | #endif // VIDEOPLAYER_H 119 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/customWidgets/rotatestackedwidget.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Animation Stacked Widget 3 | * @brief 翻转动画 Stacked Widget 4 | * @anchor Ho229 5 | * @date 2020/12/12 6 | */ 7 | 8 | #include "rotatestackedwidget.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | RotateStackedWidget::RotateStackedWidget(QWidget *parent) : 15 | QStackedWidget(parent), 16 | m_animation(new QVariantAnimation(this)) 17 | { 18 | m_animation->setStartValue(0); 19 | m_animation->setEndValue(180); 20 | m_animation->setDuration(500); 21 | m_animation->setEasingCurve(QEasingCurve::Linear); 22 | 23 | connect(m_animation, &QVariantAnimation::valueChanged, this, 24 | [this](const QVariant value){ 25 | m_rotateValue = value.toInt(); 26 | this->repaint(); 27 | }); 28 | connect(m_animation, &QVariantAnimation::finished, this, 29 | &RotateStackedWidget::on_finished); 30 | } 31 | 32 | RotateStackedWidget::~RotateStackedWidget() 33 | { 34 | 35 | } 36 | 37 | void RotateStackedWidget::rotate(int index, bool exec) 38 | { 39 | if(m_animation->state() == QVariantAnimation::Running || this->currentIndex() == index) 40 | return; 41 | 42 | m_nextIndex = index; 43 | 44 | m_currentWidget = this->currentWidget(); 45 | m_nextWidget = this->widget(m_nextIndex); 46 | 47 | if(m_nextWidget == nullptr) 48 | return; 49 | 50 | this->updateFrame(); 51 | 52 | m_currentWidget->hide(); 53 | m_animation->start(); 54 | 55 | if(exec) 56 | { 57 | QEventLoop loop; 58 | connect(m_animation, &QVariantAnimation::finished, &loop, &QEventLoop::quit); 59 | loop.exec(QEventLoop::ExcludeUserInputEvents); 60 | } 61 | } 62 | 63 | void RotateStackedWidget::setAnimationDuration(int duration) 64 | { 65 | m_animation->setDuration(duration); 66 | } 67 | 68 | int RotateStackedWidget::animationDuration() 69 | { 70 | return m_animation->duration(); 71 | } 72 | 73 | void RotateStackedWidget::setAnimationEasingCurve(const QEasingCurve& easing) 74 | { 75 | m_animation->setEasingCurve(easing); 76 | } 77 | 78 | QEasingCurve RotateStackedWidget::animationEasingCurve() 79 | { 80 | return m_animation->easingCurve(); 81 | } 82 | 83 | void RotateStackedWidget::on_finished() 84 | { 85 | m_rotateValue = 0; 86 | 87 | m_nextWidget->show(); 88 | m_nextWidget->raise(); 89 | 90 | this->setCurrentIndex(m_nextIndex); 91 | this->repaint(); 92 | } 93 | 94 | void RotateStackedWidget::updateFrame() 95 | { 96 | m_nextWidget->setGeometry(0, 0, this->width(), this->height()); 97 | m_currentWidget->setGeometry(0, 0, this->width(), this->height()); 98 | 99 | m_currentPixmap = QPixmap(m_currentWidget->size()); 100 | m_currentWidget->render(&m_currentPixmap); 101 | 102 | m_nextPixmap = QPixmap(m_nextWidget->size()); 103 | m_nextWidget->render(&m_nextPixmap); 104 | } 105 | 106 | void RotateStackedWidget::paintEvent(QPaintEvent *event) 107 | { 108 | if(m_animation->state() == QVariantAnimation::Running) 109 | { 110 | QPainter painter(this); 111 | 112 | QTransform transform; 113 | if(m_rotateValue > 90) 114 | { 115 | transform.translate(this->width() / 2, 0); 116 | transform.rotate(m_rotateValue + 180, Qt::YAxis); 117 | 118 | painter.setTransform(transform); 119 | painter.drawPixmap(-1 * this->width() / 2, 0, m_nextPixmap); 120 | } 121 | else 122 | { 123 | transform.translate(this->width() / 2, 0); 124 | transform.rotate(m_rotateValue, Qt::YAxis); 125 | 126 | painter.setTransform(transform); 127 | painter.drawPixmap(-1 * this->width() / 2, 0, m_currentPixmap); 128 | } 129 | } 130 | else 131 | QStackedWidget::paintEvent(event); 132 | } 133 | 134 | void RotateStackedWidget::resizeEvent(QResizeEvent *event) 135 | { 136 | if(m_animation->state() == QVariantAnimation::Running) 137 | this->updateFrame(); 138 | 139 | QStackedWidget::resizeEvent(event); 140 | } 141 | -------------------------------------------------------------------------------- /QmlFireworks/src/qml/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Window 2.15 3 | import QtQuick.Particles 2.12 4 | 5 | Window { 6 | width: 640 7 | height: 480 8 | visible: true 9 | title: qsTr("Fireworks") 10 | 11 | maximumHeight: height 12 | minimumHeight: height 13 | maximumWidth: width 14 | minimumWidth: width 15 | 16 | Image { 17 | id: background 18 | source: "qrc:/image/background.png" 19 | anchors.fill: parent 20 | visible: true 21 | } 22 | 23 | ParticleSystem { 24 | id: fireworksSystem 25 | anchors.fill: parent 26 | 27 | Emitter { 28 | x: 274 29 | y: 358 30 | width: 314 31 | height: 35 32 | rotation: -25 33 | 34 | group: "launch" 35 | emitRate: 2 36 | lifeSpan: 1500 37 | size: 40 38 | endSize: 10 39 | 40 | maximumEmitted: 6 41 | 42 | velocity: AngleDirection { 43 | angle: 255 44 | angleVariation: 5 45 | 46 | magnitude: 250 47 | magnitudeVariation: 25 48 | } 49 | 50 | acceleration: PointDirection { y: 100 } 51 | } 52 | 53 | ImageParticle { 54 | id: fireball 55 | groups: "launch" 56 | source: "qrc:///particleresources/star.png" 57 | colorVariation: 1 58 | } 59 | 60 | ImageParticle { 61 | id: flame 62 | groups: ["flame", "burstFlame"] 63 | source: "qrc:///particleresources/glowdot.png" 64 | } 65 | 66 | TrailEmitter { 67 | id: fireballFlame 68 | anchors.fill: parent 69 | group: "flame" 70 | follow: "launch" 71 | 72 | emitRatePerParticle: 50 73 | lifeSpan: 400 74 | lifeSpanVariation: 50 75 | 76 | size: 10 77 | endSize: 3 78 | 79 | 80 | 81 | onEmitFollowParticles: { 82 | for(var i = 0; i < particles.length; i++) { 83 | particles[i].red = followed.red; 84 | particles[i].green = followed.green; 85 | particles[i].blue = followed.blue; 86 | } 87 | } 88 | } 89 | 90 | ImageParticle { 91 | id: burstFirework 92 | groups: "burst" 93 | source: "qrc:///particleresources/star.png" 94 | } 95 | 96 | Emitter { 97 | id: burstEmitter 98 | group: "burst" 99 | lifeSpan: 2300 100 | lifeSpanVariation: 200 101 | 102 | size: 30 103 | endSize: 15 104 | 105 | enabled: false 106 | velocity: AngleDirection { 107 | angleVariation: 360 108 | magnitudeVariation: 60 109 | } 110 | acceleration: PointDirection { y: 15 } 111 | } 112 | 113 | TrailEmitter { 114 | group: "burstFlame" 115 | follow: "burst" 116 | anchors.fill: parent 117 | 118 | lifeSpan: 400 119 | 120 | emitRatePerParticle: 30 121 | size: 7 122 | endSize: 5 123 | 124 | onEmitFollowParticles: { 125 | for(var i = 0; i < particles.length; i++) { 126 | particles[i].red = followed.red; 127 | particles[i].green = followed.green; 128 | particles[i].blue = followed.blue; 129 | //flame.color = Qt.rgba(followed.red, followed.green, followed.blue, 1); Linux use this 130 | } 131 | } 132 | } 133 | 134 | Affector { 135 | x: 17 136 | y: 46 137 | width: 607 138 | height: 192 139 | once: true 140 | groups: "launch" 141 | 142 | onAffectParticles: { 143 | for(const particle of particles) { 144 | if(particle.lifeLeft() < 0.02) { 145 | burstFirework.color = Qt.hsva(Math.random(), 1, 1, 1); 146 | burstEmitter.burst(150, particle.x, particle.y); 147 | } 148 | } 149 | } 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/customWidgets/notifywidget.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Notify Widget 3 | * @brief 右下角提示窗 4 | * @anchor Ho229<2189684957@qq.com> 5 | * @date 2021/2/1 6 | */ 7 | 8 | #include "notifywidget.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "countdownbutton.h" 20 | 21 | NotifyWidget::NotifyWidget(QWidget *parent, const QString &title, 22 | const QString &messsage, const QIcon& icon, 23 | const QString &style) : 24 | QWidget(parent), 25 | m_iconLabel(new QLabel(this)), 26 | m_titleLabel(new QLabel(this)), 27 | m_messageLabel(new QLabel(this)), 28 | m_hLayout(new QHBoxLayout()), 29 | m_mLayout(new QHBoxLayout()), 30 | m_vLayout(new QVBoxLayout(this)), 31 | m_hSpacer(new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum)), 32 | m_icon(icon), 33 | m_closeButton(new CountdownButton(this)), 34 | m_animation(new QPropertyAnimation(this, "pos", this)) 35 | { 36 | // Title and close button 37 | m_hLayout->addWidget(m_titleLabel); 38 | m_hLayout->addSpacerItem(m_hSpacer); 39 | m_hLayout->addWidget(m_closeButton); 40 | 41 | // Icon and message 42 | m_mLayout->addWidget(m_iconLabel); 43 | m_mLayout->addWidget(m_messageLabel); 44 | m_mLayout->setStretch(1, 1); 45 | m_mLayout->setSpacing(9); 46 | 47 | m_vLayout->addLayout(m_hLayout); 48 | m_vLayout->addLayout(m_mLayout); 49 | 50 | QFont font = m_titleLabel->font(); 51 | font.setPointSize(15); 52 | font.setBold(true); 53 | m_titleLabel->setFont(font); 54 | m_titleLabel->setText(title); 55 | m_titleLabel->setMaximumWidth(300); 56 | 57 | if(m_icon.isNull()) 58 | m_iconLabel->hide(); 59 | else 60 | { 61 | m_iconLabel->setPixmap(m_icon.pixmap(m_iconLabel->size())); 62 | m_iconLabel->adjustSize(); 63 | } 64 | 65 | m_messageLabel->setText(messsage); 66 | m_messageLabel->setWordWrap(true); 67 | m_messageLabel->setMaximumSize(QSize(300, 70)); 68 | 69 | m_closeButton->setFixedSize(QSize(30, 30)); 70 | m_closeButton->setIcon(QIcon(":/image/resource/image/close.png")); 71 | 72 | QObject::connect(m_closeButton, &CountdownButton::clicked, this, 73 | &NotifyWidget::closeAnimation); 74 | 75 | m_animation->setDuration(350); 76 | m_animation->setEasingCurve(QEasingCurve::OutQuart); 77 | 78 | this->setAttribute(Qt::WA_DeleteOnClose); 79 | this->setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowStaysOnTopHint 80 | | Qt::WindowSystemMenuHint); 81 | this->setStyleSheet(style); 82 | this->setLayout(m_vLayout); 83 | this->adjustSize(); 84 | } 85 | 86 | NotifyWidget::~NotifyWidget() 87 | { 88 | 89 | } 90 | 91 | void NotifyWidget::setCloseCountdown(int ms) 92 | { 93 | m_closeButton->conutdownCilk(ms); 94 | } 95 | 96 | int NotifyWidget::closeCountdown() const 97 | { 98 | return m_closeButton->countdown(); 99 | } 100 | 101 | void NotifyWidget::animatMove(int x, int y) 102 | { 103 | if(m_animation->state() == QPropertyAnimation::Running) 104 | m_animation->stop(); 105 | 106 | m_animation->setEndValue(QPoint(x, y)); 107 | m_animation->setStartValue(this->pos()); 108 | 109 | m_animation->start(); 110 | } 111 | 112 | int NotifyWidget::leftTime() const 113 | { 114 | return m_animation->duration() - m_animation->currentLoopTime(); 115 | } 116 | 117 | void NotifyWidget::showEvent(QShowEvent *event) 118 | { 119 | this->animatMove(this->x() - this->width(), this->y()); 120 | 121 | return QWidget::showEvent(event); 122 | } 123 | 124 | void NotifyWidget::closeAnimation() 125 | { 126 | m_isClosing = true; 127 | this->animatMove(this->x() + this->width(), this->y()); 128 | 129 | QObject::connect(m_animation, &QPropertyAnimation::finished, this, 130 | [this]{ 131 | if(QGuiApplication::primaryScreen()->size().width() == this->x()) 132 | this->close(); 133 | }); 134 | } 135 | void NotifyWidget::closeEvent(QCloseEvent *event) 136 | { 137 | emit closed(); 138 | return QWidget::closeEvent(event); 139 | } 140 | 141 | void NotifyWidget::mouseReleaseEvent(QMouseEvent *event) 142 | { 143 | emit clicked(); 144 | return QWidget::mouseReleaseEvent(event); 145 | } 146 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/customWidgets/translationstackedwidget.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Animation Stacked Widget 3 | * @brief 平移动画 Stacked Widget 4 | * @anchor Ho229 5 | * @date 2020/12/12 6 | */ 7 | 8 | #include "translationstackedwidget.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | TranslationStackedWidget::TranslationStackedWidget(QWidget *parent) : 15 | QStackedWidget(parent), 16 | m_animation(new QVariantAnimation(this)) 17 | { 18 | m_animation->setDuration(500); 19 | m_animation->setEasingCurve(QEasingCurve::OutQuint); 20 | 21 | connect(m_animation, &QVariantAnimation::finished, this, 22 | &TranslationStackedWidget::on_finished); 23 | connect(m_animation, &QVariantAnimation::valueChanged, this, 24 | [this](const QVariant value){ 25 | m_animationX = value.toInt(); 26 | this->repaint(); 27 | }); 28 | } 29 | 30 | TranslationStackedWidget::~TranslationStackedWidget() 31 | { 32 | 33 | } 34 | 35 | void TranslationStackedWidget::moveToIndex(int index, bool exec) 36 | { 37 | if(m_animation->state() == QVariantAnimation::Running || this->currentIndex() == index) 38 | return; 39 | 40 | m_nextIndex = index; 41 | 42 | m_currentWidget = this->currentWidget(); 43 | m_nextWidget = this->widget(index); 44 | 45 | if(m_nextWidget == nullptr) 46 | return; 47 | 48 | this->updateFrame(); 49 | this->updateAnimation(); 50 | 51 | m_currentWidget->hide(); 52 | m_animation->start(); 53 | 54 | if(exec) 55 | { 56 | QEventLoop loop; 57 | connect(m_animation, &QVariantAnimation::finished, &loop, &QEventLoop::quit); 58 | loop.exec(QEventLoop::ExcludeUserInputEvents); 59 | } 60 | } 61 | 62 | void TranslationStackedWidget::setAnimationDuration(int duration) 63 | { 64 | m_animation->setDuration(duration); 65 | } 66 | 67 | int TranslationStackedWidget::animationDuration() 68 | { 69 | return m_animation->duration(); 70 | } 71 | 72 | void TranslationStackedWidget::setAnimationEasingCurve(const QEasingCurve &easing) 73 | { 74 | m_animation->setEasingCurve(easing); 75 | } 76 | 77 | QEasingCurve TranslationStackedWidget::animationEasingCurve() 78 | { 79 | return m_animation->easingCurve(); 80 | } 81 | 82 | void TranslationStackedWidget::on_finished() 83 | { 84 | m_nextWidget->show(); 85 | m_nextWidget->raise(); 86 | 87 | this->setCurrentIndex(m_nextIndex); 88 | this->repaint(); 89 | } 90 | 91 | QPixmap TranslationStackedWidget::mergePixmap(const QPixmap &leftPixmap, const QPixmap &rightPixmap) 92 | { 93 | QPixmap newPixmap(leftPixmap.width() + rightPixmap.width(), 94 | qMax(leftPixmap.height(), rightPixmap.height())); 95 | 96 | QPainter painter(&newPixmap); 97 | 98 | painter.drawPixmap(QPoint(0, 0), leftPixmap); 99 | painter.drawPixmap(QPoint(leftPixmap.width(), 0), rightPixmap); 100 | 101 | return newPixmap; 102 | } 103 | 104 | void TranslationStackedWidget::updateFrame() 105 | { 106 | m_nextWidget->setGeometry(0, 0, this->width(), this->height()); 107 | m_currentWidget->setGeometry(0, 0, this->width(), this->height()); 108 | 109 | m_nextWidget->setGeometry(0, 0, this->width(), this->height()); 110 | 111 | QPixmap currentPixmap(m_currentWidget->size()); 112 | m_currentWidget->render(¤tPixmap); 113 | 114 | QPixmap nextPixmap(m_nextWidget->size()); 115 | m_nextWidget->render(&nextPixmap); 116 | 117 | if(m_nextIndex < this->currentIndex()) 118 | m_animationPixmap = mergePixmap(nextPixmap, currentPixmap); 119 | else 120 | m_animationPixmap = mergePixmap(currentPixmap, nextPixmap); 121 | } 122 | 123 | 124 | void TranslationStackedWidget::updateAnimation() 125 | { 126 | if(m_nextIndex < this->currentIndex()) 127 | { 128 | // Last page 129 | m_animation->setStartValue(-this->width() + 1); 130 | m_animation->setEndValue(0); 131 | } 132 | else 133 | { 134 | // Next page 135 | m_animation->setStartValue(0); 136 | m_animation->setEndValue(-this->width() - 1); 137 | } 138 | } 139 | 140 | void TranslationStackedWidget::paintEvent(QPaintEvent *event) 141 | { 142 | if(m_animation->state() == QVariantAnimation::Running) 143 | { 144 | QPainter painter(this); 145 | painter.drawPixmap(QPoint(m_animationX, 0), m_animationPixmap); 146 | } 147 | else 148 | QStackedWidget::paintEvent(event); 149 | } 150 | 151 | void TranslationStackedWidget::resizeEvent(QResizeEvent *event) 152 | { 153 | if(m_animation->state() == QVariantAnimation::Running) 154 | { 155 | this->updateFrame(); 156 | this->updateAnimation(); 157 | } 158 | 159 | QStackedWidget::resizeEvent(event); 160 | } 161 | -------------------------------------------------------------------------------- /MultithreadedDownloader/src/customWidgets/translationstackedwidget.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Animation Stacked Widget 3 | * @brief 平移动画 Stacked Widget 4 | * @anchor Ho229 5 | * @date 2020/12/12 6 | */ 7 | 8 | #include "translationstackedwidget.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | TranslationStackedWidget::TranslationStackedWidget(QWidget *parent) : 15 | QStackedWidget(parent), 16 | m_animation(new QVariantAnimation(this)) 17 | { 18 | m_animation->setDuration(350); 19 | m_animation->setEasingCurve(QEasingCurve::InCubic); 20 | 21 | connect(m_animation, &QVariantAnimation::finished, this, 22 | &TranslationStackedWidget::on_finished); 23 | connect(m_animation, &QVariantAnimation::valueChanged, this, 24 | [this](const QVariant value){ 25 | m_animationX = value.toInt(); 26 | this->repaint(); 27 | }); 28 | } 29 | 30 | TranslationStackedWidget::~TranslationStackedWidget() 31 | { 32 | 33 | } 34 | 35 | void TranslationStackedWidget::moveToIndex(int index, bool exec) 36 | { 37 | if(m_animation->state() == QVariantAnimation::Running || this->currentIndex() == index) 38 | return; 39 | 40 | m_nextIndex = index; 41 | 42 | m_currentWidget = this->currentWidget(); 43 | m_nextWidget = this->widget(index); 44 | 45 | if(m_nextWidget == nullptr) 46 | return; 47 | 48 | this->updateFrame(); 49 | this->updateAnimation(); 50 | 51 | m_currentWidget->hide(); 52 | m_animation->start(); 53 | 54 | if(exec) 55 | { 56 | QEventLoop loop; 57 | connect(m_animation, &QVariantAnimation::finished, &loop, &QEventLoop::quit); 58 | loop.exec(QEventLoop::ExcludeUserInputEvents); 59 | } 60 | } 61 | 62 | void TranslationStackedWidget::setAnimationDuration(int duration) 63 | { 64 | m_animation->setDuration(duration); 65 | } 66 | 67 | int TranslationStackedWidget::animationDuration() 68 | { 69 | return m_animation->duration(); 70 | } 71 | 72 | void TranslationStackedWidget::setAnimationEasingCurve(const QEasingCurve &easing) 73 | { 74 | m_animation->setEasingCurve(easing); 75 | } 76 | 77 | QEasingCurve TranslationStackedWidget::animationEasingCurve() 78 | { 79 | return m_animation->easingCurve(); 80 | } 81 | 82 | void TranslationStackedWidget::on_finished() 83 | { 84 | m_nextWidget->show(); 85 | m_nextWidget->raise(); 86 | 87 | this->setCurrentIndex(m_nextIndex); 88 | this->repaint(); 89 | } 90 | 91 | QPixmap TranslationStackedWidget::mergePixmap(const QPixmap &leftPixmap, const QPixmap &rightPixmap) 92 | { 93 | QPixmap newPixmap(leftPixmap.width() + rightPixmap.width(), 94 | qMax(leftPixmap.height(), rightPixmap.height())); 95 | 96 | QPainter painter(&newPixmap); 97 | 98 | painter.drawPixmap(QPoint(0, 0), leftPixmap); 99 | painter.drawPixmap(QPoint(leftPixmap.width(), 0), rightPixmap); 100 | 101 | return newPixmap; 102 | } 103 | 104 | void TranslationStackedWidget::updateFrame() 105 | { 106 | m_nextWidget->setGeometry(0, 0, this->width(), this->height()); 107 | m_currentWidget->setGeometry(0, 0, this->width(), this->height()); 108 | 109 | m_nextWidget->setGeometry(0, 0, this->width(), this->height()); 110 | 111 | QPixmap currentPixmap(m_currentWidget->size()); 112 | m_currentWidget->render(¤tPixmap); 113 | 114 | QPixmap nextPixmap(m_nextWidget->size()); 115 | m_nextWidget->render(&nextPixmap); 116 | 117 | if(m_nextIndex < this->currentIndex()) 118 | m_animationPixmap = mergePixmap(nextPixmap, currentPixmap); 119 | else 120 | m_animationPixmap = mergePixmap(currentPixmap, nextPixmap); 121 | } 122 | 123 | 124 | void TranslationStackedWidget::updateAnimation() 125 | { 126 | if(m_nextIndex < this->currentIndex()) 127 | { 128 | // Last page 129 | m_animation->setStartValue(0 - this->width()); 130 | m_animation->setEndValue(0); 131 | } 132 | else 133 | { 134 | // Next page 135 | m_animation->setStartValue(0); 136 | m_animation->setEndValue(0 - this->width()); 137 | } 138 | } 139 | 140 | void TranslationStackedWidget::paintEvent(QPaintEvent *event) 141 | { 142 | if(m_animation->state() == QVariantAnimation::Running) 143 | { 144 | QPainter painter(this); 145 | painter.drawPixmap(QPoint(m_animationX, 0), m_animationPixmap); 146 | } 147 | else 148 | QStackedWidget::paintEvent(event); 149 | } 150 | 151 | void TranslationStackedWidget::resizeEvent(QResizeEvent *event) 152 | { 153 | if(m_animation->state() == QVariantAnimation::Running) 154 | { 155 | this->updateFrame(); 156 | this->updateAnimation(); 157 | } 158 | 159 | QStackedWidget::resizeEvent(event); 160 | } 161 | -------------------------------------------------------------------------------- /QmlDemo/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Window 2.12 3 | import QtQuick.Controls 2.12 4 | import QtQuick.Layouts 1.12 5 | 6 | import com.MyItems.Effect 1.0 7 | 8 | Window { 9 | id: root 10 | width: 550 11 | height: 400 12 | visible: true 13 | title: qsTr("Hello World") 14 | 15 | minimumWidth: 400 16 | minimumHeight: 350 17 | 18 | ColumnLayout { 19 | anchors.fill: parent 20 | spacing: 6 21 | 22 | GroupBox { 23 | id:fontStyleGroup 24 | title: qsTr("Font Style") 25 | Layout.fillWidth: true 26 | Layout.margins: 9 27 | 28 | RowLayout { 29 | anchors.fill: parent 30 | 31 | CheckBox { 32 | id: underlineCheck 33 | text: qsTr("Underline") 34 | 35 | onCheckedChanged: { 36 | textEdit.font.underline = checkState == Qt.Checked 37 | } 38 | } 39 | CheckBox { 40 | id: italicCheck 41 | text: qsTr("Italic") 42 | 43 | onCheckStateChanged: { 44 | textEdit.font.italic = checkState == Qt.Checked 45 | } 46 | } 47 | CheckBox { 48 | id: boldCheck 49 | text: qsTr("Bold") 50 | 51 | onCheckStateChanged: { 52 | textEdit.font.bold = checkState == Qt.Checked 53 | } 54 | } 55 | } 56 | } 57 | 58 | GroupBox { 59 | id: fontColorGroup 60 | title: qsTr("Color") 61 | Layout.fillWidth: true 62 | Layout.margins: 9 63 | 64 | RowLayout { 65 | anchors.fill: parent 66 | 67 | RadioButton { 68 | id: blackRadio 69 | text: qsTr("Black") 70 | hoverEnabled: true 71 | checked: true 72 | 73 | onClicked: { 74 | textEdit.color = "black" 75 | } 76 | } 77 | RadioButton { 78 | id: redRadio 79 | text: qsTr("Red") 80 | 81 | onClicked: { 82 | textEdit.color = "red" 83 | } 84 | } 85 | RadioButton { 86 | id: blueRadio 87 | text: qsTr("Blud") 88 | 89 | onClicked: { 90 | textEdit.color = "blue" 91 | } 92 | } 93 | } 94 | } 95 | 96 | TextField { 97 | id: textEdit 98 | text: qsTr("Hello World") 99 | Layout.fillWidth: true 100 | Layout.fillHeight: true 101 | Layout.margins: 9 102 | 103 | font.pointSize: 20 104 | horizontalAlignment: Text.AlignHCenter 105 | verticalAlignment: Text.AlignVCenter 106 | selectionColor: "#0000ff" 107 | 108 | selectByMouse: true 109 | readOnly: true 110 | 111 | background: ClickWaveEffect { 112 | target: textEdit 113 | 114 | maxRadius: 20 115 | waveDuration: 100 116 | 117 | anchors.fill: parent 118 | } 119 | } 120 | 121 | RowLayout { 122 | id: rowLayout 123 | Layout.preferredWidth: parent.width 124 | 125 | Button { 126 | id: myButton 127 | text: qsTr("Push Button") 128 | Layout.margins: 9 129 | Layout.alignment: Qt.AlignLeft //| Qt.AlignHCenter 130 | 131 | background: Rectangle { 132 | implicitWidth: 100 133 | implicitHeight: 40 134 | 135 | color: "#E0E0E0" 136 | 137 | ClickWaveEffect { 138 | target: myButton 139 | 140 | anchors.fill: parent 141 | } 142 | } 143 | } 144 | 145 | Button { 146 | id: closeButton 147 | text: qsTr("Close") 148 | Layout.alignment: Qt.AlignRight //| Qt.AlignHCenter 149 | Layout.margins: 9 150 | 151 | background: Rectangle { 152 | implicitWidth: 100 153 | implicitHeight: 40 154 | 155 | color: "#E0E0E0" 156 | 157 | ClickWaveEffect { 158 | target: closeButton 159 | 160 | anchors.fill: parent 161 | } 162 | } 163 | 164 | onClicked: root.close() 165 | } 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /VideoPlayer/src/player/ffmpegdecoder.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief FFmpeg decoder 3 | * @anchor Ho 229 4 | * @date 2021/4/13 5 | */ 6 | 7 | #ifndef FFMPEGDECODER_H 8 | #define FFMPEGDECODER_H 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | #define FUNC_ERROR qCritical() << __FUNCTION__ 24 | 25 | struct SubtitleFrame 26 | { 27 | SubtitleFrame(int width, int height) : 28 | image(width, height, QImage::Format_RGBA8888) 29 | { image.fill(Qt::transparent); } 30 | 31 | QImage image; 32 | qreal start = 0; 33 | }; 34 | 35 | class FFmpegDecoder final : public QObject 36 | { 37 | Q_OBJECT 38 | public: 39 | enum State 40 | { 41 | Opened, 42 | Closed 43 | }; 44 | Q_ENUM(State) 45 | 46 | explicit FFmpegDecoder(QObject *parent = nullptr); 47 | ~FFmpegDecoder() Q_DECL_OVERRIDE; 48 | 49 | /** 50 | * @brief Request the interruption of the FFmpegDecoer::decode 51 | */ 52 | void requestInterrupt() { m_runnable = false; } 53 | 54 | void setUrl(const QUrl& url) { m_url = url; } 55 | QUrl url() const { return m_url; } 56 | 57 | State state() const { return m_state; } 58 | 59 | QString errorString() const { return m_errorBuf; } 60 | 61 | int activeVideoTrack() const; 62 | int activeAudioTrack() const; 63 | int activeSubtitleTrack() const; 64 | 65 | int videoTrackCount() const; 66 | int audioTrackCount() const; 67 | int subtitleTrackCount() const; 68 | 69 | bool hasFrame() const { return m_videoCache.count() || m_audioCache.count(); } 70 | 71 | bool seekable() const; 72 | 73 | bool isEnd() const { return m_isEnd; } 74 | 75 | /** 76 | * @return duration of the media in seconds. 77 | */ 78 | int duration() const; 79 | 80 | const QAudioFormat audioFormat() const; 81 | 82 | AVFrame *takeVideoFrame(); 83 | AVFrame *takeAudioFrame(); 84 | SubtitleFrame *takeSubtitleFrame(qreal time); 85 | 86 | /** 87 | * @return qQNaN() if not available(eg. no video frames or only a single frame like album cover), 88 | * otherwise returns frame rate of video stream 89 | */ 90 | qreal fps() const; 91 | 92 | static qreal framePts(const AVFrame *frame); 93 | static qreal frameDuration(const AVFrame *frame); 94 | 95 | signals: 96 | void stateChanged(FFmpegDecoder::State); 97 | 98 | void activeVideoTrackChanged(int); 99 | void activeAudioTrackChanged(int); 100 | void activeSubtitleTrackChanged(int); 101 | 102 | public slots: 103 | void load(); 104 | void release(); 105 | 106 | void seek(int position); 107 | 108 | void setActiveVideoTrack(int index); 109 | void setActiveAudioTrack(int index); 110 | void setActiveSubtitleTrack(int index); 111 | 112 | void decode(); 113 | 114 | private: 115 | void decodeVideo(AVPacket *packet); 116 | void decodeAudio(AVPacket *packet); 117 | void decodeSubtitle(AVPacket *packet); 118 | 119 | bool shouldDecode() const; 120 | 121 | void clearCache(); 122 | 123 | bool openCodecContext(AVStream *&stream, AVCodecContext *&codecContext, 124 | AVMediaType type, int index); 125 | void closeCodecContext(AVStream *&stream, AVCodecContext *&codecContext); 126 | 127 | bool openSubtitleFilter(const QString &args, const QString &filterDesc); 128 | void closeSubtitleFilter(); 129 | 130 | private: 131 | State m_state = Closed; 132 | 133 | char m_errorBuf[AV_ERROR_MAX_STRING_SIZE]; 134 | 135 | mutable QMutex m_mutex; 136 | 137 | QUrl m_url; 138 | 139 | AVFormatContext *m_formatContext = nullptr; 140 | 141 | AVStream *m_videoStream = nullptr; 142 | AVCodecContext *m_videoCodecContext = nullptr; 143 | 144 | AVStream *m_audioStream = nullptr; 145 | AVCodecContext *m_audioCodecContext = nullptr; 146 | 147 | AVStream *m_subtitleStream = nullptr; 148 | AVCodecContext *m_subtitleCodecContext = nullptr; 149 | 150 | AVFilterGraph *m_filterGraph = nullptr; 151 | AVFilterContext *m_buffersrcContext = nullptr; 152 | AVFilterContext *m_buffersinkContext = nullptr; 153 | 154 | SwrContext *m_swrContext = nullptr; 155 | SwsContext *m_swsContext = nullptr; 156 | 157 | QContiguousCache m_videoCache; 158 | QContiguousCache m_audioCache; 159 | QContiguousCache m_subtitleCache; 160 | 161 | qreal m_fps = qQNaN(); // See also FFmpegDecoder::fps() 162 | 163 | volatile bool m_isDecoding = false; 164 | volatile bool m_runnable = false; // Is FFmpegDecoder::decode() could run 165 | volatile bool m_isEnd = false; 166 | 167 | int m_seekTarget = -1; // -1 means undefined 168 | 169 | QList m_videoIndexes; 170 | QList m_audioIndexes; 171 | QList m_subtitleIndexes; 172 | int m_subtitleIndex = -1; 173 | }; 174 | 175 | #endif // FFMPEGDECODER_H 176 | -------------------------------------------------------------------------------- /QmlDemo/src/customItem/clickwaveeffect.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Click Wave Effect 3 | * @anchor Ho 229 4 | * @date 2021/7/18 5 | */ 6 | 7 | #include "clickwaveeffect.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | ClickWaveEffect::ClickWaveEffect(QQuickItem *parent) 14 | : QQuickPaintedItem(parent), 15 | m_animation(new QSequentialAnimationGroup(this)), 16 | m_alphaAnimation(new QVariantAnimation(this)), 17 | m_radiusAnimation(new QVariantAnimation(this)) 18 | { 19 | m_color = {207, 207, 207}; // Default color 20 | 21 | m_alphaAnimation->setStartValue(255); 22 | m_alphaAnimation->setEndValue(0); 23 | m_alphaAnimation->setDuration(200); 24 | 25 | m_radiusAnimation->setStartValue(0); 26 | m_radiusAnimation->setDuration(400); 27 | 28 | m_animation->addAnimation(m_radiusAnimation); 29 | m_animation->addAnimation(m_alphaAnimation); 30 | 31 | QObject::connect(m_radiusAnimation, &QVariantAnimation::valueChanged, this, 32 | [this](const QVariant& value){ 33 | m_radius = value.toInt(); 34 | this->update(); 35 | }); 36 | QObject::connect(m_alphaAnimation, &QVariantAnimation::valueChanged, this, 37 | [this](const QVariant& value){ 38 | m_currentColor.setAlpha(value.toInt()); 39 | this->update(); 40 | }); 41 | QObject::connect(m_radiusAnimation, &QVariantAnimation::finished, this, 42 | [this]{ 43 | if(m_isPressed) 44 | m_animation->pause(); 45 | }); 46 | QObject::connect(m_animation, &QSequentialAnimationGroup::finished, this, 47 | &ClickWaveEffect::on_finished); 48 | } 49 | 50 | ClickWaveEffect::~ClickWaveEffect() 51 | { 52 | 53 | } 54 | 55 | void ClickWaveEffect::setTarget(QQuickItem *target) 56 | { 57 | if(!target) 58 | return; 59 | 60 | target->installEventFilter(this); 61 | m_target = target; 62 | 63 | emit targetChanged(target); 64 | } 65 | 66 | void ClickWaveEffect::setColor(const QColor &color) 67 | { 68 | m_alphaAnimation->setStartValue(color.alpha()); 69 | m_color = color; 70 | emit colorChanged(color); 71 | } 72 | 73 | void ClickWaveEffect::setWaveDuration(int duration) 74 | { 75 | m_radiusAnimation->setDuration(duration); 76 | emit waveDurationChanged(duration); 77 | } 78 | 79 | int ClickWaveEffect::waveDuration() const 80 | { 81 | return m_radiusAnimation->duration(); 82 | } 83 | 84 | void ClickWaveEffect::setMaxRadius(int radius) 85 | { 86 | m_maxRadius = radius; 87 | emit maxRadiusChanged(radius); 88 | } 89 | 90 | void ClickWaveEffect::paint(QPainter *painter) 91 | { 92 | painter->setPen(Qt::NoPen); 93 | painter->setBrush(m_currentColor); 94 | painter->drawEllipse(m_pos, m_radius, m_radius); 95 | } 96 | 97 | bool ClickWaveEffect::eventFilter(QObject *obj, QEvent *event) 98 | { 99 | if(obj == m_target) 100 | { 101 | if(event->type() == QEvent::MouseButtonPress) 102 | { 103 | QMouseEvent *mouseEvent = static_cast(event); 104 | m_pos = mouseEvent->pos(); 105 | m_isPressed = true; 106 | 107 | if(m_animation->state() == QSequentialAnimationGroup::Running) 108 | { 109 | m_animation->stop(); 110 | m_radius = 0; 111 | } 112 | 113 | m_currentColor = m_color; 114 | 115 | m_radiusAnimation->setEndValue( 116 | m_maxRadius < 0 ? maxRadius(m_pos, this->size().toSize()) : m_maxRadius); 117 | 118 | m_animation->start(); 119 | } 120 | else if(event->type() == QEvent::MouseButtonRelease) 121 | { 122 | m_isPressed = false; 123 | 124 | if(m_animation->state() == QSequentialAnimationGroup::Paused) 125 | m_animation->resume(); 126 | } 127 | } 128 | 129 | return QQuickPaintedItem::eventFilter(obj, event); 130 | } 131 | 132 | void ClickWaveEffect::on_finished() 133 | { 134 | m_radius = 0; 135 | emit finished(); 136 | } 137 | 138 | int ClickWaveEffect::maxRadius(const QPoint &pos, const QSize &size) 139 | { 140 | int radius = 0; 141 | 142 | auto dist = [](const QPoint& x, const QPoint& y) -> int { 143 | const QPoint diff = x - y; 144 | return diff.manhattanLength(); 145 | }; 146 | 147 | if(pos.x() < size.width() / 2) // Left 148 | { 149 | if(pos.y() < size.height() / 2) // Top 150 | radius = dist(pos, {size.width(), size.height()}); // RightBottom 151 | else // Bottom 152 | radius = dist(pos, {size.width(), 0}); // RightTop 153 | } 154 | else // Right 155 | { 156 | if(pos.y() < size.height() / 2) // Top 157 | radius = dist(pos, {0, size.height()}); // LeftBottom 158 | else // Bottom 159 | radius = dist(pos, {0, 0}); // RightBottom 160 | } 161 | 162 | return radius; 163 | } 164 | -------------------------------------------------------------------------------- /MultithreadedDownloader/src/mainwidget.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief MainWidget 3 | * @anchor Ho229<2189684957@qq.com> 4 | * @date 2021/2/1 5 | */ 6 | 7 | #include "util.h" 8 | #include "mainwidget.h" 9 | #include "ui_mainwidget.h" 10 | 11 | #include 12 | #include 13 | 14 | MainWidget::MainWidget(QWidget *parent) 15 | : QWidget(parent) 16 | , ui(new Ui::MainWidget), 17 | m_downloader(new MultithreadedDownloader(this)), 18 | m_toast(new Toast(this)) 19 | { 20 | ui->setupUi(this); 21 | 22 | m_oldProgressedBytes = 0; 23 | m_old_progressed_bytes = 0; 24 | 25 | this->initUI(); 26 | this->initSignalSlots(); 27 | } 28 | 29 | MainWidget::~MainWidget() 30 | { 31 | delete ui; 32 | } 33 | 34 | void MainWidget::initDownloadUrl(const QString &url) 35 | { 36 | if(url.isEmpty()) 37 | return; 38 | 39 | ui->urlEdit->setPlainText(url); 40 | this->on_downloadBtn_clicked(); 41 | } 42 | 43 | inline void MainWidget::initUI() 44 | { 45 | ui->failedLabel->hide(); 46 | ui->retranslateUi(this); 47 | } 48 | 49 | inline void MainWidget::initSignalSlots() 50 | { 51 | QObject::connect(m_downloader, &MultithreadedDownloader::finished, this, 52 | [this]{ 53 | QMessageBox::information(this, tr("infomation"), tr("download finished.")); 54 | ui->stackedWidget->moveToIndex(0); 55 | }); 56 | 57 | QObject::connect(m_downloader, &MultithreadedDownloader::error, this, 58 | [this](MultithreadedDownloader::Error err){ 59 | if(err == MultithreadedDownloader::OpenFileFailed) 60 | { 61 | QMessageBox::critical(this, tr("error"), tr("File can not open.")); 62 | this->on_stopBtn_clicked(); 63 | } 64 | else 65 | { 66 | if(QMessageBox::critical(this, tr("error"), 67 | tr("Download Failed.\nNetwork Error:%1\nRetry ?") 68 | .arg(m_downloader->networkErrorString()), 69 | QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) 70 | this->on_startBtn_clicked(); 71 | else 72 | this->on_stopBtn_clicked(); 73 | } 74 | }); 75 | 76 | QObject::connect(m_downloader, &MultithreadedDownloader::stateChanged, this, 77 | [this](MultithreadedDownloader::State state){ 78 | switch (state) 79 | { 80 | case MultithreadedDownloader::Running: 81 | ui->stateLabel->setText(tr("Running")); 82 | break; 83 | case MultithreadedDownloader::Paused: 84 | ui->stateLabel->setText(tr("Paused")); 85 | break; 86 | case MultithreadedDownloader::Stopped: 87 | ui->stateLabel->setText(tr("Stopped")); 88 | break; 89 | } 90 | }); 91 | 92 | QObject::connect(m_downloader, &MultithreadedDownloader::downloadProgress, this, 93 | [this](qint64 bytesReceived, qint64 bytesTotal){ 94 | 95 | ui->progressBar->setValue(static_cast( 96 | static_cast(bytesReceived) / bytesTotal * 100)); 97 | 98 | ui->byteLabel->setText(tr("Speed: %1/s | %2 / %3") 99 | .arg(Until::readableFileSize( 100 | static_cast( 101 | static_cast(bytesReceived - m_oldProgressedBytes) 102 | / (1000 / m_downloader->notifyInterval())))) 103 | 104 | .arg(Until::readableFileSize(bytesReceived)) 105 | .arg(Until::readableFileSize(bytesTotal))); 106 | 107 | m_oldProgressedBytes = bytesReceived; 108 | }); 109 | } 110 | 111 | void MainWidget::on_downloadBtn_clicked() 112 | { 113 | 114 | ui->retranslateUi(this); 115 | QString url(ui->urlEdit->toPlainText()); 116 | if(url.isEmpty()) 117 | { 118 | m_toast->toast(tr("URL is empty.")); 119 | return; 120 | } 121 | 122 | m_downloader->setUrl(url); 123 | if(m_downloader->load()) 124 | { 125 | ui->failedLabel->hide(); 126 | 127 | QString dir = QFileDialog::getExistingDirectory(this, 128 | tr("Please select the download directory"), QDir::homePath()); 129 | 130 | if(dir.isEmpty()) 131 | return; 132 | 133 | ui->fileNameLabel->setText(m_downloader->fileName()); 134 | ui->startBtn->setEnabled(false); 135 | ui->pauseBtn->setEnabled(m_downloader->isRangeSupport()); 136 | ui->stopBtn->setEnabled(true); 137 | 138 | m_toast->toast(tr("Download started.")); 139 | 140 | ui->stackedWidget->moveToIndex(1); 141 | 142 | QDir::setCurrent(dir); 143 | m_downloader->start(); 144 | } 145 | else 146 | ui->failedLabel->show(); 147 | } 148 | 149 | void MainWidget::on_startBtn_clicked() 150 | { 151 | ui->startBtn->setEnabled(false); 152 | ui->pauseBtn->setEnabled(true); 153 | ui->stopBtn->setEnabled(true); 154 | m_downloader->start(); 155 | m_toast->toast(tr("Download started.")); 156 | } 157 | 158 | void MainWidget::on_pauseBtn_clicked() 159 | { 160 | ui->startBtn->setEnabled(true); 161 | ui->pauseBtn->setEnabled(false); 162 | ui->stopBtn->setEnabled(true); 163 | 164 | m_downloader->pause(); 165 | m_toast->toast(tr("Download has been pause.")); 166 | } 167 | 168 | void MainWidget::on_stopBtn_clicked() 169 | { 170 | m_downloader->stop(); 171 | m_toast->toast(tr("Download terminated.")); 172 | ui->stackedWidget->moveToIndex(0); 173 | } 174 | -------------------------------------------------------------------------------- /CustomWidgetDemos/src/mainwidget.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief Demo 3 | * @anchor Ho229 4 | * @date 2020/12/12 5 | */ 6 | 7 | #include "mainwidget.h" 8 | #include "ui_mainwidget.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #ifdef Q_OS_WIN 15 | # if _MSC_VER >= 1600 16 | # pragma execution_character_set("utf-8") 17 | # endif 18 | #endif 19 | 20 | MainWidget::MainWidget(QWidget *parent) 21 | : QWidget(parent) 22 | , ui(new Ui::MainWidget), 23 | m_toast(new Toast(this)), 24 | m_buttonGroup_1(new QButtonGroup(this)), 25 | m_buttonGroup_2(new QButtonGroup(this)), 26 | m_langGroup(new QButtonGroup(this)), 27 | m_notifyManager(new NotifyManager(this)), 28 | m_trans(new QTranslator(this)) 29 | { 30 | ui->setupUi(this); 31 | 32 | m_buttonGroup_1->addButton(ui->rotate_0, 0); 33 | m_buttonGroup_1->addButton(ui->rotate_1, 1); 34 | m_buttonGroup_1->addButton(ui->rotate_2, 2); 35 | #if QT_VERSION >= QT_VERSION_CHECK(5,15,0) 36 | connect(m_buttonGroup_1, &QButtonGroup::idClicked, this, 37 | &MainWidget::on_buttonClicked); 38 | #else 39 | connect(m_buttonGroup_1, QOverload::of(&QButtonGroup::buttonClicked), this, 40 | &MainWidget::on_buttonClicked); 41 | #endif 42 | 43 | m_buttonGroup_2->addButton(ui->translation_0, 0); 44 | m_buttonGroup_2->addButton(ui->translation_1, 1); 45 | m_buttonGroup_2->addButton(ui->translation_3, 2); 46 | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) 47 | connect(m_buttonGroup_2, &QButtonGroup::idClicked, this, 48 | &MainWidget::on_buttonClicked); 49 | #else 50 | connect(m_buttonGroup_2, QOverload::of(&QButtonGroup::buttonClicked), this, 51 | &MainWidget::on_buttonClicked); 52 | #endif 53 | 54 | m_langGroup->addButton(ui->EnBtn, 0); 55 | m_langGroup->addButton(ui->CnBtn, 1); 56 | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) 57 | connect(m_langGroup, &QButtonGroup::idClicked, this, 58 | &MainWidget::on_buttonClicked); 59 | #else 60 | connect(m_langGroup, QOverload::of(&QButtonGroup::buttonClicked), this, 61 | &MainWidget::on_buttonClicked); 62 | #endif 63 | 64 | ui->pushButton->setLeftIcon(QApplication::style()-> 65 | standardIcon(QStyle::SP_MessageBoxInformation)); 66 | ui->pushButton->setRightIcon(QApplication::style()-> 67 | standardIcon(QStyle::SP_MessageBoxWarning)); 68 | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) 69 | connect(ui->sideMarginEdit, &QSpinBox::textChanged, this, 70 | [this](QString value){ 71 | ui->pushButton->setSideMargin(value.toInt()); 72 | ui->pushButton->repaint(); 73 | }); 74 | 75 | connect(ui->topBottomMarginEdit, &QSpinBox::textChanged, this, 76 | [this](QString value){ 77 | ui->pushButton->setTopBottomMargin(value.toInt()); 78 | ui->pushButton->repaint(); 79 | }); 80 | #else 81 | connect(ui->sideMarginEdit, QOverload::of(&QSpinBox::valueChanged), this, 82 | [this](int value){ 83 | ui->pushButton->setSideMargin(value); 84 | ui->pushButton->repaint(); 85 | }); 86 | 87 | connect(ui->topBottomMarginEdit, QOverload::of(&QSpinBox::valueChanged), this, 88 | [this](int value){ 89 | ui->pushButton->setTopBottomMargin(value); 90 | ui->pushButton->repaint(); 91 | }); 92 | #endif 93 | 94 | ui->progressButton->setValue(25); 95 | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) 96 | connect(ui->valueSpinBox, &QSpinBox::textChanged, this, 97 | [this](QString value){ 98 | ui->progressButton->setValue(value.toInt()); 99 | ui->progressButton->setText(value.append("%")); 100 | }); 101 | #else 102 | connect(ui->valueSpinBox, QOverload::of(&QSpinBox::valueChanged), this, 103 | [this](int value){ 104 | ui->progressButton->setValue(value); 105 | ui->progressButton->setText(QString::number(value).append("%")); 106 | }); 107 | #endif 108 | 109 | ui->countdownBtn->setIcon(QApplication::style()-> 110 | standardIcon(QStyle::SP_MessageBoxInformation)); 111 | 112 | m_trans->load(":/translations/CustomWidgetDemos_zh_CN.qm"); 113 | } 114 | 115 | MainWidget::~MainWidget() 116 | { 117 | delete ui; 118 | } 119 | 120 | void MainWidget::on_toastBtn_clicked() 121 | { 122 | QString text = ui->toastEdit->text(); 123 | if(text.isEmpty()) 124 | m_toast->toast(tr("Please enter the tip text.")); 125 | else 126 | m_toast->toast(ui->toastEdit->text()); 127 | } 128 | 129 | void MainWidget::on_buttonClicked(int id) 130 | { 131 | QObject *sender = this->sender(); 132 | 133 | if(sender == m_buttonGroup_1) // Rotate Stacked Widget 134 | ui->rotateStackedWidget->rotate(id); 135 | else if(sender == m_buttonGroup_2) // Translation Stacked Widget 136 | ui->translationStackedWidget->moveToIndex(id); 137 | else if(sender == m_langGroup) // Language 138 | { 139 | if(id == 0) 140 | QApplication::removeTranslator(m_trans); 141 | else 142 | QApplication::installTranslator(m_trans); 143 | ui->retranslateUi(this); 144 | } 145 | } 146 | 147 | void MainWidget::on_countdownStartBtn_clicked() 148 | { 149 | ui->countdownBtn->conutdownCilk(ui->countdownSpinBox->value()); 150 | } 151 | 152 | void MainWidget::on_countdownBtn_clicked() 153 | { 154 | m_toast->toast(tr("Countdown Button clicked.")); 155 | } 156 | 157 | void MainWidget::on_notifyBtn_clicked() 158 | { 159 | QString title = ui->titleEdit->text(); 160 | QString message = ui->messageEdit->toPlainText(); 161 | 162 | m_notifyManager->notify(this, title.isEmpty() ? "Hello" : title, 163 | message.isEmpty() ? "Hello World.\nHow are you today." : message, 164 | QApplication::style()-> 165 | standardIcon(QStyle::SP_MessageBoxInformation)); 166 | } 167 | -------------------------------------------------------------------------------- /MultithreadedDownloader/MultithreadedDownloader_zh_CN.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MainWidget 6 | 7 | 8 | Qt Multithreaded Downloader 9 | Multithreaded Downloader 10 | Qt 多线程下载器 11 | 12 | 13 | 14 | Please enter your URL 15 | Please enter your URL. 16 | 请输入下载链接 17 | 18 | 19 | 20 | <html><head/><body><p><span style=" color:#ef0000;">Failed to get resource.</span></p></body></html> 21 | <html><head/><body><p><span style=" color:#ef0000;">资源失踪了哦。</span></p></body></html> 22 | 23 | 24 | 25 | Download 26 | 开始下载 27 | 28 | 29 | 30 | FileName 31 | 32 | 33 | 34 | 35 | Download Progress: 36 | 下载进度: 37 | 38 | 39 | 40 | Received: 0 / Total: 0 (Byte) 41 | 42 | 43 | 44 | 45 | 46 | Running 47 | 下载中 48 | 49 | 50 | 51 | Start 52 | 开始 53 | 54 | 55 | 56 | Pause 57 | 暂停 58 | 59 | 60 | 61 | Stop 62 | 停止 63 | 64 | 65 | 66 | infomation 67 | 消息 68 | 69 | 70 | 71 | download finished. 72 | 下载完成。 73 | 74 | 75 | 76 | 77 | error 78 | 错误 79 | 80 | 81 | 82 | File can not open. 83 | 文件无法打开。 84 | 85 | 86 | Download Failed. 87 | Network Error Code: 88 | Download Failed. 89 | Network Error Code 90 | 下载失败。 91 | 网络错误码: 92 | 93 | 94 | 95 | Download Failed. 96 | Network Error:%1 97 | Retry ? 98 | 下载失败。 99 | 网络错误:%1 100 | 是否重试? 101 | 102 | 103 | 104 | Paused 105 | 暂停 106 | 107 | 108 | 109 | Stopped 110 | 停止 111 | 112 | 113 | Received: %1 / Total: %2 (Byte) 114 | 已接收: %1 / 总计: %2 (字节) 115 | 116 | 117 | Received: %1 / Total: %2 118 | 已接收: %1 / 总计: %2 119 | 120 | 121 | 122 | Speed: %1/s | %2 / %3 123 | Speed: %1 | %2 / %3 124 | 速度: %1/s | %2 / %3 125 | 126 | 127 | 128 | URL is empty. 129 | 下载链接为空。 130 | 131 | 132 | 133 | Please select the download directory 134 | 请选择下载目录 135 | 136 | 137 | 138 | 139 | Download started. 140 | 下载已开始。 141 | 142 | 143 | 144 | Download has been pause. 145 | 下载已暂停。 146 | 147 | 148 | 149 | Download terminated. 150 | 下载已停止。 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /CustomWidgetDemos/CustomWidgetDemos_zh_CN.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MainWidget 6 | 7 | 8 | Qt Custom Widgets 9 | Qt 自定义控件 10 | 11 | 12 | 13 | RotateStackedWidget 14 | 15 | 16 | 17 | 18 | 19 | Index 20 | 索引 21 | 22 | 23 | 24 | 25 | 26 | 27 | Page 1 28 | 第一页 29 | 30 | 31 | 32 | 33 | 34 | 35 | Page 2 36 | 第二页 37 | 38 | 39 | 40 | 41 | 42 | 43 | Page 3 44 | 第三页 45 | 46 | 47 | 48 | TranslationStackedWidget 49 | 50 | 51 | 52 | 53 | AlignIconButton 54 | 55 | 56 | 57 | 58 | Side Margin: 59 | 左右边距: 60 | 61 | 62 | 63 | Top and Bottom Margin: 64 | 上下边距: 65 | 66 | 67 | 68 | PushButton 69 | 按钮 70 | 71 | 72 | 73 | ProgressButton 74 | 75 | 76 | 77 | 78 | Value: 79 | 值: 80 | 81 | 82 | 83 | 25% 84 | 85 | 86 | 87 | 88 | CountdownButton 89 | 90 | 91 | 92 | 93 | Countdown: 94 | 倒计时: 95 | 96 | 97 | 98 | ms 99 | 毫秒 100 | 101 | 102 | 103 | Start 104 | 开始 105 | 106 | 107 | 108 | Toast 109 | 110 | 111 | 112 | 113 | Tip Text: 114 | Tip Text 115 | 提示文字: 116 | 117 | 118 | 119 | Show 120 | 显示 121 | 122 | 123 | 124 | Language 125 | 语言 126 | 127 | 128 | 129 | English 130 | 131 | 132 | 133 | 134 | 简体中文 135 | 136 | 137 | 138 | 139 | ProgressDial 140 | 141 | 142 | 143 | 144 | Notfily 145 | 146 | 147 | 148 | 149 | Title: 150 | 标题: 151 | 152 | 153 | 154 | Message: 155 | 消息: 156 | 157 | 158 | 159 | Show Time: 160 | 展示时间: 161 | 162 | 163 | 164 | Notify 165 | 弹出提示 166 | 167 | 168 | 169 | Please enter the tip text. 170 | Please enter your information. 171 | 请输入提示文本。 172 | 173 | 174 | 175 | Countdown Button clicked. 176 | 倒计时按钮已按下。 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /MultithreadedDownloader/src/downloader/multithreadeddownloader.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief MultithreadedDownloader 3 | * @anchor Ho229<2189684957@qq.com> 4 | * @date 2021/2/1 5 | */ 6 | 7 | #include "multithreadeddownloader.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | MultithreadedDownloader::MultithreadedDownloader(QObject *parent) 17 | : AbstractMission(parent), 18 | m_manager(new QNetworkAccessManager(this)), 19 | m_writer(new MultithreadedDownloaderWriter(this)), 20 | m_threadCount(QThread::idealThreadCount()) 21 | { 22 | 23 | } 24 | 25 | MultithreadedDownloader::~MultithreadedDownloader() 26 | { 27 | if(m_state == Running) 28 | this->MultithreadedDownloader::stop(); 29 | 30 | if(m_writer->isRunning()) 31 | m_writer->terminate(); 32 | } 33 | 34 | bool MultithreadedDownloader::load() 35 | { 36 | if(!m_url.isValid() || m_state != Stopped) 37 | return false; 38 | 39 | QNetworkRequest request(m_url); 40 | request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); 41 | 42 | QPointer reply = m_manager->head(request); 43 | 44 | QEventLoop loop; 45 | QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); 46 | loop.exec(QEventLoop::ExcludeUserInputEvents); 47 | 48 | qint64 size = 0; 49 | bool ok = false; 50 | if(reply->hasRawHeader("Content-Length")) 51 | size = reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(&ok); 52 | 53 | QString fileName; 54 | if(reply->hasRawHeader("Content-Disposition")) 55 | fileName = reply->header(QNetworkRequest::ContentDispositionHeader) 56 | .toString().split("filename=").at(1); 57 | else 58 | fileName = reply->url().fileName(); 59 | 60 | m_isRangeSupport = reply->rawHeader("Accept-Ranges") == "bytes"; 61 | 62 | if(fileName.isEmpty() || (!ok) || size <= 0) 63 | return false; 64 | else 65 | { 66 | m_writer->setFileName(fileName); 67 | m_writer->setSize(size); 68 | } 69 | 70 | return true; 71 | } 72 | 73 | void MultithreadedDownloader::start() 74 | { 75 | if(m_state == Running || m_writer->fileName().isEmpty() || 76 | m_writer->size() <= 0 || m_url.isEmpty()) 77 | return; 78 | 79 | if(m_state == Stopped) 80 | { 81 | if(!m_writer->open()) 82 | { 83 | emit error(OpenFileFailed); 84 | return; 85 | } 86 | 87 | if(m_isRangeSupport) 88 | { 89 | qint64 start, end; 90 | qint64 segmentSize = m_writer->size() / m_threadCount; 91 | for(int i = 0; i < m_threadCount; i++) 92 | { 93 | start = i * segmentSize; 94 | if(i != m_threadCount - 1) 95 | end = start + segmentSize -1; 96 | else 97 | end = m_writer->size(); // Last mission 98 | 99 | m_missions.push_back(this->createMission(start, end)); 100 | } 101 | } 102 | else 103 | m_missions.push_back(this->createMission(0, -1)); 104 | 105 | 106 | this->setFinished(false); 107 | } 108 | else // Paused 109 | { 110 | const QList& constlist = m_missions; 111 | for(DownloadMission *mission : constlist) 112 | if(!mission->isFinished()) 113 | mission->start(); 114 | } 115 | 116 | m_timerId = this->startTimer(m_notifyInterval); 117 | this->updateState(Running); 118 | } 119 | 120 | void MultithreadedDownloader::pause() 121 | { 122 | if(m_state == Running) 123 | { 124 | if(!m_isRangeSupport) 125 | { 126 | qWarning() << __FUNCTION__ << ": Pause is not available"; 127 | return; 128 | } 129 | 130 | const QList& constlist = m_missions; 131 | for(DownloadMission *misson : constlist) 132 | if(!misson->isFinished()) 133 | misson->pause(); 134 | 135 | this->killTimer(m_timerId); 136 | this->updateState(Paused); 137 | } 138 | } 139 | 140 | void MultithreadedDownloader::stop() 141 | { 142 | if(m_state != Stopped) 143 | { 144 | if(m_state == Running) 145 | this->killTimer(m_timerId); 146 | 147 | this->destoryMissions(); 148 | 149 | if(m_writer->isRunning()) // Wait for the write finish 150 | { 151 | QEventLoop loop; 152 | QObject::connect(m_writer, &MultithreadedDownloaderWriter::finished, &loop, 153 | &QEventLoop::quit); 154 | loop.exec(); 155 | } 156 | 157 | m_writer->close(); 158 | m_finishedCount = 0; 159 | 160 | this->reset(); 161 | this->updateState(Stopped); 162 | } 163 | } 164 | 165 | void MultithreadedDownloader::errorHanding(QNetworkReply::NetworkError err) 166 | { 167 | m_networkError = err; 168 | m_networkErrorString = qobject_cast 169 | (this->sender())->replyErrorString(); 170 | if(err == QNetworkReply::OperationCanceledError || err == QNetworkReply::NoError) 171 | return; 172 | 173 | this->pause(); 174 | emit error(DownloadFailed); 175 | } 176 | 177 | void MultithreadedDownloader::on_finished() 178 | { 179 | m_finishedCount++; 180 | 181 | qDebug() << "MultithreadedDownloader: finishedCount:" << m_finishedCount; 182 | 183 | if(m_isRangeSupport && m_finishedCount != m_threadCount) 184 | return; 185 | 186 | this->updateProgress(); 187 | this->stop(); 188 | 189 | emit finished(); 190 | } 191 | 192 | DownloadMission *MultithreadedDownloader::createMission(qint64 start, qint64 end) 193 | { 194 | DownloadMission *mission = new DownloadMission(this); 195 | 196 | QObject::connect(mission, &DownloadMission::finished, this, 197 | &MultithreadedDownloader::on_finished); 198 | QObject::connect(mission, &DownloadMission::replyError, this, 199 | &MultithreadedDownloader::errorHanding); 200 | 201 | mission->setManager(m_manager); 202 | mission->setWriter(m_writer); 203 | mission->setRange(start, end); 204 | mission->setUrl(m_url); 205 | mission->start(); 206 | 207 | return mission; 208 | } 209 | 210 | void MultithreadedDownloader::destoryMissions() 211 | { 212 | const QList& constlist = m_missions; 213 | for(DownloadMission *mission : constlist) 214 | { 215 | if(mission->state() != Stopped) 216 | mission->stop(); 217 | mission->deleteLater(); 218 | } 219 | m_missions.clear(); 220 | } 221 | 222 | void MultithreadedDownloader::updateProgress() 223 | { 224 | qint64 bytesReceived = 0; 225 | 226 | const QList& constlist = m_missions; 227 | for(const DownloadMission* mission : constlist) 228 | bytesReceived += mission->downloadedSize(); 229 | 230 | emit downloadProgress(bytesReceived, m_writer->size()); 231 | } 232 | 233 | void MultithreadedDownloader::reset() 234 | { 235 | m_writer->setFileName({}); 236 | m_writer->setSize(0); 237 | 238 | m_timerId = 0; 239 | m_isRangeSupport = false; 240 | } 241 | 242 | void MultithreadedDownloader::timerEvent(QTimerEvent *event) 243 | { 244 | if(event->timerId() == m_timerId) 245 | this->updateProgress(); 246 | } 247 | -------------------------------------------------------------------------------- /MultithreadedDownloader/src/mainwidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 375 10 | 223 11 | 12 | 13 | 14 | Qt Multithreaded Downloader 15 | 16 | 17 | 18 | 0 19 | 20 | 21 | 0 22 | 23 | 24 | 0 25 | 26 | 27 | 0 28 | 29 | 30 | 0 31 | 32 | 33 | 34 | 35 | 0 36 | 37 | 38 | 39 | #startPage 40 | { 41 | background:white; 42 | } 43 | 44 | #urlEdit 45 | { 46 | background:white; 47 | padding:3px; 48 | border-radius:5px; 49 | border:1px solid rgb(119,119,119); 50 | } 51 | 52 | QPushButton 53 | { 54 | color:white; 55 | background:rgb(102,153,255); 56 | border-style:none; 57 | border-radius:5px; 58 | } 59 | 60 | QPushButton:hover 61 | { 62 | background:rgb(75, 135, 255); 63 | } 64 | 65 | 66 | 67 | 68 | 69 | 70 | 微软雅黑 71 | 12 72 | 73 | 74 | 75 | Please enter your URL 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 微软雅黑 84 | 85 | 86 | 87 | <html><head/><body><p><span style=" color:#ef0000;">Failed to get resource.</span></p></body></html> 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 0 96 | 35 97 | 98 | 99 | 100 | 101 | 微软雅黑 102 | 12 103 | 104 | 105 | 106 | Download 107 | 108 | 109 | true 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | #downloadPage 118 | { 119 | background:white; 120 | } 121 | 122 | QLabel 123 | { 124 | color:black; 125 | } 126 | 127 | QPushButton 128 | { 129 | color:white; 130 | background:rgb(102,153,255); 131 | border-style:none; 132 | border-radius:5px; 133 | } 134 | 135 | QPushButton:hover 136 | { 137 | background:rgb(75, 135, 255); 138 | } 139 | 140 | 141 | 142 | 143 | 144 | 145 | 微软雅黑 146 | 22 147 | 148 | 149 | 150 | FileName 151 | 152 | 153 | 154 | 155 | 156 | 157 | Qt::Vertical 158 | 159 | 160 | 161 | 20 162 | 19 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 微软雅黑 172 | 11 173 | 174 | 175 | 176 | Download Progress: 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 16777215 185 | 10 186 | 187 | 188 | 189 | 190 | 微软雅黑 191 | 8 192 | 193 | 194 | 195 | QProgressBar 196 | { 197 | border:1px solid #FFFFFF; 198 | background: white; 199 | } 200 | 201 | QProgressBar::chunk 202 | { 203 | background-color:#05B8CC; 204 | border-radius:3px; 205 | } 206 | 207 | 208 | 0 209 | 210 | 211 | Qt::AlignCenter 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 微软雅黑 222 | 12 223 | 224 | 225 | 226 | Received: 0 / Total: 0 (Byte) 227 | 228 | 229 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 微软雅黑 238 | 12 239 | 240 | 241 | 242 | Running 243 | 244 | 245 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | Qt::Vertical 255 | 256 | 257 | 258 | 20 259 | 20 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 0 271 | 35 272 | 273 | 274 | 275 | 276 | 微软雅黑 277 | 12 278 | 279 | 280 | 281 | Start 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 0 290 | 35 291 | 292 | 293 | 294 | 295 | 微软雅黑 296 | 12 297 | 298 | 299 | 300 | Pause 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 0 309 | 35 310 | 311 | 312 | 313 | 314 | 微软雅黑 315 | 12 316 | 317 | 318 | 319 | Stop 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | TranslationStackedWidget 334 | QStackedWidget 335 |
translationstackedwidget.h
336 | 1 337 |
338 |
339 | 340 | 341 |
342 | -------------------------------------------------------------------------------- /VideoPlayer/src/player/videoplayer.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief Video Player 3 | * @anchor Ho 229 4 | * @date 2021/4/14 5 | */ 6 | 7 | #include "audiooutput.h" 8 | #include "videoplayer.h" 9 | #include "videoplayer_p.h" 10 | #include "videorenderer.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | VideoPlayer::VideoPlayer(QQuickItem *parent) : 18 | QQuickFramebufferObject(parent), 19 | d_ptr(new VideoPlayerPrivate(this)) 20 | { 21 | Q_D(VideoPlayer); 22 | 23 | d->decoder = new FFmpegDecoder(nullptr); 24 | d->decoder->moveToThread(new QThread(this)); 25 | d->decoder->thread()->start(); 26 | 27 | d->audioOutput = new AudioOutput([d](char *data, qint64 maxlen) 28 | { return d->updateAudioData(data, maxlen); }, this); 29 | 30 | QObject::connect(d->decoder, &FFmpegDecoder::activeVideoTrackChanged, 31 | this, &VideoPlayer::activeVideoTrackChanged); 32 | QObject::connect(d->decoder, &FFmpegDecoder::activeAudioTrackChanged, 33 | this, &VideoPlayer::activeAudioTrackChanged); 34 | QObject::connect(d->decoder, &FFmpegDecoder::activeSubtitleTrackChanged, 35 | this, &VideoPlayer::activeSubtitleTrackChanged); 36 | 37 | QObject::connect(d->decoder, &FFmpegDecoder::activeAudioTrackChanged, 38 | this, [this] { d_ptr->restartAudioOutput(); }); 39 | } 40 | 41 | VideoPlayer::~VideoPlayer() 42 | { 43 | Q_D(VideoPlayer); 44 | 45 | if(d->state != Stopped) 46 | this->stop(); 47 | 48 | // Tell the decode thread exit 49 | d->decoder->thread()->quit(); 50 | 51 | // Wait for finished 52 | if(!d->decoder->thread()->wait()) 53 | FUNC_ERROR << ": Decode thread exit failed"; 54 | 55 | // Delete the VideoPlayerPrivate 56 | delete d; 57 | } 58 | 59 | QQuickFramebufferObject::Renderer *VideoPlayer::createRenderer() const 60 | { 61 | d_ptr->videoRenderer = new VideoRenderer; 62 | return d_ptr->videoRenderer; // Create custom renderer 63 | } 64 | 65 | void VideoPlayer::setSource(const QUrl& source) 66 | { 67 | Q_D(VideoPlayer); 68 | 69 | d->decoder->setUrl(source); 70 | emit sourceChanged(source); 71 | } 72 | 73 | QUrl VideoPlayer::source() const 74 | { 75 | return d_ptr->decoder->url(); 76 | } 77 | 78 | VideoPlayer::State VideoPlayer::playbackState() const 79 | { 80 | return d_ptr->state; 81 | } 82 | 83 | void VideoPlayer::play() 84 | { 85 | Q_D(VideoPlayer); 86 | 87 | if(d->state == Playing) 88 | return; 89 | else if(d->state == Stopped && d->decoder->state() == FFmpegDecoder::Closed) 90 | { 91 | QEventLoop loop; 92 | QObject::connect(d->decoder, &FFmpegDecoder::stateChanged, &loop, &QEventLoop::exit); 93 | QMetaObject::invokeMethod(d->decoder, &FFmpegDecoder::load, Qt::QueuedConnection); 94 | 95 | if(loop.exec() != FFmpegDecoder::Opened) 96 | { 97 | emit errorOccurred(this->errorString()); 98 | return; 99 | } 100 | 101 | emit loaded(); 102 | 103 | const auto fps = d->decoder->fps(); 104 | d->interval = qIsNaN(fps) ? 1000.0 : 1000 / fps; 105 | } 106 | else if(d->state == Paused) 107 | { 108 | d->videoClock.resume(); 109 | d->audioClock.resume(); 110 | } 111 | 112 | d->timerId = this->startTimer(d->interval, Qt::PreciseTimer); 113 | d->audioOutput->play(); 114 | 115 | d->state = Playing; 116 | emit playbackStateChanged(Playing); 117 | } 118 | 119 | void VideoPlayer::pause() 120 | { 121 | Q_D(VideoPlayer); 122 | 123 | if(d->state != Playing) 124 | return; 125 | 126 | this->killTimer(d->timerId); 127 | d->audioOutput->pause(); 128 | 129 | d->videoClock.pause(); 130 | d->audioClock.pause(); 131 | 132 | d->state = Paused; 133 | emit playbackStateChanged(Paused); 134 | } 135 | 136 | void VideoPlayer::stop() 137 | { 138 | Q_D(VideoPlayer); 139 | 140 | if(d->state == Stopped) 141 | return; 142 | else if(d->state == Playing) 143 | this->killTimer(d->timerId); 144 | 145 | d->audioOutput->stop(); 146 | 147 | d->decoder->requestInterrupt(); 148 | QEventLoop loop; 149 | QObject::connect(d->decoder, &FFmpegDecoder::stateChanged, &loop, &QEventLoop::quit); 150 | QMetaObject::invokeMethod(d->decoder, &FFmpegDecoder::release, Qt::QueuedConnection); 151 | loop.exec(); 152 | 153 | av_frame_free(&d->audioFrame); 154 | d->audioFramePos = 0; 155 | 156 | d->videoClock.invalidate(); 157 | d->audioClock.invalidate(); 158 | d->videoRenderer->updateSubtitleFrame(nullptr); 159 | 160 | d->position = 0; 161 | emit positionChanged(0); 162 | 163 | d->videoRenderer->updateVideoFrame(nullptr); 164 | this->update(); 165 | 166 | d->state = Stopped; 167 | emit playbackStateChanged(Stopped); 168 | } 169 | 170 | void VideoPlayer::setVolume(qreal volume) 171 | { 172 | Q_D(VideoPlayer); 173 | d->audioOutput->setVolume(volume); 174 | emit volumeChanged(volume); 175 | } 176 | 177 | qreal VideoPlayer::volume() const 178 | { 179 | return d_ptr->audioOutput->volume(); 180 | } 181 | 182 | void VideoPlayer::setActiveVideoTrack(int index) 183 | { 184 | Q_D(VideoPlayer); 185 | 186 | if(!this->hasVideo() || d->decoder->activeVideoTrack() == index) 187 | return; 188 | 189 | d->decoder->requestInterrupt(); 190 | QMetaObject::invokeMethod(d->decoder, "setActiveVideoTrack", 191 | Qt::QueuedConnection, Q_ARG(int, index)); 192 | QMetaObject::invokeMethod(d->decoder, &FFmpegDecoder::decode, 193 | Qt::QueuedConnection); 194 | 195 | av_frame_free(&d->audioFrame); 196 | d->audioFramePos = 0; 197 | 198 | d->videoClock.invalidate(); 199 | d->audioClock.invalidate(); 200 | d->videoRenderer->updateSubtitleFrame(nullptr); 201 | } 202 | 203 | int VideoPlayer::activeVideoTrack() const 204 | { 205 | return d_ptr->decoder->activeVideoTrack(); 206 | } 207 | 208 | void VideoPlayer::setActiveAudioTrack(int index) 209 | { 210 | Q_D(VideoPlayer); 211 | 212 | if(!this->hasAudio() || d->decoder->activeAudioTrack() == index) 213 | return; 214 | 215 | d->decoder->requestInterrupt(); 216 | QMetaObject::invokeMethod(d->decoder, "setActiveAudioTrack", 217 | Qt::QueuedConnection, Q_ARG(int, index)); 218 | 219 | av_frame_free(&d->audioFrame); 220 | d->audioFramePos = 0; 221 | 222 | d->videoClock.invalidate(); 223 | d->audioClock.invalidate(); 224 | d->videoRenderer->updateSubtitleFrame(nullptr); 225 | } 226 | 227 | int VideoPlayer::activeAudioTrack() const 228 | { 229 | return d_ptr->decoder->activeAudioTrack(); 230 | } 231 | 232 | void VideoPlayer::setActiveSubtitleTrack(int index) 233 | { 234 | Q_D(VideoPlayer); 235 | 236 | if(!this->hasSubtitle() || d->decoder->activeSubtitleTrack() == index) 237 | return; 238 | 239 | d->decoder->requestInterrupt(); 240 | QMetaObject::invokeMethod(d->decoder, "setActiveSubtitleTrack", 241 | Qt::QueuedConnection, Q_ARG(int, index)); 242 | QMetaObject::invokeMethod(d->decoder, &FFmpegDecoder::decode, 243 | Qt::QueuedConnection); 244 | 245 | av_frame_free(&d->audioFrame); 246 | d->audioFramePos = 0; 247 | 248 | d->videoClock.invalidate(); 249 | d->audioClock.invalidate(); 250 | d->videoRenderer->updateSubtitleFrame(nullptr); 251 | } 252 | 253 | int VideoPlayer::activeSubtitleTrack() const 254 | { 255 | return d_ptr->decoder->activeSubtitleTrack(); 256 | } 257 | 258 | int VideoPlayer::videoTrackCount() const 259 | { 260 | return d_ptr->decoder->videoTrackCount(); 261 | } 262 | 263 | int VideoPlayer::audioTrackCount() const 264 | { 265 | return d_ptr->decoder->audioTrackCount(); 266 | } 267 | 268 | int VideoPlayer::subtitleTrackCount() const 269 | { 270 | return d_ptr->decoder->subtitleTrackCount(); 271 | } 272 | 273 | int VideoPlayer::duration() const 274 | { 275 | return d_ptr->decoder->duration(); 276 | } 277 | 278 | int VideoPlayer::position() const 279 | { 280 | return d_ptr->position; 281 | } 282 | 283 | bool VideoPlayer::hasVideo() const 284 | { 285 | return d_ptr->decoder->videoTrackCount(); 286 | } 287 | 288 | bool VideoPlayer::hasAudio() const 289 | { 290 | return d_ptr->decoder->audioTrackCount(); 291 | } 292 | 293 | bool VideoPlayer::hasSubtitle() const 294 | { 295 | return d_ptr->decoder->subtitleTrackCount(); 296 | } 297 | 298 | bool VideoPlayer::seekable() const 299 | { 300 | return d_ptr->decoder->seekable(); 301 | } 302 | 303 | QString VideoPlayer::errorString() const 304 | { 305 | return d_ptr->decoder->errorString(); 306 | } 307 | 308 | void VideoPlayer::seek(int position) 309 | { 310 | Q_D(VideoPlayer); 311 | 312 | if(d->position == position || d->state == State::Stopped || !this->seekable()) 313 | return; 314 | 315 | d->position = position; 316 | emit positionChanged(position); 317 | 318 | d->decoder->requestInterrupt(); 319 | QMetaObject::invokeMethod(d->decoder, "seek", Qt::BlockingQueuedConnection, Q_ARG(int, position)); 320 | 321 | d->audioOutput->reset(); 322 | 323 | av_frame_free(&d->audioFrame); 324 | d->audioFramePos = 0; 325 | 326 | d->videoClock.invalidate(); 327 | d->audioClock.invalidate(); 328 | d->videoRenderer->updateSubtitleFrame(nullptr); 329 | 330 | if(d->state == Paused) 331 | { 332 | AVFrame *frame = nullptr; 333 | while(!(frame = d->decoder->takeVideoFrame())) 334 | QThread::yieldCurrentThread(); 335 | 336 | d->videoRenderer->updateVideoFrame(frame); 337 | this->update(); 338 | } 339 | } 340 | 341 | void VideoPlayer::timerEvent(QTimerEvent *) 342 | { 343 | Q_D(VideoPlayer); 344 | 345 | d->updateVideoFrame(); 346 | d->updateSubtitleFrame(); 347 | this->update(); 348 | 349 | if(d->videoClock.isValid() || d->audioClock.isValid()) 350 | { 351 | const int position = d->videoClock.isValid() ? d->videoClock.time() : d->audioClock.time(); 352 | if(position != d->position) 353 | { 354 | d->position = position; 355 | emit positionChanged(position); 356 | } 357 | } 358 | 359 | if(!d->decoder->hasFrame() && d->decoder->isEnd()) 360 | this->stop(); 361 | } 362 | --------------------------------------------------------------------------------