From 7a99cc9d3f95dbc5819770573e0edf0bb0d8a3de Mon Sep 17 00:00:00 2001
From: Sergey Fedorov <vital.had@gmail.com>
Date: Tue, 24 Feb 2026 14:39:58 +0800
Subject: [PATCH 1/6] Build with Qt4

---
 CMakeLists.txt                                | 182 +++--
 lang/CMakeLists.txt                           |   8 +-
 src/gui/AVThread.cpp                          |   3 +
 src/gui/AVThread.hpp                          |   9 +-
 src/gui/AboutWidget.cpp                       |  18 +-
 src/gui/AboutWidget.hpp                       |   6 +-
 src/gui/AddressBox.cpp                        |   4 +-
 src/gui/AddressDialog.hpp                     |   2 +-
 src/gui/Appearance.cpp                        |  41 +-
 src/gui/Appearance.hpp                        |   3 +-
 src/gui/AudioThr.cpp                          |  24 +-
 src/gui/AudioThr.hpp                          |   6 +-
 src/gui/CMakeLists.txt                        |  28 +-
 src/gui/DeintSettingsW.cpp                    |   2 +-
 src/gui/DeintSettingsW.hpp                    |   2 +-
 src/gui/DemuxerThr.cpp                        |  33 +-
 src/gui/DemuxerThr.hpp                        |   6 +-
 src/gui/EntryProperties.cpp                   |   2 +-
 src/gui/EntryProperties.hpp                   |   6 +-
 src/gui/InfoDock.cpp                          |   7 +-
 src/gui/InfoDock.hpp                          |   6 +-
 src/gui/KeyBindingsDialog.cpp                 |   5 +
 src/gui/Main.cpp                              | 277 ++++++--
 src/gui/Main.hpp                              |   6 +-
 src/gui/MainWidget.cpp                        | 631 +++++++++++++-----
 src/gui/MainWidget.hpp                        |  88 ++-
 src/gui/MenuBar.cpp                           |  25 +-
 src/gui/OSDSettingsW.cpp                      |  24 +-
 src/gui/OSDSettingsW.hpp                      |   5 +
 src/gui/PanGestureEventFilter.cpp             |   6 +
 src/gui/PlayClass.cpp                         |  77 ++-
 src/gui/PlayClass.hpp                         |  14 +-
 src/gui/PlaylistDock.cpp                      |  28 +-
 src/gui/PlaylistDock.hpp                      |   2 +-
 src/gui/PlaylistWidget.cpp                    |  64 +-
 src/gui/PlaylistWidget.hpp                    |  53 +-
 src/gui/SettingsWidget.cpp                    | 445 +++++++-----
 src/gui/SettingsWidget.hpp                    |  55 +-
 src/gui/ShortcutHandler.hpp                   |  14 +-
 src/gui/TagEditor.cpp                         |  15 +-
 src/gui/TagEditor.hpp                         |  12 +-
 src/gui/Unix/ScreenSaver.cpp                  |  25 +-
 src/gui/Updater.hpp                           |   2 +-
 src/gui/VideoAdjustmentW.cpp                  |  77 ++-
 src/gui/VideoAdjustmentW.hpp                  |  17 +-
 src/gui/VideoDock.cpp                         |  46 +-
 src/gui/VideoDock.hpp                         |  26 +-
 src/gui/VideoThr.cpp                          |  61 +-
 src/gui/VideoThr.hpp                          |  28 +-
 src/gui/Windows/PanGestureEventFilter.cpp     |   6 +-
 src/gui/macOS/QMPlay2MacExtensions.hpp        |   4 +-
 src/gui/macOS/QMPlay2MacExtensions.mm         | 142 ++--
 src/modules/ALSA/ALSA.hpp                     |  12 +-
 src/modules/ALSA/ALSAWriter.hpp               |  16 +-
 src/modules/AudioCD/AudioCD.hpp               |  14 +-
 src/modules/AudioCD/AudioCDDemux.cpp          |   4 +-
 src/modules/AudioCD/AudioCDDemux.hpp          |  28 +-
 src/modules/AudioFilters/AVAudioFilter.cpp    |   8 +-
 src/modules/AudioFilters/AVAudioFilter.hpp    |  16 +-
 src/modules/AudioFilters/AudioFilters.cpp     |  58 +-
 src/modules/AudioFilters/AudioFilters.hpp     |  17 +-
 src/modules/AudioFilters/BS2B.hpp             |  10 +-
 src/modules/AudioFilters/DysonCompressor.hpp  |  12 +-
 src/modules/AudioFilters/Echo.hpp             |   8 +-
 src/modules/AudioFilters/Equalizer.cpp        |   3 +
 src/modules/AudioFilters/Equalizer.hpp        |  22 +-
 src/modules/AudioFilters/EqualizerGUI.cpp     |   6 +-
 src/modules/AudioFilters/EqualizerGUI.hpp     |  12 +-
 src/modules/AudioFilters/PhaseReverse.hpp     |   8 +-
 src/modules/AudioFilters/SwapStereo.hpp       |   8 +-
 src/modules/AudioFilters/VoiceRemoval.hpp     |   8 +-
 src/modules/CUVID/Cuvid.hpp                   |  12 +-
 src/modules/CUVID/CuvidDec.hpp                |  18 +-
 src/modules/CUVID/CuvidOpenGL.cpp             |   2 +-
 src/modules/CUVID/CuvidOpenGL.hpp             |  16 +-
 src/modules/CUVID/CuvidVulkan.hpp             |  10 +-
 src/modules/Chiptune/Chiptune.hpp             |  12 +-
 src/modules/Chiptune/GME.hpp                  |  24 +-
 src/modules/Chiptune/SIDPlay.hpp              |  24 +-
 src/modules/DirectX/DirectDraw.hpp            |  26 +-
 src/modules/DirectX/DirectX.hpp               |  12 +-
 src/modules/Extensions/CMakeLists.txt         |   1 +
 src/modules/Extensions/Downloader.cpp         | 177 +++--
 src/modules/Extensions/Downloader.hpp         |  41 +-
 src/modules/Extensions/Extensions.hpp         |  12 +-
 src/modules/Extensions/LastFM.cpp             |  77 ++-
 src/modules/Extensions/LastFM.hpp             |   9 +-
 src/modules/Extensions/Lyrics.cpp             |  59 +-
 src/modules/Extensions/Lyrics.hpp             |   6 +-
 src/modules/Extensions/MPRIS2.cpp             |   2 +-
 src/modules/Extensions/MPRIS2.hpp             |  13 +-
 src/modules/Extensions/MediaBrowser.cpp       |  10 +-
 src/modules/Extensions/MediaBrowser.hpp       |  16 +-
 src/modules/Extensions/OpenSubtitles.cpp      | 508 ++++++++------
 src/modules/Extensions/OpenSubtitles.hpp      |  43 +-
 src/modules/Extensions/Radio.cpp              | 100 ++-
 src/modules/Extensions/Radio.hpp              |  25 +-
 .../Extensions/Radio/RadioBrowserModel.cpp    |  22 +-
 .../Extensions/Radio/RadioBrowserModel.hpp    |  18 +-
 src/modules/Extensions/YouTube.cpp            | 353 ++++++----
 src/modules/Extensions/YouTube.hpp            |  35 +-
 src/modules/FFmpeg/D3D11VAVulkan.hpp          |   8 +-
 src/modules/FFmpeg/DXVA2OpenGL.cpp            |  16 +-
 src/modules/FFmpeg/DXVA2OpenGL.hpp            |  20 +-
 src/modules/FFmpeg/FFCommon.cpp               |   8 +-
 src/modules/FFmpeg/FFDec.hpp                  |   4 +-
 src/modules/FFmpeg/FFDecD3D11VA.hpp           |  10 +-
 src/modules/FFmpeg/FFDecDXVA2.hpp             |  10 +-
 src/modules/FFmpeg/FFDecHWAccel.hpp           |   6 +-
 src/modules/FFmpeg/FFDecSW.cpp                |  12 +-
 src/modules/FFmpeg/FFDecSW.hpp                |  24 +-
 src/modules/FFmpeg/FFDecVAAPI.cpp             |   2 +-
 src/modules/FFmpeg/FFDecVAAPI.hpp             |  12 +-
 src/modules/FFmpeg/FFDecVTB.hpp               |   8 +-
 src/modules/FFmpeg/FFDecVkVideo.hpp           |  10 +-
 src/modules/FFmpeg/FFDemux.cpp                |  28 +-
 src/modules/FFmpeg/FFDemux.hpp                |  44 +-
 src/modules/FFmpeg/FFReader.cpp               |   2 +-
 src/modules/FFmpeg/FFReader.hpp               |  24 +-
 src/modules/FFmpeg/FFmpeg.cpp                 |  28 +-
 src/modules/FFmpeg/FFmpeg.hpp                 |  14 +-
 src/modules/FFmpeg/FormatContext.cpp          |  14 +-
 src/modules/FFmpeg/VAAPI.cpp                  |  10 +-
 src/modules/FFmpeg/VAAPI.hpp                  |  41 +-
 src/modules/FFmpeg/VAAPIOpenGL.cpp            |   5 +-
 src/modules/FFmpeg/VAAPIOpenGL.hpp            |  16 +-
 src/modules/FFmpeg/VAAPIVulkan.cpp            |   2 +-
 src/modules/FFmpeg/VAAPIVulkan.hpp            |  10 +-
 src/modules/FFmpeg/VTBOpenGL.cpp              |   2 +-
 src/modules/FFmpeg/VTBOpenGL.hpp              |  20 +-
 src/modules/FFmpeg/VkVideoVulkan.hpp          |  12 +-
 .../FileAssociation/FileAssociation.hpp       |  12 +-
 src/modules/Inputs/Inputs.cpp                 |   2 +-
 src/modules/Inputs/Inputs.hpp                 |  18 +-
 src/modules/Inputs/PCM.hpp                    |  20 +-
 src/modules/Inputs/ToneGenerator.cpp          |  18 +-
 src/modules/Inputs/ToneGenerator.hpp          |  24 +-
 src/modules/Notify/Notify.hpp                 |  12 +-
 src/modules/Notify/NotifyExtension.cpp        |   2 +-
 src/modules/Notify/NotifyExtension.hpp        |   9 +-
 src/modules/OpenMPT/OpenMPT.cpp               |  17 +-
 src/modules/OpenMPT/OpenMPT.hpp               |  25 +-
 src/modules/OpenMPT/OpenMPTDemux.cpp          |   4 +-
 src/modules/OpenMPT/OpenMPTDemux.hpp          |  28 +-
 src/modules/OpenMPT/OpenMPTVisBase.cpp        |   2 +-
 src/modules/OpenMPT/OpenMPTVisPatterns.cpp    |  10 +
 src/modules/OpenMPT/OpenMPTVisPatterns.hpp    |   4 +-
 src/modules/OpenMPT/OpenMPTVisSamples.cpp     |   9 +-
 src/modules/OpenMPT/OpenMPTVisSamples.hpp     |   4 +-
 src/modules/OpenSLES/OpenSLESWriter.hpp       |  16 +-
 src/modules/PipeWire/PipeWire.hpp             |  12 +-
 src/modules/PipeWire/PipeWireWriter.hpp       |  16 +-
 src/modules/Playlists/M3U.cpp                 |   8 +-
 src/modules/Playlists/M3U.hpp                 |   6 +-
 src/modules/Playlists/PLS.cpp                 |   4 +-
 src/modules/Playlists/PLS.hpp                 |   6 +-
 src/modules/Playlists/Playlists.hpp           |  12 +-
 src/modules/Playlists/XSPF.cpp                |  24 +-
 src/modules/Playlists/XSPF.hpp                |   6 +-
 .../3rdparty/CoreAudio/AudioDevice.h          |   7 +-
 src/modules/PortAudio/PortAudio.cpp           |   8 +-
 src/modules/PortAudio/PortAudio.hpp           |  14 +-
 src/modules/PortAudio/PortAudioWriter.cpp     |  16 +-
 src/modules/PortAudio/PortAudioWriter.hpp     |  38 +-
 src/modules/PulseAudio/PulseAudio.hpp         |  12 +-
 src/modules/PulseAudio/PulseAudioWriter.hpp   |  14 +-
 src/modules/QPainter/QPainter.hpp             |  12 +-
 src/modules/QPainter/QPainterWriter.cpp       |  27 +-
 src/modules/QPainter/QPainterWriter.hpp       |  27 +-
 src/modules/Subtitles/Classic.cpp             |  34 +-
 src/modules/Subtitles/Classic.hpp             |   4 +-
 src/modules/Subtitles/SRT.cpp                 |   4 +-
 src/modules/Subtitles/SRT.hpp                 |   4 +-
 src/modules/Subtitles/Subtitles.hpp           |  12 +-
 src/modules/VideoFilters/BlendDeint.hpp       |   6 +-
 src/modules/VideoFilters/BobDeint.hpp         |   6 +-
 src/modules/VideoFilters/DiscardDeint.hpp     |   6 +-
 src/modules/VideoFilters/FPSDoubler.cpp       |   6 +-
 src/modules/VideoFilters/FPSDoubler.hpp       |   8 +-
 src/modules/VideoFilters/MotionBlur.hpp       |   6 +-
 src/modules/VideoFilters/VFilters.cpp         |   5 +
 src/modules/VideoFilters/VFilters.hpp         |  18 +-
 src/modules/VideoFilters/YadifDeint.cpp       |  12 +-
 src/modules/VideoFilters/YadifDeint.hpp       |   6 +-
 src/modules/Visualizations/FFTSpectrum.hpp    |  26 +-
 src/modules/Visualizations/SimpleVis.hpp      |  26 +-
 src/modules/Visualizations/VisWidget.cpp      |  10 +-
 src/modules/Visualizations/VisWidget.hpp      |   6 +-
 src/modules/Visualizations/Visualizations.cpp |   2 +-
 src/modules/Visualizations/Visualizations.hpp |  12 +-
 src/modules/XVideo/XVideo.cpp                 |  16 +-
 src/modules/XVideo/XVideo.hpp                 |  12 +-
 src/modules/XVideo/XVideoWriter.hpp           |  24 +-
 src/qmplay2/CMakeLists.txt                    |  93 +--
 src/qmplay2/ChapterProgramInfo.hpp            |   2 +
 src/qmplay2/ColorButton.cpp                   |   7 +-
 src/qmplay2/ColorButton.hpp                   |   7 +-
 src/qmplay2/CommonJS.cpp                      |  66 +-
 src/qmplay2/CommonJS.hpp                      |  12 +
 src/qmplay2/Decoder.cpp                       |  10 +-
 src/qmplay2/DeintHWPrepareFilter.cpp          |   2 +-
 src/qmplay2/DeintHWPrepareFilter.hpp          |   4 +-
 src/qmplay2/Demuxer.cpp                       |   2 +-
 src/qmplay2/DockWidget.cpp                    |  45 +-
 src/qmplay2/DockWidget.hpp                    |  11 +-
 src/qmplay2/Functions.cpp                     | 180 +++--
 src/qmplay2/Functions.hpp                     |   4 +-
 src/qmplay2/GPUInstance.cpp                   |  16 +-
 src/qmplay2/IPC.hpp                           |  12 +-
 src/qmplay2/IPC_Unix.cpp                      |   2 +-
 src/qmplay2/IPC_Windows.cpp                   |   2 +-
 src/qmplay2/InDockW.cpp                       |   3 +
 src/qmplay2/InDockW.hpp                       |  13 +-
 src/qmplay2/LibASS.cpp                        |   7 +-
 src/qmplay2/LibASS.hpp                        |   6 +-
 src/qmplay2/LineEdit.cpp                      |  26 +-
 src/qmplay2/LineEdit.hpp                      |  12 +-
 src/qmplay2/Module.cpp                        |   2 +-
 src/qmplay2/Module.hpp                        |   3 +-
 src/qmplay2/NetworkAccess.cpp                 |  38 +-
 src/qmplay2/NetworkAccess.hpp                 |  42 +-
 src/qmplay2/NetworkAccessJS.cpp               |  67 +-
 src/qmplay2/NetworkAccessJS.hpp               |  11 +
 src/qmplay2/NotifiesAndroid.hpp               |   6 +-
 src/qmplay2/NotifiesFreedesktop.hpp           |   6 +-
 src/qmplay2/NotifiesMacOS.hpp                 |   6 +-
 src/qmplay2/NotifiesMacOS.mm                  |  25 +
 src/qmplay2/NotifiesTray.hpp                  |   8 +-
 src/qmplay2/QMPlay2Core.cpp                   |  93 ++-
 src/qmplay2/QMPlay2Extensions.cpp             |   2 +-
 src/qmplay2/Reader.cpp                        |   2 +-
 src/qmplay2/Settings.cpp                      |   2 +-
 src/qmplay2/Slider.cpp                        |  11 +-
 src/qmplay2/Slider.hpp                        |  16 +-
 src/qmplay2/SndResampler.cpp                  |   2 +-
 src/qmplay2/StreamInfo.hpp                    |   4 +-
 src/qmplay2/StreamMuxer.cpp                   |  23 +-
 src/qmplay2/VideoFilter.cpp                   |  12 +-
 src/qmplay2/VideoFilter.hpp                   |   4 +-
 src/qmplay2/VideoFilters.cpp                  |   6 +-
 src/qmplay2/VideoOutputCommon.cpp             |  66 +-
 src/qmplay2/VideoOutputCommon.hpp             |  23 +-
 src/qmplay2/Writer.cpp                        |  20 +-
 src/qmplay2/X11BypassCompositor.cpp           |  27 +-
 src/qmplay2/X11BypassCompositor.hpp           |  10 +-
 src/qmplay2/YouTubeDL.cpp                     | 137 +++-
 src/qmplay2/YouTubeDL.hpp                     |   4 +-
 src/qmplay2/opengl/OpenGLCommon.cpp           |  69 +-
 src/qmplay2/opengl/OpenGLCommon.hpp           |  22 +-
 src/qmplay2/opengl/OpenGLInstance.cpp         | 116 +++-
 src/qmplay2/opengl/OpenGLInstance.hpp         |   8 +-
 src/qmplay2/opengl/OpenGLWidget.cpp           |  15 +-
 src/qmplay2/opengl/OpenGLWidget.hpp           |  20 +-
 src/qmplay2/opengl/OpenGLWindow.cpp           |  81 ++-
 src/qmplay2/opengl/OpenGLWindow.hpp           |  24 +-
 src/qmplay2/opengl/OpenGLWriter.cpp           |  20 +-
 src/qmplay2/opengl/OpenGLWriter.hpp           |  22 +-
 src/qmplay2/opengl/Qt4OpenGLCompat.hpp        | 248 +++++++
 src/qmplay2/opengl/shaders/OSD.frag           |   1 +
 src/qmplay2/opengl/shaders/OSD.vert           |   1 +
 src/qmplay2/opengl/shaders/Video.vert         |   1 +
 src/qmplay2/opengl/shaders/VideoRGB.frag      |   1 +
 src/qmplay2/opengl/shaders/VideoYCbCr.frag    |   1 +
 src/qmplay2/vulkan/VulkanInstance.cpp         |   4 +-
 src/qmplay2/vulkan/VulkanInstance.hpp         |  10 +-
 src/qmplay2/vulkan/VulkanWindow.cpp           |   6 +-
 src/qmplay2/vulkan/VulkanWindow.hpp           |   4 +-
 src/qmplay2/vulkan/VulkanWriter.hpp           |  20 +-
 src/qmplay2/vulkan/VulkanYadifDeint.hpp       |   4 +-
 269 files changed, 5251 insertions(+), 2498 deletions(-)
 create mode 100644 src/qmplay2/opengl/Qt4OpenGLCompat.hpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index ae8b8809..80e80e5e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -38,16 +38,47 @@ else()
     find_package(DummyPkgConfig REQUIRED)
 endif()
 
-option(BUILD_WITH_QT6 "Build with Qt6" ON)
-add_feature_info(Qt6 BUILD_WITH_QT6 "Build with Qt6")
-
-if(BUILD_WITH_QT6)
-    set(QT_PREFIX Qt6)
-    find_package(Qt6Widgets 6.1 REQUIRED)
-    find_package(Qt6Core5Compat 6.1 REQUIRED)
+option(BUILD_WITH_QT4 "Build with Qt4 (for macOS 10.5/10.6 PowerPC compatibility)" OFF)
+add_feature_info(Qt4 BUILD_WITH_QT4 "Build with Qt4")
+
+if(BUILD_WITH_QT4)
+    set(QT_PREFIX Qt)
+    find_package(Qt4 4.8 REQUIRED QtCore QtGui QtSvg QtOpenGL QtNetwork)
+    include(${QT_USE_FILE})
+
+    if(APPLE)
+        # moc doesn't get the compiler's predefined macros (no __APPLE__),
+        # so #ifdef Q_OS_MAC guards around slots would otherwise be silently
+        # dropped from the meta-object at moc time even though the real
+        # compiler sees them fine. Feed moc the define directly instead of
+        # relying on CMAKE_AUTOMOC_COMPILER_PREDEFINES (which needs a newer
+        # CMake than some Qt4/macOS toolchains ship with).
+        add_definitions(-DQ_OS_MAC)
+    endif()
 else()
-    set(QT_PREFIX Qt5)
-    find_package(Qt5Widgets 5.15.2 REQUIRED)
+    option(BUILD_WITH_QT6 "Build with Qt6" ON)
+    add_feature_info(Qt6 BUILD_WITH_QT6 "Build with Qt6")
+
+    if(BUILD_WITH_QT6)
+        set(QT_PREFIX Qt6)
+        find_package(Qt6Widgets 6.1 REQUIRED)
+        find_package(Qt6Core5Compat 6.1 REQUIRED)
+    else()
+        set(QT_PREFIX Qt5)
+        find_package(Qt5Widgets 5.15.2 REQUIRED)
+    endif()
+endif()
+
+# Add QJson4 for JSON support (Qt4 doesn't have native JSON)
+# QJson4 library from https://github.com/eteran/qjson4
+set(QJSON4_INCLUDE_DIR "" CACHE PATH "Path to QJson4 headers")
+if(QJSON4_INCLUDE_DIR)
+    include_directories(${QJSON4_INCLUDE_DIR})
+    add_definitions(-DUSE_QJSON4)
+    set(QJSON4_LIBRARY QJson4)
+    message(STATUS "QJson4 enabled: headers at ${QJSON4_INCLUDE_DIR}")
+elseif(BUILD_WITH_QT4)
+    message(WARNING "QJSON4_INCLUDE_DIR not set. Qt4 builds require QJson4 from https://github.com/eteran/qjson4")
 endif()
 
 pkg_check_modules(LIBAVFORMAT REQUIRED libavformat>=58.12.100)
@@ -62,7 +93,16 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
 set(CMAKE_CXX_STANDARD 17)
 set(CMAKE_AUTOMOC ON)
 set(CMAKE_AUTORCC ON)
-set(CMAKE_AUTOMOC_COMPILER_PREDEFINES OFF)
+if(BUILD_WITH_QT4)
+    # Qt4: give moc the compiler's real predefines (e.g. __APPLE__) so
+    # #ifdef Q_OS_MAC guards around slots resolve the same way for moc as
+    # for the real compiler (see the explicit add_definitions(-DQ_OS_MAC)
+    # above as a belt-and-suspenders fallback: this CMake feature requires
+    # a fairly recent CMake and silently no-ops on older ones).
+    set(CMAKE_AUTOMOC_COMPILER_PREDEFINES ON)
+else()
+    set(CMAKE_AUTOMOC_COMPILER_PREDEFINES OFF)
+endif()
 
 option(USE_PCH "Use precompiled headers" OFF)
 add_feature_info(PCH USE_PCH "Use precompiled headers")
@@ -92,9 +132,10 @@ endif()
 
 add_definitions(-D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS -DQT_USE_QSTRINGBUILDER)
 
-if (BUILD_WITH_QT6)
+if(BUILD_WITH_QT6)
     add_definitions(-DQ_ENTER_EVENT=QEnterEvent)
 else()
+    # Qt4 uses QEvent for enter events
     add_definitions(-DQ_ENTER_EVENT=QEvent)
 endif()
 
@@ -111,28 +152,41 @@ option(USE_OPENGL "Build with OpenGL support" ON)
 add_feature_info(OpenGL USE_OPENGL "Build with OpenGL support")
 if(USE_OPENGL)
     add_definitions(-DUSE_OPENGL)
-    if(BUILD_WITH_QT6)
+    if(BUILD_WITH_QT4)
+        # Qt4 has OpenGL support built into QtOpenGL module (already required above)
+        # Limiting to OpenGL 2.0 for macOS 10.6.8 compatibility
+        add_definitions(-DMAX_OPENGL_VERSION=2.0)
+    elseif(BUILD_WITH_QT6)
         find_package(Qt6OpenGL REQUIRED)
         find_package(Qt6OpenGLWidgets REQUIRED)
     endif()
 endif()
 
-if(NOT APPLE)
-    if (BUILD_WITH_QT6)
-        get_target_property(${QT_PREFIX}GuiFeatures ${QT_PREFIX}::Gui QT_ENABLED_PUBLIC_FEATURES)
-    else()
-        get_target_property(${QT_PREFIX}GuiFeatures ${QT_PREFIX}::Gui INTERFACE_QT_ENABLED_FEATURES)
+if(BUILD_WITH_QT4)
+    # Vulkan is not supported on macOS 10.6.8 and not available in Qt4
+    set(USE_VULKAN OFF)
+    add_feature_info(Vulkan USE_VULKAN "Build with Vulkan support (disabled for Qt4/macOS 10.6.8)")
+
+    # GLSLC is only needed for Vulkan shader compilation (not used in Qt4 build)
+    set(USE_GLSLC OFF)
+else()
+    if(NOT APPLE)
+        if (BUILD_WITH_QT6)
+            get_target_property(${QT_PREFIX}GuiFeatures ${QT_PREFIX}::Gui QT_ENABLED_PUBLIC_FEATURES)
+        else()
+            get_target_property(${QT_PREFIX}GuiFeatures ${QT_PREFIX}::Gui INTERFACE_QT_ENABLED_FEATURES)
+        endif()
+        list(FIND ${QT_PREFIX}GuiFeatures "vulkan" ${QT_PREFIX}Vulkan)
+        if(${${QT_PREFIX}Vulkan} GREATER -1)
+            set(VULKAN_AVAILABLE ON)
+        endif()
     endif()
-    list(FIND ${QT_PREFIX}GuiFeatures "vulkan" ${QT_PREFIX}Vulkan)
-    if(${${QT_PREFIX}Vulkan} GREATER -1)
-        set(VULKAN_AVAILABLE ON)
+    option(USE_VULKAN "Build with Vulkan support" ${VULKAN_AVAILABLE})
+    add_feature_info(Vulkan USE_VULKAN "Build with Vulkan support")
+    if(USE_VULKAN)
+        add_definitions(-DUSE_VULKAN)
     endif()
 endif()
-option(USE_VULKAN "Build with Vulkan support" ${VULKAN_AVAILABLE})
-add_feature_info(Vulkan USE_VULKAN "Build with Vulkan support")
-if(USE_VULKAN)
-    add_definitions(-DUSE_VULKAN)
-endif()
 
 if(NOT WIN32 AND NOT APPLE AND NOT ANDROID AND NOT HAIKU)
     option(USE_FREEDESKTOP_NOTIFICATIONS "Use Freedesktop notifications" ON)
@@ -158,8 +212,14 @@ option(USE_EXTENSIONS "Build with Extensions module" ON)
 add_feature_info(Extensions USE_EXTENSIONS "Build with Extensions module")
 
 if(USE_EXTENSIONS)
-    option(USE_MEDIABROWSER "Build with MediaBrowser support" ON)
-    add_feature_info(MediaBrowser USE_MEDIABROWSER "Build with MediaBrowser support")
+    if(BUILD_WITH_QT4)
+        # MediaBrowser requires QtQml which is not available in Qt4
+        set(USE_MEDIABROWSER OFF)
+        add_feature_info(MediaBrowser USE_MEDIABROWSER "Build with MediaBrowser support (disabled - requires Qt5+)")
+    else()
+        option(USE_MEDIABROWSER "Build with MediaBrowser support" ON)
+        add_feature_info(MediaBrowser USE_MEDIABROWSER "Build with MediaBrowser support")
+    endif()
 
     option(USE_LASTFM "Build with LastFM support" ON)
     add_feature_info(LastFM USE_LASTFM "Build with LastFM support")
@@ -215,14 +275,20 @@ endif()
 option(USE_TAGLIB "Build with tags editor" ${TAGLIB_FOUND})
 add_feature_info(TagLib USE_TAGLIB "Build with tags editor")
 
-if(NOT DEFINED LIBAVUTIL_VERSION)
-    set(LIBAVUTIL_VERSION "58.29.100")
-endif()
-if(NOT DEFINED USE_FFMPEG_VKVIDEO AND USE_VULKAN AND ${LIBAVUTIL_VERSION} VERSION_GREATER_EQUAL "58.29.100")
-    set(DEFAULT_USE_FFMPEG_VKVIDEO ON)
+if(BUILD_WITH_QT4)
+    # VkVideo requires Vulkan which is disabled for Qt4/macOS 10.6.8
+    set(USE_FFMPEG_VKVIDEO OFF)
+    add_feature_info(VkVideo USE_FFMPEG_VKVIDEO "Build VkVideo acceleration into FFmpeg (disabled for Qt4)")
+else()
+    if(NOT DEFINED LIBAVUTIL_VERSION)
+        set(LIBAVUTIL_VERSION "58.29.100")
+    endif()
+    if(NOT DEFINED USE_FFMPEG_VKVIDEO AND USE_VULKAN AND ${LIBAVUTIL_VERSION} VERSION_GREATER_EQUAL "58.29.100")
+        set(DEFAULT_USE_FFMPEG_VKVIDEO ON)
+    endif()
+    option(USE_FFMPEG_VKVIDEO "Build VkVideo acceleration into FFmpeg" ${DEFAULT_USE_FFMPEG_VKVIDEO})
+    add_feature_info(VkVideo USE_FFMPEG_VKVIDEO "Build VkVideo acceleration into FFmpeg")
 endif()
-option(USE_FFMPEG_VKVIDEO "Build VkVideo acceleration into FFmpeg" ${DEFAULT_USE_FFMPEG_VKVIDEO})
-add_feature_info(VkVideo USE_FFMPEG_VKVIDEO "Build VkVideo acceleration into FFmpeg")
 
 if(NOT DEFINED USE_FFMPEG_VAAPI)
     pkg_check_modules(LIBS_VAAPI libva>=1.1.0 libva-drm)
@@ -298,11 +364,22 @@ add_feature_info(PipeWire USE_PIPEWIRE "Build with PipeWire module")
 if(NOT DEFINED USE_XVIDEO)
     pkg_check_modules(LIB_X11_XV x11 xv)
 endif()
-option(USE_XVIDEO "Build with XVideo module" ${LIB_X11_XV_FOUND})
-add_feature_info(XVideo USE_XVIDEO "Build with XVideo module")
+if(BUILD_WITH_QT4)
+    option(USE_XVIDEO "Build with XVideo module" OFF)
+    add_feature_info(XVideo USE_XVIDEO "Build with XVideo module (disabled for Qt4 build)")
+else()
+    option(USE_XVIDEO "Build with XVideo module" ${LIB_X11_XV_FOUND})
+    add_feature_info(XVideo USE_XVIDEO "Build with XVideo module")
+endif()
 
-option(USE_CUVID "Build with CUVID module" ${DEFAULT_CUVID})
-add_feature_info(CUVID USE_CUVID "Build with CUVID module")
+if(BUILD_WITH_QT4)
+    # CUVID (NVIDIA) is not available on PowerPC macOS
+    set(USE_CUVID OFF)
+    add_feature_info(CUVID USE_CUVID "Build with CUVID module (disabled for PowerPC)")
+else()
+    option(USE_CUVID "Build with CUVID module" ${DEFAULT_CUVID})
+    add_feature_info(CUVID USE_CUVID "Build with CUVID module")
+endif()
 
 option(USE_NOTIFY "Build additional notifications module" ON)
 add_feature_info(Notifications USE_NOTIFY "Build additional notifications module")
@@ -420,16 +497,29 @@ if(NOT WIN32 AND NOT APPLE)
     set(SOLID_ACTIONS_INSTALL_PATH ${SOLID_ACTIONS_DEFAULT} CACHE STRING "The destination path for the Solid actions (\"None\" to disable)")
 endif()
 
-if(USE_FREEDESKTOP_NOTIFICATIONS OR USE_DBUS_PM OR USE_MPRIS2)
-    find_package(${QT_PREFIX}DBus REQUIRED)
-endif()
+if(BUILD_WITH_QT4)
+    # Qt4 has DBus support in QtDBus module (already included if needed)
+    # Note: DBus features are primarily for Linux, not macOS 10.6.8
+    if(USE_FREEDESKTOP_NOTIFICATIONS OR USE_DBUS_PM OR USE_MPRIS2)
+        message(STATUS "DBus support requested but may not be needed for macOS build")
+    endif()
 
-if(USE_MEDIABROWSER)
-    find_package(${QT_PREFIX}Qml REQUIRED)
-    set(USE_QML ON)
-endif()
+    # MediaBrowser is disabled for Qt4 (requires QtQml)
+    set(USE_QML OFF)
 
-find_package(${QT_PREFIX}Svg REQUIRED)
+    # QtSvg is already required in find_package(Qt4) above
+else()
+    if(USE_FREEDESKTOP_NOTIFICATIONS OR USE_DBUS_PM OR USE_MPRIS2)
+        find_package(${QT_PREFIX}DBus REQUIRED)
+    endif()
+
+    if(USE_MEDIABROWSER)
+        find_package(${QT_PREFIX}Qml REQUIRED)
+        set(USE_QML ON)
+    endif()
+
+    find_package(${QT_PREFIX}Svg REQUIRED)
+endif()
 
 if(USE_VULKAN)
     set(Vulkan_INCLUDE_DIRS "${CMAKE_SOURCE_DIR}/src/qmplay2/vulkan/headers/include")
diff --git a/lang/CMakeLists.txt b/lang/CMakeLists.txt
index b4e82ec3..8aee69e9 100644
--- a/lang/CMakeLists.txt
+++ b/lang/CMakeLists.txt
@@ -12,9 +12,15 @@ else()
     endforeach()
 endif()
 
-find_package(${QT_PREFIX} REQUIRED COMPONENTS LinguistTools)
+# Qt4 doesn't have LinguistTools as a separate component
+if(NOT BUILD_WITH_QT4)
+    find_package(${QT_PREFIX} REQUIRED COMPONENTS LinguistTools)
+endif()
+
 if(BUILD_WITH_QT6)
     qt6_add_translation(QM_FILES ${QMPLAY2_TSS})
+elseif(BUILD_WITH_QT4)
+    qt4_add_translation(QM_FILES ${QMPLAY2_TSS})
 else()
     qt5_add_translation(QM_FILES ${QMPLAY2_TSS})
 endif()
diff --git a/src/gui/AVThread.cpp b/src/gui/AVThread.cpp
index e2a14f8f..5e44f755 100644
--- a/src/gui/AVThread.cpp
+++ b/src/gui/AVThread.cpp
@@ -26,6 +26,9 @@
 
 AVThread::AVThread(PlayClass &playC)
     : playC(playC)
+#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
+    , mutex(QMutex::Recursive)
+#endif
 {
     connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
     mutex.lock();
diff --git a/src/gui/AVThread.hpp b/src/gui/AVThread.hpp
index b96d4f56..bbb6bd27 100644
--- a/src/gui/AVThread.hpp
+++ b/src/gui/AVThread.hpp
@@ -22,6 +22,9 @@
 #include <QStringList>
 #include <QThread>
 #include <QMutex>
+#if QT_VERSION >= 0x050E00
+    #include <QRecursiveMutex>
+#endif
 
 #include <atomic>
 
@@ -31,7 +34,7 @@ class Writer;
 
 class AVThread : public QThread
 {
-    Q_DECLARE_TR_FUNCTIONS(AVThread)
+    Q_OBJECT
 
 public:
     virtual void setDec(Decoder *_dec);
@@ -73,6 +76,10 @@ protected:
 
     std::atomic_bool br = false, br2 = false;
     bool waiting = false;
+#if QT_VERSION >= 0x050E00
     QRecursiveMutex mutex;
+#else
+    QMutex mutex;
+#endif
     QMutex updateMutex;
 };
diff --git a/src/gui/AboutWidget.cpp b/src/gui/AboutWidget.cpp
index 0084cb46..c7c09a83 100644
--- a/src/gui/AboutWidget.cpp
+++ b/src/gui/AboutWidget.cpp
@@ -33,7 +33,7 @@
 #include <QMouseEvent>
 #include <QMessageBox>
 #include <QPlainTextEdit>
-#include <QGuiApplication>
+#include <QApplication>
 #include <QDesktopServices>
 
 AboutWidget::AboutWidget()
@@ -44,9 +44,14 @@ AboutWidget::AboutWidget()
     QString labelText;
     labelText += "<b>QMPlay2:</b> " + tr("video and audio player");
     labelText += "<br/><b>" + tr("Programmer") + ":</b> <a href='mailto:mumei6102@gmail.com'>Błażej Szczygieł</a>";
+#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
     labelText += "<br/><b>" + tr("Version") + ":</b> " + Version::get() + " (" + QSysInfo::buildCpuArchitecture();
-#if !defined(Q_OS_MACOS) && !defined(Q_OS_WIN)
-    labelText += ", " + QGuiApplication::platformName();
+#else
+    // Qt4: QSysInfo::buildCpuArchitecture() doesn't exist
+    labelText += "<br/><b>" + tr("Version") + ":</b> " + Version::get() + " (";
+#endif
+#if !defined(Q_OS_MAC) && !defined(Q_OS_WIN)
+    labelText += ", " + QApplication::platformName();
 #endif
     labelText += ")";
     labelText += "<br/><br/><a href='https://github.com/zaps166/QMPlay2/releases/latest'>" + tr("GitHub download page") + "</a>";
@@ -60,7 +65,14 @@ AboutWidget::AboutWidget()
 
     QTabWidget *tabW = new QTabWidget;
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
     const QFont font(QFontDatabase::systemFont(QFontDatabase::FixedFont));
+#else
+    // Qt4: QFontDatabase::systemFont() doesn't exist, use a monospace font
+    QFont font;
+    font.setFamily("Courier");
+    font.setStyleHint(QFont::TypeWriter);
+#endif
 
     logE = new QPlainTextEdit;
     logE->setFont(font);
diff --git a/src/gui/AboutWidget.hpp b/src/gui/AboutWidget.hpp
index abc800c5..92c17bf0 100644
--- a/src/gui/AboutWidget.hpp
+++ b/src/gui/AboutWidget.hpp
@@ -24,14 +24,14 @@
 class QPlainTextEdit;
 class QPushButton;
 
-class AboutWidget final : public QWidget
+class AboutWidget : public QWidget
 {
     Q_OBJECT
 public:
     AboutWidget();
 private:
-    void showEvent(QShowEvent *) override;
-    void closeEvent(QCloseEvent *) override;
+    void showEvent(QShowEvent *);
+    void closeEvent(QCloseEvent *);
 
     QPlainTextEdit *logE, *clE, *auE;
     QPushButton *clrLogB;
diff --git a/src/gui/AddressBox.cpp b/src/gui/AddressBox.cpp
index 4e400b91..449b5e1f 100644
--- a/src/gui/AddressBox.cpp
+++ b/src/gui/AddressBox.cpp
@@ -23,7 +23,7 @@
 #include <Module.hpp>
 #include <Main.hpp>
 
-#include <QGuiApplication>
+#include <QApplication>
 #include <QGridLayout>
 #include <QClipboard>
 #include <QDir>
@@ -55,7 +55,7 @@ AddressBox::AddressBox(Qt::Orientation o, QString url, const QString &choice)
 
     if (url.isNull())
     {
-        const auto text = QGuiApplication::clipboard()->text();
+        const auto text = QApplication::clipboard()->text();
         if (text.contains("://") && text.indexOf("://") == text.indexOf(":") && QUrl(text).isValid())
             url = text;
     }
diff --git a/src/gui/AddressDialog.hpp b/src/gui/AddressDialog.hpp
index 314973de..c8c7fe33 100644
--- a/src/gui/AddressDialog.hpp
+++ b/src/gui/AddressDialog.hpp
@@ -24,7 +24,7 @@
 #include <QCheckBox>
 #include <QDialog>
 
-class AddressDialog final : public QDialog
+class AddressDialog : public QDialog
 {
     Q_DECLARE_TR_FUNCTIONS(AddressDialog)
 public:
diff --git a/src/gui/Appearance.cpp b/src/gui/Appearance.cpp
index 65059b66..c1b54b44 100644
--- a/src/gui/Appearance.cpp
+++ b/src/gui/Appearance.cpp
@@ -58,7 +58,7 @@ public:
         return pixmap;
     }
 private:
-    void paintEvent(QPaintEvent *) override
+    void paintEvent(QPaintEvent *)
     {
         if (!pixmap.isNull())
         {
@@ -77,14 +77,20 @@ constexpr auto DEFAULT_GRAD1  = 0xFF000000;
 constexpr auto DEFAULT_GRAD2  = 0xFF333366;
 constexpr auto DEFAULT_QMPTXT = 0xFFFFFFFF;
 
-static const auto QMPlay2ColorExtension = QStringLiteral(".QMPlay2Color");
+static const auto QMPlay2ColorExtension = QString(".QMPlay2Color");
 static QPalette systemPalette;
 static QString colorsDir;
 
 template<bool sort = false, typename Fn>
 static void iterateRoles(Fn &&fn)
 {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
     const auto colorRoles = QMetaEnum::fromType<QPalette::ColorRole>();
+#else
+    // Qt4: QMetaEnum::fromType() doesn't exist, use metaObject indexOfEnumerator
+    const int idx = QPalette::staticMetaObject.indexOfEnumerator("ColorRole");
+    const QMetaEnum colorRoles = QPalette::staticMetaObject.enumerator(idx);
+#endif
     const int colorRolesCount = colorRoles.keyCount();
     auto filterFn = [](auto role) {
         return role != QPalette::WindowText
@@ -114,10 +120,19 @@ static void iterateRoles(Fn &&fn)
 
                 for (int i = 0; i < nameLen; ++i)
                 {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
                     if (i > 0 && QChar::isUpper(name[i]))
                     {
                         newName += ' ';
                         newName += QChar::toLower(name[i]);
+#else
+                    // Qt4: QChar static methods don't exist, use instance methods
+                    QChar ch(name[i]);
+                    if (i > 0 && ch.isUpper())
+                    {
+                        newName += ' ';
+                        newName += ch.toLower().toLatin1();
+#endif
                     }
                     else
                     {
@@ -239,7 +254,7 @@ void Appearance::init()
             if (colorScheme.value("Colors/Use").toBool())
             {
                 iterateRoles([&](QPalette::ColorRole role, const char *name) {
-                    const QString settingsKey = QStringLiteral("Colors/") + name;
+                    const QString settingsKey = QString("Colors/") + name;
                     const auto color = colorScheme.value(settingsKey).toUInt();
                     if (colorScheme.contains(settingsKey))
                     {
@@ -338,14 +353,14 @@ Appearance::Appearance(QWidget *p) :
     QFormLayout *formLayout = new QFormLayout(formWidget);
     iterateRoles<true>([&](QPalette::ColorRole role, const char *name) {
         auto button = new ColorButton;
-        formLayout->addRow(QStringLiteral("%1 color:").arg(name), button);
-        connect(button, &ColorButton::colorChanged, this, &Appearance::showReadOnlyWarning);
+        formLayout->addRow(QString("%1 color:").arg(name), button);
+        connect(button, SIGNAL(colorChanged()), this, SLOT(showReadOnlyWarning()));
         m_colorButtons[role] = button;
     });
     formLayout->addRow(tr("Slider button color") + ":", m_colorSliderButton = new ColorButton);
     formLayout->addRow(tr("Slider highlight color") + ":", m_colorSliderHighlight = new ColorButton);
-    connect(m_colorSliderButton, &ColorButton::colorChanged, this, &Appearance::showReadOnlyWarning);
-    connect(m_colorSliderHighlight, &ColorButton::colorChanged, this, &Appearance::showReadOnlyWarning);
+    connect(m_colorSliderButton, SIGNAL(colorChanged()), this, SLOT(showReadOnlyWarning()));
+    connect(m_colorSliderHighlight, SIGNAL(colorChanged()), this, SLOT(showReadOnlyWarning()));
     formLayout->setContentsMargins(3, 3, 3, 3);
 
     auto colorsScroll = new QScrollArea;
@@ -407,9 +422,9 @@ Appearance::Appearance(QWidget *p) :
         loadCurrentPalette();
 
 
-    connect(grad1C, &ColorButton::colorChanged, this, &Appearance::showReadOnlyWarning);
-    connect(grad2C, &ColorButton::colorChanged, this, &Appearance::showReadOnlyWarning);
-    connect(qmpTxtC, &ColorButton::colorChanged, this, &Appearance::showReadOnlyWarning);
+    connect(grad1C, SIGNAL(colorChanged()), this, SLOT(showReadOnlyWarning()));
+    connect(grad2C, SIGNAL(colorChanged()), this, SLOT(showReadOnlyWarning()));
+    connect(qmpTxtC, SIGNAL(colorChanged()), this, SLOT(showReadOnlyWarning()));
 }
 
 void Appearance::schemesIndexChanged(int idx)
@@ -434,7 +449,7 @@ void Appearance::schemesIndexChanged(int idx)
             if (colorScheme.value("Colors/Use").toBool())
             {
                 iterateRoles([&](QPalette::ColorRole role, const char *name) {
-                    const QString settingsKey = QStringLiteral("Colors/") + name;
+                    const QString settingsKey = QString("Colors/") + name;
                     if (colorScheme.contains(settingsKey))
                     {
                         m_colorButtons.value(role)->setColor(colorScheme.value(settingsKey).toUInt());
@@ -442,7 +457,7 @@ void Appearance::schemesIndexChanged(int idx)
                     else if (role == QPalette::AlternateBase)
                     {
                         // Backward compatibility
-                        const QString settingsKey2 = QStringLiteral("Colors/Base");
+                        const QString settingsKey2 = QString("Colors/Base");
                         if (colorScheme.contains(settingsKey2))
                         {
                             m_colorButtons.value(role)->setColor(baseToAlternateBase(colorScheme.value(settingsKey2).toUInt()));
@@ -574,7 +589,7 @@ void Appearance::saveScheme(QSettings &colorScheme)
     if (useColorsB->isChecked())
     {
         iterateRoles([&](QPalette::ColorRole role, const char *name) {
-            const QString settingsKey = QStringLiteral("Colors/") + name;
+            const QString settingsKey = QString("Colors/") + name;
             colorScheme.setValue(settingsKey, m_colorButtons.value(role)->getColor().rgb());
         });
         colorScheme.setValue("Colors/SliderButton", m_colorSliderButton->getColor().rgb());
diff --git a/src/gui/Appearance.hpp b/src/gui/Appearance.hpp
index 9adf5ecc..a13789fe 100644
--- a/src/gui/Appearance.hpp
+++ b/src/gui/Appearance.hpp
@@ -45,8 +45,9 @@ private slots:
     void deleteScheme();
     void chooseWallpaper();
     void buttonBoxClicked(QAbstractButton *b);
-private:
+
     void showReadOnlyWarning();
+private:
     void saveScheme(QSettings &colorScheme);
     void reloadSchemes();
     void loadCurrentPalette();
diff --git a/src/gui/AudioThr.cpp b/src/gui/AudioThr.cpp
index c1263cd4..d750f128 100644
--- a/src/gui/AudioThr.cpp
+++ b/src/gui/AudioThr.cpp
@@ -58,16 +58,16 @@ AudioThr::~AudioThr()
 
 void AudioThr::stop(bool terminate)
 {
-    for (QMPlay2Extensions *vis : std::as_const(visualizations))
+    for (QMPlay2Extensions *vis : visualizations)
         vis->visState(false);
-    for (AudioFilter *filter : std::as_const(filters))
+    for (AudioFilter *filter : filters)
         delete filter;
     playC.audioSeekPos = -1;
     AVThread::stop(terminate);
 }
 void AudioThr::clearVisualizations()
 {
-    for (QMPlay2Extensions *vis : std::as_const(visualizations))
+    for (QMPlay2Extensions *vis : visualizations)
         vis->clearSoundData();
 }
 
@@ -111,9 +111,9 @@ bool AudioThr::setParams(uchar realChn, uint realSRate, uchar chn, uint sRate, b
             sample_rate = realSample_rate;
         }
 
-        for (QMPlay2Extensions *vis : std::as_const(visualizations))
+        for (QMPlay2Extensions *vis : visualizations)
             vis->visState(true, currentChannels(), currentSampleRate());
-        for (AudioFilter *filter : std::as_const(filters))
+        for (AudioFilter *filter : filters)
             filter->setAudioParameters(currentChannels(), currentSampleRate());
 
         return true;
@@ -187,7 +187,7 @@ void AudioThr::run()
             bool hasBufferedSamplesInResampler = false;
             if (playC.endOfStream && !hasAPackets)
             {
-                for (AudioFilter *filter : std::as_const(filters))
+                for (AudioFilter *filter : filters)
                 {
                     if (filter->bufferedSamples())
                     {
@@ -232,7 +232,7 @@ void AudioThr::run()
             waiting = false;
 
             const bool flushAudio = playC.flushAudio;
-            double ts = qQNaN();
+            double ts = std::numeric_limits<double>::quiet_NaN();
 
             Packet packet;
             if (!hasBufferedSamples && (dec->pendingFrames() == 0 || flushAudio))
@@ -289,7 +289,7 @@ void AudioThr::run()
             }
 
             delay = writer->getParam("delay").toDouble() + sndResampler.getDelay();
-            for (AudioFilter *filter : std::as_const(filters))
+            for (AudioFilter *filter : filters)
             {
                 if (flushAudio)
                     filter->clearBuffers();
@@ -335,7 +335,7 @@ void AudioThr::run()
                 decodedSize -= chunk;
 
                 playC.audio_last_delay = (double)decodedChunk.size() / (double)(sizeof(float) * currentChannels() * currentSampleRate());
-                if (!qIsNaN(ts))
+                if (!std::isnan(ts))
                 {
                     audio_pts = playC.audio_current_pts = ts - delay;
                     if ((!playC.vThr || ((AVThread *)playC.vThr)->hasError() || (((AVThread *)playC.vThr)->dec && ((AVThread *)playC.vThr)->dec->isDummyDecoder())) && playC.audioSeekPos <= 0)
@@ -361,7 +361,7 @@ void AudioThr::run()
 
                 if (!dec->isDummyDecoder())
                     tmp_time += playC.audio_last_delay * 1000.0;
-                if (!qIsNaN(ts))
+                if (!std::isnan(ts))
                     ts += playC.audio_last_delay;
 
                 if (playC.skipAudioFrame <= 0.0 || oneFrame)
@@ -383,7 +383,7 @@ void AudioThr::run()
                             data[i] *= vol[i & 1];
                     }
 
-                    for (QMPlay2Extensions *vis : std::as_const(visualizations))
+                    for (QMPlay2Extensions *vis : visualizations)
                         vis->sendSoundData(decodedChunk);
 
                     QByteArray dataToWrite;
@@ -469,6 +469,6 @@ inline uint AudioThr::currentSampleRate() const
 
 void AudioThr::pauseVis(bool b)
 {
-    for (QMPlay2Extensions *vis : std::as_const(visualizations))
+    for (QMPlay2Extensions *vis : visualizations)
         vis->visState(!b, currentChannels(), currentSampleRate());
 }
diff --git a/src/gui/AudioThr.hpp b/src/gui/AudioThr.hpp
index c4f3b87d..dc76ef91 100644
--- a/src/gui/AudioThr.hpp
+++ b/src/gui/AudioThr.hpp
@@ -28,14 +28,14 @@ class QMPlay2Extensions;
 class PlayClass;
 class AudioFilter;
 
-class AudioThr final : public AVThread
+class AudioThr : public AVThread
 {
     Q_OBJECT
 public:
     AudioThr(PlayClass &, const QStringList &pluginsName = {});
     ~AudioThr();
 
-    void stop(bool terminate = false) override;
+    void stop(bool terminate = false);
     void clearVisualizations();
 
     bool setParams(uchar realChn, uint realSRate, uchar chn, uint sRate, bool resamplerFirst);
@@ -47,7 +47,7 @@ public:
         allowAudioDrain = true;
     }
 private:
-    void run() override;
+    void run();
 
     bool createResampler(bool cleanBuffers);
 
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 39da2286..479d7c18 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -225,7 +225,8 @@ if(WIN32)
 elseif(APPLE)
     set_target_properties(${PROJECT_NAME} PROPERTIES
         MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/macOS/BundleInfo.plist.in")
-    target_link_libraries(${PROJECT_NAME} PRIVATE ${APPKIT_LIBRARY} ${IOKIT_LIBRARY})
+    find_library(CARBON_LIBRARY Carbon)
+    target_link_libraries(${PROJECT_NAME} PRIVATE ${APPKIT_LIBRARY} ${IOKIT_LIBRARY} ${CARBON_LIBRARY})
 endif()
 if(USE_TAGLIB AND (NOT WIN32 OR CMAKE_HOST_WIN32))
     target_link_libraries(${PROJECT_NAME} PRIVATE ${TAGLIB_LIBRARIES})
@@ -240,11 +241,12 @@ if(WIN32)
 elseif(APPLE)
     install(TARGETS ${PROJECT_NAME} BUNDLE DESTINATION ${CMAKE_INSTALL_PREFIX})
 
-    set(QT_LIBS_DIR "${${QT_PREFIX}Widgets_DIR}/../..")
-    set(QT_PLUGINS_DIR "${QT_LIBS_DIR}/../plugins")
+    set(QT_LIBS_DIR "@qt_libs_dir@")
+    set(QT_PLUGINS_DIR "@qt_plugins_dir@")
     install(FILES
         "${QT_PLUGINS_DIR}/platforms/libqcocoa.dylib"
-        DESTINATION "${MAC_BUNDLE_PATH}/Contents/plugins/platforms")
+        DESTINATION "${MAC_BUNDLE_PATH}/Contents/plugins/platforms"
+        OPTIONAL)
     install(FILES
         "${QT_PLUGINS_DIR}/iconengines/libqsvgicon.dylib"
         DESTINATION "${MAC_BUNDLE_PATH}/Contents/plugins/iconengines")
@@ -262,23 +264,7 @@ elseif(APPLE)
         DESTINATION "${MAC_BUNDLE_PATH}/Contents"
         FILES_MATCHING
         PATTERN "qtbase_*.qm")
-    if(EXISTS "/usr/local/bin/ffmpeg")
-        install(PROGRAMS
-            "/usr/local/bin/ffmpeg"
-            DESTINATION "${MAC_BUNDLE_PATH}/Contents/MacOS")
-    else()
-        message(WARNING "FFmpeg executable not copied!")
-    endif()
-    install(CODE "
-        include(BundleUtilities)
-        set(BU_CHMOD_BUNDLE_ITEMS ON)
-        list(APPEND DIRS /usr/local/lib ${QT_LIBS_DIR})
-        file(GLOB_RECURSE QMPLAY2_MODULES_AND_QT_PLUGINS
-            \"${MAC_BUNDLE_PATH}/Contents/MacOS/modules/*\"
-            \"${MAC_BUNDLE_PATH}/Contents/plugins/*.dylib\")
-        file(WRITE \"${MAC_BUNDLE_PATH}/Contents/Resources/qt.conf\")
-        fixup_bundle(${MAC_BUNDLE_PATH} \"\${QMPLAY2_MODULES_AND_QT_PLUGINS}\" \"\${DIRS}\")
-    ")
+    file(WRITE \"@destroot@${MAC_BUNDLE_PATH}/Contents/Resources/qt.conf\")
 elseif(NOT ANDROID)
     # executable
     install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
diff --git a/src/gui/DeintSettingsW.cpp b/src/gui/DeintSettingsW.cpp
index 06038d71..2242ffc8 100644
--- a/src/gui/DeintSettingsW.cpp
+++ b/src/gui/DeintSettingsW.cpp
@@ -139,7 +139,7 @@ void DeintSettingsW::writeSettings()
     for (QObject *obj : children())
         if (obj->isWidgetType() && !obj->property("module").isNull())
             videoDeintModules.insert((Module *)obj->property("module").value<void *>());
-    for (Module *module : std::as_const(videoDeintModules))
+    for (Module *module : videoDeintModules)
         module->videoDeintSave();
 }
 
diff --git a/src/gui/DeintSettingsW.hpp b/src/gui/DeintSettingsW.hpp
index f5f9b599..06b783ee 100644
--- a/src/gui/DeintSettingsW.hpp
+++ b/src/gui/DeintSettingsW.hpp
@@ -24,7 +24,7 @@ class QCheckBox;
 class QComboBox;
 class Module;
 
-class DeintSettingsW final : public QGroupBox
+class DeintSettingsW : public QGroupBox
 {
     Q_OBJECT
 public:
diff --git a/src/gui/DemuxerThr.cpp b/src/gui/DemuxerThr.cpp
index 2c9b240d..7df063fb 100644
--- a/src/gui/DemuxerThr.cpp
+++ b/src/gui/DemuxerThr.cpp
@@ -122,7 +122,7 @@ void DemuxerThr::loadImage()
                     }
                 }
             }
-            if (img.isNull() && (!artist.isEmpty() || title.contains(QStringLiteral(" - "))) && (!title.isEmpty() || !album.isEmpty())) //Ładowanie okładki z cache
+            if (img.isNull() && (!artist.isEmpty() || title.contains(QString(" - "))) && (!title.isEmpty() || !album.isEmpty())) //Ładowanie okładki z cache
             {
                 QString coverPath = getCoverFile(title, artist, album);
                 if (!title.isEmpty() && !album.isEmpty() && !QFile::exists(coverPath)) //Try to load cover for title if album cover doesn't exist
@@ -331,7 +331,14 @@ bool DemuxerThr::load(bool canEmitInfo)
     emit load(demuxer.rawPtr()); //to wykonuje się w głównym wątku (wywołuje load() z "playC")
     playC.loadMutex.lock(); //i czeka na koniec wykonywania
     playC.loadMutex.unlock();
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     demuxer->selectStreams({playC.audioStream, playC.videoStream, playC.subtitlesStream});
+#else
+    // Qt4: No initializer lists, build QSet explicitly
+    QSet<int> selectedStreams;
+    selectedStreams << playC.audioStream << playC.videoStream << playC.subtitlesStream;
+    demuxer->selectStreams(selectedStreams);
+#endif
     if (canEmitInfo)
         emitInfo();
     return playC.audioStream >= 0 || playC.videoStream >= 0;
@@ -487,12 +494,7 @@ void DemuxerThr::run()
     connect(&QMPlay2Core, SIGNAL(updateCover(QString, QString, QString, QByteArray)), this, SLOT(updateCover(QString, QString, QString, QByteArray)));
 
     QObject o;
-    connect(&QMPlay2Core, &QMPlay2CoreClass::updateInformationPanel,
-            &o, [this] {
-        Q_ASSERT(QThread::currentThread() != thread());
-        if (demuxer)
-            emitInfo();
-    });
+    connect(&QMPlay2Core, SIGNAL(updateInformationPanel()), this, SLOT(handleUpdateInformationPanel()));
 
     if (forwardTime > 0.0)
     {
@@ -1284,7 +1286,7 @@ void DemuxerThr::emitInfo()
         ++i;
     }
     i = 0;
-    for (const QString &fName : std::as_const(playC.fileSubsList))
+    for (const QString &fName : playC.fileSubsList)
         addSubtitleStream(fName == playC.fileSubs, subtitlesStreams, i++, ++subtitlesStreamCount, "fileSubs", QString(), Functions::fileName(fName), subtitlesStreamsMenu);
 
     if (!videoStreams.isEmpty())
@@ -1454,7 +1456,12 @@ void DemuxerThr::stopVADec()
     playC.stopVDec();
     playC.stopADec();
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
     const QString key = playC.getUrl().toUtf8().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
+#else
+    // Qt4: Use basic base64 encoding
+    const QString key = playC.getUrl().toUtf8().toBase64();
+#endif
     const double pos = playC.getPos();
     bool store = !err && !unknownLength && demuxer && QMPlay2Core.getSettings().getBool("StoreUrlPos");
     if (store)
@@ -1490,7 +1497,7 @@ void DemuxerThr::updateCover(const QString &title, const QString &artist, const
                !title.isEmpty()
             && !artist.isEmpty()
             && this->artist.isEmpty()
-            && this->title.contains(QStringLiteral(" - "))
+            && this->title.contains(QString(" - "))
             && this->title.contains(title)
             && this->title.contains(artist)
         ;
@@ -1536,3 +1543,11 @@ void DemuxerTimer::timeout()
     if (demuxerThr.canUpdateBuffered())
         demuxerThr.emitBufferInfo(true);
 }
+
+// Qt4: Helper slot implementation
+void DemuxerThr::handleUpdateInformationPanel()
+{
+    Q_ASSERT(QThread::currentThread() != thread());
+    if (demuxer)
+        emitInfo();
+}
diff --git a/src/gui/DemuxerThr.hpp b/src/gui/DemuxerThr.hpp
index 8c3d835d..bfdb5707 100644
--- a/src/gui/DemuxerThr.hpp
+++ b/src/gui/DemuxerThr.hpp
@@ -33,7 +33,7 @@ class AVThread;
 class Demuxer;
 class BasicIO;
 
-class DemuxerThr final : public QThread
+class DemuxerThr : public QThread
 {
     friend class DemuxerTimer;
     friend class PlayClass;
@@ -69,7 +69,7 @@ private:
     void startRecording();
     void stopRecording();
 
-    void run() override;
+    void run();
 
     void startRecordingInternal(QHash<int, int> &recStreamsMap);
     void stopRecordingInternal(QHash<int, int> &recStreamsMap);
@@ -114,6 +114,8 @@ private:
 private slots:
     void stopVADec();
     void updateCover(const QString &title, const QString &artist, const QString &album, const QByteArray &cover);
+    // Qt4: Helper slot for lambda conversion
+    void handleUpdateInformationPanel();
 signals:
     void load(Demuxer *);
     void allowRecording(bool allow);
diff --git a/src/gui/EntryProperties.cpp b/src/gui/EntryProperties.cpp
index c708e530..993ab496 100644
--- a/src/gui/EntryProperties.cpp
+++ b/src/gui/EntryProperties.cpp
@@ -116,7 +116,7 @@ EntryProperties::EntryProperties(QWidget *p, QTreeWidget *treeWidget, bool &sync
         openUrlB = new QToolButton;
         openUrlB->setToolTip(tr("Open URL or directory containing chosen file"));
         openUrlB->setIcon(QMPlay2Core.getIconFromTheme("folder-open"));
-        connect(openUrlB, &QToolButton::clicked, this, &EntryProperties::openUrl);
+        connect(openUrlB, SIGNAL(clicked()), this, SLOT(openUrl()));
         layout.addWidget(openUrlB, row, 2, 1, 1);
 
         fileSizeL = new QLabel;
diff --git a/src/gui/EntryProperties.hpp b/src/gui/EntryProperties.hpp
index 55ca6da6..dab9571f 100644
--- a/src/gui/EntryProperties.hpp
+++ b/src/gui/EntryProperties.hpp
@@ -31,7 +31,7 @@ class QCheckBox;
 #endif
 class QLabel;
 
-class EntryProperties final : public QDialog
+class EntryProperties : public QDialog
 {
     Q_OBJECT
 public:
@@ -56,6 +56,6 @@ private slots:
 #endif
     void browse();
     void openUrl();
-    void accept() override;
-    void reject() override;
+    void accept();
+    void reject();
 };
diff --git a/src/gui/InfoDock.cpp b/src/gui/InfoDock.cpp
index 40206b34..3893f690 100644
--- a/src/gui/InfoDock.cpp
+++ b/src/gui/InfoDock.cpp
@@ -18,7 +18,7 @@
 
 #include <InfoDock.hpp>
 
-#include <QGuiApplication>
+#include <QApplication>
 #include <QMouseEvent>
 #include <QScrollBar>
 #include <QLabel>
@@ -99,9 +99,8 @@ InfoDock::InfoDock()
         infoE->document()->setDefaultStyleSheet("a {color: " + infoE->palette().text().color().name() + "; text-decoration: none;}");
         infoE->setHtml(m_info);
     };
-    connect(qGuiApp, &QGuiApplication::paletteChanged,
-            this, setInfoEditStyleSheet,
-            Qt::QueuedConnection);
+    // Qt4: QApplication does not have paletteChanged signal
+    // connect(qGuiApp, &QApplication::paletteChanged, this, setInfoEditStyleSheet, Qt::QueuedConnection);
     setInfoEditStyleSheet();
 }
 
diff --git a/src/gui/InfoDock.hpp b/src/gui/InfoDock.hpp
index f419b826..851e9ba7 100644
--- a/src/gui/InfoDock.hpp
+++ b/src/gui/InfoDock.hpp
@@ -22,11 +22,11 @@
 
 #include <DockWidget.hpp>
 
-class TextEdit final : public QTextEdit
+class TextEdit : public QTextEdit
 {
 private:
-    void mouseMoveEvent(QMouseEvent *) override;
-    void mousePressEvent(QMouseEvent *) override;
+    void mouseMoveEvent(QMouseEvent *);
+    void mousePressEvent(QMouseEvent *);
 };
 
 /**/
diff --git a/src/gui/KeyBindingsDialog.cpp b/src/gui/KeyBindingsDialog.cpp
index 61244300..1412454f 100644
--- a/src/gui/KeyBindingsDialog.cpp
+++ b/src/gui/KeyBindingsDialog.cpp
@@ -36,8 +36,13 @@ KeyBindingsDialog::KeyBindingsDialog(QWidget *p) :
     shortcuts->setModel(QMPlay2GUI.shortcutHandler);
     shortcuts->setFrameShape(QFrame::NoFrame);
     shortcuts->setAlternatingRowColors(true);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     shortcuts->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
     shortcuts->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
+#else
+    shortcuts->horizontalHeader()->setResizeMode(0, QHeaderView::ResizeToContents);
+    shortcuts->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch);
+#endif
     shortcuts->setSelectionMode(QAbstractItemView::SingleSelection);
     shortcuts->verticalHeader()->setVisible(false);
 
diff --git a/src/gui/Main.cpp b/src/gui/Main.cpp
index bdd81906..7f15a2da 100644
--- a/src/gui/Main.cpp
+++ b/src/gui/Main.cpp
@@ -30,24 +30,29 @@
 #include <Module.hpp>
 #include <IPC.hpp>
 
-#include <QCommandLineParser>
-#include <QVersionNumber>
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+    #include <QCommandLineParser>
+#endif
+// Qt4: No QVersionNumber (added in Qt5.6)
 #include <QApplication>
 #include <QImageReader>
 #include <QTreeWidget>
 #include <QMessageBox>
 #include <QFileDialog>
 #include <QPainter>
-#include <QWindow>
-#include <QScreen>
+// Qt4: No QWindow, use QWidget
+#include <QWidget>
+// Qt4: QScreen APIs are in QDesktopWidget
+#include <QDesktopWidget>
 #include <QBuffer>
 #include <QFile>
 #include <QDir>
 #ifdef CHECK_FOR_EGL
     #include <QLibrary>
 #endif
-#if defined(CHECK_FOR_EGL) || defined(Q_OS_MACOS)
-    #include <QSurfaceFormat>
+#if defined(CHECK_FOR_EGL) || defined(Q_OS_MAC)
+    // Qt4: Use QGLFormat instead of QSurfaceFormat
+    #include <QtOpenGL/QGLFormat>
 #endif
 
 #ifdef Q_OS_WIN
@@ -77,10 +82,10 @@ QString QMPlay2GUIClass::getPipe()
     wchar_t userName[UNLEN + 1];
     DWORD bufferSize = UNLEN + 1;
     if (GetUserNameW(userName, &bufferSize) && bufferSize > 1)
-        userNameSuffix = QStringLiteral(".") + QString::fromWCharArray(userName, bufferSize - 1);
+        userNameSuffix = QString(".") + QString::fromWCharArray(userName, bufferSize - 1);
 
-    return QStringLiteral("\\\\.\\pipe\\QMPlay2") + userNameSuffix;
-#elif defined Q_OS_MACOS
+    return QString("\\\\.\\pipe\\QMPlay2") + userNameSuffix;
+#elif defined Q_OS_MAC
     return "/tmp/QMPlay2." + QString(getenv("USER"));
 #else
     return QDir::tempPath() + "/QMPlay2." + QString(getenv("USER"));
@@ -120,7 +125,14 @@ void QMPlay2GUIClass::setTreeWidgetItemIcon(QTreeWidgetItem *tWI, const QIcon &i
         if (!treeWidget)
             treeWidget = tWI->treeWidget();
         if (treeWidget)
+        {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
             tWI->setData(column, Qt::DecorationRole, icon.pixmap(treeWidget->window()->windowHandle(), treeWidget->iconSize()));
+#else
+            // Qt4: Use basic pixmap without window handle
+            tWI->setData(column, Qt::DecorationRole, icon.pixmap(treeWidget->iconSize()));
+#endif
+        }
         else
             setDefaultIcon = true;
     }
@@ -151,19 +163,18 @@ void QMPlay2GUIClass::loadIcons()
 {
     deleteIcons();
 
-    if (QVersionNumber::fromString(qVersion()) >= QVersionNumber(6, 7, 0))
-    {
-        qmplay2Icon = new QIcon(":/QMPlay2.svgz");
-    }
-    else
-    {
-        // QtSvg doesn't support effects. This workaround generates drop shadow effect similar to
-        // drop shadow in SVG file. Remove / modify this workaround if QMPlay2 SVG icon changes!
-        const QSize size(128, 128);
-        const QPixmap pixmap = QIcon(":/QMPlay2.svgz").pixmap(size);
-        const qreal sizeRatio = (((qreal)pixmap.size().width()  / (qreal)size.width()) + ((qreal)pixmap.size().height() / (qreal)size.height())) / 2.0;
-        qmplay2Icon = new QIcon(Functions::applyDropShadow(pixmap, 10.0 * sizeRatio, QPointF(3.0 * sizeRatio, 3.0 * sizeRatio), QColor(0, 0, 0, 127)));
-    }
+    // Qt4: No QVersionNumber, use QT_VERSION macro instead
+    // Qt4/5 need drop shadow workaround, Qt6.7+ has it in SVG
+    #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
+    qmplay2Icon = new QIcon(":/QMPlay2.svgz");
+    #else
+    // QtSvg doesn't support effects. This workaround generates drop shadow effect similar to
+    // drop shadow in SVG file. Remove / modify this workaround if QMPlay2 SVG icon changes!
+    const QSize size(128, 128);
+    const QPixmap pixmap = QIcon(":/QMPlay2.svgz").pixmap(size);
+    const qreal sizeRatio = (((qreal)pixmap.size().width()  / (qreal)size.width()) + ((qreal)pixmap.size().height() / (qreal)size.height())) / 2.0;
+    qmplay2Icon = new QIcon(Functions::applyDropShadow(pixmap, 10.0 * sizeRatio, QPointF(3.0 * sizeRatio, 3.0 * sizeRatio), QColor(0, 0, 0, 127)));
+    #endif
 
     groupIcon = new QIcon(getIconFromTheme("folder-orange"));
     mediaIcon = new QIcon(getIconFromTheme("applications-multimedia"));
@@ -184,7 +195,11 @@ QString QMPlay2GUIClass::getCurrentPth(QString pth, bool leaveFilename)
         pth.remove(0, 7);
     if (!leaveFilename)
         pth = Functions::filePath(pth);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
     if (!QFileInfo::exists(pth))
+#else
+    if (!QFileInfo(pth).exists())
+#endif
     {
         pth = settings->getString("currPth");
 #ifdef Q_OS_ANDROID
@@ -201,6 +216,7 @@ void QMPlay2GUIClass::setCurrentPth(const QString &pth)
 
 void QMPlay2GUIClass::restoreGeometry(const QString &pth, QWidget *w, const int defaultSizePercent)
 {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     const auto window = w->window()->windowHandle();
     if (Q_UNLIKELY(!window))
         return;
@@ -227,6 +243,26 @@ void QMPlay2GUIClass::restoreGeometry(const QString &pth, QWidget *w, const int
         w->move(screenSize.width() / 2 - defaultSize.width() / 2, screenSize.height() / 2 - defaultSize.height() / 2);
         w->resize(defaultSize);
     }
+#else
+    // Qt4: Use QDesktopWidget for screen geometry
+    const QRect geo = settings->getRect(pth);
+    const QDesktopWidget *desktop = QApplication::desktop();
+    const QRect screenGeo = desktop->screenGeometry(w);
+
+    if (geo.isValid())
+    {
+        w->setGeometry(geo);
+        if (!screenGeo.contains(w->pos()))
+            w->move(screenGeo.width() / 2 - w->width() / 2, screenGeo.height() / 2 - w->height() / 2);
+    }
+    else
+    {
+        const QRect availGeo = desktop->availableGeometry(w);
+        const QSize defaultSize = availGeo.size() * defaultSizePercent / 100;
+        w->move(screenGeo.width() / 2 - defaultSize.width() / 2, screenGeo.height() / 2 - defaultSize.height() / 2);
+        w->resize(defaultSize);
+    }
+#endif
 }
 
 void QMPlay2GUIClass::updateInDockW()
@@ -254,6 +290,18 @@ QMPlay2GUIClass::~QMPlay2GUIClass()
 
 /**/
 
+static QString fileArg(const QString &arg)
+{
+    if (!arg.contains("://"))
+    {
+        const QFileInfo argInfo(arg);
+        if (!argInfo.isAbsolute())
+            return argInfo.absoluteFilePath();
+    }
+    return arg;
+}
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
 static QCommandLineParser *createCmdParser(bool descriptions)
 {
     static constexpr const char *translations[] = {
@@ -304,7 +352,7 @@ static QCommandLineParser *createCmdParser(bool descriptions)
         {"next", maybeGetTranslatedText(translations[13])},
         {"prev", maybeGetTranslatedText(translations[14])},
         {"quit", maybeGetTranslatedText(translations[15])},
-#if !(defined Q_OS_MACOS || defined Q_OS_ANDROID)
+#if !(defined Q_OS_MAC || defined Q_OS_ANDROID)
         {"tray", maybeGetTranslatedText(translations[18])},
 #endif
         {{"h", "help"}, maybeGetTranslatedText(translations[16])},
@@ -312,23 +360,14 @@ static QCommandLineParser *createCmdParser(bool descriptions)
 
     return parser;
 }
-static QString fileArg(const QString &arg)
-{
-    if (!arg.contains("://"))
-    {
-        const QFileInfo argInfo(arg);
-        if (!argInfo.isAbsolute())
-            return argInfo.absoluteFilePath();
-    }
-    return arg;
-}
+
 static QList<QPair<QString, QString>> parseArguments(const QCommandLineParser &parser)
 {
     QList<QPair<QString, QString>> arguments;
     for (const QString &option : parser.optionNames())
     {
         QString value = parser.value(option);
-        if (option == QStringLiteral("open") || option == QStringLiteral("enqueue") || option == QStringLiteral("opennew"))
+        if (option == QString("open") || option == QString("enqueue") || option == QString("opennew"))
             value = fileArg(value);
         arguments += {option, value};
     }
@@ -342,7 +381,7 @@ static QList<QPair<QString, QString>> parseArguments(const QCommandLineParser &p
         bool found = false;
         for (int i = arguments.count() - 1; i >= 0; --i)
         {
-            if (arguments.at(i).first == QStringLiteral("open") || arguments.at(i).first == QStringLiteral("enqueue") || arguments.at(i).first == QStringLiteral("opennew"))
+            if (arguments.at(i).first == QString("open") || arguments.at(i).first == QString("enqueue") || arguments.at(i).first == QString("opennew"))
             {
                 arguments[i].second += "\n" + urlLines;
                 found = true;
@@ -355,6 +394,38 @@ static QList<QPair<QString, QString>> parseArguments(const QCommandLineParser &p
 
     return arguments;
 }
+#else
+// Qt4 fallback: Simple argument parsing
+static QList<QPair<QString, QString>> parseArguments()
+{
+    QList<QPair<QString, QString>> arguments;
+    const QStringList args = QCoreApplication::arguments();
+
+    for (int i = 1; i < args.count(); ++i)
+    {
+        const QString &arg = args.at(i);
+        if (arg.startsWith("--") || arg.startsWith("-"))
+        {
+            QString option = arg;
+            option.remove(QRegExp("^-+"));
+
+            QString value;
+            if (i + 1 < args.count() && !args.at(i + 1).startsWith("-"))
+            {
+                value = fileArg(args.at(++i));
+            }
+            arguments += {option, value};
+        }
+        else
+        {
+            // Positional argument - treat as "open" command
+            arguments += {"open", fileArg(arg)};
+        }
+    }
+
+    return arguments;
+}
+#endif
 
 static bool writeToSocket(IPCSocket &socket, QList<QPair<QString, QString>> &arguments)
 {
@@ -494,15 +565,16 @@ static LRESULT CALLBACK MMKeysHookProc(int code, WPARAM wparam, LPARAM lparam)
 }
 #endif
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
 static QtMessageHandler g_defaultMsgHandler = nullptr;
 static QMutex g_messageHandlerMutex;
 static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
 {
-    if (qstrcmp(context.category, "qt.qpa.wayland") == 0 && message.endsWith(QStringLiteral("Please fix the transient parent of the popup.")))
+    if (qstrcmp(context.category, "qt.qpa.wayland") == 0 && message.endsWith(QString("Please fix the transient parent of the popup.")))
         return;
 
     bool skip = false;
-    if (message.startsWith(QStringLiteral("load glyph failed")))
+    if (message.startsWith(QString("load glyph failed")))
         skip = true;
 
     bool qmplay2Log = false;
@@ -547,13 +619,59 @@ static void messageHandler(QtMsgType type, const QMessageLogContext &context, co
 #endif
     }
 }
+#else
+// Qt4 version
+static QtMsgHandler g_defaultMsgHandler = nullptr;
+static QMutex g_messageHandlerMutex;
+static void messageHandler(QtMsgType type, const char *msg)
+{
+    QString message = QString::fromLocal8Bit(msg);
+
+    bool skip = false;
+    if (message.startsWith(QString("load glyph failed")))
+        skip = true;
+
+    bool qmplay2Log = false;
+    if (!skip && QCoreApplication::instance())
+    {
+        switch (type)
+        {
+            case QtWarningMsg:
+            case QtCriticalMsg:
+            case QtFatalMsg:
+                g_messageHandlerMutex.lock();
+                QMPlay2Core.logError(message, false);
+                g_messageHandlerMutex.unlock();
+                qmplay2Log = true;
+                break;
+            case QtDebugMsg:
+                // Qt4: No QtInfoMsg, only log debug messages
+                break;
+            default:
+                break;
+        }
+    }
+    if (!qmplay2Log)
+    {
+        if (g_defaultMsgHandler)
+            g_defaultMsgHandler(type, msg);
+        else
+        {
+            g_messageHandlerMutex.lock();
+            fprintf(stderr, "%s\n", msg);
+            fflush(stderr);
+            g_messageHandlerMutex.unlock();
+        }
+    }
+}
+#endif
 
 #ifdef CHECK_FOR_EGL
 static void checkForEGL()
 {
     // FIXME: Non-X11 environment must also get the device file.
 
-    if (QGuiApplication::platformName() != "xcb")
+    if (QApplication::platformName() != "xcb")
         return;
 
     QLibrary libX11("libX11.so.6");
@@ -636,9 +754,10 @@ static void checkForEGL()
 
         if (!isMesa && isEgl)
         {
-            auto fmt = QSurfaceFormat::defaultFormat();
-            fmt.setRenderableType(QSurfaceFormat::OpenGLES);
-            QSurfaceFormat::setDefaultFormat(fmt);
+            // Qt4: Use QGLFormat instead of QSurfaceFormat for OpenGL ES
+            QGLFormat fmt = QGLFormat::defaultFormat();
+            fmt.setProfile(QGLFormat::NoProfile); // OpenGL ES doesn't use profiles
+            QGLFormat::setDefaultFormat(fmt);
         }
     }
 }
@@ -669,20 +788,30 @@ int main(int argc, char *argv[])
 #endif
 
 #ifdef Q_OS_ANDROID
-    QGuiApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); // Doesn't work in Qt6 (bug)
+    // Qt4: Use QApplication instead of QGuiApplication
+    QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar);
 #endif
 
-#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
-    QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
-#endif
+// Qt4: AA_EnableHighDpiScaling is Qt5.6+, not available in Qt4
+// High DPI not supported on macOS 10.6.8 anyway
+
 #ifndef USE_OPENGL
-    QGuiApplication::setAttribute(Qt::AA_ForceRasterWidgets);
+    // Qt4: AA_ForceRasterWidgets may not be available
+    #if defined(Qt::AA_ForceRasterWidgets)
+    QApplication::setAttribute(Qt::AA_ForceRasterWidgets);
+    #endif
 #endif
 
-#ifdef Q_OS_MACOS
-    auto fmt = QSurfaceFormat::defaultFormat();
-    fmt.setColorSpace(QSurfaceFormat::sRGBColorSpace);
-    QSurfaceFormat::setDefaultFormat(fmt);
+#ifdef Q_OS_MAC
+    // Qt4: Use QGLFormat instead of QSurfaceFormat
+    // sRGB color space handling for OpenGL 2.0
+    QGLFormat fmt = QGLFormat::defaultFormat();
+    fmt.setAlpha(true);
+    fmt.setDepth(false);
+    fmt.setStencil(false);
+    fmt.setDoubleBuffer(true);
+    fmt.setSwapInterval(1); // VSync
+    QGLFormat::setDefaultFormat(fmt);
 #endif
 
 #ifndef Q_OS_WIN
@@ -704,10 +833,15 @@ int main(int argc, char *argv[])
     QCoreApplication::setApplicationName("QMPlay2");
 
     QMPlay2GUIClass &qmplay2Gui = QMPlay2GUI; //Create "QMPlay2GUI" instance
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     g_defaultMsgHandler = qInstallMessageHandler(messageHandler);
+#else
+    g_defaultMsgHandler = qInstallMsgHandler(messageHandler);
+#endif
 
     g_qAppOK = true;
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     QCommandLineParser *parser = createCmdParser(false);
     parser->setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
     parser->process(*qApp);
@@ -715,6 +849,19 @@ int main(int argc, char *argv[])
     const bool help = parser->isSet("help");
     QString cmdLineProfile = parser->value("profile");
     delete parser;
+#else
+    // Qt4 fallback
+    QList<QPair<QString, QString>> arguments = parseArguments();
+    bool help = false;
+    QString cmdLineProfile;
+    for (const auto &arg : arguments)
+    {
+        if (arg.first == "help" || arg.first == "h")
+            help = true;
+        if (arg.first == "profile")
+            cmdLineProfile = arg.second;
+    }
+#endif
 
     bool createPpipeInvoked = false;
 
@@ -745,7 +892,7 @@ int main(int argc, char *argv[])
                 argument.first = "open";
                 useSocket = false;
             }
-#if !(defined Q_OS_MACOS || defined Q_OS_ANDROID)
+#if !(defined Q_OS_MAC || defined Q_OS_ANDROID)
             else if (argument.first == "tray")
             {
                 startInvisible = true;
@@ -788,7 +935,7 @@ int main(int argc, char *argv[])
 
     QString libPath, sharePath = QCoreApplication::applicationDirPath();
     bool cmakeBuildFound = false;
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     if (QDir(sharePath).exists("../../../CMakeFiles/QMPlay2.dir"))
         sharePath += "/../../.."; //Probably CMake not-installed build in Bundle
 #endif
@@ -802,7 +949,7 @@ int main(int argc, char *argv[])
     }
     if (!cmakeBuildFound)
     {
-#if !defined Q_OS_WIN && !defined Q_OS_MACOS && !defined Q_OS_ANDROID && !defined Q_OS_HAIKU
+#if !defined Q_OS_WIN && !defined Q_OS_MAC && !defined Q_OS_ANDROID && !defined Q_OS_HAIKU
         sharePath = QCoreApplication::applicationDirPath() + "/../share/qmplay2";
         libPath = QMPlay2CoreClass::getLibDir();
         if (libPath.isEmpty() || !QDir(libPath).exists("qmplay2"))
@@ -816,7 +963,7 @@ int main(int argc, char *argv[])
                 libPath += "lib";
         }
         libPath += "/qmplay2";
-#elif defined Q_OS_MACOS
+#elif defined Q_OS_MAC
         libPath = QCoreApplication::applicationDirPath();
         sharePath += "/../share/qmplay2";
 #else
@@ -827,10 +974,12 @@ int main(int argc, char *argv[])
     qRegisterMetaType<AVRational>("AVRational");
     qRegisterMetaType<Demuxer *>("Demuxer*");
 
-#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
-    QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
 #endif
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     QApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
+#endif
 
     QDir::setCurrent(QCoreApplication::applicationDirPath()); //Is it really needed?
 
@@ -840,9 +989,9 @@ int main(int argc, char *argv[])
         QApplication::setQuitOnLastWindowClosed(false);
         PanGestureEventFilter::install();
 #ifdef Q_OS_WIN
-        auto font = QGuiApplication::font();
+        auto font = QApplication::font();
         font.setHintingPreference(QFont::PreferVerticalHinting);
-        QGuiApplication::setFont(font);
+        QApplication::setFont(font);
 #endif
     }
 
@@ -859,11 +1008,23 @@ int main(int argc, char *argv[])
 
         if (help)
         {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
             parser = createCmdParser(true);
             parser->setApplicationDescription(QString("QMPlay2 - Qt Media Player 2 (%1)").arg((QString)Version::get()));
             printf("%s", parser->helpText().toLocal8Bit().constData());
             fflush(stdout);
             delete parser;
+#else
+            // Qt4: Simple help text
+            printf("QMPlay2 - Qt Media Player 2 (%s)\n", Version::get().constData());
+            printf("Usage: qmplay2 [options] [url...]\n");
+            printf("Options:\n");
+            printf("  --open <url>       Open and play specified URL\n");
+            printf("  --enqueue <url>    Add specified URL to playlist\n");
+            printf("  --profile <name>   Start with given profile\n");
+            printf("  --help, -h         Display this help\n");
+            fflush(stdout);
+#endif
             break;
         }
 
diff --git a/src/gui/Main.hpp b/src/gui/Main.hpp
index a1646f00..550c7a66 100644
--- a/src/gui/Main.hpp
+++ b/src/gui/Main.hpp
@@ -35,7 +35,7 @@ class VideoDock;
 class MenuBar;
 class QWidget;
 
-class QMPlay2GUIClass final : private QMPlay2CoreClass
+class QMPlay2GUIClass : private QMPlay2CoreClass
 {
     Q_DECLARE_TR_FUNCTIONS(QMPlay2GUIClass)
 
@@ -68,8 +68,8 @@ public:
 
     void updateInDockW();
 
-    QWidget *getVideoDock() const override;
-    QWidget *getMainWindow() const override;
+    QWidget *getVideoDock() const;
+    QWidget *getMainWindow() const;
 
     QColor grad1, grad2, qmpTxt;
     QIcon *groupIcon, *mediaIcon, *folderIcon;
diff --git a/src/gui/MainWidget.cpp b/src/gui/MainWidget.cpp
index 22e46721..c900b8f2 100644
--- a/src/gui/MainWidget.cpp
+++ b/src/gui/MainWidget.cpp
@@ -30,11 +30,15 @@
 #include <QFileDialog>
 #include <QTreeWidget>
 #include <QListWidget>
-#include <QWindow>
-#include <QScreen>
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+    #include <QWindow>
+    #include <QScreen>
+#else
+    #include <QDesktopWidget>
+#endif
 #include <QActionGroup>
 #include <QProxyStyle>
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     #include <QProcess>
 #endif
 #if defined(Q_OS_WIN)
@@ -67,7 +71,7 @@
 #include <ShortcutHandler.hpp>
 #include <VolWidget.hpp>
 #include <ScreenSaver.hpp>
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     #include <QMPlay2MacExtensions.hpp>
 #endif
 
@@ -92,7 +96,7 @@ public:
     }
     ~MainWidgetTmpStyle() = default;
 
-    int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const override
+    int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const
     {
         const int pM = QProxyStyle::pixelMetric(metric, option, widget);
         if (metric == QStyle::PM_DockWidgetSeparatorExtent)
@@ -101,7 +105,7 @@ public:
     }
 };
 
-#ifndef Q_OS_MACOS
+#ifndef Q_OS_MAC
 static void copyMenu(QMenu *dest, QMenu *src, const QSet<QMenu *> &dontCopy = {}, const QSet<QMenu *> &dontAdd = {})
 {
     QMenu *newMenu = new QMenu(src->title(), dest);
@@ -117,7 +121,7 @@ static void copyMenu(QMenu *dest, QMenu *src, const QSet<QMenu *> &dontCopy = {}
             else
             {
                 const auto actions = newMenu->actions();
-                if (!actions.isEmpty() && !actions.constLast()->isSeparator())
+                if (!actions.isEmpty() && !actions.last()->isSeparator())
                     newMenu->addAction(act);
             }
         }
@@ -174,7 +178,7 @@ MainWidget::MainWidget(QList<QPair<QString, QString>> &arguments)
 
     QMPlay2GUI.menuBar = new MenuBar;
 
-#if !defined Q_OS_MACOS && !defined Q_OS_ANDROID
+#if !defined Q_OS_MAC && !defined Q_OS_ANDROID
     tray = new QSystemTrayIcon(this);
     tray->setIcon(QMPlay2Core.getIconFromTheme("QMPlay2-panel", QMPlay2Core.getQMPlay2Icon()));
     tray->setVisible(settings.getBool("TrayVisible", true));
@@ -268,14 +272,13 @@ MainWidget::MainWidget(QList<QPair<QString, QString>> &arguments)
 
     for (auto &&dock : getDockWidgets())
     {
-        connect(dock, &DockWidget::shouldStoreSizes, this, [this] {
-            m_storeDockSizesTimer.start();
-        });
+        // Qt4: Use SIGNAL/SLOT syntax
+        connect(dock, SIGNAL(shouldStoreSizes()), this, SLOT(handleShouldStoreSizes()));
     }
 
     m_storeDockSizesTimer.setSingleShot(true);
     m_storeDockSizesTimer.setInterval(0);
-    connect(&m_storeDockSizesTimer, &QTimer::timeout, this, &MainWidget::storeDockSizes);
+    connect(&m_storeDockSizesTimer, SIGNAL(timeout()), this, SLOT(storeDockSizes()));
 
     /* ToolBar and MenuBar */
     createMenuBar();
@@ -342,15 +345,15 @@ MainWidget::MainWidget(QList<QPair<QString, QString>> &arguments)
     connect(infoDock, SIGNAL(chStream(const QString &)), &playC, SLOT(chStream(const QString &)));
     connect(infoDock, SIGNAL(saveCover()), &playC, SLOT(saveCover()));
 
-    connect(videoDock, &VideoDock::resized, &playC, &PlayClass::videoResized);
+    // Qt4: Use SIGNAL/SLOT syntax
+    connect(videoDock, SIGNAL(resized(const QSize &)), &playC, SLOT(videoResized(const QSize &)));
     connect(videoDock, SIGNAL(itemDropped(const QString &, bool)), this, SLOT(itemDropped(const QString &, bool)));
 
     connect(playlistDock, SIGNAL(play(const QString &)), &playC, SLOT(play(const QString &)));
     connect(playlistDock, SIGNAL(repeatEntry(bool)), &playC, SLOT(repeatEntry(bool)));
     connect(playlistDock, SIGNAL(stop()), &playC, SLOT(stop()));
-    connect(playlistDock, &PlaylistDock::addAndPlayRestoreWindow, this, [this] {
-        m_restoreWindowOnVideo = true;
-    });
+    // Qt4: Use helper slot
+    connect(playlistDock, SIGNAL(addAndPlayRestoreWindow()), this, SLOT(handleAddAndPlayRestoreWindow()));
 
     connect(seekS, SIGNAL(valueChanged(int)), this, SLOT(seek(int)));
     connect(seekS, SIGNAL(mousePosition(int)), this, SLOT(mousePositionOnSlider(int)));
@@ -373,14 +376,13 @@ MainWidget::MainWidget(QList<QPair<QString, QString>> &arguments)
     connect(&playC, SIGNAL(playStateChanged(bool)), this, SLOT(playStateChanged(bool)));
     connect(&playC, SIGNAL(setCurrentPlaying()), playlistDock, SLOT(setCurrentPlaying()));
     connect(&playC, SIGNAL(setInfo(const QString &, bool, bool)), infoDock, SLOT(setInfo(const QString &, bool, bool)));
-    connect(&playC, &PlayClass::setStreamsMenu, this, &MainWidget::setStreamsMenu);
+    // Qt4: Use SIGNAL/SLOT syntax
+    connect(&playC, SIGNAL(setStreamsMenu(const QStringList &, const QStringList &, const QStringList &, const QStringList &, const QStringList &)), this, SLOT(setStreamsMenu(const QStringList &, const QStringList &, const QStringList &, const QStringList &, const QStringList &)));
     connect(&playC, SIGNAL(updateCurrentEntry(const QString &, double)), playlistDock, SLOT(updateCurrentEntry(const QString &, double)));
     connect(&playC, SIGNAL(playNext(bool)), playlistDock, SLOT(next(bool)));
     connect(&playC, SIGNAL(clearCurrentPlaying()), playlistDock, SLOT(clearCurrentPlaying()));
-    connect(&playC, &PlayClass::clearInfo, this, [this] {
-        infoDock->clear();
-        setStreamsMenu({}, {}, {}, {}, {});
-    });
+    // Qt4: Use helper slot
+    connect(&playC, SIGNAL(clearInfo()), this, SLOT(handleClearInfo()));
     connect(&playC, SIGNAL(quit()), this, SLOT(deleteLater()));
     connect(&playC, SIGNAL(resetARatio()), this, SLOT(resetARatio()));
     connect(&playC, SIGNAL(updateBitrateAndFPS(int, int, double, double, bool)), infoDock, SLOT(updateBitrateAndFPS(int, int, double, double, bool)));
@@ -388,38 +390,23 @@ MainWidget::MainWidget(QList<QPair<QString, QString>> &arguments)
     connect(&playC, SIGNAL(updateBufferedRange(int, int)), seekS, SLOT(drawRange(int, int)));
     connect(&playC, SIGNAL(updateWindowTitle(const QString &)), this, SLOT(updateWindowTitle(const QString &)));
     connect(&playC, SIGNAL(updateImage(const QImage &)), videoDock, SLOT(updateImage(const QImage &)));
-    connect(&playC, &PlayClass::videoStarted, this, &MainWidget::videoStarted);
-    connect(&playC, &PlayClass::videoNotStarted, this, [this] {
-        m_restoreWindowOnVideo = false;
-    });
+    // Qt4: Use SIGNAL/SLOT syntax
+    connect(&playC, SIGNAL(videoStarted(bool)), this, SLOT(videoStarted(bool)));
+    // Qt4: Use helper slot
+    connect(&playC, SIGNAL(videoNotStarted()), this, SLOT(handleVideoNotStarted()));
     connect(&playC, SIGNAL(uncheckSuspend()), this, SLOT(uncheckSuspend()));
-    connect(&playC, &PlayClass::setVideoCheckState, this, [this](bool rotate90, bool hFlip, bool vFlip, bool spherical) {
-        menuBar->playback->videoFilters->rotate90->setChecked(rotate90);
-        menuBar->playback->videoFilters->hFlip->setChecked(hFlip);
-        menuBar->playback->videoFilters->vFlip->setChecked(vFlip);
-        menuBar->playback->videoFilters->spherical->setChecked(spherical);
-    });
-    connect(&playC, &PlayClass::continuePos, this, [this](double pos, bool canSetVar) {
-        const bool enabled = (pos > 0.0);
-        menuBar->player->continuePlayback->setProperty("Pos", pos);
-        menuBar->player->continuePlayback->setEnabled(enabled);
-        if (canSetVar)
-            playC.setDontResetContinuePlayback(enabled);
-    });
-    connect(&playC, &PlayClass::allowRecording, this, [this](bool allow) {
-        menuBar->player->rec->setEnabled(allow);
-    });
-    connect(&playC, &PlayClass::recording, this, [this](bool status) {
-        QSignalBlocker blocker(menuBar->player->rec);
-        menuBar->player->rec->setChecked(status);
-    });
+    // Qt4: Use helper slots
+    connect(&playC, SIGNAL(setVideoCheckState(bool, bool, bool, bool)), this, SLOT(handleSetVideoCheckState(bool, bool, bool, bool)));
+    connect(&playC, SIGNAL(continuePos(double, bool)), this, SLOT(handleContinuePos(double, bool)));
+    connect(&playC, SIGNAL(allowRecording(bool)), this, SLOT(handleAllowRecording(bool)));
+    connect(&playC, SIGNAL(recording(bool)), this, SLOT(handleRecording(bool)));
 
     /**/
 
     if (settings.getBool("MainWidget/TabPositionNorth"))
         setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North);
 
-#if !defined Q_OS_MACOS && !defined Q_OS_ANDROID
+#if !defined Q_OS_MAC && !defined Q_OS_ANDROID
     const bool menuHidden = settings.getBool("MainWidget/MenuHidden", false);
     menuBar->setVisible(!menuHidden);
     hideMenuAct = new QAction(tr("&Hide menu bar"), menuBar);
@@ -487,7 +474,7 @@ MainWidget::MainWidget(QList<QPair<QString, QString>> &arguments)
         setAttribute(Qt::WA_NoSystemBackground, false);
     }
 
-#if defined Q_OS_MACOS || defined Q_OS_ANDROID
+#if defined Q_OS_MAC || defined Q_OS_ANDROID
     show();
 #else
     setVisible(!QMPlay2GUI.startInvisible && settings.getBool("MainWidget/isVisible", true) ? true : !isTrayVisible());
@@ -499,25 +486,20 @@ MainWidget::MainWidget(QList<QPair<QString, QString>> &arguments)
         if (tabBar)
         {
             tabBar->setAcceptDrops(true);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
             tabBar->setChangeCurrentOnDrag(true);
+#endif
         }
     }
 
     m_hideDocksTimer.setSingleShot(true);
-    connect(&m_hideDocksTimer, &QTimer::timeout, this, [this] {
-        if (fullScreen || isCompactView)
-        {
-            if (playlistDock->isVisible())
-                hideDocks();
-            else
-                showToolBar(false);
-        }
-    });
+    // Qt4: Use helper slot
+    connect(&m_hideDocksTimer, SIGNAL(timeout()), this, SLOT(handleHideDocksTimeout()));
 
     playlistDock->load(QMPlay2Core.getSettingsDir() + "Playlist.pls");
 
     bool noplay = false;
-    for (const auto &argument : std::as_const(arguments))
+    for (const auto &argument : arguments)
     {
         const QString &param = argument.first;
         const QString &data  = argument.second;
@@ -540,7 +522,14 @@ MainWidget::MainWidget(QList<QPair<QString, QString>> &arguments)
             {
                 double urlPos = 0.0;
                 if (settings.getBool("StoreUrlPos"))
+                {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
                     urlPos = QMPlay2Core.getUrlPosSets().getDouble(url.toUtf8().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals));
+#else
+                    // Qt4: Use basic base64 encoding
+                    urlPos = QMPlay2Core.getUrlPosSets().getDouble(url.toUtf8().toBase64());
+#endif
+                }
                 if (urlPos <= 0.0 || qAbs(urlPos - pos) <= 0.75)
                     seek(pos);
             }
@@ -549,9 +538,9 @@ MainWidget::MainWidget(QList<QPair<QString, QString>> &arguments)
 
     qApp->installEventFilter(this);
 
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     fileOpenTimer.setSingleShot(true);
-    connect(&fileOpenTimer, &QTimer::timeout, this, &MainWidget::fileOpenTimerTimeout);
+    connect(&fileOpenTimer, SIGNAL(timeout()), this, SLOT(fileOpenTimerTimeout()));
     if (QMPlay2GUI.pipe) // Register media keys only for first QMPlay2 instance
         QMPlay2MacExtensions::registerMacOSMediaKeys(std::bind(&MainWidget::processParam, this, std::placeholders::_1, QString()));
 #endif
@@ -564,7 +553,7 @@ MainWidget::MainWidget(QList<QPair<QString, QString>> &arguments)
 }
 MainWidget::~MainWidget()
 {
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     QMPlay2MacExtensions::unregisterMacOSMediaKeys();
 #endif
     QMPlay2Extensions::closeExtensions();
@@ -594,7 +583,7 @@ void MainWidget::focusChanged(QWidget *old, QWidget *now)
         if (w)
         {
             QWidget *tlw = w->window();
-            if (tlw && (tlw == settingsW || tlw == aboutW))
+            if (tlw && (tlw == static_cast<QWidget *>(settingsW) || tlw == static_cast<QWidget *>(aboutW)))
                 return true;
         }
         return (qobject_cast<QAbstractItemView *>(w) || qobject_cast<QTextEdit *>(w) || qobject_cast<QAbstractSlider *>(w) || qobject_cast<QComboBox *>(w) || qobject_cast<QLineEdit *>(w) || qobject_cast<QMenu *>(w));
@@ -616,7 +605,7 @@ void MainWidget::focusChanged(QWidget *old, QWidget *now)
 void MainWidget::processParam(const QString &param, const QString &data)
 {
     auto getItemsToPlay = [&] {
-        auto items = data.split('\n', Qt::SkipEmptyParts);
+        auto items = data.split('\n', QString::SkipEmptyParts);
         for (auto &&item : items)
             item = Functions::maybeExtensionAddress(item);
         return items;
@@ -854,9 +843,14 @@ void MainWidget::visualizationFullScreen()
             toggleFullScreen();
         }
     };
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     // On macOS if full screen is toggled to fast after double click, mouse remains in clicked state...
+#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
     QTimer::singleShot(200, maybeGoFullScreen);
+#else
+    QTimer::singleShot(200, this, SLOT(handleMaybeGoFullScreen()));
+    m_maybeGoFullScreenCallback = maybeGoFullScreen;
+#endif
 #else
     maybeGoFullScreen();
 #endif
@@ -878,7 +872,7 @@ void MainWidget::toggleVisibility()
             toggleFullScreen();
         if (!isTray)
         {
-#ifndef Q_OS_MACOS
+#ifndef Q_OS_MAC
             showMinimized();
 #else
             QMPlay2MacExtensions::setApplicationVisible(false);
@@ -937,12 +931,10 @@ void MainWidget::createMenuBar()
     connect(menuBar->window->toggleCompactView, SIGNAL(triggered()), this, SLOT(toggleCompactView()));
 #ifndef Q_OS_ANDROID
     if (menuBar->window->alwaysOnTop)
-        connect(menuBar->window->alwaysOnTop, &QAction::triggered, this, &MainWidget::toggleAlwaysOnTop);
+        connect(menuBar->window->alwaysOnTop, SIGNAL(triggered(bool)), this, SLOT(toggleAlwaysOnTop(bool)));
     menuBar->window->hideOnClose->setChecked(QMPSettings.getBool("MainWidget/HideOnClose"));
-    connect(menuBar->window->hideOnClose, &QAction::triggered,
-            this, [](bool checked) {
-        QMPlay2Core.getSettings().set("MainWidget/HideOnClose", checked);
-    });
+    // Qt4: Use helper slot
+    connect(menuBar->window->hideOnClose, SIGNAL(triggered(bool)), this, SLOT(handleHideOnCloseTriggered(bool)));
 #endif
     connect(menuBar->window->close, SIGNAL(triggered()), this, SLOT(close()));
 
@@ -958,22 +950,16 @@ void MainWidget::createMenuBar()
     connect(menuBar->playlist->newGroup, SIGNAL(triggered()), playlistDock, SLOT(newGroup()));
     connect(menuBar->playlist->renameGroup, SIGNAL(triggered()), playlistDock, SLOT(renameGroup()));
     connect(menuBar->playlist->lock, SIGNAL(triggered()), playlistDock, SLOT(toggleLock()));
-    connect(menuBar->playlist->alwaysSync, &QAction::triggered, playlistDock, &PlaylistDock::alwaysSyncTriggered);
-    connect(menuBar->playlist->delEntries, &QAction::triggered, playlistDock, [this] {
-        playlistDock->delEntries(false);
-    });
-    connect(menuBar->playlist->delEntriesFromDisk, &QAction::triggered, playlistDock, [this] {
-        playlistDock->delEntries(true);
-    });
+    connect(menuBar->playlist->alwaysSync, SIGNAL(triggered(bool)), playlistDock, SLOT(alwaysSyncTriggered(bool)));
+    // Qt4: Use helper slots
+    connect(menuBar->playlist->delEntries, SIGNAL(triggered()), this, SLOT(handleDelEntries()));
+    connect(menuBar->playlist->delEntriesFromDisk, SIGNAL(triggered()), this, SLOT(handleDelEntriesFromDisk()));
     connect(menuBar->playlist->delNonGroupEntries, SIGNAL(triggered()), playlistDock, SLOT(delNonGroupEntries()));
     connect(menuBar->playlist->clear, SIGNAL(triggered()), playlistDock, SLOT(clear()));
     connect(menuBar->playlist->copy, SIGNAL(triggered()), playlistDock, SLOT(copy()));
-    connect(menuBar->playlist->paste, &QAction::triggered, playlistDock, [this] {
-        playlistDock->paste(false);
-    });
-    connect(menuBar->playlist->pasteAndPlay, &QAction::triggered, playlistDock, [this] {
-        playlistDock->paste(true);
-    });
+    // Qt4: Use helper slots
+    connect(menuBar->playlist->paste, SIGNAL(triggered()), this, SLOT(handlePaste()));
+    connect(menuBar->playlist->pasteAndPlay, SIGNAL(triggered()), this, SLOT(handlePasteAndPlay()));
     connect(menuBar->playlist->find, SIGNAL(triggered()), playlistDock->findEdit(), SLOT(setFocus()));
     connect(menuBar->playlist->sort->timeSort1, SIGNAL(triggered()), playlistDock, SLOT(timeSort1()));
     connect(menuBar->playlist->sort->timeSort2, SIGNAL(triggered()), playlistDock, SLOT(timeSort2()));
@@ -991,7 +977,7 @@ void MainWidget::createMenuBar()
     connect(menuBar->player->stop, SIGNAL(triggered()), &playC, SLOT(stop()));
     connect(menuBar->player->next, SIGNAL(triggered()), playlistDock, SLOT(next()));
     connect(menuBar->player->prev, SIGNAL(triggered()), playlistDock, SLOT(prev()));
-    connect(menuBar->player->rec, &QAction::triggered, &playC, &PlayClass::setRecording);
+    connect(menuBar->player->rec, SIGNAL(triggered(bool)), &playC, SLOT(setRecording(bool)));
     connect(menuBar->player->prevFrame, SIGNAL(triggered()), &playC, SLOT(prevFrame()));
     connect(menuBar->player->nextFrame, SIGNAL(triggered()), &playC, SLOT(nextFrame()));
     for (QAction *act : menuBar->player->repeat->actions())
@@ -1004,15 +990,15 @@ void MainWidget::createMenuBar()
     connect(menuBar->player->speedUp, SIGNAL(triggered()), &playC, SLOT(speedUp()));
     connect(menuBar->player->slowDown, SIGNAL(triggered()), &playC, SLOT(slowDown()));
     connect(menuBar->player->setSpeed, SIGNAL(triggered()), &playC, SLOT(setSpeed()));
-    connect(menuBar->player->zoom->integerScaling, &QAction::toggled, menuBar->player->zoom->setZoom, &QAction::setDisabled);
-    connect(menuBar->player->zoom->integerScaling, &QAction::toggled, menuBar->player->zoom->preciseZoom, &QAction::setDisabled);
+    connect(menuBar->player->zoom->integerScaling, SIGNAL(toggled(bool)), menuBar->player->zoom->setZoom, SLOT(setDisabled(bool)));
+    connect(menuBar->player->zoom->integerScaling, SIGNAL(toggled(bool)), menuBar->player->zoom->preciseZoom, SLOT(setDisabled(bool)));
     menuBar->player->zoom->integerScaling->setChecked(QMPSettings.getBool("IntegerScaling"));
     menuBar->player->zoom->preciseZoom->setChecked(QMPSettings.getBool("PreciseZoom"));
-    connect(menuBar->player->zoom->integerScaling, &QAction::triggered, &playC, &PlayClass::setIntegerScaling);
-    connect(menuBar->player->zoom->preciseZoom, &QAction::triggered, &playC, &PlayClass::setPreciseZoom);
+    connect(menuBar->player->zoom->integerScaling, SIGNAL(triggered(bool)), &playC, SLOT(setIntegerScaling(bool)));
+    connect(menuBar->player->zoom->preciseZoom, SIGNAL(triggered(bool)), &playC, SLOT(setPreciseZoom(bool)));
     connect(menuBar->player->zoom->zoomIn, SIGNAL(triggered()), &playC, SLOT(zoomIn()));
     connect(menuBar->player->zoom->zoomOut, SIGNAL(triggered()), &playC, SLOT(zoomOut()));
-    connect(menuBar->player->zoom->setZoom, &QAction::triggered, &playC, &PlayClass::setZoom);
+    connect(menuBar->player->zoom->setZoom, SIGNAL(triggered()), &playC, SLOT(setZoom()));
     connect(menuBar->player->switchARatio, SIGNAL(triggered()), this, SLOT(switchARatio()));
     {
         const QString initialAspectRatio = QMPSettings.getBool("StoreARatioAndZoom") ? QMPSettings.getString("AspectRatio") : QString();
@@ -1020,7 +1006,7 @@ void MainWidget::createMenuBar()
         bool aRatioTriggered = false;
         for (QAction *act : menuBar->player->aRatio->actions())
         {
-            connect(act, &QAction::triggered, &playC, &PlayClass::aRatio);
+            connect(act, SIGNAL(triggered()), &playC, SLOT(aRatio()));
             if (aRatioTriggered)
                 continue;
             const auto objectName = act->objectName();
@@ -1042,13 +1028,8 @@ void MainWidget::createMenuBar()
     connect(menuBar->player->reset, SIGNAL(triggered()), this, SLOT(resetARatio()));
     connect(menuBar->player->reset, SIGNAL(triggered()), &playC, SLOT(zoomReset()));
     connect(menuBar->player->reset, SIGNAL(triggered()), &playC, SLOT(otherReset()));
-    connect(menuBar->player->continuePlayback, &QAction::triggered, this, [this] {
-        const double pos = menuBar->player->continuePlayback->property("Pos").toDouble();
-        if (pos > 0.0)
-            playC.seek(pos);
-        else
-            emit playC.continuePos(0.0, true);
-    });
+    // Qt4: Use helper slot
+    connect(menuBar->player->continuePlayback, SIGNAL(triggered()), this, SLOT(handleContinuePlayback()));
     connect(menuBar->player->volUp, SIGNAL(triggered()), this, SLOT(volUpDown()));
     connect(menuBar->player->volDown, SIGNAL(triggered()), this, SLOT(volUpDown()));
     connect(menuBar->player->toggleMute, SIGNAL(triggered()), &playC, SLOT(toggleMute()));
@@ -1062,7 +1043,7 @@ void MainWidget::createMenuBar()
     if (menuBar->playback->keepAudioPitch)
     {
         menuBar->playback->keepAudioPitch->setChecked(QMPSettings.getBool("KeepAudioPitch"));
-        connect(menuBar->playback->keepAudioPitch, &QAction::triggered, &playC, &PlayClass::setKeepAudioPitch);
+        connect(menuBar->playback->keepAudioPitch, SIGNAL(triggered(bool)), &playC, SLOT(setKeepAudioPitch(bool)));
     }
     connect(menuBar->playback->toggleVideo, SIGNAL(triggered(bool)), &playC, SLOT(toggleAVS(bool)));
     connect(menuBar->playback->toggleSubtitles, SIGNAL(triggered(bool)), &playC, SLOT(toggleAVS(bool)));
@@ -1108,7 +1089,7 @@ void MainWidget::createMenuBar()
 
     setMenuBar(menuBar);
 
-#ifndef Q_OS_MACOS
+#ifndef Q_OS_MAC
     if (tray)
     {
         auto secondMenu = new QMenu(this);
@@ -1138,18 +1119,8 @@ void MainWidget::createMenuBar()
         }
         tray->setContextMenu(secondMenu);
 
-        connect(secondMenu, &QMenu::aboutToShow,
-                this, [=] {
-            for (auto ext : QMPlay2Extensions::QMPlay2ExtensionsList())
-            {
-                if (auto menu = ext->getTrayMenu())
-                {
-                    ext->ensureTrayMenu();
-                    if (!secondMenu->actions().contains(menu->menuAction()))
-                        secondMenu->addMenu(menu);
-                }
-            }
-        });
+        // Qt4: Use helper slot
+        connect(secondMenu, SIGNAL(aboutToShow()), this, SLOT(handleSecondMenuAboutToShow()));
     }
 #else //On OS X add only the most important menu actions to dock menu
     auto secondMenu = new QMenu(this);
@@ -1162,22 +1133,23 @@ void MainWidget::createMenuBar()
     secondMenu->addSeparator();
     // Copy action, because PreferencesRole doesn't show in dock menu.
     QAction *settings = new QAction(menuBar->options->settings->icon(), menuBar->options->settings->text(), menuBar->options->settings->parent());
-    connect(settings, &QAction::triggered, menuBar->options->settings, &QAction::trigger);
+    connect(settings, SIGNAL(triggered()), menuBar->options->settings, SLOT(trigger()));
     secondMenu->addAction(settings);
 
     QAction *newInstanceAct = new QAction(tr("New window"), secondMenu);
-    connect(newInstanceAct, &QAction::triggered, [] {
-        QProcess::startDetached(QCoreApplication::applicationFilePath(), {"-noplay"}, QCoreApplication::applicationDirPath());
-    });
+    // Qt4: Use helper slot
+    connect(newInstanceAct, SIGNAL(triggered()), this, SLOT(handleNewInstance()));
     secondMenu->addSeparator();
     secondMenu->addAction(newInstanceAct);
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
     secondMenu->setAsDockMenu();
 #endif
+#endif
 }
 void MainWidget::trayIconClicked(QSystemTrayIcon::ActivationReason reason)
 {
-#if !defined Q_OS_MACOS && !defined Q_OS_ANDROID
+#if !defined Q_OS_MAC && !defined Q_OS_ANDROID
     switch (reason)
     {
         case QSystemTrayIcon::Trigger:
@@ -1205,7 +1177,7 @@ void MainWidget::toggleCompactView()
 
         hideDockWidgetsAndDisableFeatures();
 
-#if !defined Q_OS_MACOS && !defined Q_OS_ANDROID
+#if !defined Q_OS_MAC && !defined Q_OS_ANDROID
         menuBar->hide();
 #endif
         mainTB->hide();
@@ -1228,7 +1200,7 @@ void MainWidget::toggleCompactView()
 
         restoreDockWidgetFeatures();
 
-#if !defined Q_OS_MACOS && !defined Q_OS_ANDROID
+#if !defined Q_OS_MAC && !defined Q_OS_ANDROID
         menuBar->setVisible(!hideMenuAct->isChecked());
 #endif
 
@@ -1251,7 +1223,7 @@ void MainWidget::toggleAlwaysOnTop(bool checked)
 void MainWidget::toggleFullScreen()
 {
     static bool visible, tb_movable;
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     if (isFullScreen())
     {
         showNormal();
@@ -1268,7 +1240,7 @@ void MainWidget::toggleFullScreen()
 #ifndef Q_OS_ANDROID
         m_maximizedBeforeFullScreen = isMaximized();
 
-#ifndef Q_OS_MACOS
+#ifndef Q_OS_MAC
 #ifndef Q_OS_WIN
         if (isFullScreen())
 #endif
@@ -1282,7 +1254,7 @@ void MainWidget::toggleFullScreen()
         dockWidgetState = saveState();
 #endif // Q_OS_ANDROID
 
-#if !defined Q_OS_MACOS
+#if !defined Q_OS_MAC
         menuBar->hide();
 #endif
         statusBar->hide();
@@ -1297,22 +1269,31 @@ void MainWidget::toggleFullScreen()
         videoDock->fullScreen(true);
         videoDock->show();
 
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
         menuBar->window->toggleVisibility->setEnabled(false);
 #endif
         menuBar->window->toggleCompactView->setEnabled(false);
         menuBar->window->toggleFullScreen->setShortcuts(QList<QKeySequence>() << menuBar->window->toggleFullScreen->shortcut() << QKeySequence("ESC"));
         fullScreen = true;
 
-#ifndef Q_OS_MACOS
+#ifndef Q_OS_MAC
         showFullScreen();
 #else
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         const auto geo = window()->windowHandle()->screen()->geometry();
         setMinimumSize(geo.size());
         setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
         setGeometry(geo);
         QMPlay2MacExtensions::showSystemUi(windowHandle(), false);
         show();
+#else
+        // Qt4: Use QDesktopWidget for screen geometry
+        const QRect geo = QApplication::desktop()->screenGeometry(this);
+        setMinimumSize(geo.size());
+        setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
+        setGeometry(geo);
+        show();
+#endif
 #endif
 
         if (playC.isPlaying())
@@ -1320,7 +1301,7 @@ void MainWidget::toggleFullScreen()
     }
     else
     {
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
         menuBar->window->toggleVisibility->setEnabled(true);
 #endif
         menuBar->window->toggleCompactView->setEnabled(true);
@@ -1330,20 +1311,22 @@ void MainWidget::toggleFullScreen()
         fullScreen = false;
 
 #ifndef Q_OS_ANDROID
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
         setMinimumSize(0, 0);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         QMPlay2MacExtensions::showSystemUi(windowHandle(), true);
+#endif
         setWindowFlags(Qt::Window);
 #else
         showNormal();
-#endif // Q_OS_MACOS
+#endif // Q_OS_MAC
         if (m_maximizedBeforeFullScreen)
         {
             showMaximized();
         }
         else
         {
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
             showNormal();
 #endif
             setGeometry(savedGeo);
@@ -1367,7 +1350,7 @@ void MainWidget::toggleFullScreen()
 
 #if defined(Q_OS_ANDROID)
         menuBar->setVisible(true);
-#elif !defined Q_OS_MACOS
+#elif !defined Q_OS_MAC
         menuBar->setVisible(!hideMenuAct->isChecked());
 #endif
         statusBar->show();
@@ -1463,26 +1446,21 @@ void MainWidget::showSettings(const QString &moduleName)
 
         settingsW = new SettingsWidget(page, moduleName, QMPlay2GUI.videoAdjustment);
         if (windowFlags() & Qt::WindowStaysOnTopHint)
+        {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
             settingsW->setWindowFlag(Qt::WindowStaysOnTopHint);
+#else
+            settingsW->setWindowFlags(settingsW->windowFlags() | Qt::WindowStaysOnTopHint);
+#endif
+        }
         settingsW->show();
-        connect(settingsW, &SettingsWidget::settingsChanged, this, [this](int page, bool forceRestart, bool initFilters) {
-            Q_UNUSED(forceRestart);
-            Q_UNUSED(initFilters);
-            if (page == 2)
-                setContinuePlaybackVisibility();
-        });
+        // Qt4: Use helper slots
+        connect(settingsW, SIGNAL(settingsChanged(int, bool, bool)), this, SLOT(handleSettingsChanged(int, bool, bool)));
         connect(settingsW, SIGNAL(settingsChanged(int, bool, bool)), &playC, SLOT(settingsChanged(int, bool, bool)));
         connect(settingsW, SIGNAL(setWheelStep(int)), seekS, SLOT(setWheelStep(int)));
         connect(settingsW, SIGNAL(setVolMax(int)), volW, SLOT(setMaximumVolume(int)));
-        connect(settingsW, &SettingsWidget::keepDocksSizeChanged, this, [this](bool keepDocksSize) {
-            m_keepDocksSize = keepDocksSize;
-            m_storeDockSizesTimer.start();
-        });
-        connect(settingsW, &SettingsWidget::fullscreenPanelsRightSideChanged, this, [this](bool checked) {
-            m_fullscreenPanelsRightSide = checked;
-            fullScreenDockWidgetState.clear();
-            m_compactViewDockWidgetState.clear();
-        });
+        connect(settingsW, SIGNAL(keepDocksSizeChanged(bool)), this, SLOT(handleKeepDocksSizeChanged(bool)));
+        connect(settingsW, SIGNAL(fullscreenPanelsRightSideChanged(bool)), this, SLOT(handleFullscreenPanelsRightSideChanged(bool)));
         connect(settingsW, SIGNAL(destroyed()), this, SLOT(showSettings()));
     }
     else
@@ -1603,7 +1581,13 @@ void MainWidget::about()
     {
         aboutW = new AboutWidget;
         if (windowFlags() & Qt::WindowStaysOnTopHint)
+        {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
             aboutW->setWindowFlag(Qt::WindowStaysOnTopHint);
+#else
+            aboutW->setWindowFlags(aboutW->windowFlags() | Qt::WindowStaysOnTopHint);
+#endif
+        }
         aboutW->show();
         connect(aboutW, SIGNAL(destroyed()), this, SLOT(about()));
     }
@@ -1614,7 +1598,7 @@ void MainWidget::about()
     }
 }
 
-#if !defined Q_OS_MACOS && !defined Q_OS_ANDROID
+#if !defined Q_OS_MAC && !defined Q_OS_ANDROID
 void MainWidget::hideMenu(bool h)
 {
     if (fullScreen || isCompactView)
@@ -1652,6 +1636,7 @@ void MainWidget::doRestoreState(const QByteArray &data, bool doToggleCompactView
     if (isMaximized())
     {
         setUpdatesEnabled(false);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
         QTimer::singleShot(0, this, [=] {
             QTimer::singleShot(0, this, [=] {
                 QTimer::singleShot(0, this, [=] {
@@ -1663,6 +1648,12 @@ void MainWidget::doRestoreState(const QByteArray &data, bool doToggleCompactView
                 });
             });
         });
+#else
+        // Qt4: Store state and trigger helper
+        m_restoreStateData = data;
+        m_restoreStateToggleCompactView = doToggleCompactView;
+        QTimer::singleShot(0, this, SLOT(handleRestoreState1()));
+#endif
     }
     else
     {
@@ -1687,7 +1678,7 @@ void MainWidget::setStreamsMenu(const QStringList &videoStreams, const QStringLi
             auto lines = videoStream.split(QLatin1Char('\n'));
             Q_ASSERT(lines.size() >= 2);
 
-            auto action = menu->addAction(lines.constFirst());
+            auto action = menu->addAction(lines.first());
             const bool hasData = !lines.at(1).isEmpty();
 
             if (menu->group)
@@ -1700,13 +1691,9 @@ void MainWidget::setStreamsMenu(const QStringList &videoStreams, const QStringLi
 
             if (hasData)
             {
-                connect(action, &QAction::triggered,
-                        this, [this, data = std::move(lines[1])] {
-                    if (data.startsWith("seek"))
-                        seek(QStringView(data).mid(4).toDouble());
-                    else
-                        playC.chStream(data);
-                });
+                // Qt4: Store data in action property, use helper slot
+                action->setProperty("streamData", lines[1]);
+                connect(action, SIGNAL(triggered()), this, SLOT(handleStreamActionTriggered()));
             }
             else
             {
@@ -1754,7 +1741,7 @@ QMenu *MainWidget::createPopupMenu()
     QMenu *popupMenu = QMainWindow::createPopupMenu();
     if (!fullScreen && !isCompactView)
     {
-#if !defined Q_OS_MACOS && !defined Q_OS_ANDROID
+#if !defined Q_OS_MAC && !defined Q_OS_ANDROID
         popupMenu->insertAction(popupMenu->actions().value(0), hideMenuAct);
         popupMenu->insertSeparator(popupMenu->actions().value(1));
         popupMenu->addSeparator();
@@ -1848,6 +1835,8 @@ bool MainWidget::getFullScreen() const
 
 void MainWidget::addChooseNextStreamKeyShortcuts()
 {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+    // Qt5+: Generic lambda (unused, kept for reference)
     auto chooseNextStream = [](const auto &actions) {
         if (actions.empty())
             return;
@@ -1867,20 +1856,18 @@ void MainWidget::addChooseNextStreamKeyShortcuts()
         }
         actions[0]->trigger();
     };
+    Q_UNUSED(chooseNextStream);
+#endif
 
     auto nextAudioStream = new QAction(tr("Next audio stream"), this);
-    connect(nextAudioStream, &QAction::triggered,
-            this, [=] {
-        chooseNextStream(menuBar->playback->audioStreams->actions());
-    });
+    // Qt4: Use helper slot
+    connect(nextAudioStream, SIGNAL(triggered()), this, SLOT(handleNextAudioStreamTriggered()));
     addAction(nextAudioStream);
     QMPlay2GUI.shortcutHandler->appendAction(nextAudioStream, "KeyBindings/NextAudioStream", "Ctrl+1");
 
     auto nextSubsStream = new QAction(tr("Next subtitles stream"), this);
-    connect(nextSubsStream, &QAction::triggered,
-            this, [=] {
-        chooseNextStream(menuBar->playback->subtitlesStreams->actions());
-    });
+    // Qt4: Use helper slot
+    connect(nextSubsStream, SIGNAL(triggered()), this, SLOT(handleNextSubsStreamTriggered()));
     addAction(nextSubsStream);
     QMPlay2GUI.shortcutHandler->appendAction(nextSubsStream, "KeyBindings/NextSubsStream", "Ctrl+2");
 }
@@ -1895,14 +1882,27 @@ inline const QList<DockWidget *> MainWidget::getDockWidgets() const
 #if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
     return findChildren<DockWidget *>(Qt::FindDirectChildrenOnly);
 #else
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     return findChildren<DockWidget *>(QString(), Qt::FindDirectChildrenOnly);
+#else
+    // Qt4: Manually filter to get only direct children
+    QList<DockWidget *> result;
+    foreach (QObject *child, children())
+    {
+        if (DockWidget *dock = qobject_cast<DockWidget *>(child))
+            result.append(dock);
+    }
+    return result;
+#endif
 #endif
 }
 
 inline QRect MainWidget::winGeo() const
 {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     if (const auto win = windowHandle())
         return win->geometry();
+#endif
     return geometry();
 }
 
@@ -1935,7 +1935,7 @@ void MainWidget::storeDockSizes()
     }
 }
 
-#if defined(Q_OS_WIN)
+#if defined(Q_OS_WIN) && QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
 void MainWidget::setWindowsTaskBarFeatures()
 {
     if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows7)
@@ -2000,7 +2000,7 @@ void MainWidget::setWindowsTaskBarFeatures()
     m_thumbnailToolBar->addButton(toggleMute);
     m_thumbnailToolBar->addButton(settings);
 }
-#endif
+#endif // Q_OS_WIN && QT_VERSION >= 5.0.0
 #if defined(Q_OS_WIN) && QT_VERSION < QT_VERSION_CHECK(6, 4, 0)
 void MainWidget::setTitleBarStyle()
 {
@@ -2047,6 +2047,7 @@ void MainWidget::mouseMoveEvent(QMouseEvent *e)
         bool canDisplayPanel = fullScreen || isCompactView;
         if (canDisplayPanel && !isToolbarVisible && fullScreen)
         {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
             const auto winScreen = windowHandle()->screen();
             const auto winScreenGeo = winScreen->geometry();
             const auto screens = QGuiApplication::screens();
@@ -2070,6 +2071,33 @@ void MainWidget::mouseMoveEvent(QMouseEvent *e)
                     break;
                 }
             }
+#else
+            // Qt4: Use QDesktopWidget
+            const QRect winScreenGeo = QApplication::desktop()->screenGeometry(this);
+            {
+                const int screenCount = QApplication::desktop()->screenCount();
+                for (int i = 0; i < screenCount; ++i)
+                {
+                    QRect geo = QApplication::desktop()->screenGeometry(i);
+                    if (geo == winScreenGeo)
+                        continue;
+
+                    if (m_fullscreenPanelsRightSide
+                        ? (geo.x() <= winScreenGeo.x())
+                        : (geo.x() >= winScreenGeo.x()))
+                    {
+                        continue;
+                    }
+
+                    geo.moveLeft(winScreenGeo.x());
+                    if (winScreenGeo.intersects(geo))
+                    {
+                        canDisplayPanel = false;
+                        break;
+                    }
+                }
+            }
+#endif
         }
 
         const int trigger1 = canDisplayPanel
@@ -2135,7 +2163,7 @@ void MainWidget::mouseMoveEvent(QMouseEvent *e)
                 playlistDock->show();
                 infoDock->show();
 
-                for (QMPlay2Extensions *QMPlay2Ext : std::as_const(visibleQMPlay2Extensions))
+                for (QMPlay2Extensions *QMPlay2Ext : visibleQMPlay2Extensions)
                     if (!QMPlay2Ext->isVisualization())
                         if (DockWidget *dw = QMPlay2Ext->getDockWidget())
                         {
@@ -2206,7 +2234,7 @@ void MainWidget::closeEvent(QCloseEvent *e)
     settings.set("MainWidget/FullScreenDockWidgetState", fullScreenDockWidgetState);
     settings.set("MainWidget/CompactViewDockWidgetState", m_compactViewDockWidgetState);
     settings.set("MainWidget/AlwaysOnTop", !!(windowFlags() & Qt::WindowStaysOnTopHint));
-#ifndef Q_OS_MACOS
+#ifndef Q_OS_MAC
     settings.set("MainWidget/isVisible", isVisible());
 #endif
     settings.set("MainWidget/IsCompactView", fullScreen ? m_compactViewBeforeFullScreen : isCompactView);
@@ -2353,8 +2381,12 @@ void MainWidget::resizeEvent(QResizeEvent *e)
             sizesH.push_back(qRound(size.height() * hRatio));
         }
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
         resizeDocks(docks, sizesH, Qt::Vertical);
         resizeDocks(docks, sizesW, Qt::Horizontal);
+#else
+        // Qt4: resizeDocks doesn't exist, skip this optimization
+#endif
     }
 }
 
@@ -2363,16 +2395,25 @@ bool MainWidget::eventFilter(QObject *obj, QEvent *event)
     if (tray && obj == tray && event->type() == QEvent::Wheel)
     {
         QWheelEvent *we = static_cast<QWheelEvent *>(event);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         volW->changeVolume(we->angleDelta().y() / 30);
+#else
+        // Qt4: Use delta() which returns an int
+        volW->changeVolume(we->delta() / 30);
+#endif
     }
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     else if (event->type() == QEvent::FileOpen)
     {
         filesToAdd.append(((QFileOpenEvent *)event)->file());
         fileOpenTimer.start(10);
     }
 #endif
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     else if (event->type() == QEvent::Resize && m_keepDocksSize && obj == windowHandle())
+#else
+    else if (event->type() == QEvent::Resize && m_keepDocksSize && obj == this)
+#endif
     {
         for (auto &&dock : getDockWidgets())
         {
@@ -2382,7 +2423,265 @@ bool MainWidget::eventFilter(QObject *obj, QEvent *event)
     return QMainWindow::eventFilter(obj, event);
 }
 
-#ifdef Q_OS_MACOS
+// Qt4: Helper slot implementations for lambda conversions
+void MainWidget::handleShouldStoreSizes()
+{
+    m_storeDockSizesTimer.start();
+}
+
+void MainWidget::handleAddAndPlayRestoreWindow()
+{
+    m_restoreWindowOnVideo = true;
+}
+
+void MainWidget::handleClearInfo()
+{
+    infoDock->clear();
+    setStreamsMenu({}, {}, {}, {}, {});
+}
+
+void MainWidget::handleVideoNotStarted()
+{
+    m_restoreWindowOnVideo = false;
+}
+
+void MainWidget::handleSetVideoCheckState(bool rotate90, bool hFlip, bool vFlip, bool spherical)
+{
+    menuBar->playback->videoFilters->rotate90->setChecked(rotate90);
+    menuBar->playback->videoFilters->hFlip->setChecked(hFlip);
+    menuBar->playback->videoFilters->vFlip->setChecked(vFlip);
+    menuBar->playback->videoFilters->spherical->setChecked(spherical);
+}
+
+void MainWidget::handleContinuePos(double pos, bool canSetVar)
+{
+    const bool enabled = (pos > 0.0);
+    menuBar->player->continuePlayback->setProperty("Pos", pos);
+    menuBar->player->continuePlayback->setEnabled(enabled);
+    if (canSetVar)
+        playC.setDontResetContinuePlayback(enabled);
+}
+
+void MainWidget::handleAllowRecording(bool allow)
+{
+    menuBar->player->rec->setEnabled(allow);
+}
+
+void MainWidget::handleRecording(bool status)
+{
+#if QT_VERSION >= QT_VERSION_CHECK(5, 3, 0)
+    QSignalBlocker blocker(menuBar->player->rec);
+#else
+    const bool blocked = menuBar->player->rec->blockSignals(true);
+#endif
+    menuBar->player->rec->setChecked(status);
+#if QT_VERSION < QT_VERSION_CHECK(5, 3, 0)
+    menuBar->player->rec->blockSignals(blocked);
+#endif
+}
+
+void MainWidget::handleHideDocksTimeout()
+{
+    if (fullScreen || isCompactView)
+    {
+        if (playlistDock->isVisible())
+            hideDocks();
+        else
+            showToolBar(false);
+    }
+}
+
+// Qt4 helper slots - MOC needs to see declarations unconditionally
+void MainWidget::handleMaybeGoFullScreen()
+{
+#if defined(Q_OS_MAC) && QT_VERSION < QT_VERSION_CHECK(5, 4, 0)
+    if (m_maybeGoFullScreenCallback)
+        m_maybeGoFullScreenCallback();
+#endif
+}
+
+void MainWidget::handleRestoreState1()
+{
+#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0)
+    QTimer::singleShot(0, this, SLOT(handleRestoreState2()));
+#endif
+}
+
+void MainWidget::handleRestoreState2()
+{
+#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0)
+    QTimer::singleShot(0, this, SLOT(handleRestoreState3()));
+#endif
+}
+
+void MainWidget::handleRestoreState3()
+{
+#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0)
+    restoreState(m_restoreStateData);
+    if (m_restoreStateToggleCompactView)
+        menuBar->window->toggleCompactView->trigger();
+    setUpdatesEnabled(true);
+    repaint();
+#endif
+}
+
+void MainWidget::handleDelEntries()
+{
+    playlistDock->delEntries(false);
+}
+
+void MainWidget::handleDelEntriesFromDisk()
+{
+    playlistDock->delEntries(true);
+}
+
+void MainWidget::handlePaste()
+{
+    playlistDock->paste(false);
+}
+
+void MainWidget::handlePasteAndPlay()
+{
+    playlistDock->paste(true);
+}
+
+void MainWidget::handleContinuePlayback()
+{
+    const double pos = menuBar->player->continuePlayback->property("Pos").toDouble();
+    if (pos > 0.0)
+        playC.seek(pos);
+    else
+    {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+        emit playC.continuePos(0.0, true);
+#else
+        // Qt4: signals are protected, use QMetaObject::invokeMethod
+        QMetaObject::invokeMethod(&playC, "continuePos", Qt::DirectConnection, Q_ARG(double, 0.0), Q_ARG(bool, true));
+#endif
+    }
+}
+
+void MainWidget::handleNewInstance()
+{
+    QProcess::startDetached(QCoreApplication::applicationFilePath(), QStringList() << "-noplay", QCoreApplication::applicationDirPath());
+}
+
+void MainWidget::handleSettingsChanged(int page, bool forceRestart, bool initFilters)
+{
+    Q_UNUSED(forceRestart);
+    Q_UNUSED(initFilters);
+    if (page == 2)
+        setContinuePlaybackVisibility();
+}
+
+void MainWidget::handleKeepDocksSizeChanged(bool keepDocksSize)
+{
+    m_keepDocksSize = keepDocksSize;
+    m_storeDockSizesTimer.start();
+}
+
+void MainWidget::handleFullscreenPanelsRightSideChanged(bool checked)
+{
+    m_fullscreenPanelsRightSide = checked;
+    fullScreenDockWidgetState.clear();
+    m_compactViewDockWidgetState.clear();
+}
+
+void MainWidget::handleHideOnCloseTriggered(bool checked)
+{
+    QMPlay2Core.getSettings().set("MainWidget/HideOnClose", checked);
+}
+
+void MainWidget::handleSecondMenuAboutToShow()
+{
+    QMenu *secondMenu = qobject_cast<QMenu *>(sender());
+    if (!secondMenu)
+        return;
+    for (auto ext : QMPlay2Extensions::QMPlay2ExtensionsList())
+    {
+        if (auto menu = ext->getTrayMenu())
+        {
+            ext->ensureTrayMenu();
+            if (!secondMenu->actions().contains(menu->menuAction()))
+                secondMenu->addMenu(menu);
+        }
+    }
+}
+
+void MainWidget::handleStreamActionTriggered()
+{
+    QAction *action = qobject_cast<QAction *>(sender());
+    if (!action)
+        return;
+    QString data = action->property("streamData").toString();
+    if (data.startsWith("seek"))
+    {
+        // Qt4: Use mid() instead of QStringView
+        seek(data.mid(4).toDouble());
+    }
+    else
+    {
+        playC.chStream(data);
+    }
+}
+
+void MainWidget::handleNextAudioStreamTriggered()
+{
+    auto actions = menuBar->playback->audioStreams->actions();
+    if (actions.empty())
+        return;
+
+    bool foundChecked = false;
+    foreach (QAction *action, actions)
+    {
+        if (action->isChecked())
+        {
+            foundChecked = true;
+        }
+        else if (foundChecked)
+        {
+            action->trigger();
+            return;
+        }
+    }
+    actions[0]->trigger();
+}
+
+void MainWidget::handleNextSubsStreamTriggered()
+{
+    auto actions = menuBar->playback->subtitlesStreams->actions();
+    if (actions.empty())
+        return;
+
+    bool foundChecked = false;
+    foreach (QAction *action, actions)
+    {
+        if (action->isChecked())
+        {
+            foundChecked = true;
+        }
+        else if (foundChecked)
+        {
+            action->trigger();
+            return;
+        }
+    }
+    actions[0]->trigger();
+}
+
+#ifdef Q_OS_WIN
+void MainWidget::handleThumbnailButtonChanged()
+{
+    // Windows-specific implementation - not needed for macOS target
+}
+
+void MainWidget::handleToggleMuteChanged()
+{
+    // Windows-specific implementation - not needed for macOS target
+}
+#endif
+
+#ifdef Q_OS_MAC
 void MainWidget::fileOpenTimerTimeout()
 {
     if (filesToAdd.count() == 1)
diff --git a/src/gui/MainWidget.hpp b/src/gui/MainWidget.hpp
index 3bd1c67a..16f8a9dc 100644
--- a/src/gui/MainWidget.hpp
+++ b/src/gui/MainWidget.hpp
@@ -45,7 +45,7 @@ class QMPlay2Extensions;
     class QWinThumbnailToolBar;
 #endif
 
-class MainWidget final : public QMainWindow
+class MainWidget : public QMainWindow
 {
     friend class QMPlay2GUIClass;
     Q_PROPERTY(bool fullScreen READ getFullScreen)
@@ -111,7 +111,7 @@ private slots:
 
     void about();
 
-#if !defined Q_OS_MACOS && !defined Q_OS_ANDROID
+#if !defined Q_OS_MAC && !defined Q_OS_ANDROID
     void hideMenu(bool);
 #else
     void hideMenu(bool) {} //To avoid incorrect CPP file generated by MOC on Android
@@ -121,12 +121,47 @@ private slots:
     void doRestoreState(const QByteArray &data, bool doToggleCompactView = false);
 
     void uncheckSuspend();
-private:
+
+    // Qt4: Helper slots for lambda conversions
+    void handleShouldStoreSizes();
+    void handleAddAndPlayRestoreWindow();
+    void handleClearInfo();
+    void handleVideoNotStarted();
+    void handleSetVideoCheckState(bool rotate90, bool hFlip, bool vFlip, bool spherical);
+    void handleContinuePos(double pos, bool canSetVar);
+    void handleAllowRecording(bool allow);
+    void handleRecording(bool status);
+    void handleHideDocksTimeout();
+    void handleDelEntries();
+    void handleDelEntriesFromDisk();
+    void handlePaste();
+    void handlePasteAndPlay();
+    void handleContinuePlayback();
+    void handleNewInstance();
+    void handleSettingsChanged(int page, bool forceRestart, bool initFilters);
+    void handleKeepDocksSizeChanged(bool keepDocksSize);
+    void handleFullscreenPanelsRightSideChanged(bool checked);
+    void handleHideOnCloseTriggered(bool checked);
+    void handleSecondMenuAboutToShow();
+    void handleStreamActionTriggered();
+    void handleNextAudioStreamTriggered();
+    void handleNextSubsStreamTriggered();
+#ifdef Q_OS_WIN
+    void handleThumbnailButtonChanged();
+    void handleToggleMuteChanged();
+#endif
+    // Qt4: These slots must be visible to MOC unconditionally
+    void handleMaybeGoFullScreen();
+    void handleRestoreState1();
+    void handleRestoreState2();
+    void handleRestoreState3();
+private slots:
     void setStreamsMenu(const QStringList &videoStreams, const QStringList &audioStreams, const QStringList &subsStreams, const QStringList &chapters, const QStringList &programs);
 
+private:
     void savePlistHelper(const QString &, const QString &, bool);
 
-    QMenu *createPopupMenu() override;
+    QMenu *createPopupMenu();
 
     void hideDockWidgetsAndDisableFeatures();
     void restoreDockWidgetFeatures();
@@ -143,7 +178,7 @@ private:
 #if defined(Q_OS_WIN)
     void setWindowsTaskBarFeatures();
 #endif
-#if defined(Q_OS_WIN) && QT_VERSION < QT_VERSION_CHECK(6, 4, 0)
+#if defined(Q_OS_WIN) && QT_VERSION < 0x060400
     void setTitleBarStyle();
 #endif
 
@@ -153,24 +188,28 @@ private:
 
     inline QRect winGeo() const;
 
+private slots:
     void storeDockSizes();
 
-    void keyPressEvent(QKeyEvent *) override;
-    void mouseMoveEvent(QMouseEvent *) override;
-    void enterEvent(Q_ENTER_EVENT *e) override;
-    void leaveEvent(QEvent *) override;
-    void closeEvent(QCloseEvent *) override;
-    void moveEvent(QMoveEvent *) override;
-    void showEvent(QShowEvent *) override;
-    void hideEvent(QHideEvent *) override;
-    void changeEvent(QEvent *e) override;
-    void resizeEvent(QResizeEvent *e) override;
-
-    bool eventFilter(QObject *obj, QEvent *event) override;
-
-#ifdef Q_OS_MACOS
+private:
+    void keyPressEvent(QKeyEvent *);
+    void mouseMoveEvent(QMouseEvent *);
+    void enterEvent(Q_ENTER_EVENT *e);
+    void leaveEvent(QEvent *);
+    void closeEvent(QCloseEvent *);
+    void moveEvent(QMoveEvent *);
+    void showEvent(QShowEvent *);
+    void hideEvent(QHideEvent *);
+    void changeEvent(QEvent *e);
+    void resizeEvent(QResizeEvent *e);
+
+    bool eventFilter(QObject *obj, QEvent *event);
+
+#ifdef Q_OS_MAC
+private slots:
     void fileOpenTimerTimeout();
 
+private:
     QTimer fileOpenTimer;
     QStringList filesToAdd;
 #endif
@@ -201,6 +240,15 @@ private:
 
     SettingsWidget *settingsW;
     AboutWidget *aboutW;
+
+#if defined(Q_OS_MAC) && QT_VERSION < 0x050400
+    std::function<void()> m_maybeGoFullScreenCallback;
+#endif
+
+#if QT_VERSION < 0x050400
+    QByteArray m_restoreStateData;
+    bool m_restoreStateToggleCompactView;
+#endif
     bool isCompactView, wasShow, fullScreen, seekSFocus, m_restoreWindowOnVideo = false;
     bool m_fullscreenPanelsRightSide = false;
     bool m_compactViewBeforeFullScreen = false;
@@ -209,7 +257,7 @@ private:
 #endif
     bool m_loaded = false;
 
-#if !defined Q_OS_MACOS && !defined Q_OS_ANDROID
+#if !defined Q_OS_MAC && !defined Q_OS_ANDROID
     QAction *hideMenuAct;
 #endif
     QAction *lockWidgetsAct;
diff --git a/src/gui/MenuBar.cpp b/src/gui/MenuBar.cpp
index 3c5a8b7b..c34e6425 100644
--- a/src/gui/MenuBar.cpp
+++ b/src/gui/MenuBar.cpp
@@ -26,14 +26,14 @@
 #include <Settings.hpp>
 #include <Main.hpp>
 
-#include <QGuiApplication>
+#include <QApplication>
 #include <QWidgetAction>
 #include <QActionGroup>
 #include <QInputDialog>
 #include <QMainWindow>
 #include <qevent.h>
 #include <QDir>
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     #include <QTimer>
 #endif
 
@@ -91,12 +91,17 @@ MenuBar::MenuBar()
     addMenu(options = new Options(this));
     addMenu(help = new Help(this));
     connect(widgets, SIGNAL(aboutToShow()), this, SLOT(widgetsMenuShow()));
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     widgets->addAction(QString()); //Mac must have got at least one item inside menu, otherwise the menu is not shown
 #endif
 
     QMPlay2Core.registerProcessWheelEventFn([this](QWheelEvent *e) {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         const auto deltaY  = e->angleDelta().y();
+#else
+        // Qt4: Use delta() which returns an int
+        const int deltaY = e->delta();
+#endif
         auto &settings = QMPlay2Core.getSettings();
         if (deltaY != 0 && e->buttons() == Qt::NoButton && settings.getBool("WheelAction"))
         {
@@ -120,7 +125,9 @@ MenuBar::Window::Window(MenuBar *parent) :
     newAction(Window::tr("&Compact view"), this, toggleCompactView, false, QIcon(), true);
     addSeparator();
 #ifndef Q_OS_ANDROID
-    if (!QGuiApplication::platformName().contains(QStringLiteral("wayland")))
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+    if (!QApplication::platformName().contains(QString("wayland")))
+#endif
         newAction(Window::tr("&Always on top"), this, alwaysOnTop, false, QIcon(), true);
     newAction(Window::tr("&Hide on close"), this, hideOnClose, false, QIcon(), true);
 #endif
@@ -342,13 +349,13 @@ MenuBar::Player::AspectRatio::AspectRatio(QMenu *parent) :
     _3x2->setObjectName(QString::number(3.0 / 2.0));
     _21x9->setObjectName(QString::number(21.0 / 9.0));
     sizeDep->setObjectName("sizeDep");
-    custom->setObjectName(QStringLiteral("custom:%1").arg(QMPlay2Core.getSettings().getDouble("CustomAspectRatio", 1.0)));
+    custom->setObjectName(QString("custom:%1").arg(QMPlay2Core.getSettings().getDouble("CustomAspectRatio", 1.0)));
     off->setObjectName("off");
 }
 
 void MenuBar::Player::seekActionsEnable(bool e)
 {
-#ifndef Q_OS_MACOS
+#ifndef Q_OS_MAC
     Qt::ShortcutContext ctx = e ? Qt::WindowShortcut : Qt::WidgetShortcut;
     seekF->setShortcutContext(ctx);
     seekB->setShortcutContext(ctx);
@@ -363,7 +370,7 @@ void MenuBar::Player::seekActionsEnable(bool e)
 }
 void MenuBar::Player::playActionEnable(bool e)
 {
-#ifndef Q_OS_MACOS
+#ifndef Q_OS_MAC
     togglePlay->setShortcutContext(e ? Qt::WindowShortcut : Qt::WidgetShortcut);
 #else
     togglePlay->setEnabled(e);
@@ -454,7 +461,7 @@ MenuBar::Playback::VideoFilters::VideoFilters(QMenu *parent) :
     QMenu(VideoFilters::tr("Video &filters"), parent)
 {
     /** Korektor wideo */
-#ifndef Q_OS_MACOS
+#ifndef Q_OS_MAC
     videoAdjustmentMenu = new QMenu(VideoFilters::tr("Video &adjustment"), this);
     addMenu(videoAdjustmentMenu);
     QWidgetAction *widgetAction = new QWidgetAction(this);
@@ -575,7 +582,7 @@ void MenuBar::setKeyShortcuts()
     shortcuts->appendAction(window->toggleFullScreen, "KeyBindings/Window-toggleFullScreen", "F");
     shortcuts->appendAction(window->toggleCompactView, "KeyBindings/Window-toggleCompactView", "Alt+V");
     shortcuts->appendAction(window->close, "KeyBindings/Window-close", "Ctrl+Q");
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     window->toggleVisibility->setShortcuts({window->toggleVisibility->shortcut(), QKeySequence("Ctrl+W")});
 #endif
 
diff --git a/src/gui/OSDSettingsW.cpp b/src/gui/OSDSettingsW.cpp
index 2ad9bf29..e1a50f3a 100644
--- a/src/gui/OSDSettingsW.cpp
+++ b/src/gui/OSDSettingsW.cpp
@@ -32,7 +32,7 @@
 #include <QSpacerItem>
 #include <QLabel>
 #include <QSpinBox>
-#include <QGuiApplication>
+#include <QApplication>
 #include <QRadioButton>
 #include <QCoreApplication>
 
@@ -41,7 +41,8 @@
 void OSDSettingsW::init(const QString &prefix, int a, int b, int c, int d, int e, int f, double g, double h, const QColor &i, const QColor &j, const QColor &k, bool l, bool m)
 {
     Settings &QMPSettings = QMPlay2Core.getSettings();
-    QMPSettings.init(prefix + "/FontName", QGuiApplication::font().family());
+    // Qt4: Use QApplication instead of QGuiApplication
+    QMPSettings.init(prefix + "/FontName", QApplication::font().family());
     QMPSettings.init(prefix + "/FontSize", a);
     QMPSettings.init(prefix + "/Spacing", b);
     QMPSettings.init(prefix + "/Bold", l);
@@ -191,12 +192,7 @@ void OSDSettingsW::setupUi(QWidget *OSDSettings)
         alignL->addWidget(alignB[align], align / 3, align % 3);
     }
 
-    connect(m_backgroundCB, &QCheckBox::toggled, this, [this, shadowText = m_shadowColorL->text()](bool checked) {
-        m_shadowL->setEnabled(!checked);
-        m_shadowB->setEnabled(!checked);
-        m_shadowColorL->setText(checked ? tr("Background") : shadowText);
-        setShadowColorB();
-    });
+    connect(m_backgroundCB, SIGNAL(toggled(bool)), this, SLOT(handleBackgroundToggled(bool)));
 
     fontGB->setTitle(tr("Font"));
     fontSizeL->setText(tr("Size: "));
@@ -215,6 +211,9 @@ void OSDSettingsW::setupUi(QWidget *OSDSettings)
     textColorL->setText(tr("Text: "));
     outlineColorL->setText(tr("Border: "));
     m_shadowColorL->setText(tr("Shadow: "));
+
+    // Qt4: Store the default shadow color label text for the helper slot
+    m_shadowText = m_shadowColorL->text();
 }
 
 void OSDSettingsW::addWidget(QWidget *w)
@@ -272,3 +271,12 @@ inline void OSDSettingsW::setShadowColorB()
 {
     m_shadowColorB->setColor(QMPlay2Core.getSettings().getColor(prefix + (m_backgroundCB->isChecked() ? "/BackgroundColor" : "/ShadowColor")));
 }
+
+// Qt4: Helper slot implementation
+void OSDSettingsW::handleBackgroundToggled(bool checked)
+{
+    m_shadowL->setEnabled(!checked);
+    m_shadowB->setEnabled(!checked);
+    m_shadowColorL->setText(checked ? tr("Background") : m_shadowText);
+    setShadowColorB();
+}
diff --git a/src/gui/OSDSettingsW.hpp b/src/gui/OSDSettingsW.hpp
index e6ec647f..ee6067b8 100644
--- a/src/gui/OSDSettingsW.hpp
+++ b/src/gui/OSDSettingsW.hpp
@@ -50,6 +50,10 @@ private:
 
     inline void setShadowColorB();
 
+private slots:
+    // Qt4: Helper slot for lambda conversion
+    void handleBackgroundToggled(bool checked);
+
 private:
     QVBoxLayout *m_layout;
     QFontComboBox *m_fontCB;
@@ -70,4 +74,5 @@ private:
 
     QRadioButton *alignB[9];
     QString prefix;
+    QString m_shadowText;
 };
diff --git a/src/gui/PanGestureEventFilter.cpp b/src/gui/PanGestureEventFilter.cpp
index 45f3b479..da31d0b7 100644
--- a/src/gui/PanGestureEventFilter.cpp
+++ b/src/gui/PanGestureEventFilter.cpp
@@ -19,6 +19,7 @@
 #include <PanGestureEventFilter.hpp>
 #include <Functions.hpp>
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
 #include <QAbstractItemView>
 #include <QCoreApplication>
 #include <QScrollBar>
@@ -81,11 +82,13 @@ class PanGestureEventFilterPriv : public QObject
     const QScroller::ScrollerGestureType m_gestureType = QScroller::TouchGesture;
 #endif
 };
+#endif // QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
 
 /**/
 
 void PanGestureEventFilter::install()
 {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     static bool installed;
 
 #ifndef Q_OS_ANDROID
@@ -113,4 +116,7 @@ void PanGestureEventFilter::install()
     qApp->installEventFilter(new PanGestureEventFilterPriv);
 
     installed = true;
+#else
+    // Qt4: QScroller doesn't exist, no-op
+#endif
 }
diff --git a/src/gui/PlayClass.cpp b/src/gui/PlayClass.cpp
index 465e588b..6e63c192 100644
--- a/src/gui/PlayClass.cpp
+++ b/src/gui/PlayClass.cpp
@@ -34,7 +34,7 @@
 #include <Decoder.hpp>
 #include <Reader.hpp>
 
-#include <QGuiApplication>
+#include <QApplication>
 #include <QVarLengthArray>
 #include <QMessageBox>
 #include <QRawFont>
@@ -90,9 +90,9 @@ PlayClass::PlayClass() :
 
 
     connect(&timTerminate, SIGNAL(timeout()), this, SLOT(timTerminateFinished()));
-    connect(this, &PlayClass::aRatioUpdate, this, &PlayClass::aRatioUpdated);
+    connect(this, SIGNAL(aRatioUpdate(const AVRational &)), this, SLOT(aRatioUpdated(const AVRational &)));
     connect(this, SIGNAL(frameSizeUpdate(int, int)), this, SLOT(frameSizeUpdated(int, int)));
-    connect(this, &PlayClass::pixelFormatUpdate, this, &PlayClass::pixelFormatUpdated);
+    connect(this, SIGNAL(pixelFormatUpdate(int)), this, SLOT(pixelFormatUpdated(int)));
     connect(this, SIGNAL(audioParamsUpdate(quint8, quint32)), this, SLOT(audioParamsUpdated(quint8, quint32)));
 }
 PlayClass::~PlayClass()
@@ -122,16 +122,8 @@ void PlayClass::play(const QString &_url)
             demuxThr->updateBufferedSeconds = QMPlay2Core.getSettings().getBool("ShowBufferedTimeOnSlider");
 
             connect(demuxThr, SIGNAL(load(Demuxer *)), this, SLOT(load(Demuxer *)));
-            connect(demuxThr, &DemuxerThr::allowRecording, this, &PlayClass::allowRecording);
-            connect(demuxThr, &DemuxerThr::recording, this, [this](bool status, bool error, const QString &fileName) {
-                if (error)
-                    messageAndOSD(tr("Recording error"));
-                else if (status)
-                    messageAndOSD(tr("Recording started: %1").arg(fileName));
-                else
-                    messageAndOSD(tr("Recording stopped"));
-                emit recording(status && !error);
-            });
+            connect(demuxThr, SIGNAL(allowRecording(bool)), this, SIGNAL(allowRecording(bool)));
+            connect(demuxThr, SIGNAL(recording(bool, bool, const QString &)), this, SLOT(handleDemuxerRecording(bool, bool, const QString &)));
             connect(demuxThr, SIGNAL(finished()), this, SLOT(demuxThrFinished()));
 
             if (!QMPlay2Core.getSettings().getBool("KeepZoom"))
@@ -188,7 +180,14 @@ void PlayClass::play(const QString &_url)
             demuxThr->start();
 
             if (QMPlay2Core.getSettings().getBool("StoreUrlPos"))
+            {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
                 emit continuePos(QMPlay2Core.getUrlPosSets().getDouble(url.toUtf8().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)), true);
+#else
+                // Qt4: Use basic base64 encoding
+                emit continuePos(QMPlay2Core.getUrlPosSets().getDouble(url.toUtf8().toBase64()), true);
+#endif
+            }
         }
     }
     else
@@ -293,14 +292,30 @@ void PlayClass::seek(double pos, bool allowAccurate)
 void PlayClass::chStream(const QString &s)
 {
     if (s.startsWith("audio"))
+#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
         chosenAudioStream = QStringView(s).right(s.length() - 5).toInt();
+#else
+        chosenAudioStream = s.right(s.length() - 5).toInt();
+#endif
     else if (s.startsWith("video"))
+#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
         chosenVideoStream = QStringView(s).right(s.length() - 5).toInt();
+#else
+        chosenVideoStream = s.right(s.length() - 5).toInt();
+#endif
     else if (s.startsWith("subtitles"))
+#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
         chosenSubtitlesStream = QStringView(s).right(s.length() - 9).toInt();
+#else
+        chosenSubtitlesStream = s.right(s.length() - 9).toInt();
+#endif
     else if (s.startsWith("fileSubs"))
     {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
         int idx = QStringView(s).right(s.length() - 8).toInt();
+#else
+        int idx = s.right(s.length() - 8).toInt();
+#endif
         if (fileSubsList.count() > idx)
             loadSubsFile(fileSubsList[idx]);
     }
@@ -436,12 +451,17 @@ void PlayClass::messageAndOSD(const QString &txt, bool onStatusBar, double durat
     if (ass && QMPlay2Core.getSettings().getBool("OSD/Enabled"))
     {
         QByteArray txtData;
-        if (QGuiApplication::layoutDirection() == Qt::RightToLeft && txt.size() >= 2)
+        if (QApplication::layoutDirection() == Qt::RightToLeft && txt.size() >= 2)
         {
             // libass puts "%" and "x" character to the right, so swap it
             if (txt.at(txt.size() - 2).isDigit() && (txt.at(txt.size() - 1).isLetter() || txt.at(txt.size() - 1) == QChar('%')))
             {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
                 txtData = (txt.at(txt.size() - 1) + txt.mid(0, txt.size() - 1)).toUtf8();
+#else
+                // Qt4: QStringBuilder doesn't have toUtf8(), wrap in QString
+                txtData = QString(txt.at(txt.size() - 1) + txt.mid(0, txt.size() - 1)).toUtf8();
+#endif
             }
         }
         if (txtData.isNull())
@@ -742,7 +762,7 @@ void PlayClass::applyZoom(bool message)
         if (m_integerScaling && zoom < 1.0)
         {
             const auto zoomRational = av_d2q(zoom, 10);
-            messageAndOSD(text.arg(QStringLiteral("%1/%2").arg(zoomRational.num).arg(zoomRational.den)));
+            messageAndOSD(text.arg(QString("%1/%2").arg(zoomRational.num).arg(zoomRational.den)));
         }
         else if (m_integerScaling)
         {
@@ -1038,7 +1058,7 @@ void PlayClass::aRatio()
         QString msgTxt = tr("Aspect ratio") + ": " + sender->text().remove('&');
         if (custom)
         {
-            msgTxt += QStringLiteral(" (%1)").arg(aRatioName);
+            msgTxt += QString(" (%1)").arg(aRatioName);
         }
         if (hasVideoStream())
         {
@@ -1054,7 +1074,7 @@ void PlayClass::aRatio()
             messageAndOSD(msgTxt);
         }
     };
-    if (aRatioName.startsWith(QStringLiteral("custom:")))
+    if (aRatioName.startsWith(QString("custom:")))
     {
         constexpr double min = 0.5;
         constexpr double max = 3.0;
@@ -1085,7 +1105,7 @@ void PlayClass::aRatio()
                     if (!qFuzzyCompare(value, valueNew))
                     {
                         value = valueNew;
-                        sender->setObjectName(QStringLiteral("custom:%1").arg(value));
+                        sender->setObjectName(QString("custom:%1").arg(value));
                         QMPlay2Core.getSettings().set("CustomAspectRatio", value);
                         setCustomARatio();
                     }
@@ -1555,8 +1575,13 @@ static Decoder *loadStream(
         };
         QVarLengthArray<StreamSearch, 2> modes;
         if (!preferredStreams.empty())
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
             modes.push_back(StreamSearch::Preferred);
         modes.push_back(StreamSearch::All);
+#else
+            modes.append(StreamSearch::Preferred);
+        modes.append(StreamSearch::All);
+#endif
 
         // First search in preferred streams (if any) which belongs to program. If not found - search in all streams.
         for (auto mode : modes)
@@ -1622,7 +1647,7 @@ static Decoder *loadStream(
 
                 if (!lang.isEmpty() && chosenLangStream < 0 && !streams[i]->skip_auto_select)
                 {
-                    for (const QMPlay2Tag &tag : std::as_const(streams[i]->other_info))
+                    for (const QMPlay2Tag &tag : streams[i]->other_info)
                     {
                         if (tag.first.toInt() == QMPLAY2_TAG_LANGUAGE)
                         {
@@ -1701,7 +1726,7 @@ void PlayClass::load(Demuxer *demuxer)
             {
                 const double aspect_ratio = getARatio();
 
-                if (!qIsNaN(streams[videoStream]->rotation))
+                if (!std::isnan(streams[videoStream]->rotation))
                 {
                     const bool is270 = qFuzzyCompare(streams[videoStream]->rotation, 270.0);
                     if (is270 || qFuzzyCompare(streams[videoStream]->rotation, 180.0))
@@ -1928,3 +1953,15 @@ void PlayClass::load(Demuxer *demuxer)
     reload = videoDecErrorLoad = false;
     loadMutex.unlock();
 }
+
+// Qt4: Helper slot implementation
+void PlayClass::handleDemuxerRecording(bool status, bool error, const QString &fileName)
+{
+    if (error)
+        messageAndOSD(tr("Recording error"));
+    else if (status)
+        messageAndOSD(tr("Recording started: %1").arg(fileName));
+    else
+        messageAndOSD(tr("Recording stopped"));
+    emit recording(status && !error);
+}
diff --git a/src/gui/PlayClass.hpp b/src/gui/PlayClass.hpp
index 7a9a688e..eeddf6d4 100644
--- a/src/gui/PlayClass.hpp
+++ b/src/gui/PlayClass.hpp
@@ -45,7 +45,7 @@ inline constexpr double SEEK_NOWHERE = -1.0;
 inline constexpr double SEEK_STREAM_RELOAD = -2.0; // Seeks to current position after stream reload
 inline constexpr double SEEK_REPEAT = -3.0;
 
-class PlayClass final : public QObject
+class PlayClass : public QObject
 {
     Q_OBJECT
     friend class DemuxerThr;
@@ -92,13 +92,16 @@ public:
         doSilenceOnStart = true;
     }
 
+public slots:
     void setKeepAudioPitch(bool keep);
 
+public:
     inline void setDontResetContinuePlayback(bool val)
     {
         dontResetContinuePlayback = val;
     }
 
+public slots:
     void setRecording(bool checked);
 
     void setIntegerScaling(bool integerScaling);
@@ -193,7 +196,7 @@ private slots:
 
     void saveCover();
     void settingsChanged(int page, bool forceRestart, bool initFilters);
-public:
+public slots:
     void videoResized(const QSize &size);
 
 private slots:
@@ -205,12 +208,12 @@ private slots:
     void setSpeed();
     void zoomIn();
     void zoomOut();
-public:
+public slots:
     void setZoom();
 private slots:
     void zoomReset();
     void otherReset();
-public:
+public slots:
     void aRatio();
 private slots:
     void volume(int, int);
@@ -243,6 +246,9 @@ private slots:
     void timTerminateFinished();
 
     void load(Demuxer *);
+
+    // Qt4: Helper slot for lambda conversion
+    void handleDemuxerRecording(bool status, bool error, const QString &fileName);
 signals:
     void frameSizeUpdate(int w, int h);
     void audioParamsUpdate(quint8 channels, quint32 sampleRate);
diff --git a/src/gui/PlaylistDock.cpp b/src/gui/PlaylistDock.cpp
index 2084e53a..c2e83297 100644
--- a/src/gui/PlaylistDock.cpp
+++ b/src/gui/PlaylistDock.cpp
@@ -35,7 +35,12 @@
 #include <QPainter>
 #include <QAction>
 #include <QMessageBox>
-#include <QRandomGenerator>
+#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
+    #include <QRandomGenerator>
+#else
+    #include <QtGlobal>
+    #include <cstdlib>
+#endif
 
 static bool urlMatchesWithItem(QTreeWidgetItem *item, const QString &url)
 {
@@ -50,6 +55,11 @@ PlaylistDock::PlaylistDock() :
     repeatMode(RepeatNormal),
     lastPlaying(nullptr)
 {
+#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
+    // Qt4: Register the metatype at runtime
+    qRegisterMetaType<ByteArrayHash>("ByteArrayHash");
+    qRegisterMetaType<ByteArrayHash>("QHash<QByteArray,QByteArray>");
+#endif
     setWindowTitle(tr("Playlist"));
     setWidget(&mainW);
 
@@ -69,7 +79,7 @@ PlaylistDock::PlaylistDock() :
 
     connect(list, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(itemDoubleClicked(QTreeWidgetItem *)));
     connect(list, SIGNAL(returnItem(QTreeWidgetItem *)), this, SLOT(addAndPlay(QTreeWidgetItem *)));
-    connect(list, &PlaylistWidget::itemExpanded, this, &PlaylistDock::maybeDoQuickSync, Qt::QueuedConnection); // Must be queued to not crash at startup in some cases
+    connect(list, SIGNAL(itemExpanded(QTreeWidgetItem *)), this, SLOT(maybeDoQuickSync(QTreeWidgetItem *)), Qt::QueuedConnection); // Must be queued to not crash at startup in some cases
     connect(list, SIGNAL(visibleItemsCount(int)), this, SLOT(visibleItemsCount(int)));
     connect(list, SIGNAL(addStatus(bool)), findE, SLOT(setDisabled(bool)));
     connect(findE, SIGNAL(textChanged(const QString &)), this, SLOT(findItems(const QString &)));
@@ -370,7 +380,11 @@ void PlaylistDock::itemDoubleClicked(QTreeWidgetItem *tWI)
     {
         const auto params = tWI->data(0, Qt::UserRole + 3).value<QHash<QByteArray, QByteArray>>();
         QByteArray rawHeaders;
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         for (auto it = params.cbegin(), itEnd = params.cend(); it != itEnd; ++it)
+#else
+        for (auto it = params.constBegin(), itEnd = params.constEnd(); it != itEnd; ++it)
+#endif
         {
             const auto &key = it.key();
             if (key == Playlist::Entry::UserAgentParam)
@@ -441,7 +455,12 @@ void PlaylistDock::next(bool playingError)
                     tWI = l.at(0);
                 else do
                 {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
                     tWI = l.at(QRandomGenerator::global()->bounded(l.count()));
+#else
+                    // Qt4: Use qrand() for random number generation
+                    tWI = l.at(qrand() % l.count());
+#endif
                     if (PlaylistWidget::getFlags(tWI) & Playlist::Entry::Skip)
                     {
                         //Don't play skipped item.
@@ -631,7 +650,12 @@ void PlaylistDock::delEntries(bool fromDisk)
         {
             if (const QString url = getUrl(tWI); url.startsWith("file://"))
             {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
                 canDeleteTWI = QFile::moveToTrash(url.mid(7));
+#else
+                // Qt4: moveToTrash doesn't exist, use remove() instead
+                canDeleteTWI = QFile::remove(url.mid(7));
+#endif
                 if (!canDeleteTWI)
                     diskDeleteFailed = true;
             }
diff --git a/src/gui/PlaylistDock.hpp b/src/gui/PlaylistDock.hpp
index 8b17403a..4ab2efa3 100644
--- a/src/gui/PlaylistDock.hpp
+++ b/src/gui/PlaylistDock.hpp
@@ -58,7 +58,7 @@ public:
     void setMode();
 
 private:
-    void showEvent(QShowEvent *e) override;
+    void showEvent(QShowEvent *e);
 
 private:
     void expandTree(QTreeWidgetItem *);
diff --git a/src/gui/PlaylistWidget.cpp b/src/gui/PlaylistWidget.cpp
index c9d1c266..75a006c3 100644
--- a/src/gui/PlaylistWidget.cpp
+++ b/src/gui/PlaylistWidget.cpp
@@ -81,7 +81,7 @@ static inline MenuBar::Playlist *playlistMenu()
 class PlaylistItem : public QTreeWidgetItem
 {
 public:
-    bool operator <(const QTreeWidgetItem &other) const override
+    bool operator <(const QTreeWidgetItem &other) const
     {
         if (auto tw = treeWidget())
         {
@@ -236,9 +236,7 @@ AddThr::AddThr(PlaylistWidget &pLW) :
     pLW(pLW),
     inProgress(false)
 {
-    connect(this, &QThread::finished, this, [this] {
-        running = false;
-    });
+    connect(this, SIGNAL(finished()), this, SLOT(handleThreadFinished()));
     connect(this, SIGNAL(finished()), this, SLOT(finished()));
 }
 
@@ -641,12 +639,26 @@ void AddThr::finished()
     inProgress = false;
 }
 
+// Qt4: Helper slot implementation
+void AddThr::handleThreadFinished()
+{
+    running = false;
+}
+
 /*PlaylistWidget class*/
 PlaylistWidget::PlaylistWidget() :
     addThr(*this),
     updateEntryThr(*this),
     repaintAll(false)
 {
+#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
+    // Qt4: Register QHash<QByteArray, QByteArray> as a metatype
+    qRegisterMetaType<ByteArrayHash>("ByteArrayHash");
+    qRegisterMetaType<ByteArrayHash>("QHash<QByteArray,QByteArray>");
+    // Qt4: Register the struct used to defer newEntry()'s icon-setting work
+    // to the GUI thread via insertEntryItem() (see NewEntryData).
+    qRegisterMetaType<NewEntryData>("NewEntryData");
+#endif
     setContextMenuPolicy(Qt::CustomContextMenu);
     setEditTriggers(QAbstractItemView::NoEditTriggers);
     setDragDropMode(QAbstractItemView::InternalMove);
@@ -657,7 +669,11 @@ PlaylistWidget::PlaylistWidget() :
     setAnimated(true);
     header()->setStretchLastSection(false);
     setHeaderHidden(true);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     header()->setSectionResizeMode(0, QHeaderView::Stretch);
+#else
+    header()->setResizeMode(0, QHeaderView::Stretch);
+#endif
     header()->hideSection(1);
     setItemsResizeToContents(true);
 
@@ -669,7 +685,7 @@ PlaylistWidget::PlaylistWidget() :
     connect(&animationTimer, SIGNAL(timeout()), this, SLOT(animationUpdate()));
     connect(&addTimer, SIGNAL(timeout()), this, SLOT(addTimerElapsed()));
     connect(&addThr, SIGNAL(status(bool)), this, SIGNAL(addStatus(bool)));
-    connect(playlistMenu(), &MenuBar::Playlist::aboutToShow, this, &PlaylistWidget::createExtensionsMenu);
+    connect(playlistMenu(), SIGNAL(aboutToShow()), this, SLOT(createExtensionsMenu()));
 }
 
 QString PlaylistWidget::getUrl(QTreeWidgetItem *tWI) const
@@ -683,7 +699,11 @@ void PlaylistWidget::setItemsResizeToContents(bool b)
 {
     const QHeaderView::ResizeMode rm = b ? QHeaderView::ResizeToContents : QHeaderView::Fixed;
     for (int i = 1; i <= 2; ++i)
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         header()->setSectionResizeMode(i, rm);
+#else
+        header()->setResizeMode(i, rm);
+#endif
 }
 
 void PlaylistWidget::sortCurrentGroup(int column, Qt::SortOrder sortOrder)
@@ -875,7 +895,11 @@ void PlaylistWidget::refresh(REFRESH Refresh)
         for (int i = 0; i < queue.size(); i++)
         {
             if (!items.contains(queue.at(i)))
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+                queue.remove(i--);
+#else
                 queue.removeAt(i--);
+#endif
             else
                 queue.at(i)->setText(1, QString::number(i + 1));
         }
@@ -985,10 +1009,15 @@ QTreeWidgetItem *PlaylistWidget::newGroup(const QString &name, const QString &ur
     if (existingEntries)
         entryCreated(url, insertChildAt, *existingEntries);
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     QMetaObject::invokeMethod(this, [this, tWI, url, parent, insertChildAt] {
         QMPlay2GUI.setTreeWidgetItemIcon(tWI, url.isEmpty() ? *QMPlay2GUI.groupIcon : *QMPlay2GUI.folderIcon, 0, this);
         insertItem(tWI, parent, insertChildAt);
     });
+#else
+    // Qt4: QMetaObject::invokeMethod() has no lambda overload
+    QMetaObject::invokeMethod(this, "insertGroupItem", Q_ARG(QTreeWidgetItem *, tWI), Q_ARG(QString, url), Q_ARG(QTreeWidgetItem *, parent), Q_ARG(int, insertChildAt));
+#endif
 
     return tWI;
 }
@@ -1009,14 +1038,33 @@ QTreeWidgetItem *PlaylistWidget::newEntry(const Playlist::Entry &entry, QTreeWid
     if (existingEntries)
         entryCreated(entry.url, insertChildAt, *existingEntries);
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     QMetaObject::invokeMethod(this, [this, tWI, icon, parent, insertChildAt] {
         setEntryIcon(icon, tWI);
         insertItem(tWI, parent, insertChildAt);
     });
+#else
+    // Qt4: QMetaObject::invokeMethod() has no lambda overload
+    NewEntryData newEntryData{tWI, icon, parent, insertChildAt};
+    QMetaObject::invokeMethod(this, "insertEntryItem", Q_ARG(NewEntryData, newEntryData));
+#endif
 
     return tWI;
 }
 
+#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
+void PlaylistWidget::insertGroupItem(QTreeWidgetItem *tWI, QString url, QTreeWidgetItem *parent, int insertChildAt)
+{
+    QMPlay2GUI.setTreeWidgetItemIcon(tWI, url.isEmpty() ? *QMPlay2GUI.groupIcon : *QMPlay2GUI.folderIcon, 0, this);
+    insertItem(tWI, parent, insertChildAt);
+}
+void PlaylistWidget::insertEntryItem(NewEntryData data)
+{
+    setEntryIcon(data.icon, data.tWI);
+    insertItem(data.tWI, data.parent, data.insertChildAt);
+}
+#endif
+
 void PlaylistWidget::setEntryIcon(const QIcon &icon, QTreeWidgetItem *tWI)
 {
     Q_ASSERT(QThread::currentThread() == thread());
@@ -1053,7 +1101,11 @@ void PlaylistWidget::quickSyncScanDirs(const QString &pth, QTreeWidgetItem *par,
         if (urlIdx > -1 && isGroup == QFileInfo(fullPth).isDir())
         {
             existingEntries.prepend(itemFileName);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+            dirEntries.remove(urlIdx);
+#else
             dirEntries.removeAt(urlIdx);
+#endif
             if (isGroup && recursive)
             {
                 if (!fullPth.endsWith('/'))
@@ -1100,7 +1152,7 @@ void PlaylistWidget::createExtensionsMenu()
             actions = QMPlay2Ext->getActions(entryName, entryLength, url, addressPrefixName, param);
         else
             actions = QMPlay2Ext->getActions(entryName, entryLength, entryUrl);
-        for (QAction *act : std::as_const(actions))
+        for (QAction *act : actions)
         {
             act->setParent(extensions);
             extensions->addAction(act);
diff --git a/src/gui/PlaylistWidget.hpp b/src/gui/PlaylistWidget.hpp
index dc4cda3b..a7d4e8fd 100644
--- a/src/gui/PlaylistWidget.hpp
+++ b/src/gui/PlaylistWidget.hpp
@@ -34,7 +34,7 @@ class QTreeWidgetItem;
 class PlaylistWidget;
 class Demuxer;
 
-class UpdateEntryThr final : public QThread
+class UpdateEntryThr : public QThread
 {
     Q_OBJECT
 
@@ -51,7 +51,7 @@ public:
     }
 
 private:
-    void run() override;
+    void run();
 
     QAtomicInt pendingUpdates;
     IOController<> ioCtrl;
@@ -84,7 +84,7 @@ private slots:
     void finished();
 };
 
-class AddThr final : public QThread
+class AddThr : public QThread
 {
     Q_OBJECT
 public:
@@ -101,7 +101,7 @@ private slots:
     void modifyItemFlags(QTreeWidgetItem *tWI, int flags);
     void deleteTreeWidgetItem(QTreeWidgetItem *tWI);
 private:
-    void run() override;
+    void run();
 
     bool add(const QStringList &urls, QTreeWidgetItem *parent, const Functions::DemuxersInfo &demuxersInfo, QStringList *existingEntries = nullptr, bool loadList = false);
     QTreeWidgetItem *insertPlaylistEntries(const Playlist::Entries &entries, QTreeWidgetItem *parent, const Functions::DemuxersInfo &demuxersInfo, int insertChildAt, QStringList *existingEntries);
@@ -118,16 +118,32 @@ public:
     bool running = false;
 private slots:
     void finished();
+    // Qt4: Helper slot for lambda conversion
+    void handleThreadFinished();
 signals:
     void status(bool s);
 };
 
-class PlaylistWidget final : public QTreeWidget
+class PlaylistWidget : public QTreeWidget
 {
     friend class AddThr;
     friend class UpdateEntryThr;
     Q_OBJECT
 public:
+#if QT_VERSION < 0x050000
+    // Qt4: QMetaObject::invokeMethod() has no lambda overload, so newEntry()
+    // defers through insertEntryItem(), bundling all its arguments into a
+    // single struct instead of passing QIcon through Q_ARG() directly (see
+    // ItemUpdated below, which does the same for a very similar purpose).
+    struct NewEntryData
+    {
+        QTreeWidgetItem *tWI;
+        QIcon icon;
+        QTreeWidgetItem *parent;
+        int insertChildAt;
+    };
+#endif
+
     enum CHILDREN {ONLY_GROUPS = 0x10, ONLY_NON_GROUPS = 0x100, ALL_CHILDREN = ONLY_GROUPS | ONLY_NON_GROUPS};
     enum REFRESH  {REFRESH_QUEUE = 0x10, REFRESH_GROUPS_TIME = 0x100, REFRESH_CURRPLAYING = 0x1000, REFRESH_ALL = REFRESH_QUEUE | REFRESH_GROUPS_TIME | REFRESH_CURRPLAYING};
 
@@ -196,14 +212,16 @@ private:
 
     void quickSyncScanDirs(const QString &pth, QTreeWidgetItem *par, bool &mustRefresh, bool recursive, QTreeWidgetItem *&itemToNull);
 
+private slots:
     void createExtensionsMenu();
 
-    void mouseMoveEvent(QMouseEvent *) override;
-    void dragEnterEvent(QDragEnterEvent *) override;
-    void dragMoveEvent(QDragMoveEvent *) override;
-    void dropEvent(QDropEvent *) override;
-    void paintEvent(QPaintEvent *) override;
-    void scrollContentsBy(int dx, int dy) override;
+private:
+    void mouseMoveEvent(QMouseEvent *);
+    void dragEnterEvent(QDragEnterEvent *);
+    void dragMoveEvent(QDragMoveEvent *);
+    void dropEvent(QDropEvent *);
+    void paintEvent(QPaintEvent *);
+    void scrollContentsBy(int dx, int dy);
 
     QRect getArcRect(int size);
 
@@ -222,6 +240,12 @@ private:
     int rotation;
 private slots:
     void insertItem(QTreeWidgetItem *, QTreeWidgetItem *, int insertChildAt);
+#if QT_VERSION < 0x050000
+    // Qt4: QMetaObject::invokeMethod() has no lambda overload, so newGroup()/
+    // newEntry() defer through these named slots instead.
+    void insertGroupItem(QTreeWidgetItem *tWI, QString url, QTreeWidgetItem *parent, int insertChildAt);
+    void insertEntryItem(NewEntryData data);
+#endif
     void popupContextMenu(const QPoint &);
     void animationUpdate();
     void addTimerElapsed();
@@ -232,3 +256,10 @@ signals:
     void visibleItemsCount(int);
     void addStatus(bool s);
 };
+
+#if QT_VERSION < 0x050000
+// Qt4: Register QHash<QByteArray, QByteArray> as a metatype
+// Use typedef to avoid comma in macro
+typedef QHash<QByteArray, QByteArray> ByteArrayHash;
+Q_DECLARE_METATYPE(ByteArrayHash)
+#endif
diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp
index f57379d7..ab94a15e 100644
--- a/src/gui/SettingsWidget.cpp
+++ b/src/gui/SettingsWidget.cpp
@@ -34,7 +34,7 @@
 #endif
 
 #include <QStackedWidget>
-#include <QStandardPaths>
+#include <QDesktopServices>
 #include <QStyleFactory>
 #include <QImageWriter>
 #include <QRadioButton>
@@ -70,7 +70,7 @@
 #include <MenuBar.hpp>
 #include <Module.hpp>
 
-#if !defined(Q_OS_WIN) && !defined(Q_OS_MACOS) && !defined(Q_OS_ANDROID)
+#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) && !defined(Q_OS_ANDROID)
     #define ICONS_FROM_THEME
 #endif
 
@@ -129,7 +129,12 @@ void SettingsWidget::InitSettings()
         while (outputFilePath.endsWith("/"))
             outputFilePath.chop(1);
         if (outputFilePath.isEmpty())
-            outputFilePath = QStandardPaths::standardLocations(QStandardPaths::DownloadLocation).value(0, QDir::homePath());
+        {
+            // Qt4 doesn't have DownloadLocation, use DocumentsLocation
+            outputFilePath = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation);
+            if (outputFilePath.isEmpty())
+                outputFilePath = QDir::homePath();
+        }
         return outputFilePath;
     };
 
@@ -142,7 +147,10 @@ void SettingsWidget::InitSettings()
 
     QMPSettings.init("AudioLanguage", QString());
     QMPSettings.init("SubtitlesLanguage", QString());
-    QMPSettings.init("screenshotPth", []{return QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).value(0, QDir::homePath());});
+    QMPSettings.init("screenshotPth", [] {
+        QString path = QDesktopServices::storageLocation(QDesktopServices::PicturesLocation);
+        return path.isEmpty() ? QDir::homePath() : path;
+    });
     QMPSettings.init("OutputFilePath", getInitialOutpuitFilePath);
 #ifdef Q_OS_WIN
     QMPSettings.init("screenshotFormat", ".bmp");
@@ -221,8 +229,14 @@ void SettingsWidget::InitSettings()
     QMPSettings.init("Vulkan/HQScaleDown", false);
     QMPSettings.init("Vulkan/HQScaleUp", false);
     QMPSettings.init("Vulkan/BypassCompositor", true);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     QMPSettings.init("Vulkan/HDR", QGuiApplication::platformName().contains(QStringLiteral("wayland")));
     QMPSettings.init("Vulkan/BT2020", QGuiApplication::platformName().contains(QStringLiteral("wayland")));
+#else
+    // Qt4: No Wayland support
+    QMPSettings.init("Vulkan/HDR", false);
+    QMPSettings.init("Vulkan/BT2020", false);
+#endif
 
     QMPSettings.init("ShortSeek", 5);
     QMPSettings.init("LongSeek", 30);
@@ -404,7 +418,7 @@ SettingsWidget::SettingsWidget(int page, const QString &moduleName, QWidget *vid
 
         m_outputFileE->setText(QMPSettings.getString("OutputFilePath"));
         m_outputFileB->setIcon(QMPlay2Core.getIconFromTheme("folder-open"));
-        connect(m_outputFileB, &QAbstractButton::clicked, this, &SettingsWidget::chooseOutputFileDir);
+        connect(m_outputFileB, SIGNAL(clicked()), this, SLOT(chooseOutputFileDir()));
 
         connect(m_setAppearanceB, SIGNAL(clicked()), this, SLOT(setAppearance()));
         connect(m_setKeyBindingsB, SIGNAL(clicked()), this, SLOT(setKeyBindings()));
@@ -493,37 +507,29 @@ SettingsWidget::SettingsWidget(int page, const QString &moduleName, QWidget *vid
 
         m_dontUpdateYtDlCB->setChecked(QMPSettings.getBool("YtDl/DontAutoUpdate"));
 
-        connect(m_cookiesFromBrowserCB, &QCheckBox::toggled, this, [this](bool checked) {
-            m_cookiesFromBrowserE->setEnabled(checked);
-        });
-        connect(m_customYtDlCB, &QCheckBox::toggled, this, [this](bool checked) {
-            m_customYtDlE->setEnabled(checked);
-            m_customYtDlB->setEnabled(checked);
-            m_removeYtDlB->setEnabled(!checked);
-        });
-        connect(m_customYtDlB, &QToolButton::clicked, this, [this] {
-            auto path = QFileDialog::getOpenFileName(this, tr("Choose youtube-dl script or executable"), m_customYtDlE->text());
-            if (!path.isEmpty())
-                m_customYtDlE->setText(path);
-        });
-        connect(m_dfltYtDlQualCB, &QCheckBox::toggled, this, [this](bool checked) {
-            m_dfltYtDlQualE->setEnabled(checked);
-        });
-        connect(m_additionalYtDlParamsCB, &QCheckBox::toggled, this, [this](bool checked) {
-            m_additionalYtDlParamsE->setEnabled(checked);
-        });
+        connect(m_cookiesFromBrowserCB, SIGNAL(toggled(bool)), this, SLOT(handleCookiesFromBrowserToggled(bool)));
+        connect(m_customYtDlCB, SIGNAL(toggled(bool)), this, SLOT(handleCustomYtDlToggled(bool)));
+        connect(m_customYtDlB, SIGNAL(clicked()), this, SLOT(handleCustomYtDlBrowseClicked()));
+        connect(m_dfltYtDlQualCB, SIGNAL(toggled(bool)), this, SLOT(handleDfltYtDlQualToggled(bool)));
+        connect(m_additionalYtDlParamsCB, SIGNAL(toggled(bool)), this, SLOT(handleAdditionalYtDlParamsToggled(bool)));
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         emit m_cookiesFromBrowserCB->toggled(m_cookiesFromBrowserCB->isChecked());
         emit m_customYtDlCB->toggled(m_customYtDlCB->isChecked());
         emit m_dfltYtDlQualCB->toggled(m_dfltYtDlQualCB->isChecked());
         emit m_additionalYtDlParamsCB->toggled(m_additionalYtDlParamsCB->isChecked());
+#else
+        // Qt4: signals are protected, trigger slots directly
+        handleCookiesFromBrowserToggled(m_cookiesFromBrowserCB->isChecked());
+        handleCustomYtDlToggled(m_customYtDlCB->isChecked());
+        handleDfltYtDlQualToggled(m_dfltYtDlQualCB->isChecked());
+        handleAdditionalYtDlParamsToggled(m_additionalYtDlParamsCB->isChecked());
+#endif
 
-        connect(m_customYtDlCB, &QCheckBox::toggled, this, [this](bool checked) {
-            m_dontUpdateYtDlCB->setChecked(checked);
-        });
+        connect(m_customYtDlCB, SIGNAL(toggled(bool)), this, SLOT(handleCustomYtDlToggledForAutoUpdate(bool)));
 
         m_removeYtDlB->setIcon(QMPlay2Core.getIconFromTheme("list-remove"));
-        connect(m_removeYtDlB, &QPushButton::clicked, this, &SettingsWidget::removeYouTubeDl);
+        connect(m_removeYtDlB, SIGNAL(clicked()), this, SLOT(removeYouTubeDl()));
 #else
         m_ytDlGB->deleteLater();
         m_ytDlGB = nullptr;
@@ -616,29 +622,13 @@ SettingsWidget::SettingsWidget(int page, const QString &moduleName, QWidget *vid
         m_wheelVolumeB->setChecked(QMPSettings.getBool("WheelVolume"));
 
         m_storeARatioAndZoomB->setChecked(QMPSettings.getBool("StoreARatioAndZoom"));
-        connect(m_storeARatioAndZoomB, &QCheckBox::toggled, this, [this](bool checked) {
-            if (checked)
-            {
-                m_keepZoom->setChecked(true);
-                m_keepARatio->setChecked(true);
-            }
-        });
+        connect(m_storeARatioAndZoomB, SIGNAL(toggled(bool)), this, SLOT(handleStoreARatioAndZoomToggled(bool)));
 
         m_keepZoom->setChecked(QMPSettings.getBool("KeepZoom"));
-        connect(m_keepZoom, &QCheckBox::toggled, this, [this](bool checked) {
-            if (!checked && !m_keepARatio->isChecked())
-            {
-                m_storeARatioAndZoomB->setChecked(false);
-            }
-        });
+        connect(m_keepZoom, SIGNAL(toggled(bool)), this, SLOT(handleKeepZoomToggled(bool)));
 
         m_keepARatio->setChecked(QMPSettings.getBool("KeepARatio"));
-        connect(m_keepARatio, &QCheckBox::toggled, this, [this](bool checked) {
-            if (!checked && !m_keepZoom->isChecked())
-            {
-                m_storeARatioAndZoomB->setChecked(false);
-            }
-        });
+        connect(m_keepARatio, SIGNAL(toggled(bool)), this, SLOT(handleKeepARatioToggled(bool)));
 
         m_showBufferedTimeOnSlider->setChecked(QMPSettings.getBool("ShowBufferedTimeOnSlider"));
         m_savePos->setChecked(QMPSettings.getBool("SavePos"));
@@ -815,7 +805,12 @@ void SettingsWidget::applyProxy()
     Settings &QMPSettings = QMPlay2Core.getSettings();
     if (!QMPSettings.getBool("Proxy/Use"))
     {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
         qunsetenv("http_proxy");
+#else
+        // Qt4: qunsetenv doesn't exist, use unsetenv from stdlib
+        unsetenv("http_proxy");
+#endif
     }
     else
     {
@@ -1136,9 +1131,9 @@ void SettingsWidget::setupModulesListUi(QGroupBox *ModulesList, UiModulesList &m
     ml.horizontalLayout->addLayout(ml.buttonsW);
 
     ml.moveUp->setToolTip(tr("Move up"));
-    ml.moveUp->setText(QStringLiteral("..."));
+    ml.moveUp->setText(QString("..."));
     ml.moveDown->setToolTip(tr("Move down"));
-    ml.moveDown->setText(QStringLiteral("..."));
+    ml.moveDown->setText(QString("..."));
 }
 
 void SettingsWidget::setupPlaybackUi(QWidget *PlaybackSettings)
@@ -1350,7 +1345,7 @@ void SettingsWidget::setupPlaybackUi(QWidget *PlaybackSettings)
     m_modulesListLayout = new QHBoxLayout();
     playbackLayout->addLayout(m_modulesListLayout);
 
-    QObject::connect(m_restoreAVSStateB, &QCheckBox::toggled, m_disableSubtitlesAtStartup, &QCheckBox::setDisabled);
+    QObject::connect(m_restoreAVSStateB, SIGNAL(toggled(bool)), m_disableSubtitlesAtStartup, SLOT(setDisabled(bool)));
 
     m_replayGain->setTitle(tr("Use available replay gain"));
     m_replayGainAlbum->setText(tr("Album mode for replay gain"));
@@ -1409,9 +1404,16 @@ void SettingsWidget::setupPlaybackUi(QWidget *PlaybackSettings)
 void SettingsWidget::createRendererSettings()
 {
     const auto currentRendererName = QMPlay2Core.rendererName();
+#ifdef USE_VULKAN
+    m_settings = &QMPlay2Core.getSettings();
+    auto settings = m_settings;
+#else
+    // Qt4/No Vulkan: Use local variable
     auto settings = &QMPlay2Core.getSettings();
+#endif
 
-    auto renderers = new QComboBox;
+    m_renderers = new QComboBox;
+    auto renderers = m_renderers;
     renderers->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred));
     renderers->addItem(tr("Legacy"), "legacy");
 #ifdef USE_OPENGL
@@ -1432,13 +1434,23 @@ void SettingsWidget::createRendererSettings()
         renderers->setItemText(i, renderers->itemText(i) + " (" + (active ? tr("active") : tr("inactive")) + ")");
     }
 
-    auto rendererStacked = new QStackedWidget;
+    m_rendererStacked = new QStackedWidget;
+    auto rendererStacked = m_rendererStacked;
 
+#ifdef USE_VULKAN
+    m_canRestart = std::make_shared<bool>(false);
+    auto canRestart = m_canRestart;
+#else
     auto canRestart = std::make_shared<bool>(false);
+#endif
 
     m_rendererApplyFunctions.push_back([=](bool &initFilters) {
         Q_UNUSED(initFilters)
+#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
         const auto rendererName = renderers->currentData().toString();
+#else
+        const auto rendererName = renderers->itemData(renderers->currentIndex()).toString();
+#endif
         settings->set("Renderer", rendererName);
         if (currentRendererName != rendererName && (*canRestart || chosenRenderer != rendererName))
         {
@@ -1453,10 +1465,10 @@ void SettingsWidget::createRendererSettings()
     };
     auto createBypassCompositor = [] {
         auto bypassCompositor = new QCheckBox(tr("Bypass compositor in full screen"));
-        if (QGuiApplication::platformName() == "xcb")
-        {
-            bypassCompositor->setToolTip(tr("This can improve performance if X11 compositor supports it"));
-        }
+        // Qt4: Use Q_WS_X11 macro
+#ifdef Q_WS_X11
+        bypassCompositor->setToolTip(tr("This can improve performance if X11 compositor supports it"));
+#endif
         return bypassCompositor;
     };
 #endif
@@ -1478,15 +1490,18 @@ void SettingsWidget::createRendererSettings()
     {
         auto openglSettings = new QWidget;
 
-        auto glOnWindow = new QCheckBox(tr("Use OpenGL on entire window"));
-        auto vsync = createVSync();
-        auto bypassCompositor = createBypassCompositor();
+        m_glOnWindow = new QCheckBox(tr("Use OpenGL on entire window"));
+        m_vsyncOpenGL = createVSync();
+        m_bypassCompositorOpenGL = createBypassCompositor();
+        auto glOnWindow = m_glOnWindow;
+        auto vsync = m_vsyncOpenGL;
+        auto bypassCompositor = m_bypassCompositorOpenGL;
 
 #ifdef Q_OS_WIN
         bypassCompositor->setToolTip(tr("This can improve performance. Some video drivers can crash when enabled."));
-#else
-        if (QGuiApplication::platformName() != "xcb")
-            bypassCompositor->setEnabled(false);
+#elif !defined(Q_WS_X11)
+        // Qt4: Not X11, disable bypass compositor
+        bypassCompositor->setEnabled(false);
 #endif
 
         glOnWindow->setToolTip(tr(
@@ -1494,13 +1509,7 @@ void SettingsWidget::createRendererSettings()
             "Use with caution, it can reduce performance of video playback."
         ));
 
-        connect(glOnWindow, &QCheckBox::toggled,
-                this, [=](bool checked) {
-            vsync->setEnabled(!checked);
-#ifdef Q_OS_WIN
-            bypassCompositor->setEnabled(!checked);
-#endif
-        });
+        connect(glOnWindow, SIGNAL(toggled(bool)), this, SLOT(handleGlOnWindowToggled(bool)));
 
         glOnWindow->setChecked(settings->getBool("OpenGL/OnWindow"));
         vsync->setChecked(settings->getBool("OpenGL/VSync"));
@@ -1544,95 +1553,43 @@ void SettingsWidget::createRendererSettings()
     {
         auto vulkanSetttings = new QWidget;
 
-        auto devices = new QComboBox;
-        auto vsync = createVSync();
-        auto gpuDeint = new QCheckBox(tr("Use GPU deinterlacing for CPU-decoded video"));
-        auto forceYadif = new QCheckBox(tr("Force Vulkan Yadif deinterlacing for all hardware decoders"));
-        auto nearestScaling = new QCheckBox(tr("Low quality image scaling (nearest neighbor)"));
-        auto hqDownscale = new QCheckBox(tr("High quality image scaling down"));
-        auto hqUpscale = new QCheckBox(tr("High quality image scaling up"));
-        auto bypassCompositor = createBypassCompositor();
-        auto hdr = new QCheckBox(tr("Try to display HDR10 videos in HDR mode (experimental)"));
-        auto bt2020 = new QCheckBox(tr("Try to display BT.2020 videos in BT.2020 color space (experimental)"));
-
-        auto selectedDeviceFiltersEnabled = std::make_shared<int>(-1);
-
-        connect(nearestScaling, &QCheckBox::toggled,
-                this, [=](bool checked) {
-            hqDownscale->setEnabled(!checked);
-            hqUpscale->setEnabled(!checked);
-        });
-
-#ifdef Q_OS_WIN
-        auto noExclusiveFullScreenDevIDs = std::make_shared<QSet<QByteArray>>();
-#endif
-        auto noFiltersDevIDs = std::make_shared<QSet<QByteArray>>();
-
-        connect(devices, qOverload<int>(&QComboBox::currentIndexChanged),
-                this, [=](int idx) {
-            if (devices->count() <= 1)
-                return;
-            if (idx == 0)
-                idx = 1;
-
-            const bool filtersEnabled = !noFiltersDevIDs->contains(devices->itemData(idx).toByteArray());
-            gpuDeint->setEnabled(filtersEnabled);
-            forceYadif->setEnabled(filtersEnabled);
-
-            if (*selectedDeviceFiltersEnabled == -1)
-                *selectedDeviceFiltersEnabled = filtersEnabled;
+        m_vulkanDevices = new QComboBox;
+        m_vulkanVsync = createVSync();
+        m_gpuDeint = new QCheckBox(tr("Use GPU deinterlacing for CPU-decoded video"));
+        m_forceYadif = new QCheckBox(tr("Force Vulkan Yadif deinterlacing for all hardware decoders"));
+        m_nearestScaling = new QCheckBox(tr("Low quality image scaling (nearest neighbor)"));
+        m_hqDownscale = new QCheckBox(tr("High quality image scaling down"));
+        m_hqUpscale = new QCheckBox(tr("High quality image scaling up"));
+        m_bypassCompositorVulkan = createBypassCompositor();
+        m_hdr = new QCheckBox(tr("Try to display HDR10 videos in HDR mode (experimental)"));
+        m_bt2020 = new QCheckBox(tr("Try to display BT.2020 videos in BT.2020 color space (experimental)"));
+
+        auto devices = m_vulkanDevices;
+        auto vsync = m_vulkanVsync;
+        auto gpuDeint = m_gpuDeint;
+        auto forceYadif = m_forceYadif;
+        auto nearestScaling = m_nearestScaling;
+        auto hqDownscale = m_hqDownscale;
+        auto hqUpscale = m_hqUpscale;
+        auto bypassCompositor = m_bypassCompositorVulkan;
+        auto hdr = m_hdr;
+        auto bt2020 = m_bt2020;
+
+        m_selectedDeviceFiltersEnabled = std::make_shared<int>(-1);
+        auto selectedDeviceFiltersEnabled = m_selectedDeviceFiltersEnabled;
+
+        connect(nearestScaling, SIGNAL(toggled(bool)), this, SLOT(handleNearestScalingToggled(bool)));
 
 #ifdef Q_OS_WIN
-            bypassCompositor->setEnabled(!noExclusiveFullScreenDevIDs->contains(devices->itemData(idx).toByteArray()));
-            hdr->setVisible(QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10);
-            bt2020->setVisible(QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10);
-#else
-            hdr->setVisible(QGuiApplication::platformName().contains("wayland"));
-            bt2020->setVisible(QGuiApplication::platformName().contains("wayland"));
+        m_noExclusiveFullScreenDevIDs = std::make_shared<QSet<QByteArray>>();
+        auto noExclusiveFullScreenDevIDs = m_noExclusiveFullScreenDevIDs;
 #endif
-        });
+        m_noFiltersDevIDs = std::make_shared<QSet<QByteArray>>();
+        auto noFiltersDevIDs = m_noFiltersDevIDs;
 
-        connect(rendererStacked, &QStackedWidget::currentChanged,
-                this, [=](int idx) {
-            if (devices->count() > 0 || idx != renderers->findData("vulkan"))
-                return;
+        connect(devices, SIGNAL(currentIndexChanged(int)), this, SLOT(handleDevicesCurrentIndexChanged(int)));
 
-            const auto storedID = settings->getByteArray("Vulkan/Device");
-            int idIdx = 0;
-
-            devices->blockSignals(true);
-            for (auto &&physicalDevice : QmVk::Instance::enumerateSupportedPhysicalDevices())
-            {
-                const auto &properties = physicalDevice->properties();
-                const auto id = QmVk::Instance::getPhysicalDeviceID(properties);
-#ifdef Q_OS_WIN
-                if (bypassCompositor->isEnabled())
-                {
-                    if (!physicalDevice->checkExtension(VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME))
-                        noExclusiveFullScreenDevIDs->insert(id);
-                }
-#endif
-                if (!QmVk::Instance::checkFiltersSupported(physicalDevice))
-                    noFiltersDevIDs->insert(id);
-                devices->addItem(static_cast<const char *>(properties.deviceName), id);
-                if (idIdx == 0 && !storedID.isEmpty() && storedID == id)
-                    idIdx = devices->count();
-            }
-            devices->setCurrentIndex(-1);
-            devices->blockSignals(false);
-
-            if (devices->count() > 0)
-            {
-                devices->insertItem(0, tr("First available device"));
-                devices->setCurrentIndex(idIdx);
-                *canRestart = true;
-            }
-            else
-            {
-                devices->addItem(tr("No supported devices found"));
-                vulkanSetttings->setEnabled(false);
-            }
-        });
+        connect(rendererStacked, SIGNAL(currentChanged(int)), this, SLOT(handleRendererStackedCurrentChanged(int)));
 
         vsync->setTristate(true);
         vsync->setToolTip(tr(
@@ -1643,9 +1600,9 @@ void SettingsWidget::createRendererSettings()
 
 #ifdef Q_OS_WIN
         bypassCompositor->setToolTip(tr("Allow for exclusive fullscreen. This can improve performance."));
-#else
-        if (QGuiApplication::platformName() != "xcb")
-            bypassCompositor->setEnabled(false);
+#elif !defined(Q_WS_X11)
+        // Qt4: Not X11, disable bypass compositor
+        bypassCompositor->setEnabled(false);
 #endif
 
         vsync->setCheckState(settings->getWithBounds("Vulkan/VSync", Qt::Unchecked, Qt::Checked));
@@ -1683,7 +1640,11 @@ void SettingsWidget::createRendererSettings()
 
             if (devices->isEnabled() && devices->count() > 0)
             {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
                 const auto vulkanDeviceID = devices->currentData().toByteArray();
+#else
+                const auto vulkanDeviceID = devices->itemData(devices->currentIndex()).toByteArray();
+#endif
                 if (vulkanDeviceID != settings->getByteArray("Vulkan/Device"))
                 {
                     settings->set("Vulkan/Device", vulkanDeviceID.constData());
@@ -1700,7 +1661,11 @@ void SettingsWidget::createRendererSettings()
             settings->set("Vulkan/BypassCompositor", bypassCompositor->isChecked());
             settings->set("Vulkan/HDR", hdr->isChecked());
             settings->set("Vulkan/BT2020", bt2020->isChecked());
+#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
             if (renderers->currentData().toString() == "vulkan")
+#else
+            if (renderers->itemData(renderers->currentIndex()).toString() == "vulkan")
+#endif
                 settings->set("Vulkan/UserApplied", true);
 
             *selectedDeviceFiltersEnabled = filtersEnabled;
@@ -1710,10 +1675,7 @@ void SettingsWidget::createRendererSettings()
     }
 #endif
 
-    connect(renderers, qOverload<int>(&QComboBox::currentIndexChanged),
-            this, [=](int idx) {
-        rendererStacked->setCurrentIndex(idx);
-    });
+    connect(renderers, SIGNAL(currentIndexChanged(int)), this, SLOT(handleRenderersCurrentIndexChanged(int)));
 
     if (activeRenderer > -1)
     {
@@ -2244,3 +2206,166 @@ void SettingsWidget::removeProfile()
             }
     }
 }
+
+// Qt4: Helper slot implementations
+
+void SettingsWidget::handleCookiesFromBrowserToggled(bool checked)
+{
+    m_cookiesFromBrowserE->setEnabled(checked);
+}
+
+void SettingsWidget::handleCustomYtDlToggled(bool checked)
+{
+    m_customYtDlE->setEnabled(checked);
+    m_customYtDlB->setEnabled(checked);
+    m_removeYtDlB->setEnabled(!checked);
+}
+
+void SettingsWidget::handleCustomYtDlBrowseClicked()
+{
+    auto path = QFileDialog::getOpenFileName(this, tr("Choose youtube-dl script or executable"), m_customYtDlE->text());
+    if (!path.isEmpty())
+        m_customYtDlE->setText(path);
+}
+
+void SettingsWidget::handleDfltYtDlQualToggled(bool checked)
+{
+    m_dfltYtDlQualE->setEnabled(checked);
+}
+
+void SettingsWidget::handleAdditionalYtDlParamsToggled(bool checked)
+{
+    m_additionalYtDlParamsE->setEnabled(checked);
+}
+
+void SettingsWidget::handleCustomYtDlToggledForAutoUpdate(bool checked)
+{
+    m_dontUpdateYtDlCB->setChecked(checked);
+}
+
+void SettingsWidget::handleStoreARatioAndZoomToggled(bool checked)
+{
+    if (checked)
+    {
+        m_keepZoom->setChecked(true);
+        m_keepARatio->setChecked(true);
+    }
+}
+
+void SettingsWidget::handleKeepZoomToggled(bool checked)
+{
+    if (!checked && !m_keepARatio->isChecked())
+    {
+        m_storeARatioAndZoomB->setChecked(false);
+    }
+}
+
+void SettingsWidget::handleKeepARatioToggled(bool checked)
+{
+    if (!checked && !m_keepZoom->isChecked())
+    {
+        m_storeARatioAndZoomB->setChecked(false);
+    }
+}
+
+void SettingsWidget::handleGlOnWindowToggled(bool checked)
+{
+#ifdef USE_OPENGL
+    m_vsyncOpenGL->setEnabled(!checked);
+#ifdef Q_OS_WIN
+    m_bypassCompositorOpenGL->setEnabled(!checked);
+#endif
+#else
+    Q_UNUSED(checked)
+#endif
+}
+
+void SettingsWidget::handleNearestScalingToggled(bool checked)
+{
+#ifdef USE_VULKAN
+    m_hqDownscale->setEnabled(!checked);
+    m_hqUpscale->setEnabled(!checked);
+#else
+    Q_UNUSED(checked)
+#endif
+}
+
+void SettingsWidget::handleDevicesCurrentIndexChanged(int idx)
+{
+#ifdef USE_VULKAN
+    if (m_vulkanDevices->count() <= 1)
+        return;
+    if (idx == 0)
+        idx = 1;
+
+    const bool filtersEnabled = !m_noFiltersDevIDs->contains(m_vulkanDevices->itemData(idx).toByteArray());
+    m_gpuDeint->setEnabled(filtersEnabled);
+    m_forceYadif->setEnabled(filtersEnabled);
+
+    if (*m_selectedDeviceFiltersEnabled == -1)
+        *m_selectedDeviceFiltersEnabled = filtersEnabled;
+
+#ifdef Q_OS_WIN
+    m_bypassCompositorVulkan->setEnabled(!m_noExclusiveFullScreenDevIDs->contains(m_vulkanDevices->itemData(idx).toByteArray()));
+    m_hdr->setVisible(QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10);
+    m_bt2020->setVisible(QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10);
+#else
+    // Qt4: No Wayland support
+    m_hdr->setVisible(false);
+    m_bt2020->setVisible(false);
+#endif
+#else
+    Q_UNUSED(idx)
+#endif
+}
+
+void SettingsWidget::handleRendererStackedCurrentChanged(int idx)
+{
+#ifdef USE_VULKAN
+    if (m_vulkanDevices->count() > 0 || idx != m_renderers->findData("vulkan").toInt())
+        return;
+
+    const auto storedID = m_settings->getByteArray("Vulkan/Device");
+    int idIdx = 0;
+
+    m_vulkanDevices->blockSignals(true);
+    for (auto &&physicalDevice : QmVk::Instance::enumerateSupportedPhysicalDevices())
+    {
+        const auto &properties = physicalDevice->properties();
+        const auto id = QmVk::Instance::getPhysicalDeviceID(properties);
+#ifdef Q_OS_WIN
+        if (m_bypassCompositorVulkan->isEnabled())
+        {
+            if (!physicalDevice->checkExtension(VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME))
+                m_noExclusiveFullScreenDevIDs->insert(id);
+        }
+#endif
+        if (!QmVk::Instance::checkFiltersSupported(physicalDevice))
+            m_noFiltersDevIDs->insert(id);
+        m_vulkanDevices->addItem(static_cast<const char *>(properties.deviceName), id);
+        if (idIdx == 0 && !storedID.isEmpty() && storedID == id)
+            idIdx = m_vulkanDevices->count();
+    }
+    m_vulkanDevices->setCurrentIndex(-1);
+    m_vulkanDevices->blockSignals(false);
+
+    if (m_vulkanDevices->count() > 0)
+    {
+        m_vulkanDevices->insertItem(0, tr("First available device"));
+        m_vulkanDevices->setCurrentIndex(idIdx);
+        *m_canRestart = true;
+    }
+    else
+    {
+        m_vulkanDevices->addItem(tr("No supported devices found"));
+        // Note: Cannot access vulkanSetttings here, but it will be disabled by the count check
+    }
+#else
+    Q_UNUSED(idx)
+#endif
+}
+
+void SettingsWidget::handleRenderersCurrentIndexChanged(int idx)
+{
+    m_rendererStacked->setCurrentIndex(idx);
+}
diff --git a/src/gui/SettingsWidget.hpp b/src/gui/SettingsWidget.hpp
index f1f8d2e4..a01e2e9d 100644
--- a/src/gui/SettingsWidget.hpp
+++ b/src/gui/SettingsWidget.hpp
@@ -36,6 +36,9 @@ class QPushButton;
 class QToolButton;
 class QListWidget;
 class QTabWidget;
+class QStackedWidget;
+class QCheckBox;
+class QComboBox;
 class Page3;
 class Page4;
 class Page5;
@@ -50,7 +53,7 @@ struct UiModulesList
     QToolButton *moveDown;
 };
 
-class SettingsWidget final : public QWidget
+class SettingsWidget : public QWidget
 {
     Q_OBJECT
 public:
@@ -79,8 +82,8 @@ private:
 
     inline QString getSelectedProfile();
 
-    void showEvent(QShowEvent *) override;
-    void closeEvent(QCloseEvent *) override;
+    void showEvent(QShowEvent *);
+    void closeEvent(QCloseEvent *);
 
 private:
     UiModulesList modulesList[3];
@@ -200,6 +203,32 @@ private:
 
     std::function<void()> m_setRenderersCurrentIndexFn;
     std::vector<std::function<void(bool &initFilters)>> m_rendererApplyFunctions;
+
+    // Qt4: Helper pointers for renderer settings
+    QCheckBox *m_glOnWindow;
+    QCheckBox *m_vsyncOpenGL;
+    QCheckBox *m_bypassCompositorOpenGL;
+    QCheckBox *m_nearestScaling;
+    QCheckBox *m_hqDownscale;
+    QCheckBox *m_hqUpscale;
+    QComboBox *m_vulkanDevices;
+    QStackedWidget *m_rendererStacked;
+    QComboBox *m_renderers;
+    QCheckBox *m_vulkanVsync;
+    QCheckBox *m_gpuDeint;
+    QCheckBox *m_forceYadif;
+    QCheckBox *m_bypassCompositorVulkan;
+    QCheckBox *m_hdr;
+    QCheckBox *m_bt2020;
+#ifdef USE_VULKAN
+    std::shared_ptr<int> m_selectedDeviceFiltersEnabled;
+#ifdef Q_OS_WIN
+    std::shared_ptr<QSet<QByteArray>> m_noExclusiveFullScreenDevIDs;
+#endif
+    std::shared_ptr<QSet<QByteArray>> m_noFiltersDevIDs;
+    Settings *m_settings;
+    std::shared_ptr<bool> m_canRestart;
+#endif
 private slots:
     void chStyle();
     void apply();
@@ -208,20 +237,36 @@ private slots:
     void openModuleSettings(QListWidgetItem *);
     void moveModule();
     void chooseScreenshotDir();
-private:
+private slots:
     void chooseOutputFileDir();
 private slots:
     void setAppearance();
     void setKeyBindings();
     void clearCoversCache();
 #ifdef USE_YOUTUBEDL
-private:
+private slots:
     void removeYouTubeDl();
 private slots:
 #endif
     void resetSettings();
     void profileListIndexChanged(int index);
     void removeProfile();
+
+    // Qt4: Helper slots for lambda conversions
+    void handleCookiesFromBrowserToggled(bool checked);
+    void handleCustomYtDlToggled(bool checked);
+    void handleCustomYtDlBrowseClicked();
+    void handleDfltYtDlQualToggled(bool checked);
+    void handleAdditionalYtDlParamsToggled(bool checked);
+    void handleCustomYtDlToggledForAutoUpdate(bool checked);
+    void handleStoreARatioAndZoomToggled(bool checked);
+    void handleKeepZoomToggled(bool checked);
+    void handleKeepARatioToggled(bool checked);
+    void handleGlOnWindowToggled(bool checked);
+    void handleNearestScalingToggled(bool checked);
+    void handleDevicesCurrentIndexChanged(int idx);
+    void handleRendererStackedCurrentChanged(int idx);
+    void handleRenderersCurrentIndexChanged(int idx);
 signals:
     void settingsChanged(int, bool, bool initFilters);
     void setWheelStep(int);
diff --git a/src/gui/ShortcutHandler.hpp b/src/gui/ShortcutHandler.hpp
index 097839db..1ec3f39e 100644
--- a/src/gui/ShortcutHandler.hpp
+++ b/src/gui/ShortcutHandler.hpp
@@ -23,7 +23,7 @@
 
 class QAction;
 
-class ShortcutHandler final : public QAbstractTableModel
+class ShortcutHandler : public QAbstractTableModel
 {
     Q_DECLARE_TR_FUNCTIONS(ShortcutHandler)
 
@@ -31,15 +31,15 @@ public:
     ShortcutHandler(QObject *parent);
     ~ShortcutHandler();
 
-    int columnCount(const QModelIndex &parent) const override;
-    int rowCount(const QModelIndex &parent) const override;
+    int columnCount(const QModelIndex &parent) const;
+    int rowCount(const QModelIndex &parent) const;
 
-    Qt::ItemFlags flags(const QModelIndex &index) const override;
+    Qt::ItemFlags flags(const QModelIndex &index) const;
 
-    QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
+    QVariant headerData(int section, Qt::Orientation orientation, int role) const;
 
-    QVariant data(const QModelIndex &index, int role) const override;
-    bool setData(const QModelIndex &index, const QVariant &value, int role) override;
+    QVariant data(const QModelIndex &index, int role) const;
+    bool setData(const QModelIndex &index, const QVariant &value, int role);
 
     void appendAction(QAction *action, const QString &settingsName, const QString &defaultShortcut);
 
diff --git a/src/gui/TagEditor.cpp b/src/gui/TagEditor.cpp
index 68790595..3749fbc6 100644
--- a/src/gui/TagEditor.cpp
+++ b/src/gui/TagEditor.cpp
@@ -98,6 +98,7 @@ static inline Ogg::XiphComment *getXiphComment(File &file)
 #include <QBoxLayout>
 #include <QLineEdit>
 #include <QMimeData>
+#include <QUrl>
 #include <QSpinBox>
 #include <QPainter>
 #include <qevent.h>
@@ -201,12 +202,10 @@ TagEditor::TagEditor() :
     saveImgB = new QPushButton(tr("Save cover picture"));
     saveImgB->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
 
-    connect(pictureW, &PictureW::loadImage, this, &TagEditor::loadImage);
+    connect(pictureW, SIGNAL(loadImage(const QString &)), this, SLOT(loadImage(const QString &)));
 
-    connect(loadImgB, &QPushButton::clicked, this, [this] {
-        loadImage(QString());
-    });
-    connect(saveImgB, &QPushButton::clicked, this, &TagEditor::saveImage);
+    connect(loadImgB, SIGNAL(clicked()), this, SLOT(handleLoadImageClicked()));
+    connect(saveImgB, SIGNAL(clicked()), this, SLOT(saveImage()));
 
     QVBoxLayout *pictureLayout = new QVBoxLayout(pictureB);
     pictureLayout->addWidget(pictureW);
@@ -665,3 +664,9 @@ void TagEditor::clearValues()
     pictureB->setChecked(false);
     pictureB->setEnabled(false);
 }
+
+// Qt4: Helper slot implementation
+void TagEditor::handleLoadImageClicked()
+{
+    loadImage(QString());
+}
diff --git a/src/gui/TagEditor.hpp b/src/gui/TagEditor.hpp
index d63af76e..c1569343 100644
--- a/src/gui/TagEditor.hpp
+++ b/src/gui/TagEditor.hpp
@@ -31,7 +31,7 @@ class QMimeData;
 class QSpinBox;
 class QLabel;
 
-class PictureW final : public QWidget
+class PictureW : public QWidget
 {
     Q_OBJECT
 
@@ -42,10 +42,10 @@ private:
     bool verifyMimeData(const QMimeData *m) const;
 
 private:
-    void paintEvent(QPaintEvent *) override;
+    void paintEvent(QPaintEvent *);
 
-    void dragEnterEvent(QDragEnterEvent *e) override;
-    void dropEvent(QDropEvent *e) override;
+    void dragEnterEvent(QDragEnterEvent *e);
+    void dropEvent(QDropEvent *e);
 
 signals:
     void loadImage(const QString &filePath);
@@ -65,9 +65,11 @@ public:
     void clear();
     bool save();
 
-private:
+private slots:
     void loadImage(QString filePath);
     void saveImage();
+    // Qt4: Helper slot for lambda conversion
+    void handleLoadImageClicked();
 
 private:
     void clearValues();
diff --git a/src/gui/Unix/ScreenSaver.cpp b/src/gui/Unix/ScreenSaver.cpp
index 8953f8ed..660c8ead 100644
--- a/src/gui/Unix/ScreenSaver.cpp
+++ b/src/gui/Unix/ScreenSaver.cpp
@@ -18,7 +18,6 @@
 
 #include <ScreenSaver.hpp>
 
-#include <QGuiApplication>
 #include <QLibrary>
 
 #ifdef DBUS_PM
@@ -41,13 +40,13 @@ public:
     ScreenSaverPrivX11() :
         m_disp(nullptr)
     {}
-    ~ScreenSaverPrivX11() override
+    ~ScreenSaverPrivX11()
     {
         if (m_disp)
             XCloseDisplayFunc(m_disp);
     }
 
-    bool load() override
+    bool load()
     {
         QLibrary libX11("libX11.so.6");
         if (libX11.load())
@@ -66,18 +65,18 @@ public:
         return false;
     }
 
-    void inhibit() override
+    void inhibit()
     {
         timerEvent(nullptr);
         m_timerID = startTimer(30000);
     }
-    void unInhibit() override
+    void unInhibit()
     {
         killTimer(m_timerID);
     }
 
 private:
-    void timerEvent(QTimerEvent *) override
+    void timerEvent(QTimerEvent *)
     {
         XForceScreenSaverFunc(m_disp, 0);
         XFlushFunc(m_disp);
@@ -106,23 +105,23 @@ public:
     ScreenSaverPrivDBus()
         : m_iface("org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver")
     {}
-    ~ScreenSaverPrivDBus() override
+    ~ScreenSaverPrivDBus()
     {
     }
 
-    bool load() override
+    bool load()
     {
         return !m_iface.lastError().isValid();
     }
 
-    void inhibit() override
+    void inhibit()
     {
         bool ok = false;
         quint32 cookie = m_iface.call("Inhibit", QCoreApplication::applicationName(), "Playback").arguments().value(0).toUInt(&ok);
         if (ok)
             m_cookie = cookie;
     }
-    void unInhibit() override
+    void unInhibit()
     {
         if (Q_UNLIKELY(m_cookie == 0))
             return;
@@ -151,15 +150,15 @@ ScreenSaver::ScreenSaver()
     m_priv = nullptr;
 #endif
 
-    if (QGuiApplication::platformName() != "xcb")
-        return;
-
+    // Qt4: Use Q_WS_X11 instead of platformName check
+#ifdef Q_WS_X11
     m_priv = new ScreenSaverPrivX11;
     if (m_priv->load())
         return;
 
     delete m_priv;
     m_priv = nullptr;
+#endif
 }
 ScreenSaver::~ScreenSaver()
 {
diff --git a/src/gui/Updater.hpp b/src/gui/Updater.hpp
index 185420cf..4a55d0ea 100644
--- a/src/gui/Updater.hpp
+++ b/src/gui/Updater.hpp
@@ -30,7 +30,7 @@
     class QLabel;
 #endif
 
-class Updater final : public
+class Updater : public
 #ifdef UPDATER
     QDialog
 #else
diff --git a/src/gui/VideoAdjustmentW.cpp b/src/gui/VideoAdjustmentW.cpp
index faaa52e8..1cb020ed 100644
--- a/src/gui/VideoAdjustmentW.cpp
+++ b/src/gui/VideoAdjustmentW.cpp
@@ -27,6 +27,7 @@
 
 #include <QGridLayout>
 #include <QPushButton>
+#include <QSignalMapper>
 #include <QAction>
 #include <QLabel>
 
@@ -58,8 +59,12 @@ VideoAdjustmentW::VideoAdjustmentW()
     QGridLayout *layout = new QGridLayout;
 
     m_sliders.reserve(CONTROLS_COUNT);
+    m_sliderWidgets.reserve(CONTROLS_COUNT);
     m_actions.reserve(CONTROLS_COUNT);
 
+    m_sliderMapper = new QSignalMapper(this);
+    connect(m_sliderMapper, SIGNAL(mapped(int)), this, SLOT(onSliderValueChanged(int)));
+
     for (int i = 0; i < CONTROLS_COUNT; ++i)
     {
         QLabel *titleL = new QLabel(tr(g_controlsNames[i]) + ": ");
@@ -81,26 +86,27 @@ VideoAdjustmentW::VideoAdjustmentW()
             slider->setRange(-100, 100);
         }
         slider->setWheelStep(1);
-        connect(slider, &Slider::valueChanged, this, [=](int v) {
-            const bool isGammaControl = (i == GAMMA);
-            const QString displayVal = isGammaControl
-                ? QString::number(Functions::sliderValueToGamma(v), 'f', 2)
-                : QString::number(v);
-            valueL->setText(displayVal);
-            emit videoAdjustmentChanged((isGammaControl ? tr("Gamma correction: ") : titleL->text()) + displayVal);
-        });
         slider->setValue(0);
+
+        SliderWidgets widgets;
+        widgets.slider = slider;
+        widgets.valueL = valueL;
+        widgets.titleL = titleL;
+        m_sliderWidgets.push_back(widgets);
         m_sliders.push_back(slider);
 
+        connect(slider, SIGNAL(valueChanged(int)), m_sliderMapper, SLOT(map()));
+        m_sliderMapper->setMapping(slider, i);
+
         QAction *actionDown = new QAction(this);
-        connect(actionDown, &QAction::triggered, this, [=] {
-            slider->setValue(slider->value() - g_step);
-        });
+        actionDown->setData(i); // Store slider index
+        actionDown->setProperty("direction", -1); // -1 for down
+        connect(actionDown, SIGNAL(triggered()), this, SLOT(onAdjustmentActionTriggered()));
 
         QAction *actionUp = new QAction(this);
-        connect(actionUp, &QAction::triggered, this, [=] {
-            slider->setValue(slider->value() + g_step);
-        });
+        actionUp->setData(i); // Store slider index
+        actionUp->setProperty("direction", 1); // 1 for up
+        connect(actionUp, SIGNAL(triggered()), this, SLOT(onAdjustmentActionTriggered()));
 
         m_actions.push_back({actionDown, actionUp});
 
@@ -110,13 +116,10 @@ VideoAdjustmentW::VideoAdjustmentW()
     }
 
     QPushButton *resetB = new QPushButton(tr("Reset"));
-    connect(resetB, &QPushButton::clicked, this, [this] {
-        for (int i = 0; i < CONTROLS_COUNT; ++i)
-            m_sliders[i]->setValue(0);
-    });
+    connect(resetB, SIGNAL(clicked()), this, SLOT(onResetClicked()));
 
     m_resetAction = new QAction(tr("Reset video adjustments"), this);
-    connect(m_resetAction, &QAction::triggered, resetB, &QPushButton::click);
+    connect(m_resetAction, SIGNAL(triggered()), this, SLOT(onResetClicked()));
 
     layout->addWidget(resetB, layout->rowCount(), 0, 1, 3);
     layout->addItem(new QSpacerItem(40, 0, QSizePolicy::Maximum, QSizePolicy::Minimum), layout->rowCount(), 2);
@@ -194,3 +197,39 @@ void VideoAdjustmentW::addActionsToWidget(QWidget *w)
     }
     w->addAction(m_resetAction);
 }
+
+void VideoAdjustmentW::onSliderValueChanged(int idx)
+{
+    if (idx >= 0 && idx < static_cast<int>(m_sliderWidgets.size()))
+    {
+        const SliderWidgets &widgets = m_sliderWidgets[idx];
+        const int v = widgets.slider->value();
+        const bool isGammaControl = (idx == GAMMA);
+        const QString displayVal = isGammaControl
+            ? QString::number(Functions::sliderValueToGamma(v), 'f', 2)
+            : QString::number(v);
+        widgets.valueL->setText(displayVal);
+        emit videoAdjustmentChanged((isGammaControl ? tr("Gamma correction: ") : widgets.titleL->text()) + displayVal);
+    }
+}
+
+void VideoAdjustmentW::onAdjustmentActionTriggered()
+{
+    QAction *act = qobject_cast<QAction*>(sender());
+    if (act)
+    {
+        int idx = act->data().toInt();
+        int direction = act->property("direction").toInt();
+        if (idx >= 0 && idx < static_cast<int>(m_sliders.size()))
+        {
+            Slider *slider = m_sliders[idx];
+            slider->setValue(slider->value() + direction * g_step);
+        }
+    }
+}
+
+void VideoAdjustmentW::onResetClicked()
+{
+    for (int i = 0; i < CONTROLS_COUNT; ++i)
+        m_sliders[i]->setValue(0);
+}
diff --git a/src/gui/VideoAdjustmentW.hpp b/src/gui/VideoAdjustmentW.hpp
index 980715c8..6aee7a45 100644
--- a/src/gui/VideoAdjustmentW.hpp
+++ b/src/gui/VideoAdjustmentW.hpp
@@ -26,8 +26,10 @@
 class ModuleParams;
 class QAction;
 class Slider;
+class QLabel;
+class QSignalMapper;
 
-class VideoAdjustmentW final : public QWidget
+class VideoAdjustmentW : public QWidget
 {
     Q_OBJECT
 
@@ -47,8 +49,21 @@ public:
 signals:
     void videoAdjustmentChanged(const QString &osdText);
 
+private slots:
+    void onSliderValueChanged(int idx);
+    void onAdjustmentActionTriggered();
+    void onResetClicked();
+
 private:
+    struct SliderWidgets {
+        Slider *slider;
+        QLabel *valueL;
+        QLabel *titleL;
+    };
+
     std::vector<Slider *> m_sliders;
+    std::vector<SliderWidgets> m_sliderWidgets;
     std::vector<std::array<QAction *, 2>> m_actions;
     QAction *m_resetAction = nullptr;
+    QSignalMapper *m_sliderMapper = nullptr;
 };
diff --git a/src/gui/VideoDock.cpp b/src/gui/VideoDock.cpp
index c2ab8205..a131365b 100644
--- a/src/gui/VideoDock.cpp
+++ b/src/gui/VideoDock.cpp
@@ -26,7 +26,6 @@
 #include <Functions.hpp>
 #include <SubsDec.hpp>
 
-#include <QVersionNumber>
 #include <QMouseEvent>
 #include <QFileInfo>
 #include <QMimeData>
@@ -85,45 +84,43 @@ VideoDock::VideoDock() :
     connect(&hideCursorTim, SIGNAL(timeout()), this, SLOT(hideCursor()));
     connect(&leftButtonPlayTim, SIGNAL(timeout()), QMPlay2GUI.menuBar->player->togglePlay, SLOT(trigger()));
     connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(popup(const QPoint &)));
-    connect(&iDW, &InDockW::resized, this, &VideoDock::resized);
+    // Qt4: Use SIGNAL/SLOT syntax
+    connect(&iDW, SIGNAL(resized(const QSize &)), this, SIGNAL(resized(const QSize &)));
     connect(&iDW, SIGNAL(hasCoverImage(bool)), this, SLOT(hasCoverImage(bool)));
     connect(this, SIGNAL(dockVisibilityChanged(bool)), this, SLOT(visibilityChanged(bool)));
-    connect(&QMPlay2Core, &QMPlay2CoreClass::dockVideo, this, [this](QWidget *w) {
-        iDW.setWidget(w);
-        mouseMoveEvent(nullptr);
-    });
+    // Qt4: Use helper slot instead of lambda
+    connect(&QMPlay2Core, SIGNAL(dockVideo(QWidget*)), this, SLOT(handleDockVideo(QWidget*)));
 
     canHideIDWCursor = false;
     doubleClicked = false;
 }
 
+// Qt4: Helper slot implementation
+void VideoDock::handleDockVideo(QWidget *w)
+{
+    iDW.setWidget(w);
+    mouseMoveEvent(nullptr);
+}
+
 void VideoDock::scheduleEnterEventWorkaround()
 {
-    if (QVersionNumber::fromString(qVersion()) >= QVersionNumber(5, 12, 0))
-    {
-        // Something is wrong with enter/leave events after going full screen  or maximized in some configurations since Qt 5.12.
-        // Create a new temporary widget which fills entire parent widget to trigger enter/leave events.
-        QTimer::singleShot(0, this, [this] {
-            QTimer::singleShot(0, this, [this] {
-                if (!underMouse())
-                {
-                    QWidget tmp(this);
-                    tmp.setGeometry(rect());
-                    tmp.show();
-                }
-            });
-        });
-    }
+    // Qt4: This workaround was only needed for Qt 5.12+, not needed here
 }
 void VideoDock::maybeTriggerWidgetVisibility()
 {
     if (auto w = iDW.getWidget(); w && w->property("loseHeight").isValid())
     {
         // Hide/show widget to not hide mouse cursor outside the window when native widgets are not used (workaround)
+#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
         QTimer::singleShot(0, w, [w] {
             w->setVisible(false);
             w->setVisible(true);
         });
+#else
+        // Qt4: Call directly since QWidget doesn't have slots for setVisible
+        w->setVisible(false);
+        w->setVisible(true);
+#endif
     }
 }
 
@@ -246,7 +243,7 @@ void VideoDock::mouseDoubleClickEvent(QMouseEvent *e)
 {
     if (e->buttons() == Qt::LeftButton)
     {
-#ifndef Q_OS_MACOS
+#ifndef Q_OS_MAC
         QMPlay2GUI.menuBar->window->toggleFullScreen->trigger();
 #else
         // On macOS if full screen is toggled to fast after double click, mouse remains in clicked state...
@@ -301,7 +298,12 @@ void VideoDock::moveEvent(QMoveEvent *e)
 }
 void VideoDock::wheelEvent(QWheelEvent *e)
 {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     const auto deltaY  = e->angleDelta().y();
+#else
+    // Qt4: Use delta() which returns an int
+    const int deltaY = e->delta();
+#endif
     auto player = QMPlay2GUI.menuBar->player;
     if (deltaY != 0 && (e->buttons() & Qt::LeftButton))
         deltaY > 0 ? player->zoom->zoomIn->trigger() : player->zoom->zoomOut->trigger();
diff --git a/src/gui/VideoDock.hpp b/src/gui/VideoDock.hpp
index 3a454e4a..77fe88f5 100644
--- a/src/gui/VideoDock.hpp
+++ b/src/gui/VideoDock.hpp
@@ -26,7 +26,7 @@
 
 class QMenu;
 
-class VideoDock final : public DockWidget
+class VideoDock : public DockWidget
 {
     Q_OBJECT
 public:
@@ -52,17 +52,17 @@ private:
 
     void unsetCursor(QWidget *w);
 
-    void dragEnterEvent(QDragEnterEvent *) override;
-    void dropEvent(QDropEvent *) override;
-    void mouseMoveEvent(QMouseEvent *) override;
-    void mouseDoubleClickEvent(QMouseEvent *) override;
-    void mousePressEvent(QMouseEvent *) override;
-    void mouseReleaseEvent(QMouseEvent *) override;
-    void moveEvent(QMoveEvent *) override;
-    void wheelEvent(QWheelEvent *) override;
-    void leaveEvent(QEvent *) override;
-    void enterEvent(Q_ENTER_EVENT *) override;
-    bool event(QEvent *) override;
+    void dragEnterEvent(QDragEnterEvent *);
+    void dropEvent(QDropEvent *);
+    void mouseMoveEvent(QMouseEvent *);
+    void mouseDoubleClickEvent(QMouseEvent *);
+    void mousePressEvent(QMouseEvent *);
+    void mouseReleaseEvent(QMouseEvent *);
+    void moveEvent(QMoveEvent *);
+    void wheelEvent(QWheelEvent *);
+    void leaveEvent(QEvent *);
+    void enterEvent(Q_ENTER_EVENT *);
+    bool event(QEvent *);
 
     QTimer hideCursorTim, leftButtonPlayTim;
     InDockW iDW;
@@ -79,6 +79,8 @@ private slots:
     void updateImage(const QImage &);
     void visibilityChanged(bool);
     void hasCoverImage(bool);
+    // Qt4: Helper slot for dockVideo signal
+    void handleDockVideo(QWidget *w);
 signals:
     void resized(const QSize &size);
     void itemDropped(const QString &, bool);
diff --git a/src/gui/VideoThr.cpp b/src/gui/VideoThr.cpp
index 132f5b17..4945a7d1 100644
--- a/src/gui/VideoThr.cpp
+++ b/src/gui/VideoThr.cpp
@@ -424,9 +424,15 @@ void VideoThr::run()
 
         if (doScreenshot && !videoFrame.isEmpty())
         {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
             QTimer::singleShot(0, this, [=] {
                 screenshot(videoFrame);
             });
+#else
+            // Qt4: Store frame and use helper slot
+            m_screenshotFrame = videoFrame;
+            QTimer::singleShot(0, this, SLOT(handleScreenshot()));
+#endif
             doScreenshot = false;
         }
 
@@ -440,9 +446,14 @@ void VideoThr::run()
         {
             if (playC.paused && !paused)
             {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
                 QTimer::singleShot(0, this, [this] {
                     pause();
                 });
+#else
+                // Qt4: Use helper slot
+                QTimer::singleShot(0, this, SLOT(handlePause()));
+#endif
                 paused = true;
                 frame_timer = -1.0;
                 emit playC.updateBitrateAndFPS(-1, -1, -1.0, 0.0, interlaced); //Set real FPS to 0 on pause
@@ -463,7 +474,7 @@ void VideoThr::run()
         paused = waiting = false;
 
         Packet packet;
-        double ts = qQNaN();
+        double ts = std::numeric_limits<double>::quiet_NaN();
         if (hasVPackets && mustFetchNewPacket)
         {
             packet = playC.vPackets.fetch();
@@ -507,7 +518,7 @@ void VideoThr::run()
             Frame decoded;
             AVPixelFormat newPixelFormat = AV_PIX_FMT_NONE;
             const int bytes_consumed = dec->decodeVideo(packet, decoded, newPixelFormat, flushVideo || skipNonKey, (skip && !skipNonKey) ? ~0 : (fast >> 1));
-            ts = decoded.isTsValid() ? decoded.ts() : qQNaN();
+            ts = decoded.isTsValid() ? decoded.ts() : std::numeric_limits<double>::quiet_NaN();
             if (newPixelFormat != AV_PIX_FMT_NONE)
                 emit playC.pixelFormatUpdate(newPixelFormat);
             if (flushVideo)
@@ -568,7 +579,7 @@ void VideoThr::run()
             ts = videoFrame.ts();
         filtersMutex.unlock();
 
-        const bool tsIsNotNan = !qIsNaN(ts);
+        const bool tsIsNotNan = !std::isnan(ts);
 
         /* Subtitles */
         QMPlay2OSDList osdList;
@@ -576,7 +587,7 @@ void VideoThr::run()
         playC.subsMutex.lock();
         const double subsPts = tsIsNotNan
             ? ts - playC.subtitlesSync
-            : qQNaN()
+            : std::numeric_limits<double>::quiet_NaN()
         ;
         const bool canDeleteSubs = (deleteSubs && m_subtitles);
         auto resetSubs = [this] {
@@ -592,7 +603,7 @@ void VideoThr::run()
         }
         else if (playC.ass)
         {
-            for (auto &&sPacket : std::as_const(sPackets))
+            for (auto &&sPacket : sPackets)
             {
                 if (sDec && m_decodeToAss)
                 {
@@ -702,7 +713,7 @@ void VideoThr::run()
 
             if (tmp_time >= 1.0)
             {
-                emit playC.updateBitrateAndFPS(-1, dec->isDummyDecoder() ? -1 : round((tmp_br << 3) / (tmp_time * 1000.0)), frames / tmp_time, canSkipFrames ? framesDisplayed / framesDisplayedTime : qQNaN(), interlaced);
+                emit playC.updateBitrateAndFPS(-1, dec->isDummyDecoder() ? -1 : round((tmp_br << 3) / (tmp_time * 1000.0)), frames / tmp_time, canSkipFrames ? framesDisplayed / framesDisplayedTime : std::numeric_limits<double>::quiet_NaN(), interlaced);
                 frames = tmp_br = framesDisplayed = 0;
                 tmp_time = framesDisplayedTime = 0.0;
             }
@@ -812,10 +823,18 @@ void VideoThr::run()
                     oneFrame = canWrite = false;
                     if (!osdList.isEmpty())
                         m_subsDisplayLocker = unique_lock<std::mutex>(m_subsDisplayMutex);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
                     QTimer::singleShot(0, this, [=, osdList = std::move(osdList)]() mutable {
                         write(videoFrame, std::move(osdList), seq);
                         m_subsDisplayLocker = {};
                     });
+#else
+                    // Qt4: Store state and use helper slot
+                    m_writeFrame = videoFrame;
+                    m_writeOsdList = osdList; // Copy for Qt4
+                    m_writeSeq = seq;
+                    QTimer::singleShot(0, this, SLOT(handleWrite()));
+#endif
                     if (canSkipFrames && !skipNonKey)
                         ++framesDisplayed;
                 }
@@ -906,9 +925,14 @@ void VideoThr::screenshot(Frame videoFrame)
         }
 #endif
         auto imgData = reinterpret_cast<uint8_t *>(av_malloc(W * H * 4 + av_cpu_max_align()));
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         img = QImage(imgData, W, H, QImage::Format_RGB32, [](void *ptr) {
             av_free(ptr);
         }, imgData);
+#else
+        // Qt4: QImage doesn't support cleanup callback, will leak but screenshot is rare
+        img = QImage(imgData, W, H, QImage::Format_RGB32);
+#endif
         if (!imgScaler.scale(videoFrame, imgData))
             img = QImage();
     }
@@ -939,3 +963,28 @@ void VideoThr::pause()
 #endif
     writer->pause();
 }
+
+// Qt4: Helper slots - MOC needs to see declarations unconditionally
+void VideoThr::handleScreenshot()
+{
+#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0)
+    screenshot(m_screenshotFrame);
+    m_screenshotFrame = Frame(); // Clear
+#endif
+}
+
+void VideoThr::handlePause()
+{
+#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0)
+    pause();
+#endif
+}
+
+void VideoThr::handleWrite()
+{
+#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0)
+    write(m_writeFrame, std::move(m_writeOsdList), m_writeSeq);
+    m_subsDisplayLocker = {};
+    m_writeFrame = Frame(); // Clear
+#endif
+}
diff --git a/src/gui/VideoThr.hpp b/src/gui/VideoThr.hpp
index 74281953..d42bbe37 100644
--- a/src/gui/VideoThr.hpp
+++ b/src/gui/VideoThr.hpp
@@ -29,7 +29,7 @@ extern "C" {
 class VideoWriter;
 class HWDecContext;
 
-class VideoThr final : public AVThread
+class VideoThr : public AVThread
 {
     Q_OBJECT
 
@@ -37,18 +37,18 @@ public:
     VideoThr(PlayClass &playC, const QStringList &pluginsName = {});
     ~VideoThr();
 
-    void setDec(Decoder *dec) override;
+    void setDec(Decoder *dec);
 
     std::shared_ptr<HWDecContext> getHWDecContext() const;
 
     bool videoWriterSet();
 
-    bool lock() override;
+    bool lock();
 
-    void stop(bool terminate = false) override;
+    void stop(bool terminate = false);
 
-    bool hasError() const override;
-    bool hasDecoderError() const override;
+    bool hasError() const;
+    bool hasDecoderError() const;
 
     AVPixelFormats getSupportedPixelFormats() const;
 
@@ -83,10 +83,16 @@ public:
 
     void updateSubs();
 
+private slots:
+    // Qt4: These slots must be visible to MOC unconditionally
+    void handleScreenshot();
+    void handlePause();
+    void handleWrite();
+
 private:
     inline VideoWriter *videoWriter() const;
 
-    void run() override;
+    void run();
 
 #ifdef Q_OS_WIN
     template<bool h>
@@ -113,6 +119,14 @@ private:
     QMutex filtersMutex;
     double m_subtitlesScale = 1.0;
 
+#if QT_VERSION < 0x050400
+    // Qt4: State for lambda callbacks
+    Frame m_screenshotFrame;
+    Frame m_writeFrame;
+    QMPlay2OSDList m_writeOsdList;
+    quint32 m_writeSeq;
+#endif
+
 #ifdef Q_OS_WIN
     bool m_timerPrecision = false;
 #endif
diff --git a/src/gui/Windows/PanGestureEventFilter.cpp b/src/gui/Windows/PanGestureEventFilter.cpp
index 85e66dca..3129405a 100644
--- a/src/gui/Windows/PanGestureEventFilter.cpp
+++ b/src/gui/Windows/PanGestureEventFilter.cpp
@@ -70,7 +70,7 @@ namespace UxTheme {
 
 class PanGestureEventFilterPriv final : public QObject, public QAbstractNativeEventFilter
 {
-    bool eventFilter(QObject *watched, QEvent *event) override
+    bool eventFilter(QObject *watched, QEvent *event)
     {
         if (event->type() == QEvent::Show)
         {
@@ -130,9 +130,9 @@ class PanGestureEventFilterPriv final : public QObject, public QAbstractNativeEv
     }
 
 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
-    bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override
+    bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result)
 #else
-    bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override
+    bool nativeEventFilter(const QByteArray &eventType, void *message, long *result)
 #endif
     {
         if (eventType == "windows_generic_MSG")
diff --git a/src/gui/macOS/QMPlay2MacExtensions.hpp b/src/gui/macOS/QMPlay2MacExtensions.hpp
index 8e98b99d..178fa87c 100644
--- a/src/gui/macOS/QMPlay2MacExtensions.hpp
+++ b/src/gui/macOS/QMPlay2MacExtensions.hpp
@@ -21,7 +21,7 @@
 #include <functional>
 
 class QString;
-class QWindow;
+class QWidget; // Qt4 uses QWidget instead of QWindow
 
 namespace QMPlay2MacExtensions
 {
@@ -32,5 +32,5 @@ namespace QMPlay2MacExtensions
     void registerMacOSMediaKeys(const MediaKeysCallback &fn);
     void unregisterMacOSMediaKeys();
 
-    void showSystemUi(QWindow *mainWindow, bool visible);
+    void showSystemUi(QWidget *mainWindow, bool visible); // Qt4: QWidget instead of QWindow
 }
diff --git a/src/gui/macOS/QMPlay2MacExtensions.mm b/src/gui/macOS/QMPlay2MacExtensions.mm
index 02183c73..edeb8c94 100644
--- a/src/gui/macOS/QMPlay2MacExtensions.mm
+++ b/src/gui/macOS/QMPlay2MacExtensions.mm
@@ -1,69 +1,101 @@
 #include "QMPlay2MacExtensions.hpp"
 
-#include <QAbstractNativeEventFilter>
-#include <QGuiApplication>
-#include <QWindow>
+// Qt4 compatibility: Use QApplication instead of QGuiApplication
+#include <QApplication>
+#include <QWidget>
 
+// For media keys on macOS 10.6.8 with gcc (no blocks)
+#include <Carbon/Carbon.h>
 #include <IOKit/hidsystem/ev_keymap.h>
 #include <AppKit/NSApplication.h>
 #include <AppKit/NSScreen.h>
 #include <AppKit/NSEvent.h>
 
-class MediaKeysFilter : public QAbstractNativeEventFilter
+// Qt4 doesn't have QAbstractNativeEventFilter (Qt5.2+)
+// For macOS 10.6.8 with gcc (no blocks support), we use a polling-based approach
+// or carbon event handlers instead of Cocoa blocks
+
+// Forward declaration for the carbon event handler
+static OSStatus MediaKeysEventHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData);
+
+class MediaKeysFilter : public QObject
 {
 public:
     MediaKeysFilter(const QMPlay2MacExtensions::MediaKeysCallback &cb) :
-        m_mediaKeysCallback(cb)
-    {}
-
-private:
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
-    bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override final
-#else
-    bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override final
-#endif
+        m_mediaKeysCallback(cb),
+        m_eventHandlerRef(NULL)
+    {
+        // Use Carbon Event Manager for media keys support on 10.6.8 with gcc
+        // This doesn't require Apple blocks
+        EventTypeSpec eventTypes[1];
+        eventTypes[0].eventClass = kEventClassKeyboard;
+        eventTypes[0].eventKind = kEventHotKeyPressed;
+
+        InstallApplicationEventHandler(
+            NewEventHandlerUPP(MediaKeysEventHandler),
+            1,
+            eventTypes,
+            this,
+            &m_eventHandlerRef
+        );
+    }
+
+    ~MediaKeysFilter()
+    {
+        if (m_eventHandlerRef != NULL)
+        {
+            RemoveEventHandler(m_eventHandlerRef);
+            m_eventHandlerRef = NULL;
+        }
+    }
+
+    void handleMediaKey(int keyCode)
     {
-        Q_UNUSED(result)
-        if (eventType == "mac_generic_NSEvent")
+        switch (keyCode)
         {
-            NSEvent *event = static_cast<NSEvent *>(message);
-#if defined(MAC_OS_X_VERSION_10_12) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12)
-            if ([event type] == NSEventTypeSystemDefined)
-#else
-            if ([event type] == NSSystemDefined)
-#endif
-            {
-                const int  keyCode   = ([event data1] & 0xFFFF0000) >> 16;
-                const int  keyFlags  = ([event data1] & 0x0000FFFF);
-                const int  keyState  = (((keyFlags & 0xFF00) >> 8) == 0xA);
-                const bool keyRepeat = (keyFlags & 0x1);
-                Q_UNUSED(keyRepeat)
-
-                if (keyState == 1)
-                {
-                    switch (keyCode)
-                    {
-                        case NX_KEYTYPE_PLAY:
-                            m_mediaKeysCallback("toggle");
-                            return true;
-                        case NX_KEYTYPE_NEXT:
-                        case NX_KEYTYPE_FAST:
-                            m_mediaKeysCallback("next");
-                            return true;
-                        case NX_KEYTYPE_PREVIOUS:
-                        case NX_KEYTYPE_REWIND:
-                            m_mediaKeysCallback("prev");
-                            return true;
-                    }
-                }
-            }
+            case NX_KEYTYPE_PLAY:
+                m_mediaKeysCallback("toggle");
+                break;
+            case NX_KEYTYPE_NEXT:
+            case NX_KEYTYPE_FAST:
+                m_mediaKeysCallback("next");
+                break;
+            case NX_KEYTYPE_PREVIOUS:
+            case NX_KEYTYPE_REWIND:
+                m_mediaKeysCallback("prev");
+                break;
         }
-        return false;
     }
 
     QMPlay2MacExtensions::MediaKeysCallback m_mediaKeysCallback;
+    EventHandlerRef m_eventHandlerRef;
 } static *g_mediaKeysFilter;
 
+// Carbon event handler callback (no blocks needed)
+static OSStatus MediaKeysEventHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData)
+{
+    Q_UNUSED(nextHandler)
+
+    MediaKeysFilter *filter = static_cast<MediaKeysFilter*>(userData);
+    if (!filter)
+        return eventNotHandledErr;
+
+    UInt32 eventClass = GetEventClass(event);
+    UInt32 eventKind = GetEventKind(event);
+
+    if (eventClass == kEventClassKeyboard && eventKind == kEventHotKeyPressed)
+    {
+        EventHotKeyID hotKeyID;
+        GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(hotKeyID), NULL, &hotKeyID);
+
+        // Handle media key based on hotKeyID
+        filter->handleMediaKey(hotKeyID.id);
+        return noErr;
+    }
+
+    return eventNotHandledErr;
+}
+
 /**/
 
 void QMPlay2MacExtensions::setApplicationVisible(bool visible)
@@ -80,29 +112,27 @@ void QMPlay2MacExtensions::registerMacOSMediaKeys(const MediaKeysCallback &cb)
     if (!g_mediaKeysFilter)
     {
         g_mediaKeysFilter = new MediaKeysFilter(cb);
-        QCoreApplication::instance()->installNativeEventFilter(g_mediaKeysFilter);
+        // Qt4 doesn't need installNativeEventFilter - Carbon events are handled directly
     }
 }
 void QMPlay2MacExtensions::unregisterMacOSMediaKeys()
 {
     if (g_mediaKeysFilter)
     {
-        QCoreApplication::instance()->removeNativeEventFilter(g_mediaKeysFilter);
         delete g_mediaKeysFilter;
         g_mediaKeysFilter = nullptr;
     }
 }
 
-void QMPlay2MacExtensions::showSystemUi(QWindow *mainWindow, bool visible)
+void QMPlay2MacExtensions::showSystemUi(QWidget *mainWindow, bool visible)
 {
-#if defined(MAC_OS_X_VERSION_10_9) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_9)
-    if (mainWindow && mainWindow->screen() != QGuiApplication::primaryScreen() && ![NSScreen screensHaveSeparateSpaces])
-        return;
-#endif
-    unsigned long flags;
+    Q_UNUSED(mainWindow)
+    // macOS 10.6.8 doesn't have screensHaveSeparateSpaces (10.9+)
+    // Simplified for older macOS version
+    NSApplicationPresentationOptions flags;
     if (visible)
         flags = NSApplicationPresentationDefault;
     else
-        flags = NSApplicationPresentationHideDock | NSApplicationPresentationAutoHideMenuBar;
+        flags = (NSApplicationPresentationOptions)(NSApplicationPresentationHideDock | NSApplicationPresentationAutoHideMenuBar);
     [NSApp setPresentationOptions:flags];
 }
diff --git a/src/modules/ALSA/ALSA.hpp b/src/modules/ALSA/ALSA.hpp
index 5bbe407f..8a1b7b6b 100644
--- a/src/modules/ALSA/ALSA.hpp
+++ b/src/modules/ALSA/ALSA.hpp
@@ -20,15 +20,15 @@
 
 #include <Module.hpp>
 
-class ALSA final : public Module
+class ALSA : public Module
 {
 public:
     ALSA();
 private:
-    QList<Info> getModulesInfo(const bool) const override;
-    void *createInstance(const QString &) override;
+    QList<Info> getModulesInfo(const bool) const;
+    void *createInstance(const QString &);
 
-    SettingsWidget *getSettingsWidget() override;
+    SettingsWidget *getSettingsWidget();
 };
 
 /**/
@@ -39,13 +39,13 @@ class QDoubleSpinBox;
 class QCheckBox;
 class QComboBox;
 
-class ModuleSettingsWidget final : public Module::SettingsWidget
+class ModuleSettingsWidget : public Module::SettingsWidget
 {
     Q_DECLARE_TR_FUNCTIONS(ModuleSettingsWidget)
 public:
     ModuleSettingsWidget(Module &);
 private:
-    void saveSettings() override;
+    void saveSettings();
 
     QCheckBox *enabledB, *autoMultichnB;
     QDoubleSpinBox *delayB;
diff --git a/src/modules/ALSA/ALSAWriter.hpp b/src/modules/ALSA/ALSAWriter.hpp
index 9856b806..9310f13e 100644
--- a/src/modules/ALSA/ALSAWriter.hpp
+++ b/src/modules/ALSA/ALSAWriter.hpp
@@ -25,7 +25,7 @@
 
 struct _snd_pcm;
 
-class ALSAWriter final : public Writer
+class ALSAWriter : public Writer
 {
     Q_DECLARE_TR_FUNCTIONS(ALSAWriter)
 public:
@@ -33,17 +33,17 @@ public:
 private:
     ~ALSAWriter();
 
-    bool set() override;
+    bool set();
 
-    bool readyWrite() const override;
+    bool readyWrite() const;
 
-    bool processParams(bool *paramsCorrected) override;
-    qint64 write(const QByteArray &) override;
-    void pause() override;
+    bool processParams(bool *paramsCorrected);
+    qint64 write(const QByteArray &);
+    void pause();
 
-    QString name() const override;
+    QString name() const;
 
-    bool open() override;
+    bool open();
 
     /**/
 
diff --git a/src/modules/AudioCD/AudioCD.hpp b/src/modules/AudioCD/AudioCD.hpp
index 4ef8ea41..8739763f 100644
--- a/src/modules/AudioCD/AudioCD.hpp
+++ b/src/modules/AudioCD/AudioCD.hpp
@@ -24,19 +24,19 @@
 
 class CDIODestroyTimer;
 
-class AudioCD final : public Module
+class AudioCD : public Module
 {
     Q_OBJECT
 public:
     AudioCD();
     ~AudioCD();
 private:
-    QList<Info> getModulesInfo(const bool) const override;
-    void *createInstance(const QString &) override;
+    QList<Info> getModulesInfo(const bool) const;
+    void *createInstance(const QString &);
 
-    QList<QAction *> getAddActions() override;
+    QList<QAction *> getAddActions();
 
-    SettingsWidget *getSettingsWidget() override;
+    SettingsWidget *getSettingsWidget();
 
     QIcon CD;
 
@@ -52,13 +52,13 @@ class QGridLayout;
 class QGroupBox;
 class QCheckBox;
 
-class ModuleSettingsWidget final : public Module::SettingsWidget
+class ModuleSettingsWidget : public Module::SettingsWidget
 {
     Q_DECLARE_TR_FUNCTIONS(ModuleSettingsWidget)
 public:
     ModuleSettingsWidget(Module &);
 private:
-    void saveSettings() override;
+    void saveSettings();
 
     QGroupBox *audioCDB;
     QCheckBox *useCDDB, *useCDTEXT;
diff --git a/src/modules/AudioCD/AudioCDDemux.cpp b/src/modules/AudioCD/AudioCDDemux.cpp
index 5fa6202d..31559f16 100644
--- a/src/modules/AudioCD/AudioCDDemux.cpp
+++ b/src/modules/AudioCD/AudioCDDemux.cpp
@@ -22,9 +22,9 @@
 #include <Packet.hpp>
 
 #ifdef Q_OS_WIN
-    #include <QRegularExpression>
+    #include <QRegExp>
     #include <QDir>
-    static const QRegularExpression cdaRegExp(R"(file:\/\/\D:\/track\d\d.cda)");
+    static const QRegExp cdaRegExp("file://\\D:/track\\d\\d.cda");
 #endif
 
 #define CD_BLOCKSIZE 2352/2
diff --git a/src/modules/AudioCD/AudioCDDemux.hpp b/src/modules/AudioCD/AudioCDDemux.hpp
index 5ea6d362..fa4bf616 100644
--- a/src/modules/AudioCD/AudioCDDemux.hpp
+++ b/src/modules/AudioCD/AudioCDDemux.hpp
@@ -26,7 +26,7 @@
 #include <cdio/cdio.h>
 #include <cddb/cddb.h>
 
-class CDIODestroyTimer final : public QObject
+class CDIODestroyTimer : public QObject
 {
     Q_OBJECT
 public:
@@ -38,7 +38,7 @@ public:
 private slots:
     void setInstanceSlot(CdIo_t *_cdio, const QString &_device, unsigned _discID);
 private:
-    void timerEvent(QTimerEvent *e) override;
+    void timerEvent(QTimerEvent *e);
 
     QAtomicInt timerId;
     CdIo_t *cdio;
@@ -48,7 +48,7 @@ private:
 
 /**/
 
-class AudioCDDemux final : public Demuxer
+class AudioCDDemux : public Demuxer
 {
     Q_DECLARE_TR_FUNCTIONS(AudioCDDemux)
 public:
@@ -58,21 +58,21 @@ public:
 private:
     ~AudioCDDemux();
 
-    bool set() override;
+    bool set();
 
-    QString name() const override;
-    QString title() const override;
-    QList<QMPlay2Tag> tags() const override;
-    double length() const override;
-    int bitrate() const override;
+    QString name() const;
+    QString title() const;
+    QList<QMPlay2Tag> tags() const;
+    double length() const;
+    int bitrate() const;
 
-    bool seek(double, bool) override;
-    bool read(Packet &, int & ) override;
-    void abort() override;
+    bool seek(double, bool);
+    bool read(Packet &, int & );
+    void abort();
 
-    bool open(const QString &) override;
+    bool open(const QString &);
 
-    Playlist::Entries fetchTracks(const QString &url, bool &ok) override;
+    Playlist::Entries fetchTracks(const QString &url, bool &ok);
 
     /**/
 
diff --git a/src/modules/AudioFilters/AVAudioFilter.cpp b/src/modules/AudioFilters/AVAudioFilter.cpp
index 3ab46f56..c6e44577 100644
--- a/src/modules/AudioFilters/AVAudioFilter.cpp
+++ b/src/modules/AudioFilters/AVAudioFilter.cpp
@@ -143,7 +143,11 @@ double AVAudioFilter::filter(QByteArray &data, bool flush)
         if (av_buffersink_get_frame(m_filtOut, m_frameOut) == 0)
         {
             const int n = m_frameOut->nb_samples * m_chn * sizeof(float);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
             m_bufferedSamples = (qintptr)m_frameIn->opaque - (qintptr)m_frameOut->opaque;
+#else
+            m_bufferedSamples = (qint64)m_frameIn->opaque - (qint64)m_frameOut->opaque;
+#endif
             delay = static_cast<double>(m_bufferedSamples) / static_cast<double>(m_srate);
             if (n == data.size())
             {
@@ -218,7 +222,7 @@ bool AVAudioFilter::ensureFilters()
         }
         m_filterGraph = avfilter_graph_alloc();
 
-        const auto argsIn = QStringLiteral("sample_rate=%1:sample_fmt=flt:channel_layout=%2").arg(m_srate).arg(chnLayout.constData()).toLatin1();
+        const auto argsIn = QString("sample_rate=%1:sample_fmt=flt:channel_layout=%2").arg(m_srate).arg(chnLayout.constData()).toLatin1();
         if (avfilter_graph_create_filter(&m_filtIn, avfilter_get_by_name("abuffer"), "in", argsIn, nullptr, m_filterGraph) < 0)
         {
             qWarning() << "AVAudioFilter :: Can't create in filter";
@@ -226,7 +230,7 @@ bool AVAudioFilter::ensureFilters()
         }
 
 #if LIBAVFILTER_VERSION_INT >= AV_VERSION_INT(11, 4, 100)
-        const auto argsOut = QStringLiteral("samplerates=%1:sample_formats=flt:channel_layouts=%2").arg(m_srate).arg(chnLayout.constData()).toLatin1();
+        const auto argsOut = QString("samplerates=%1:sample_formats=flt:channel_layouts=%2").arg(m_srate).arg(chnLayout.constData()).toLatin1();
 #else
         const char *argsOut = nullptr;
 #endif
diff --git a/src/modules/AudioFilters/AVAudioFilter.hpp b/src/modules/AudioFilters/AVAudioFilter.hpp
index 8a3077c5..22dbba93 100644
--- a/src/modules/AudioFilters/AVAudioFilter.hpp
+++ b/src/modules/AudioFilters/AVAudioFilter.hpp
@@ -25,7 +25,7 @@ struct AVFilterGraph;
 struct AVFilterInOut;
 struct AVFrame;
 
-class AVAudioFilter final : public AudioFilter
+class AVAudioFilter : public AudioFilter
 {
 public:
     static QStringList getAvailableFilters();
@@ -36,13 +36,13 @@ public:
     AVAudioFilter(Module &module);
     ~AVAudioFilter();
 
-    bool set() override;
+    bool set();
 
 private:
-    bool setAudioParameters(uchar chn, uint srate) override;
-    int bufferedSamples() const override;
-    void clearBuffers() override;
-    double filter(QByteArray &data, bool flush) override;
+    bool setAudioParameters(uchar chn, uint srate);
+    int bufferedSamples() const;
+    void clearBuffers();
+    double filter(QByteArray &data, bool flush);
 
 private:
     inline void setCanFilter();
@@ -71,7 +71,11 @@ private:
     AVFrame *m_frameIn = nullptr;
     AVFrame *m_frameOut = nullptr;
 
+#if QT_VERSION >= 0x050000
     qintptr m_overallSamples = 0;
+#else
+    qint64 m_overallSamples = 0;
+#endif
     int m_bufferedSamples = 0;
 
     bool m_paramsChanged = false;
diff --git a/src/modules/AudioFilters/AudioFilters.cpp b/src/modules/AudioFilters/AudioFilters.cpp
index 102e7550..8cb09e14 100644
--- a/src/modules/AudioFilters/AudioFilters.cpp
+++ b/src/modules/AudioFilters/AudioFilters.cpp
@@ -349,16 +349,21 @@ ModuleSettingsWidget::ModuleSettingsWidget(Module &module) :
     m_avAudioFilterB = new QGroupBox(tr("FFmpeg audio filters"));
     m_avAudioFilterB->setCheckable(true);
     m_avAudioFilterB->setChecked(sets().getBool("AVAudioFilter"));
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     connect(m_avAudioFilterB, &QGroupBox::toggled, this, [this](bool checked) {
         sets().set("AVAudioFilter", checked);
         SetInstance<AVAudioFilter>();
     });
+#else
+    connect(m_avAudioFilterB, SIGNAL(toggled(bool)), this, SLOT(avAudioFilterToggled(bool)));
+#endif
 
     auto audioFiltersInfoB = new QToolButton;
     audioFiltersInfoB->setText("?");
     audioFiltersInfoB->setToolTip(tr("Help"));
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     connect(audioFiltersInfoB, &QToolButton::clicked, this, [this] {
-        const auto sep = QStringLiteral("<br/>&nbsp;&nbsp;&nbsp;");
+        const auto sep = QString("<br/>&nbsp;&nbsp;&nbsp;");
         auto te = new QTextBrowser(window());
         te->setWindowFlag(Qt::Window);
         te->setOpenExternalLinks(true);
@@ -369,16 +374,20 @@ ModuleSettingsWidget::ModuleSettingsWidget(Module &module) :
                "To use some filters like '%4', you need to enable \"force channels conversion\" and \"use channel conversion before filters\" in playback settings. "
                "Please refer to the %1FFmpeg documentation%2.%3Available audio filters:"
               )
-                .arg(QStringLiteral("<a href='https://ffmpeg.org/ffmpeg-filters.html#Audio-Filters'>"), QStringLiteral("</a>"), QStringLiteral("<br/><br/>"), "surround")
+                .arg(QString("<a href='https://ffmpeg.org/ffmpeg-filters.html#Audio-Filters'>"), QString("</a>"), QString("<br/><br/>"), "surround")
                 + sep + AVAudioFilter::getAvailableFilters().join(sep)
         );
         te->show();
     });
+#else
+    connect(audioFiltersInfoB, SIGNAL(clicked()), this, SLOT(audioFiltersInfo()));
+#endif
 
     m_avAudioFilterE = new QLineEdit;
     m_avAudioFilterE->setPlaceholderText(
-        tr("FFmpeg audio filters, example: %1").arg(QStringLiteral("volume=0.6,extrastereo"))
+        tr("FFmpeg audio filters, example: %1").arg(QString("volume=0.6,extrastereo"))
     );
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     connect(m_avAudioFilterE, &QLineEdit::textChanged, this, [this](const QString &text) {
         const bool ok = AVAudioFilter::validateFilters(text.toLatin1().trimmed());
         auto font = m_avAudioFilterE->font();
@@ -390,6 +399,9 @@ ModuleSettingsWidget::ModuleSettingsWidget(Module &module) :
             SetInstance<AVAudioFilter>();
         }
     });
+#else
+    connect(m_avAudioFilterE, SIGNAL(textChanged(QString)), this, SLOT(avAudioFilterTextChanged(QString)));
+#endif
     m_avAudioFilterE->setText(sets().getByteArray("AVAudioFilter/Filters"));
 
     auto avAudioFilterLayout = new QHBoxLayout(m_avAudioFilterB);
@@ -529,3 +541,43 @@ void ModuleSettingsWidget::saveSettings()
     sets().set("Equalizer/minFreq", eqMinFreqB->value());
     sets().set("Equalizer/maxFreq", eqMaxFreqB->value());
 }
+
+#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) && defined(USE_AVAUDIOFILTER)
+void ModuleSettingsWidget::avAudioFilterToggled(bool checked)
+{
+    sets().set("AVAudioFilter", checked);
+    SetInstance<AVAudioFilter>();
+}
+
+void ModuleSettingsWidget::audioFiltersInfo()
+{
+    const auto sep = QString("<br/>&nbsp;&nbsp;&nbsp;");
+    auto te = new QTextBrowser(window());
+    te->setWindowFlags(te->windowFlags() | Qt::Window);
+    te->setOpenExternalLinks(true);
+    te->setAttribute(Qt::WA_DeleteOnClose);
+    te->setWindowTitle(m_avAudioFilterB->title());
+    te->setHtml(
+        tr("Not all audio filters are supported in QMPlay2. "
+           "To use some filters like '%4', you need to enable \"force channels conversion\" and \"use channel conversion before filters\" in playback settings. "
+           "Please refer to the %1FFmpeg documentation%2.%3Available audio filters:"
+          )
+            .arg(QString("<a href='https://ffmpeg.org/ffmpeg-filters.html#Audio-Filters'>"), QString("</a>"), QString("<br/><br/>"), "surround")
+            + sep + AVAudioFilter::getAvailableFilters().join(sep)
+    );
+    te->show();
+}
+
+void ModuleSettingsWidget::avAudioFilterTextChanged(const QString &text)
+{
+    const bool ok = AVAudioFilter::validateFilters(text.toLatin1().trimmed());
+    auto font = m_avAudioFilterE->font();
+    font.setUnderline(!ok);
+    m_avAudioFilterE->setFont(font);
+    if (ok)
+    {
+        sets().set("AVAudioFilter/Filters", m_avAudioFilterE->text().toLatin1());
+        SetInstance<AVAudioFilter>();
+    }
+}
+#endif
diff --git a/src/modules/AudioFilters/AudioFilters.hpp b/src/modules/AudioFilters/AudioFilters.hpp
index f0b18ae4..436a2bca 100644
--- a/src/modules/AudioFilters/AudioFilters.hpp
+++ b/src/modules/AudioFilters/AudioFilters.hpp
@@ -20,15 +20,15 @@
 
 #include <Module.hpp>
 
-class AudioFilters final : public Module
+class AudioFilters : public Module
 {
 public:
     AudioFilters();
 private:
-    QList<Info> getModulesInfo(const bool) const override;
-    void *createInstance(const QString &) override;
+    QList<Info> getModulesInfo(const bool) const;
+    void *createInstance(const QString &);
 
-    SettingsWidget *getSettingsWidget() override;
+    SettingsWidget *getSettingsWidget();
 };
 
 /**/
@@ -42,7 +42,7 @@ class QLineEdit;
 class QSpinBox;
 class Slider;
 
-class ModuleSettingsWidget final : public Module::SettingsWidget
+class ModuleSettingsWidget : public Module::SettingsWidget
 {
     Q_OBJECT
 public:
@@ -55,8 +55,13 @@ private slots:
     void echo();
     void compressor();
     void defaultSettings();
+#if QT_VERSION < 0x050000 && defined(USE_AVAUDIOFILTER)
+    void avAudioFilterToggled(bool checked);
+    void audioFiltersInfo();
+    void avAudioFilterTextChanged(const QString &text);
+#endif
 private:
-    void saveSettings() override;
+    void saveSettings();
 
     bool restoringDefault;
 
diff --git a/src/modules/AudioFilters/BS2B.hpp b/src/modules/AudioFilters/BS2B.hpp
index 92a3c44e..3260b1b1 100644
--- a/src/modules/AudioFilters/BS2B.hpp
+++ b/src/modules/AudioFilters/BS2B.hpp
@@ -22,17 +22,17 @@
 
 #include <bs2b/bs2b.hpp>
 
-class BS2B final : public AudioFilter
+class BS2B : public AudioFilter
 {
 public:
     BS2B(Module &module);
     ~BS2B();
 
-    bool set() override;
+    bool set();
 private:
-    bool setAudioParameters(uchar, uint srate) override;
-    void clearBuffers() override;
-    double filter(QByteArray &data, bool flush) override;
+    bool setAudioParameters(uchar, uint srate);
+    void clearBuffers();
+    double filter(QByteArray &data, bool flush);
 
     void alloc();
 
diff --git a/src/modules/AudioFilters/DysonCompressor.hpp b/src/modules/AudioFilters/DysonCompressor.hpp
index 63b4fe68..20caa6f5 100644
--- a/src/modules/AudioFilters/DysonCompressor.hpp
+++ b/src/modules/AudioFilters/DysonCompressor.hpp
@@ -25,19 +25,19 @@
 #define NFILT  12
 #define NEFILT 17
 
-class DysonCompressor final : public AudioFilter
+class DysonCompressor : public AudioFilter
 {
 public:
     DysonCompressor(Module &module);
     ~DysonCompressor();
 
-    bool set() override;
+    bool set();
 
 private:
-    bool setAudioParameters(uchar chn, uint srate) override;
-    int bufferedSamples() const override;
-    void clearBuffers() override;
-    double filter(QByteArray &data, bool flush) override;
+    bool setAudioParameters(uchar chn, uint srate);
+    int bufferedSamples() const;
+    void clearBuffers();
+    double filter(QByteArray &data, bool flush);
 
     using FloatVector = QVector<float>;
 
diff --git a/src/modules/AudioFilters/Echo.hpp b/src/modules/AudioFilters/Echo.hpp
index 62a9c8d1..37bbae11 100644
--- a/src/modules/AudioFilters/Echo.hpp
+++ b/src/modules/AudioFilters/Echo.hpp
@@ -20,15 +20,15 @@
 
 #include <AudioFilter.hpp>
 
-class Echo final : public AudioFilter
+class Echo : public AudioFilter
 {
 public:
     Echo(Module &);
 
-    bool set() override;
+    bool set();
 private:
-    bool setAudioParameters(uchar, uint) override;
-    double filter(QByteArray &, bool) override;
+    bool setAudioParameters(uchar, uint);
+    double filter(QByteArray &, bool);
 
     void alloc(bool);
 
diff --git a/src/modules/AudioFilters/Equalizer.cpp b/src/modules/AudioFilters/Equalizer.cpp
index 09a0f6eb..d75be492 100644
--- a/src/modules/AudioFilters/Equalizer.cpp
+++ b/src/modules/AudioFilters/Equalizer.cpp
@@ -65,6 +65,9 @@ float Equalizer::getAmpl(int val)
 }
 
 Equalizer::Equalizer(Module &module)
+#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
+    : m_mutex(QMutex::Recursive)
+#endif
 {
     SetModule(module);
 }
diff --git a/src/modules/AudioFilters/Equalizer.hpp b/src/modules/AudioFilters/Equalizer.hpp
index 59aae7f0..7889cf64 100644
--- a/src/modules/AudioFilters/Equalizer.hpp
+++ b/src/modules/AudioFilters/Equalizer.hpp
@@ -21,9 +21,15 @@
 #include <AudioFilter.hpp>
 #include <FFT.hpp>
 
+#if QT_VERSION >= 0x050E00
+    #include <QRecursiveMutex>
+#else
+    #include <QMutex>
+#endif
+
 #include <vector>
 
-class Equalizer final : public AudioFilter
+class Equalizer : public AudioFilter
 {
 public:
     static QVector<float> interpolate(const QVector<float> &, const int);
@@ -33,12 +39,12 @@ public:
     Equalizer(Module &);
     ~Equalizer();
 
-    bool set() override;
+    bool set();
 private:
-    bool setAudioParameters(uchar, uint) override;
-    int bufferedSamples() const override;
-    void clearBuffers() override;
-    double filter(QByteArray &data, bool flush) override;
+    bool setAudioParameters(uchar, uint);
+    int bufferedSamples() const;
+    void clearBuffers();
+    double filter(QByteArray &data, bool flush);
 
     /**/
 
@@ -56,7 +62,11 @@ private:
     bool m_hasParameters = false;
     bool m_enabled = false;
 
+#if QT_VERSION >= 0x050E00
     mutable QRecursiveMutex m_mutex;
+#else
+    mutable QMutex m_mutex;
+#endif
     FFT m_fftIn;
     FFT m_fftOut;
     FFT::Complex *m_complex = nullptr;
diff --git a/src/modules/AudioFilters/EqualizerGUI.cpp b/src/modules/AudioFilters/EqualizerGUI.cpp
index a25b02b2..ecbd3fbf 100644
--- a/src/modules/AudioFilters/EqualizerGUI.cpp
+++ b/src/modules/AudioFilters/EqualizerGUI.cpp
@@ -342,7 +342,7 @@ void EqualizerGUI::setSliders()
 {
     const QString objectName = sender()->objectName();
     graph.hide();
-    for (QSlider *slider : std::as_const(sliders))
+    for (QSlider *slider : sliders)
     {
         const bool isPreamp = (sliders.at(0) == slider);
 
@@ -377,7 +377,7 @@ void EqualizerGUI::addPreset()
         }
 
         QMap<int, int> values;
-        for (QSlider *slider : std::as_const(sliders))
+        for (QSlider *slider : sliders)
         {
             const bool isPreamp = (sliders.at(0) == slider);
             if (isPreamp)
@@ -432,7 +432,7 @@ void EqualizerGUI::setPresetValues()
         QMap<int, int> values = getPresetValues(act->text());
         if (values.count() > 1)
         {
-            for (QSlider *slider : std::as_const(sliders))
+            for (QSlider *slider : sliders)
             {
                 QCheckBox *checkB = getSliderCheckBox(slider);
                 const bool isPreamp = (sliders.at(0) == slider);
diff --git a/src/modules/AudioFilters/EqualizerGUI.hpp b/src/modules/AudioFilters/EqualizerGUI.hpp
index e7ca95ad..58609ad5 100644
--- a/src/modules/AudioFilters/EqualizerGUI.hpp
+++ b/src/modules/AudioFilters/EqualizerGUI.hpp
@@ -20,7 +20,7 @@
 
 #include <QMPlay2Extensions.hpp>
 
-class GraphW final : public QWidget
+class GraphW : public QWidget
 {
 public:
     GraphW();
@@ -31,7 +31,7 @@ public:
         values.resize(vals);
     }
 private:
-    void paintEvent(QPaintEvent *) override;
+    void paintEvent(QPaintEvent *);
 
     QVector<float> values;
     float preamp;
@@ -44,15 +44,15 @@ class QCheckBox;
 class QSlider;
 class QMenu;
 
-class EqualizerGUI final : public QWidget, public QMPlay2Extensions
+class EqualizerGUI : public QWidget, public QMPlay2Extensions
 {
     Q_OBJECT
 public:
     EqualizerGUI(Module &);
 
-    bool set() override;
+    bool set();
 
-    DockWidget *getDockWidget() override;
+    DockWidget *getDockWidget();
 private slots:
     void wallpaperChanged(bool hasWallpaper, double alpha);
     void enabled(bool);
@@ -80,7 +80,7 @@ private:
 
     void loadPresets();
 
-    void showEvent(QShowEvent *event) override;
+    void showEvent(QShowEvent *event);
 
     QMap<int, int> getPresetValues(const QString &name);
 
diff --git a/src/modules/AudioFilters/PhaseReverse.hpp b/src/modules/AudioFilters/PhaseReverse.hpp
index 1ad382b8..6ff1d469 100644
--- a/src/modules/AudioFilters/PhaseReverse.hpp
+++ b/src/modules/AudioFilters/PhaseReverse.hpp
@@ -20,15 +20,15 @@
 
 #include <AudioFilter.hpp>
 
-class PhaseReverse final : public AudioFilter
+class PhaseReverse : public AudioFilter
 {
 public:
     PhaseReverse(Module &);
 
-    bool set() override;
+    bool set();
 private:
-    bool setAudioParameters(uchar, uint) override;
-    double filter(QByteArray &, bool) override;
+    bool setAudioParameters(uchar, uint);
+    double filter(QByteArray &, bool);
 
     bool enabled, hasParameters, canFilter, reverseRight;
     uchar chn;
diff --git a/src/modules/AudioFilters/SwapStereo.hpp b/src/modules/AudioFilters/SwapStereo.hpp
index 259ea912..d6786f35 100644
--- a/src/modules/AudioFilters/SwapStereo.hpp
+++ b/src/modules/AudioFilters/SwapStereo.hpp
@@ -20,15 +20,15 @@
 
 #include <AudioFilter.hpp>
 
-class SwapStereo final : public AudioFilter
+class SwapStereo : public AudioFilter
 {
 public:
     SwapStereo(Module &);
 
-    bool set() override;
+    bool set();
 private:
-    bool setAudioParameters(uchar, uint) override;
-    double filter(QByteArray &, bool) override;
+    bool setAudioParameters(uchar, uint);
+    double filter(QByteArray &, bool);
 
     bool m_enabled = false, m_hasParameters = false, m_canFilter = false;
     uchar m_chn = 0;
diff --git a/src/modules/AudioFilters/VoiceRemoval.hpp b/src/modules/AudioFilters/VoiceRemoval.hpp
index 60b1f66e..35190aaa 100644
--- a/src/modules/AudioFilters/VoiceRemoval.hpp
+++ b/src/modules/AudioFilters/VoiceRemoval.hpp
@@ -20,15 +20,15 @@
 
 #include <AudioFilter.hpp>
 
-class VoiceRemoval final : public AudioFilter
+class VoiceRemoval : public AudioFilter
 {
 public:
     VoiceRemoval(Module &);
 
-    bool set() override;
+    bool set();
 private:
-    bool setAudioParameters(uchar, uint) override;
-    double filter(QByteArray &, bool) override;
+    bool setAudioParameters(uchar, uint);
+    double filter(QByteArray &, bool);
 
     bool enabled, hasParameters, canFilter;
     uchar chn;
diff --git a/src/modules/CUVID/Cuvid.hpp b/src/modules/CUVID/Cuvid.hpp
index 1442fc45..6861cf3e 100644
--- a/src/modules/CUVID/Cuvid.hpp
+++ b/src/modules/CUVID/Cuvid.hpp
@@ -24,7 +24,7 @@
 
 class QComboBox;
 
-class Cuvid final : public Module
+class Cuvid : public Module
 {
     Q_DECLARE_TR_FUNCTIONS(Cuvid)
 
@@ -33,12 +33,12 @@ public:
     ~Cuvid();
 
 private:
-    QList<Info> getModulesInfo(const bool showDisabled) const override;
-    void *createInstance(const QString &name) override;
+    QList<Info> getModulesInfo(const bool showDisabled) const;
+    void *createInstance(const QString &name);
 
-    SettingsWidget *getSettingsWidget() override;
+    SettingsWidget *getSettingsWidget();
 
-    void videoDeintSave() override;
+    void videoDeintSave();
 
     /**/
 
@@ -58,7 +58,7 @@ public:
     ModuleSettingsWidget(Module &module);
 
 private:
-    void saveSettings() override;
+    void saveSettings();
 
     QCheckBox *m_enabledB, *m_decodeMPEG4;
 #ifdef Q_OS_WIN
diff --git a/src/modules/CUVID/CuvidDec.hpp b/src/modules/CUVID/CuvidDec.hpp
index 82ecc346..0b959c14 100644
--- a/src/modules/CUVID/CuvidDec.hpp
+++ b/src/modules/CUVID/CuvidDec.hpp
@@ -33,7 +33,7 @@ struct AVBSFContext;
 struct SwsContext;
 struct AVPacket;
 
-class CuvidDec final : public Decoder
+class CuvidDec : public Decoder
 {
     Q_DECLARE_TR_FUNCTIONS(CuvidDec)
 
@@ -43,25 +43,25 @@ public:
     CuvidDec(Module &module);
     ~CuvidDec();
 
-    bool set() override;
+    bool set();
 
     int videoSequence(CUVIDEOFORMAT *format);
     int pictureDecode(CUVIDPICPARAMS *picParams);
     int pictureDisplay(CUVIDPARSERDISPINFO *dispInfo);
 
 private:
-    QString name() const override;
+    QString name() const;
 
-    bool hasHWDecContext() const override;
-    std::shared_ptr<VideoFilter> hwAccelFilter() const override;
+    bool hasHWDecContext() const;
+    std::shared_ptr<VideoFilter> hwAccelFilter() const;
 
-    void setSupportedPixelFormats(const AVPixelFormats &pixelFormats) override;
+    void setSupportedPixelFormats(const AVPixelFormats &pixelFormats);
 
-    int decodeVideo(const Packet &encodedPacket, Frame &decoded, AVPixelFormat &newPixFmt, bool flush, unsigned hurry_up) override;
+    int decodeVideo(const Packet &encodedPacket, Frame &decoded, AVPixelFormat &newPixFmt, bool flush, unsigned hurry_up);
 
-    bool hasCriticalError() const override;
+    bool hasCriticalError() const;
 
-    bool open(StreamInfo &streamInfo) override;
+    bool open(StreamInfo &streamInfo);
 
     /**/
 
diff --git a/src/modules/CUVID/CuvidOpenGL.cpp b/src/modules/CUVID/CuvidOpenGL.cpp
index bac00ae8..50b766a5 100644
--- a/src/modules/CUVID/CuvidOpenGL.cpp
+++ b/src/modules/CUVID/CuvidOpenGL.cpp
@@ -19,7 +19,7 @@
 #include <CuvidOpenGL.hpp>
 #include <Frame.hpp>
 
-#include <QOpenGLContext>
+#include <QtOpenGL/QGLContext>
 #include <QImage>
 
 extern "C" {
diff --git a/src/modules/CUVID/CuvidOpenGL.hpp b/src/modules/CUVID/CuvidOpenGL.hpp
index 0a524144..50426b5e 100644
--- a/src/modules/CUVID/CuvidOpenGL.hpp
+++ b/src/modules/CUVID/CuvidOpenGL.hpp
@@ -21,23 +21,23 @@
 #include <CuvidHWInterop.hpp>
 #include <opengl/OpenGLHWInterop.hpp>
 
-class CuvidOpenGL final : public CuvidHWInterop, public OpenGLHWInterop
+class CuvidOpenGL : public CuvidHWInterop, public OpenGLHWInterop
 {
 public:
     CuvidOpenGL(const std::shared_ptr<CUcontext> &cuCtx);
     ~CuvidOpenGL();
 
-    QString name() const override;
+    QString name() const;
 
-    Format getFormat() const override;
+    Format getFormat() const;
 
-    bool init(const int *widths, const int *heights, const SetTextureParamsFn &setTextureParamsFn) override;
-    void clear() override;
+    bool init(const int *widths, const int *heights, const SetTextureParamsFn &setTextureParamsFn);
+    void clear();
 
-    bool mapFrame(Frame &videoFrame) override;
-    quint32 getTexture(int plane) override;
+    bool mapFrame(Frame &videoFrame);
+    quint32 getTexture(int plane);
 
-    Frame getCpuFrame(const Frame &videoFrame) override;
+    Frame getCpuFrame(const Frame &videoFrame);
 
 private:
     quint32 m_textures[2] = {};
diff --git a/src/modules/CUVID/CuvidVulkan.hpp b/src/modules/CUVID/CuvidVulkan.hpp
index 4a1517ee..51fd58f2 100644
--- a/src/modules/CUVID/CuvidVulkan.hpp
+++ b/src/modules/CUVID/CuvidVulkan.hpp
@@ -28,18 +28,18 @@ class ImagePool;
 class Semaphore;
 }
 
-class CuvidVulkan final : public CuvidHWInterop, public QmVk::HWInterop
+class CuvidVulkan : public CuvidHWInterop, public QmVk::HWInterop
 {
 public:
     CuvidVulkan(const std::shared_ptr<CUcontext> &cuCtx);
     ~CuvidVulkan();
 
-    QString name() const override;
+    QString name() const;
 
-    void map(Frame &frame) override;
-    void clear() override;
+    void map(Frame &frame);
+    void clear();
 
-    SyncDataPtr sync(const std::vector<Frame> &frames, vk::SubmitInfo *submitInfo = nullptr) override;
+    SyncDataPtr sync(const std::vector<Frame> &frames, vk::SubmitInfo *submitInfo = nullptr);
 
 private:
     void ensureSemaphore();
diff --git a/src/modules/Chiptune/Chiptune.hpp b/src/modules/Chiptune/Chiptune.hpp
index 8455193a..9770ff7b 100644
--- a/src/modules/Chiptune/Chiptune.hpp
+++ b/src/modules/Chiptune/Chiptune.hpp
@@ -20,15 +20,15 @@
 
 #include <Module.hpp>
 
-class Chiptune final : public Module
+class Chiptune : public Module
 {
 public:
     Chiptune();
 private:
-    QList<Info> getModulesInfo(const bool) const override;
-    void *createInstance(const QString &) override;
+    QList<Info> getModulesInfo(const bool) const;
+    void *createInstance(const QString &);
 
-    SettingsWidget *getSettingsWidget() override;
+    SettingsWidget *getSettingsWidget();
 
     QIcon GMEIcon, SIDIcon;
 };
@@ -40,13 +40,13 @@ private:
 class QCheckBox;
 class QSpinBox;
 
-class ModuleSettingsWidget final : public Module::SettingsWidget
+class ModuleSettingsWidget : public Module::SettingsWidget
 {
     Q_DECLARE_TR_FUNCTIONS(ModuleSettingsWidget)
 public:
     ModuleSettingsWidget(Module &);
 private:
-    void saveSettings() override;
+    void saveSettings();
 
 #ifdef USE_GME
     QCheckBox *gmeB;
diff --git a/src/modules/Chiptune/GME.hpp b/src/modules/Chiptune/GME.hpp
index 952c31e9..a7ffe641 100644
--- a/src/modules/Chiptune/GME.hpp
+++ b/src/modules/Chiptune/GME.hpp
@@ -24,28 +24,28 @@
 
 class Reader;
 
-class GME final : public Demuxer
+class GME : public Demuxer
 {
     Q_DECLARE_TR_FUNCTIONS(GME)
 public:
     GME(Module &);
     ~GME();
 private:
-    bool set() override;
+    bool set();
 
-    QString name() const override;
-    QString title() const override;
-    QList<QMPlay2Tag> tags() const override;
-    double length() const override;
-    int bitrate() const override;
+    QString name() const;
+    QString title() const;
+    QList<QMPlay2Tag> tags() const;
+    double length() const;
+    int bitrate() const;
 
-    bool seek(double, bool) override;
-    bool read(Packet &, int &) override;
-    void abort() override;
+    bool seek(double, bool);
+    bool read(Packet &, int &);
+    void abort();
 
-    bool open(const QString &) override;
+    bool open(const QString &);
 
-    Playlist::Entries fetchTracks(const QString &url, bool &ok) override;
+    Playlist::Entries fetchTracks(const QString &url, bool &ok);
 
 
     bool open(const QString &url, bool tracksOnly);
diff --git a/src/modules/Chiptune/SIDPlay.hpp b/src/modules/Chiptune/SIDPlay.hpp
index b9b9feb0..27ded134 100644
--- a/src/modules/Chiptune/SIDPlay.hpp
+++ b/src/modules/Chiptune/SIDPlay.hpp
@@ -31,28 +31,28 @@
 class SidTuneInfo;
 class Reader;
 
-class SIDPlay final : public Demuxer
+class SIDPlay : public Demuxer
 {
     Q_DECLARE_TR_FUNCTIONS(SIDPlay)
 public:
     SIDPlay(Module &);
     ~SIDPlay();
 private:
-    bool set() override;
+    bool set();
 
-    QString name() const override;
-    QString title() const override;
-    QList<QMPlay2Tag> tags() const override;
-    double length() const override;
-    int bitrate() const override;
+    QString name() const;
+    QString title() const;
+    QList<QMPlay2Tag> tags() const;
+    double length() const;
+    int bitrate() const;
 
-    bool seek(double, bool backward) override;
-    bool read(Packet &, int &) override;
-    void abort() override;
+    bool seek(double, bool backward);
+    bool read(Packet &, int &);
+    void abort();
 
-    bool open(const QString &) override;
+    bool open(const QString &);
 
-    Playlist::Entries fetchTracks(const QString &url, bool &ok) override;
+    Playlist::Entries fetchTracks(const QString &url, bool &ok);
 
 
     bool open(const QString &url, bool tracksOnly);
diff --git a/src/modules/DirectX/DirectDraw.hpp b/src/modules/DirectX/DirectDraw.hpp
index 06d1cd56..413fcdbe 100644
--- a/src/modules/DirectX/DirectDraw.hpp
+++ b/src/modules/DirectX/DirectDraw.hpp
@@ -28,7 +28,7 @@
 
 class DirectDrawWriter;
 
-class Drawable final : public QWidget
+class Drawable : public QWidget
 {
     Q_OBJECT
 public:
@@ -48,7 +48,7 @@ public:
 
     void draw(const Frame &videoFrame);
 
-    void resizeEvent(QResizeEvent *) override;
+    void resizeEvent(QResizeEvent *);
 
     QMPlay2OSDList osd_list;
     bool isOK, isOverlay, paused;
@@ -63,10 +63,10 @@ private:
 
     bool restoreLostSurface();
 
-    void paintEvent(QPaintEvent *) override;
-    bool event(QEvent *) override;
+    void paintEvent(QPaintEvent *);
+    bool event(QEvent *);
 
-    QPaintEngine *paintEngine() const override;
+    QPaintEngine *paintEngine() const;
 
     QImage osdImg;
     QVector<quint64> osd_ids;
@@ -89,7 +89,7 @@ private:
 
 /**/
 
-class DirectDrawWriter final : public VideoWriter
+class DirectDrawWriter : public VideoWriter
 {
     friend class Drawable;
 public:
@@ -97,18 +97,18 @@ public:
 private:
         ~DirectDrawWriter();
 
-    bool set() override;
+    bool set();
 
-    bool readyWrite() const override;
+    bool readyWrite() const;
 
-    bool processParams(bool *paramsCorrected) override;
-    void writeVideo(const Frame &videoFrame, QMPlay2OSDList &&osdList) override;
+    bool processParams(bool *paramsCorrected);
+    void writeVideo(const Frame &videoFrame, QMPlay2OSDList &&osdList);
 
-    void pause() override;
+    void pause();
 
-    QString name() const override;
+    QString name() const;
 
-    bool open() override;
+    bool open();
 
     /**/
 
diff --git a/src/modules/DirectX/DirectX.hpp b/src/modules/DirectX/DirectX.hpp
index c9495e86..380d59d8 100644
--- a/src/modules/DirectX/DirectX.hpp
+++ b/src/modules/DirectX/DirectX.hpp
@@ -22,29 +22,29 @@
 
 #include <QCoreApplication>
 
-class DirectX final : public Module
+class DirectX : public Module
 {
 public:
     DirectX();
 private:
-    QList<Info> getModulesInfo(const bool) const override;
-    void *createInstance(const QString &) override;
+    QList<Info> getModulesInfo(const bool) const;
+    void *createInstance(const QString &);
 
-    SettingsWidget *getSettingsWidget() override;
+    SettingsWidget *getSettingsWidget();
 };
 
 /**/
 
 class QCheckBox;
 
-class ModuleSettingsWidget final : public Module::SettingsWidget
+class ModuleSettingsWidget : public Module::SettingsWidget
 {
     Q_DECLARE_TR_FUNCTIONS(ModuleSettingsWidget)
 
 public:
     ModuleSettingsWidget(Module &);
 private:
-    void saveSettings() override;
+    void saveSettings();
 
     QCheckBox *ddrawB;
 };
diff --git a/src/modules/Extensions/CMakeLists.txt b/src/modules/Extensions/CMakeLists.txt
index e521ee5e..587eeed8 100644
--- a/src/modules/Extensions/CMakeLists.txt
+++ b/src/modules/Extensions/CMakeLists.txt
@@ -80,6 +80,7 @@ endif()
 target_link_libraries(${PROJECT_NAME}
     PRIVATE
     ${QML}
+    ${QJSON4_LIBRARY}
 )
 libqmplay2_set_target_params()
 
diff --git a/src/modules/Extensions/Downloader.cpp b/src/modules/Extensions/Downloader.cpp
index e6d8c2b1..c982a76b 100644
--- a/src/modules/Extensions/Downloader.cpp
+++ b/src/modules/Extensions/Downloader.cpp
@@ -31,8 +31,12 @@
 #include <QTimer>
 #include <QLabel>
 #include <QAction>
-#include <QScreen>
-#include <QWindow>
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+    #include <QScreen>
+    #include <QWindow>
+#else
+    #include <QDesktopWidget>
+#endif
 #include <QProcess>
 #include <QMimeData>
 #include <QFileInfo>
@@ -48,12 +52,31 @@
 #include <QProgressBar>
 #include <QApplication>
 #include <QElapsedTimer>
-#include <QLoggingCategory>
+#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
+    #include <QLoggingCategory>
+#else
+    #include <QDebug>
+#endif
 #include <QDialogButtonBox>
 
 #include <functional>
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
 Q_LOGGING_CATEGORY(downloader, "Downloader")
+#else
+    // Qt4: Create a simple logger class for compatibility
+    class DownloaderLogger {
+    public:
+        DownloaderLogger() : m_debug(qWarning()) {}
+        QDebug &noquote() { return m_debug.nospace(); }
+        operator QDebug&() { return m_debug; }
+        QDebug& operator<<(const QString &s) { m_debug << s; return m_debug; }
+    private:
+        QDebug m_debug;
+    };
+    #define qCWarning(category) DownloaderLogger()
+    #define qCDebug(category) DownloaderLogger()
+#endif
 
 /**/
 
@@ -67,7 +90,8 @@ constexpr const char *g_conversionError = QT_TRANSLATE_NOOP("DownloadItemW", "Co
 
 /**/
 
-static QStringView getCommandOutput(const QString &command)
+// Qt4: Return QString instead of QStringView
+static QString getCommandOutput(const QString &command)
 {
     const int idx1 = command.indexOf("<output>");
     if (idx1 < 0)
@@ -77,7 +101,7 @@ static QStringView getCommandOutput(const QString &command)
     if (idx2 < 0)
         return {};
 
-    return QStringView(command).mid(idx1 + 8, idx2 - idx1 - 8);
+    return command.mid(idx1 + 8, idx2 - idx1 - 8);
 }
 
 static void maybeAddAbsolutePath(QString &convertCommand)
@@ -361,7 +385,11 @@ void DownloadItemW::downloadStop(bool ok)
     finished = true;
     if (!dontDeleteDownloadThr)
     {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         if (visibleRegion().isNull())
+#else
+        if (visibleRegion().isEmpty())
+#endif
             emit QMPlay2Core.sendMessage(titleL->text(), sizeL->text());
     }
 }
@@ -371,31 +399,18 @@ void DownloadItemW::startConversion()
     deleteConvertProcess();
 
     m_convertProcess = new QProcess(this);
-    m_convertProcessConn[0] = connect(m_convertProcess, qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
-        Q_UNUSED(exitStatus)
-        if (exitCode == 0)
-        {
-            sizeL->setText(tr(g_downloadComplete));
-            QFile::remove(filePath);
-            m_needsConversion = false;
-            filePath = m_convertedFilePath;
-            downloadStop(true);
-        }
-        else
-        {
-            sizeL->setText(tr(g_conversionError));
-            qCWarning(downloader) << "Failed to convert:" << m_convertProcess->program() << m_convertProcess->arguments() << m_convertProcess->readAllStandardError().constData();
-            downloadStop(false);
-        }
-    });
-    m_convertProcessConn[1] = connect(m_convertProcess, &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
-        if (error == QProcess::FailedToStart)
-        {
-            sizeL->setText(tr(g_conversionError));
-            downloadStop(false);
-            qCWarning(downloader) << "Failed to start process:" << m_convertProcess->program();
-        }
-    });
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+    m_convertProcessConn[0] = connect(m_convertProcess, SIGNAL(finished(int, QProcess::ExitStatus)),
+                                      this, SLOT(onConvertProcessFinished(int, QProcess::ExitStatus)));
+    m_convertProcessConn[1] = connect(m_convertProcess, SIGNAL(error(QProcess::ProcessError)),
+                                      this, SLOT(onConvertProcessError(QProcess::ProcessError)));
+#else
+    // Qt4: Direct connect without storing connection
+    connect(m_convertProcess, SIGNAL(finished(int, QProcess::ExitStatus)),
+            this, SLOT(onConvertProcessFinished(int, int)));
+    connect(m_convertProcess, SIGNAL(error(QProcess::ProcessError)),
+            this, SLOT(onConvertProcessError(int)));
+#endif
 
     m_needsConversion = true;
     finished = false;
@@ -436,7 +451,7 @@ void DownloadItemW::startConversion()
     const int idx1 = convertCommand.indexOf("<output>");
     const int idx2 = convertCommand.indexOf("</output>", idx1);
 
-    m_convertedFilePath = getCommandOutput(convertCommand).toString();
+    m_convertedFilePath = getCommandOutput(convertCommand);
     m_convertedFilePath.replace("%f", Functions::filePath(filePath) + Functions::fileName(filePath, false));
     if (m_convertedFilePath.isEmpty() || idx1 < 0 || idx2 < idx1)
     {
@@ -453,25 +468,95 @@ void DownloadItemW::startConversion()
     maybeAddAbsolutePath(convertCommand);
 
     qDebug() << "Starting conversion:" << convertCommand.toUtf8().constData();
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
     auto args = QProcess::splitCommand(convertCommand);
     if (!args.isEmpty())
     {
         const QString program = args.takeFirst();
         m_convertProcess->start(program, args);
     }
+#else
+    // Qt4: Use shell to execute command
+    m_convertProcess->start("/bin/sh", QStringList() << "-c" << convertCommand);
+#endif
 }
 void DownloadItemW::deleteConvertProcess()
 {
     if (m_convertProcess)
     {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         disconnect(m_convertProcessConn[0]);
         disconnect(m_convertProcessConn[1]);
+#else
+        // Qt4: Disconnect all signals from m_convertProcess
+        disconnect(m_convertProcess, 0, this, 0);
+#endif
         m_convertProcess->close();
         delete m_convertProcess;
         m_convertProcess = nullptr;
     }
 }
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+void DownloadItemW::onConvertProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
+{
+    Q_UNUSED(exitStatus)
+    if (exitCode == 0)
+    {
+        sizeL->setText(tr(g_downloadComplete));
+        QFile::remove(filePath);
+        m_needsConversion = false;
+        filePath = m_convertedFilePath;
+        downloadStop(true);
+    }
+    else
+    {
+        sizeL->setText(tr(g_conversionError));
+        qCWarning(downloader) << "Failed to convert:" << m_convertProcess->program() << m_convertProcess->arguments() << m_convertProcess->readAllStandardError().constData();
+        downloadStop(false);
+    }
+}
+
+void DownloadItemW::onConvertProcessError(QProcess::ProcessError error)
+{
+    if (error == QProcess::FailedToStart)
+    {
+        sizeL->setText(tr(g_conversionError));
+        downloadStop(false);
+        qCWarning(downloader) << "Failed to start process:" << m_convertProcess->program();
+    }
+}
+#else
+void DownloadItemW::onConvertProcessFinished(int exitCode, int exitStatus)
+{
+    Q_UNUSED(exitStatus)
+    if (exitCode == 0)
+    {
+        sizeL->setText(tr(g_downloadComplete));
+        QFile::remove(filePath);
+        m_needsConversion = false;
+        filePath = m_convertedFilePath;
+        downloadStop(true);
+    }
+    else
+    {
+        sizeL->setText(tr(g_conversionError));
+        qCWarning(downloader) << "Failed to convert:" << m_convertProcess->readAllStandardError().constData();
+        downloadStop(false);
+    }
+}
+
+void DownloadItemW::onConvertProcessError(int error)
+{
+    if (error == QProcess::FailedToStart)
+    {
+        sizeL->setText(tr(g_conversionError));
+        downloadStop(false);
+        qCWarning(downloader) << "Failed to start process";
+    }
+}
+#endif
+
 /**/
 
 DownloaderThread::DownloaderThread(QDataStream *stream, const QString &url, DownloadListW *downloadLW, const QMenu *convertsMenu, const QString &name, const QString &prefix, const QString &param, const QString &preset) :
@@ -648,7 +733,7 @@ void DownloaderThread::run()
                 int i = 0;
                 selectedStreams.reserve(streamsInfo.count());
                 selectedStreamsMap.reserve(streamsInfo.count());
-                for (auto &&streamInfo : std::as_const(streamsInfo))
+                for (auto &&streamInfo : streamsInfo)
                 {
                     if (streamInfo->params->codec_type == AVMEDIA_TYPE_VIDEO && streamInfo->is_default)
                     {
@@ -912,7 +997,7 @@ void Downloader::init()
     connect(downloadLW, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(itemDoubleClicked(QTreeWidgetItem *)));
 
     m_convertsMenu = new QMenu(this);
-    connect(m_convertsMenu->addAction(tr("&Add")), &QAction::triggered, this, &Downloader::addConvertPreset);
+    connect(m_convertsMenu->addAction(tr("&Add")), SIGNAL(triggered()), this, SLOT(addConvertPreset()));
     m_convertsMenu->addSeparator();
 
     clearFinishedB = new QToolButton;
@@ -975,7 +1060,7 @@ void Downloader::init()
         const auto createPreset = [this](const QString &name, const QString &data) {
             QAction *act = m_convertsMenu->addAction(name);
             act->setData(data);
-            connect(act, &QAction::triggered, this, &Downloader::editConvertAction);
+            connect(act, SIGNAL(triggered()), this, SLOT(editConvertAction()));
             return act;
         };
 
@@ -1022,7 +1107,7 @@ QVector<QAction *> Downloader::getActions(const QString &name, double, const QSt
     const auto createAction = [&](const QString &actionName, const QString &preset) {
         QAction *act = new QAction(actionName, nullptr);
         act->setIcon(QIcon(":/downloader.svgz"));
-        act->connect(act, &QAction::triggered, this, &Downloader::download);
+        act->connect(act, SIGNAL(triggered()), this, SLOT(download()));
         act->setProperty("name", name);
         if (!prefix.isEmpty())
         {
@@ -1055,7 +1140,7 @@ void Downloader::addConvertPreset()
     QAction *action = m_convertsMenu->addAction("MP3 224k");
     action->setData(g_defaultMp3ConvertCommand);
     if (modifyConvertAction(action, false))
-        connect(action, &QAction::triggered, this, &Downloader::editConvertAction);
+        connect(action, SIGNAL(triggered()), this, SLOT(editConvertAction()));
     else
         action->deleteLater();
 }
@@ -1075,19 +1160,16 @@ bool Downloader::modifyConvertAction(QAction *action, bool addRemoveButton)
     commandE->setToolTip(tr("Command line to execute after download.\n\n<input/> - specifies downloaded file.\n<output>%f.mp3</output> - converted file will be input file with \"mp3\" extension."));
 
     QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
-    connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
-    connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
+    connect(buttons, SIGNAL(accepted()), &dialog, SLOT(accept()));
+    connect(buttons, SIGNAL(rejected()), &dialog, SLOT(reject()));
     if (addRemoveButton)
     {
         QPushButton *removeB = buttons->addButton(tr("Remove"), QDialogButtonBox::DestructiveRole);
         removeB->setIcon(QMPlay2Core.getIconFromTheme("list-remove"));
-        connect(buttons, &QDialogButtonBox::clicked, &dialog, [&](QAbstractButton *button) {
-            if (button == removeB)
-            {
-                action->deleteLater();
-                dialog.reject();
-            }
-        });
+        // Qt4: Store action pointer as property for the clicked handler
+        removeB->setProperty("targetAction", QVariant::fromValue(static_cast<QObject*>(action)));
+        connect(removeB, SIGNAL(clicked()), action, SLOT(deleteLater()));
+        connect(removeB, SIGNAL(clicked()), &dialog, SLOT(reject()));
     }
 
     QFormLayout *layout = new QFormLayout(&dialog);
@@ -1097,11 +1179,16 @@ bool Downloader::modifyConvertAction(QAction *action, bool addRemoveButton)
     layout->addRow(tr("Command line"), commandE);
     layout->addRow(buttons);
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     if (QWindow *win = window()->windowHandle())
     {
         if (QScreen *screen = win->screen())
             dialog.resize(screen->availableGeometry().width() / 2, 1);
     }
+#else
+    // Qt4: Use QDesktopWidget instead of QWindow/QScreen
+    dialog.resize(QApplication::desktop()->availableGeometry().width() / 2, 1);
+#endif
 
     while (dialog.exec() == QDialog::Accepted)
     {
diff --git a/src/modules/Extensions/Downloader.hpp b/src/modules/Extensions/Downloader.hpp
index 6ee25c59..ba493ef8 100644
--- a/src/modules/Extensions/Downloader.hpp
+++ b/src/modules/Extensions/Downloader.hpp
@@ -25,14 +25,24 @@
 #include <QToolButton>
 #include <QThread>
 
+#if QT_VERSION >= 0x050000
+    #include <QProcess>
+#else
+    #include <QProcess>
+    // Qt4: Forward declare enum types
+    namespace QProcessEnums {
+        enum ExitStatus { NormalExit, CrashExit };
+        enum ProcessError { FailedToStart, Crashed, Timedout, WriteError, ReadError, UnknownError };
+    }
+#endif
+
 class QLabel;
-class QProcess;
 class QGridLayout;
 class QProgressBar;
 class QTreeWidgetItem;
 class DownloaderThread;
 
-class DownloadItemW final : public QWidget
+class DownloadItemW : public QWidget
 {
     Q_OBJECT
 public:
@@ -69,6 +79,13 @@ signals:
     void stop();
 private slots:
     void toggleStartStop();
+#if QT_VERSION >= 0x050000
+    void onConvertProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
+    void onConvertProcessError(QProcess::ProcessError error);
+#else
+    void onConvertProcessFinished(int exitCode, int exitStatus);
+    void onConvertProcessError(int error);
+#endif
 private:
     void downloadStop(bool);
 
@@ -90,7 +107,9 @@ private:
     } *speedProgressW = nullptr;
 
     QProcess *m_convertProcess = nullptr;
+#if QT_VERSION >= 0x050000
     QMetaObject::Connection m_convertProcessConn[2];
+#endif
     bool finished, readyToPlay, m_needsConversion = false;
     QString m_convertPreset;
     QString filePath;
@@ -99,13 +118,13 @@ private:
 
 /**/
 
-class DownloadListW final : public QTreeWidget
+class DownloadListW : public QTreeWidget
 {
 };
 
 /**/
 
-class DownloaderThread final : public QThread
+class DownloaderThread : public QThread
 {
     Q_OBJECT
     enum {ADD_ENTRY, NAME, SET, SET_POS, SET_SPEED, DOWNLOAD_ERROR, FINISH};
@@ -123,7 +142,7 @@ private slots:
     void stop();
     void finished();
 private:
-    void run() override;
+    void run();
 
     QIcon getIcon();
 
@@ -137,7 +156,7 @@ private:
 
 /**/
 
-class Downloader final : public QWidget, public QMPlay2Extensions
+class Downloader : public QWidget, public QMPlay2Extensions
 {
     Q_OBJECT
 
@@ -145,15 +164,17 @@ public:
     Downloader(Module &module);
     ~Downloader();
 
-    void init() override;
+    void init();
 
-    DockWidget *getDockWidget() override;
+    DockWidget *getDockWidget();
 
-    QVector<QAction *> getActions(const QString &, double, const QString &, const QString &, const QString &) override;
+    QVector<QAction *> getActions(const QString &, double, const QString &, const QString &, const QString &);
 
-private:
+private slots:
     void addConvertPreset();
     void editConvertAction();
+
+private:
     bool modifyConvertAction(QAction *action, bool addRemoveButton = true);
 
 private slots:
diff --git a/src/modules/Extensions/Extensions.hpp b/src/modules/Extensions/Extensions.hpp
index 9e559031..8eff8a6c 100644
--- a/src/modules/Extensions/Extensions.hpp
+++ b/src/modules/Extensions/Extensions.hpp
@@ -20,15 +20,15 @@
 
 #include <Module.hpp>
 
-class Extensions final : public Module
+class Extensions : public Module
 {
 public:
     Extensions();
 private:
-    QList<Info> getModulesInfo(const bool) const override;
-    void *createInstance(const QString &) override;
+    QList<Info> getModulesInfo(const bool) const;
+    void *createInstance(const QString &);
 
-    SettingsWidget *getSettingsWidget() override;
+    SettingsWidget *getSettingsWidget();
 
     QIcon downloader;
 #ifdef USE_YOUTUBE
@@ -54,7 +54,7 @@ class QGroupBox;
 class QCheckBox;
 class LineEdit;
 
-class ModuleSettingsWidget final : public Module::SettingsWidget
+class ModuleSettingsWidget : public Module::SettingsWidget
 {
     Q_OBJECT
 public:
@@ -65,7 +65,7 @@ private slots:
     void passwordEdited();
 #endif
 private:
-    void saveSettings() override;
+    void saveSettings();
 
 #ifdef USE_MPRIS2
     QCheckBox *MPRIS2B;
diff --git a/src/modules/Extensions/LastFM.cpp b/src/modules/Extensions/LastFM.cpp
index c44e7fb3..699b863e 100644
--- a/src/modules/Extensions/LastFM.cpp
+++ b/src/modules/Extensions/LastFM.cpp
@@ -29,7 +29,11 @@ Q_DECLARE_METATYPE(LastFM::Scrobble)
 
 #include <QCryptographicHash>
 #include <QStringList>
-#include <QUrlQuery>
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+    #include <QUrlQuery>
+#else
+    #include <QUrl>
+#endif
 #include <QImage>
 
 LastFM::LastFM(Module &module) :
@@ -43,7 +47,7 @@ LastFM::LastFM(Module &module) :
     loginTimer.setSingleShot(true);
     connect(&updateTim, SIGNAL(timeout()), this, SLOT(processScrobbleQueue()));
     connect(&loginTimer, SIGNAL(timeout()), this, SLOT(login()));
-    connect(&QMPlay2Core, &QMPlay2CoreClass::updatePlaying, this, &LastFM::updatePlaying);
+    connect(&QMPlay2Core, SIGNAL(updatePlaying(bool, QString, QString, QString, int, bool, QString, QString)), this, SLOT(updatePlaying(bool, QString, QString, QString, int, bool, QString, QString)));
 }
 
 bool LastFM::set()
@@ -162,6 +166,7 @@ void LastFM::updateNowPlayingAndScrobble(const Scrobble &scrobble)
         QCryptographicHash::Md5
     ).toHex();
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     QUrlQuery updateNowPlayingQuery;
     updateNowPlayingQuery.addQueryItem("method", "track.updatenowplaying");
     updateNowPlayingQuery.addQueryItem("artist", scrobble.artist);
@@ -173,8 +178,20 @@ void LastFM::updateNowPlayingAndScrobble(const Scrobble &scrobble)
     updateNowPlayingQuery.addQueryItem("sk", session_key);
 
     reply = net.start(audioScrobbler2URL, updateNowPlayingQuery.toString(QUrl::EncodeDelimiters).toUtf8(), NetworkAccess::UrlEncoded);
-    connect(reply, &NetworkReply::finished,
-            reply, &NetworkReply::deleteLater);
+#else
+    QUrl updateNowPlayingQuery;
+    updateNowPlayingQuery.addQueryItem("method", "track.updatenowplaying");
+    updateNowPlayingQuery.addQueryItem("artist", scrobble.artist);
+    updateNowPlayingQuery.addQueryItem("track", scrobble.title);
+    updateNowPlayingQuery.addQueryItem("album", scrobble.album.isEmpty() ? "" : scrobble.album);
+    updateNowPlayingQuery.addQueryItem("duration", duration);
+    updateNowPlayingQuery.addQueryItem("api_key", api_key);
+    updateNowPlayingQuery.addQueryItem("api_sig", apiSig);
+    updateNowPlayingQuery.addQueryItem("sk", session_key);
+
+    reply = net.start(audioScrobbler2URL, updateNowPlayingQuery.encodedQuery(), NetworkAccess::UrlEncoded);
+#endif
+    connect(reply, SIGNAL(finished()), reply, SLOT(deleteLater()));
 
     // scrobble
     const auto ts = QString::number(scrobble.startTime);
@@ -186,6 +203,7 @@ void LastFM::updateNowPlayingAndScrobble(const Scrobble &scrobble)
         QCryptographicHash::Md5
     ).toHex();
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     QUrlQuery scrobbleQuery;
     scrobbleQuery.addQueryItem("method", "track.scrobble");
     scrobbleQuery.addQueryItem("artist", scrobble.artist);
@@ -197,14 +215,24 @@ void LastFM::updateNowPlayingAndScrobble(const Scrobble &scrobble)
     scrobbleQuery.addQueryItem("sk", session_key);
 
     reply = net.start(audioScrobbler2URL, scrobbleQuery.toString(QUrl::EncodeDelimiters).toUtf8(), NetworkAccess::UrlEncoded);
+#else
+    QUrl scrobbleQuery;
+    scrobbleQuery.addQueryItem("method", "track.scrobble");
+    scrobbleQuery.addQueryItem("artist", scrobble.artist);
+    scrobbleQuery.addQueryItem("track", scrobble.title);
+    scrobbleQuery.addQueryItem("timestamp", ts);
+    scrobbleQuery.addQueryItem("album", scrobble.album.isEmpty() ? "" : scrobble.album);
+    scrobbleQuery.addQueryItem("api_key", api_key);
+    scrobbleQuery.addQueryItem("api_sig", apiSig);
+    scrobbleQuery.addQueryItem("sk", session_key);
+
+    reply = net.start(audioScrobbler2URL, scrobbleQuery.encodedQuery(), NetworkAccess::UrlEncoded);
+#endif
     reply->setProperty("scrobble", QVariant::fromValue(scrobble));
     m_scrobbleReplies.push_back(reply);
-    connect(reply, &NetworkReply::destroyed,
-            this, [=] {
-        m_scrobbleReplies.removeOne(reply);
-    });
-    connect(reply, &NetworkReply::finished,
-            this, &LastFM::scrobbleFinished);
+    m_scrobbleReplyMap[reply] = reply;
+    connect(reply, SIGNAL(destroyed(QObject*)), this, SLOT(onScrobbleReplyDestroyed()));
+    connect(reply, SIGNAL(finished()), this, SLOT(scrobbleFinished()));
 }
 
 void LastFM::clear()
@@ -225,7 +253,7 @@ void LastFM::updatePlaying(bool play, QString title, QString artist, const QStri
     QString origTitle;
     if (!title.isEmpty() && artist.isEmpty())
     {
-        const int idx = title.indexOf(QStringLiteral(" - "));
+        const int idx = title.indexOf(QString(" - "));
         if (idx > 0)
         {
             origTitle = title;
@@ -256,7 +284,11 @@ void LastFM::updatePlaying(bool play, QString title, QString artist, const QStri
             }
             else for (int i = 0; i < scrobbleQueue.count(); ++i)
                 if (scrobbleQueue[i] == scrobble && currTime - scrobbleQueue[i].startTime < scrobbleSec)
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+                    scrobbleQueue.remove(i);
+#else
                     scrobbleQueue.removeAt(i);
+#endif
             if (scrobbleQueue.isEmpty())
                 updateTim.stop();
             else if (!session_key.isEmpty())
@@ -278,7 +310,7 @@ void LastFM::albumFinished()
     bool coverNotFound = false;
     if (coverReply->hasError())
     {
-        if (!isCoverImage && coverReply->error() == NetworkReply::Error::Connection400)
+        if (!isCoverImage && coverReply->error() == NetworkReply::ErrorConnection400)
             coverNotFound = true;
     }
     else
@@ -288,7 +320,7 @@ void LastFM::albumFinished()
             emit QMPlay2Core.updateCover(taa[0], taa[1], taa[2], reply);
         else
         {
-            for (const QString &size : std::as_const(imageSizes))
+            for (const QString &size : imageSizes)
             {
                 int idx = reply.indexOf(size.toLatin1());
                 if (idx > -1)
@@ -316,7 +348,7 @@ void LastFM::albumFinished()
     if (coverNotFound && !origTitle.isEmpty())
     {
         // Swap title and artist and set "origTitle" to empty (non-null) to not enter into else-if
-        getAlbumCover(taa[1], taa[0], QString(), QStringLiteral(""), false); //This also will delete the old "coverReply"
+        getAlbumCover(taa[1], taa[0], QString(), QString(""), false); //This also will delete the old "coverReply"
         return;
     }
     else if (coverNotFound && !titleAsAlbum && origTitle.isNull())
@@ -341,7 +373,7 @@ void LastFM::loginFinished()
 {
     if (loginReply->hasError())
     {
-        const bool wrongLoginOrPassword = (loginReply->error() == NetworkReply::Error::Connection403);
+        const bool wrongLoginOrPassword = (loginReply->error() == NetworkReply::ErrorConnection403);
         if (!dontShowLoginError || wrongLoginOrPassword)
             QMPlay2Core.logError(tr("LastFM login error.") + (wrongLoginOrPassword ? (" " + tr("Check login and password!")) : QString()));
         if (wrongLoginOrPassword)
@@ -401,3 +433,18 @@ void LastFM::processScrobbleQueue()
     while (!scrobbleQueue.isEmpty())
         updateNowPlayingAndScrobble(scrobbleQueue.dequeue());
 }
+
+void LastFM::onScrobbleReplyDestroyed()
+{
+    QObject *obj = sender();
+    if (!obj)
+        return;
+
+    auto it = m_scrobbleReplyMap.find(obj);
+    if (it != m_scrobbleReplyMap.end())
+    {
+        NetworkReply *reply = it.value();
+        m_scrobbleReplyMap.erase(it);
+        m_scrobbleReplies.removeOne(reply);
+    }
+}
diff --git a/src/modules/Extensions/LastFM.hpp b/src/modules/Extensions/LastFM.hpp
index a2704197..29f965a0 100644
--- a/src/modules/Extensions/LastFM.hpp
+++ b/src/modules/Extensions/LastFM.hpp
@@ -29,7 +29,7 @@
 
 class QImage;
 
-class LastFM final : public QObject, public QMPlay2Extensions
+class LastFM : public QObject, public QMPlay2Extensions
 {
     Q_OBJECT
 public:
@@ -49,7 +49,7 @@ public:
 
     LastFM(Module &module);
 private:
-    bool set() override;
+    bool set();
 
     void getAlbumCover(const QString &title, const QString &artist, const QString &album, const QString &origTitle, bool titleAsAlbum = false);
 
@@ -60,7 +60,7 @@ private:
 
     void clear();
 
-    void updatePlaying(bool play, QString title, QString artist, const QString &album, int length, bool needCover, const QString &fileName, const QString &url);
+    Q_SLOT void updatePlaying(bool play, QString title, QString artist, const QString &album, int length, bool needCover, const QString &fileName, const QString &url);
 
 private slots:
     void albumFinished();
@@ -68,9 +68,12 @@ private slots:
     void scrobbleFinished();
 
     void processScrobbleQueue();
+
+    void onScrobbleReplyDestroyed();
 private:
     NetworkReply *coverReply, *loginReply;
     QList<NetworkReply *> m_scrobbleReplies;
+    QHash<QObject*, NetworkReply*> m_scrobbleReplyMap;
     bool downloadCovers, dontShowLoginError, firstTime;
     QString user, md5pass, session_key;
     QQueue<Scrobble> scrobbleQueue;
diff --git a/src/modules/Extensions/Lyrics.cpp b/src/modules/Extensions/Lyrics.cpp
index a69ec6b1..cd5f2d07 100644
--- a/src/modules/Extensions/Lyrics.cpp
+++ b/src/modules/Extensions/Lyrics.cpp
@@ -20,7 +20,11 @@
 
 #include <Functions.hpp>
 
-#include <QRegularExpression>
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+    #include <QRegularExpression>
+#else
+    #include <QRegExp>
+#endif
 #include <QFile>
 
 #include <algorithm>
@@ -57,8 +61,13 @@ Lyrics::Lyrics(Module &module) :
 {
     SetModule(module);
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     connect(&QMPlay2Core, &QMPlay2CoreClass::updatePlaying,
             this, &Lyrics::updatePlaying);
+#else
+    connect(&QMPlay2Core, SIGNAL(updatePlaying(bool, QString, QString, QString, int, bool, QString, QString, QString)),
+            this, SLOT(updatePlaying(bool, QString, QString, QString, int, bool, QString, QString, QString)));
+#endif
     connect(&m_net, SIGNAL(finished(NetworkReply *)), this, SLOT(finished(NetworkReply *)));
 
     m_dW = new DockWidget;
@@ -121,7 +130,7 @@ void Lyrics::updatePlaying(bool play, const QString &title, const QString &artis
         m_artist = artist;
         if (!m_title.isEmpty() && m_artist.isEmpty())
         {
-            const int idx = m_title.indexOf(QStringLiteral(" - "));
+            const int idx = m_title.indexOf(QString(" - "));
             if (idx > 0)
             {
                 m_artist = m_title.mid(0, idx);
@@ -169,7 +178,7 @@ void Lyrics::finished(NetworkReply *reply)
             using Guess = std::tuple<QString, QString, quint8>;
             std::vector<Guess> guesses;
 
-            for (const QString &chunk : std::as_const(list))
+            for (const QString &chunk : list)
             {
                 const QString name = extractTag(chunk, "title");
 
@@ -280,13 +289,13 @@ void Lyrics::finished(NetworkReply *reply)
 
 bool Lyrics::loadLrcFile(const QString &url)
 {
-    if (!url.startsWith(QStringLiteral("file://")))
+    if (!url.startsWith(QString("file://")))
         return false;
 
     const QString localPath = url.mid(7);
     const QString filePath = Functions::filePath(localPath);
     const QString baseName = Functions::fileName(localPath, false);
-    const QString lrcPath = filePath + baseName + QStringLiteral(".lrc");
+    const QString lrcPath = filePath + baseName + QString(".lrc");
 
     QFile file(lrcPath);
     if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
@@ -302,25 +311,45 @@ bool Lyrics::loadLrcFile(const QString &url)
         return false;
 
     const QString data = Functions::textWithFallbackEncoding(rawData);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
     const QStringList lines = data.split('\n', Qt::SkipEmptyParts);
+#else
+    const QStringList lines = data.split('\n', QString::SkipEmptyParts);
+#endif
 
     QString artist, title;
     QStringList lyricsLines;
 
-    const QRegularExpression tagRegex(QStringLiteral(R"(^\[(\w+):(.*)\]$)"));
-    const QRegularExpression timeTagRegex(QStringLiteral(R"(\[\d{1,2}:\d{2}(?:\.\d{1,2})?\])"));
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+    const QRegularExpression tagRegex(QString(R"(^\[(\w+):(.*)\]$)"));
+    const QRegularExpression timeTagRegex(QString(R"(\[\d{1,2}:\d{2}(?:\.\d{1,2})?\])"));
+#else
+    // Qt4: No QRegularExpression
+    QRegExp tagRegex(QString(R"(^\[(\w+):(.*)\]$)"));
+    QRegExp timeTagRegex(QString(R"(\[\d{1,2}:\d{2}(?:\.\d{1,2})?\])"));
+#endif
 
     for (const QString &line : lines)
     {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         const QRegularExpressionMatch match = tagRegex.match(line.trimmed());
-        if (match.hasMatch())
+        const bool hasMatch = match.hasMatch();
+#else
+        const bool hasMatch = (tagRegex.indexIn(line.trimmed()) != -1);
+#endif
+        if (hasMatch)
         {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
             const QString key = match.captured(1);
             const QString value = match.captured(2).trimmed();
+#else
+            const QString key = tagRegex.cap(1);
+            const QString value = tagRegex.cap(2).trimmed();
+#endif
 
-            if (key == QStringLiteral("ar"))
+            if (key == QString("ar"))
                 artist = value;
-            else if (key == QStringLiteral("ti"))
+            else if (key == QString("ti"))
                 title = value;
         }
         else
@@ -336,21 +365,21 @@ bool Lyrics::loadLrcFile(const QString &url)
     if (lyricsLines.isEmpty())
         return false;
 
-    QString html = QStringLiteral("<center>");
+    QString html = QString("<center>");
 
     if (!title.isEmpty() || !artist.isEmpty())
     {
-        html += QStringLiteral("<b>");
+        html += QString("<b>");
         if (!title.isEmpty() && !artist.isEmpty())
-            html += title + QStringLiteral(" - ") + artist;
+            html += title + QString(" - ") + artist;
         else if (!title.isEmpty())
             html += title;
         else
             html += artist;
-        html += QStringLiteral("</b><br/><br/>");
+        html += QString("</b><br/><br/>");
     }
 
-    html += lyricsLines.join(QStringLiteral("<br/>")) + QStringLiteral("</center>");
+    html += lyricsLines.join(QString("<br/>")) + QString("</center>");
     setHtml(html);
 
     return true;
diff --git a/src/modules/Extensions/Lyrics.hpp b/src/modules/Extensions/Lyrics.hpp
index 6c2538f8..2ab6c6ff 100644
--- a/src/modules/Extensions/Lyrics.hpp
+++ b/src/modules/Extensions/Lyrics.hpp
@@ -25,7 +25,7 @@
 #include <QTextEdit>
 #include <QPointer>
 
-class Lyrics final : public QTextEdit, public QMPlay2Extensions
+class Lyrics : public QTextEdit, public QMPlay2Extensions
 {
     Q_OBJECT
 
@@ -33,15 +33,13 @@ public:
     Lyrics(Module &module);
     ~Lyrics();
 
-    DockWidget *getDockWidget() override;
+    DockWidget *getDockWidget();
 
 private slots:
     void visibilityChanged(bool v);
 
-private:
     void updatePlaying(bool play, const QString &title, const QString &artist, const QString &album, int length, bool needCover, const QString &fileName, const QString &url, const QString &lyrics);
 
-private slots:
     void finished(NetworkReply *reply);
 
 private:
diff --git a/src/modules/Extensions/MPRIS2.cpp b/src/modules/Extensions/MPRIS2.cpp
index 6705aa72..64175446 100644
--- a/src/modules/Extensions/MPRIS2.cpp
+++ b/src/modules/Extensions/MPRIS2.cpp
@@ -114,7 +114,7 @@ MediaPlayer2Player::MediaPlayer2Player(QObject *p) :
 {
     clearMetaData();
     m_data["mpris:trackid"] = QVariant::fromValue<QDBusObjectPath>(trackID);
-    connect(&QMPlay2Core, &QMPlay2CoreClass::updatePlaying, this, &MediaPlayer2Player::updatePlaying);
+    connect(&QMPlay2Core, SIGNAL(updatePlaying(bool, const QString &, const QString &, const QString &, int, bool, const QString &, const QString &)), this, SLOT(updatePlaying(bool, const QString &, const QString &, const QString &, int, bool, const QString &, const QString &)));
     connect(&QMPlay2Core, SIGNAL(coverDataFromMediaFile(const QByteArray &)), this, SLOT(coverDataFromMediaFile(const QByteArray &)));
     connect(&QMPlay2Core, SIGNAL(playStateChanged(const QString &)), this, SLOT(playStateChanged(const QString &)));
     connect(&QMPlay2Core, SIGNAL(coverFile(const QString &)), this, SLOT(coverFile(const QString &)));
diff --git a/src/modules/Extensions/MPRIS2.hpp b/src/modules/Extensions/MPRIS2.hpp
index 5e4c87d3..9fd67a64 100644
--- a/src/modules/Extensions/MPRIS2.hpp
+++ b/src/modules/Extensions/MPRIS2.hpp
@@ -23,7 +23,7 @@
 
 #include <memory>
 
-class MediaPlayer2Root final : public QDBusAbstractAdaptor
+class MediaPlayer2Root : public QDBusAbstractAdaptor
 {
     Q_OBJECT
 
@@ -62,7 +62,7 @@ private:
 
 /**/
 
-class MediaPlayer2Player final : public QDBusAbstractAdaptor
+class MediaPlayer2Player : public QDBusAbstractAdaptor
 {
     Q_OBJECT
 
@@ -115,9 +115,8 @@ public slots:
     void OpenUri(const QString &Uri);
 signals:
     void Seeked(qint64 Position);
-private:
-    void updatePlaying(bool play, const QString &title, const QString &artist, const QString &album, int length, bool needCover, const QString &fileName, const QString &url);
 private slots:
+    void updatePlaying(bool play, const QString &title, const QString &artist, const QString &album, int length, bool needCover, const QString &fileName, const QString &url);
     void coverDataFromMediaFile(const QByteArray &cover);
     void playStateChanged(const QString &plState);
     void coverFile(const QString &filePath);
@@ -140,7 +139,7 @@ private:
 
 /**/
 
-class MPRIS2Interface final : public QObject
+class MPRIS2Interface : public QObject
 {
 public:
     MPRIS2Interface();
@@ -158,13 +157,13 @@ private:
 
 #include <QMPlay2Extensions.hpp>
 
-class MPRIS2 final : public QMPlay2Extensions
+class MPRIS2 : public QMPlay2Extensions
 {
 public:
     MPRIS2(Module &module);
     ~MPRIS2();
 private:
-    bool set() override;
+    bool set();
 
     std::unique_ptr<MPRIS2Interface> mpris2Interface;
 };
diff --git a/src/modules/Extensions/MediaBrowser.cpp b/src/modules/Extensions/MediaBrowser.cpp
index e5a9017d..806bef97 100644
--- a/src/modules/Extensions/MediaBrowser.cpp
+++ b/src/modules/Extensions/MediaBrowser.cpp
@@ -42,10 +42,10 @@
 #include <QDir>
 #include <QUrl>
 
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QJsonValue>
-#include <QJsonArray>
+#include <QJsonDocument.h>
+#include <QJsonObject.h>
+#include <QJsonValue.h>
+#include <QJsonArray.h>
 
 #include <algorithm>
 
@@ -786,7 +786,7 @@ void MediaBrowser::netFinished(NetworkReply *reply)
             m_pages->hide();
             m_loadAllB->hide();
             m_progressB->hide();
-            if (reply->error() == NetworkReply::Error::Connection404)
+            if (reply->error() == NetworkReply::ErrorConnection404)
                 emit QMPlay2Core.sendMessage(tr("Website doesn't exist"), MediaBrowserName, 3);
             else
                 emit QMPlay2Core.sendMessage(tr("Connection error"), MediaBrowserName, 3);
diff --git a/src/modules/Extensions/MediaBrowser.hpp b/src/modules/Extensions/MediaBrowser.hpp
index 93ff96e7..6a948e43 100644
--- a/src/modules/Extensions/MediaBrowser.hpp
+++ b/src/modules/Extensions/MediaBrowser.hpp
@@ -33,7 +33,7 @@ class MediaBrowserJS;
 
 /**/
 
-class MediaBrowserResults final : public QTreeWidget
+class MediaBrowserResults : public QTreeWidget
 {
     Q_OBJECT
 
@@ -69,7 +69,7 @@ private:
 class QToolButton;
 class QComboBox;
 
-class MediaBrowserPages final : public QWidget
+class MediaBrowserPages : public QWidget
 {
     Q_OBJECT
 
@@ -114,7 +114,7 @@ class QCompleter;
 class QTextEdit;
 class LineEdit;
 
-class MediaBrowser final : public QWidget, public QMPlay2Extensions
+class MediaBrowser : public QWidget, public QMPlay2Extensions
 {
     Q_OBJECT
 
@@ -123,14 +123,14 @@ public:
     ~MediaBrowser();
 
 private:
-    DockWidget *getDockWidget() override;
+    DockWidget *getDockWidget();
 
-    bool canConvertAddress() const override;
+    bool canConvertAddress() const;
 
-    QList<AddressPrefix> addressPrefixList(bool) const override;
-    void convertAddress(const QString &prefix, const QString &url, const QString &param, QString *streamUrl, QString *name, QIcon *icon, QString *extension, IOController<> *ioCtrl) override;
+    QList<AddressPrefix> addressPrefixList(bool) const;
+    void convertAddress(const QString &prefix, const QString &url, const QString &param, QString *streamUrl, QString *name, QIcon *icon, QString *extension, IOController<> *ioCtrl);
 
-    QVector<QAction *> getActions(const QString &, double, const QString &, const QString &, const QString &) override;
+    QVector<QAction *> getActions(const QString &, double, const QString &, const QString &, const QString &);
 
 private:
     inline void setCompleterListCallback();
diff --git a/src/modules/Extensions/OpenSubtitles.cpp b/src/modules/Extensions/OpenSubtitles.cpp
index 06c6050b..eae623ff 100644
--- a/src/modules/Extensions/OpenSubtitles.cpp
+++ b/src/modules/Extensions/OpenSubtitles.cpp
@@ -25,12 +25,14 @@
 #include <QDesktopServices>
 #include <QXmlStreamReader>
 #include <QStringListModel>
-#include <QJsonDocument>
+#include <QJsonDocument.h>
+#include <QJsonObject.h>
 #include <QGridLayout>
+#include <QUrl>
 #include <QTreeWidget>
 #include <QHeaderView>
 #include <QScrollBar>
-#include <QJsonArray>
+#include <QJsonArray.h>
 #include <QCompleter>
 #include <QComboBox>
 #include <QAction>
@@ -65,15 +67,15 @@ enum
 
 static inline QString getBaseUrl()
 {
-    return QStringLiteral("https://www.opensubtitles.org");
+    return QString("https://www.opensubtitles.org");
 }
 static inline QString getSuggestUrl(const QString &text, const QString &lang)
 {
-    return getBaseUrl() + QStringLiteral("/libs/suggest.php?format=json3&MovieName=%1&SubLanguageID=%2").arg(text.toUtf8().toPercentEncoding(), lang);
+    return getBaseUrl() + QString("/libs/suggest.php?format=json3&MovieName=%1&SubLanguageID=%2").arg(text.toUtf8().toPercentEncoding(), lang);
 }
 static inline QString getSearchUrl(const QString &text, const QString &lang)
 {
-    return getBaseUrl() + QStringLiteral("/search2/moviename-%1/sublanguageid-%2/xml").arg(text.toUtf8().toPercentEncoding(), lang);
+    return getBaseUrl() + QString("/search2/moviename-%1/sublanguageid-%2/xml").arg(text.toUtf8().toPercentEncoding(), lang);
 }
 
 struct OpenSubtitles::Subtitle
@@ -115,29 +117,17 @@ OpenSubtitles::OpenSubtitles(Module &module, const QIcon &icon)
 
     m_completerTimer->setSingleShot(true);
     m_completerTimer->setInterval(250);
-    connect(m_completerTimer, &QTimer::timeout, this, [this] {
-        complete();
-    });
+    connect(m_completerTimer, SIGNAL(timeout()), this, SLOT(onCompleterTimeout()));
 
     m_searchEdit->setPlaceholderText(tr("Type the movie name and press enter"));
     m_searchEdit->setCompleter(completer);
-    connect(m_searchEdit, &LineEdit::clearButtonClicked, this, [this] {
-        clearCompleter();
-        search();
-    });
-    connect(m_searchEdit, &QLineEdit::textEdited, this, [this](const QString &text) {
-        Q_UNUSED(text);
-        m_dontComplete = false;
-        m_completerTimer->start();
-    });
-    connect(m_searchEdit, &QLineEdit::returnPressed, this, [this] {
-        m_dontComplete = true;
-        search();
-    });
+    connect(m_searchEdit, SIGNAL(clearButtonClicked()), this, SLOT(onSearchEditClearButtonClicked()));
+    connect(m_searchEdit, SIGNAL(textEdited(QString)), this, SLOT(onSearchEditTextEdited(QString)));
+    connect(m_searchEdit, SIGNAL(returnPressed()), this, SLOT(onSearchEditReturnPressed()));
 
     m_languages->setToolTip(tr("Choose subtitles language"));
     m_languages->setSizeAdjustPolicy(QComboBox::AdjustToContents);
-    const auto lang = sets().getString(QStringLiteral("Language"), QStringLiteral("eng"));
+    const auto lang = sets().getString(QString("Language"), QString("eng"));
 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
     const auto languagesMap = QMPlay2Core.getLanguagesMap();
     for (auto it = languagesMap.constBegin(), itEnd = languagesMap.constEnd(); it != itEnd; ++it)
@@ -162,78 +152,19 @@ OpenSubtitles::OpenSubtitles(Module &module, const QIcon &icon)
             m_languages->setCurrentIndex(idx);
         }
     }
-#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
-    connect(m_languages, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int idx) {
-#else
-    connect(m_languages, &QComboBox::currentIndexChanged, this, [this](int idx) {
-#endif
-        Q_UNUSED(idx)
-        sets().set(QStringLiteral("Language"), getLanguage());
-        search();
-    });
+    connect(m_languages, SIGNAL(currentIndexChanged(int)), this, SLOT(onLanguageChanged(int)));
 
     m_results->setContextMenuPolicy(Qt::CustomContextMenu);
     m_results->setIconSize(QSize(47, 47));
     m_results->setSortingEnabled(true);
     m_results->setColumnCount(3);
     m_results->setAnimated(true);
-    connect(m_results, &QTreeWidget::customContextMenuRequested, this, [this](const QPoint &p) {
-        Q_UNUSED(p);
-        if (const auto twi = m_results->currentItem())
-        {
-            const auto url = twi->data(0, DownloadUrlRole).toString();
-            if (!url.isEmpty())
-            {
-                QMenu menu(m_results);
-                menu.addAction(tr("Download in default web browser"), this, [url] {
-                    QDesktopServices::openUrl(url);
-                });
-                menu.exec(QCursor::pos());
-            }
-        }
-    });
-    connect(m_results, &QTreeWidget::itemExpanded, this, [this](QTreeWidgetItem *twi) {
-        if (Q_UNLIKELY(!twi))
-            return;
-
-        if (twi->childCount() == 1)
-        {
-            const auto subItem = twi->child(0);
-            const auto url = twi->data(0, SubItemUrlRole).toString();
-            if (!url.isEmpty())
-            {
-                subItem->setText(0, tr("Loading..."));
-                twi->setData(0, SubItemUrlRole, QVariant());
-                twi->setData(0, SubItemUrlRoleBak, url);
-                loadSubItem(url, m_results->indexFromItem(twi), false);
-            }
-        }
-    });
-    connect(m_results, &QTreeWidget::itemDoubleClicked, this, [](QTreeWidgetItem *twi, int column) {
-        Q_UNUSED(column);
-
-        if (Q_UNLIKELY(!twi))
-            return;
+    connect(m_results, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onResultsCustomContextMenuRequested(QPoint)));
+    connect(m_results, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(onResultsItemExpanded(QTreeWidgetItem*)));
+    connect(m_results, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(onResultsItemDoubleClicked(QTreeWidgetItem*,int)));
 
-        if (!twi->parent() || Q_UNLIKELY(twi->childCount() != 0))
-            return;
-
-        const auto url = twi->data(0, DownloadUrlRole).toString();
-        if (url.isEmpty())
-            return;
-
-        QDesktopServices::openUrl(url);
-    });
-
-    connect(m_results->verticalScrollBar(), &QScrollBar::rangeChanged, this, [this](int min, int max) {
-        Q_UNUSED(min)
-        Q_UNUSED(max)
-        scanForToUrl();
-    });
-    connect(m_results->verticalScrollBar(), &QScrollBar::valueChanged, this, [this](int value) {
-        Q_UNUSED(value)
-        scanForToUrl();
-    });
+    connect(m_results->verticalScrollBar(), SIGNAL(rangeChanged(int,int)), this, SLOT(onScrollBarRangeChanged(int,int)));
+    connect(m_results->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onScrollBarValueChanged(int)));
 
     const auto headerItem = m_results->headerItem();
     headerItem->setText(0, tr("Movie name"));
@@ -242,9 +173,15 @@ OpenSubtitles::OpenSubtitles(Module &module, const QIcon &icon)
 
     const auto header = m_results->header();
     header->setStretchLastSection(false);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     header->setSectionResizeMode(0, QHeaderView::Stretch);
     header->setSectionResizeMode(1, QHeaderView::ResizeToContents);
     header->setSectionResizeMode(2, QHeaderView::ResizeToContents);
+#else
+    header->setResizeMode(0, QHeaderView::Stretch);
+    header->setResizeMode(1, QHeaderView::ResizeToContents);
+    header->setResizeMode(2, QHeaderView::ResizeToContents);
+#endif
     header->setSortIndicator(-1, Qt::AscendingOrder);
 
     auto layout = new QGridLayout(this);
@@ -266,13 +203,8 @@ QVector<QAction *> OpenSubtitles::getActions(const QString &name, double, const
     if (name != url)
     {
         auto act = new QAction(tr("Search on OpenSubtitles"), nullptr);
-        act->connect(act, &QAction::triggered, this, [this, name](bool checked) {
-            Q_UNUSED(checked)
-            clearCompleter();
-            m_searchEdit->setText(name);
-            search();
-            m_dw->raise();
-        });
+        act->setData(name); // Store search term for slot
+        act->connect(act, SIGNAL(triggered(bool)), this, SLOT(onLanguageActionTriggered(bool)));
         act->setIcon(m_icon);
         return {act};
     }
@@ -287,7 +219,8 @@ void OpenSubtitles::parseCompleterJson(const QByteArray &data)
     titles.reserve(array.size());
     for (auto &&element : array)
     {
-        const auto title = element[QStringLiteral("name")].toString();
+        const QJsonObject elementObj = element.toObject();
+        const auto title = elementObj[QString("name")].toString();
         if (!title.isEmpty())
         {
             titles.push_back(title);
@@ -328,8 +261,8 @@ void OpenSubtitles::parseXml(const QByteArray &data, QTreeWidgetItem *parentTwi)
         const auto name = xml.name();
         const auto attrs = xml.attributes();
 
-        const bool isResultsFound = (name.compare(QStringLiteral("results_found"), Qt::CaseInsensitive) == 0);
-        const bool isSubtitle = (name.compare(QStringLiteral("subtitle"), Qt::CaseInsensitive) == 0);
+        const bool isResultsFound = (name.compare(QString("results_found"), Qt::CaseInsensitive) == 0);
+        const bool isSubtitle = (name.compare(QString("subtitle"), Qt::CaseInsensitive) == 0);
 
         if (tokenType != QXmlStreamReader::StartElement)
         {
@@ -350,11 +283,11 @@ void OpenSubtitles::parseXml(const QByteArray &data, QTreeWidgetItem *parentTwi)
 
         if (inResultsFound)
         {
-            if (name == QStringLiteral("to"))
+            if (name == QString("to"))
             {
                 if (toUrl.isEmpty())
                 {
-                    toUrl = attrs.value(QStringLiteral("Linker")).toString();
+                    toUrl = attrs.value(QString("Linker")).toString();
                     if (!toUrl.isEmpty())
                         toUrl.prepend(getBaseUrl());
                 }
@@ -363,90 +296,90 @@ void OpenSubtitles::parseXml(const QByteArray &data, QTreeWidgetItem *parentTwi)
         else if (inSubtitle)
         {
             Q_ASSERT(subtitle);
-            if (name == QStringLiteral("IDSubtitle"))
+            if (name == QString("IDSubtitle"))
             {
                 if (subtitle->downloadUrl.isEmpty())
-                    subtitle->downloadUrl = attrs.value(QStringLiteral("LinkDownload")).toString();
+                    subtitle->downloadUrl = attrs.value(QString("LinkDownload")).toString();
             }
-            else if (name == QStringLiteral("MovieName"))
+            else if (name == QString("MovieName"))
             {
                 if (subtitle->name.isEmpty())
                     subtitle->name = xml.readElementText();
             }
-            else if (name == QStringLiteral("EpisodeName"))
+            else if (name == QString("EpisodeName"))
             {
                 if (subtitle->episodeName.isEmpty())
                     subtitle->episodeName = xml.readElementText();
                 if (subtitle->episodeUrl.isEmpty())
-                    subtitle->episodeUrl = getBaseUrl() + attrs.value(QStringLiteral("Link")).toString() + "/xml";
+                    subtitle->episodeUrl = getBaseUrl() + attrs.value(QString("Link")).toString() + "/xml";
             }
-            else if (name == QStringLiteral("MovieThumb"))
+            else if (name == QString("MovieThumb"))
             {
                 if (subtitle->thumbUrl.isEmpty())
                     subtitle->thumbUrl = xml.readElementText();
             }
-            else if (name == QStringLiteral("MovieReleaseName"))
+            else if (name == QString("MovieReleaseName"))
             {
                 if (subtitle->releaseName.isEmpty())
                     subtitle->releaseName = xml.readElementText();
                 if (subtitle->downloadUrl.isEmpty())
-                    subtitle->downloadUrl = getBaseUrl() + attrs.value(QStringLiteral("Link")).toString();
+                    subtitle->downloadUrl = getBaseUrl() + attrs.value(QString("Link")).toString();
             }
-            else if (name == QStringLiteral("SubFormat"))
+            else if (name == QString("SubFormat"))
             {
                 if (subtitle->format.isEmpty())
                     subtitle->format = xml.readElementText();
             }
-            else if (name == QStringLiteral("SubDownloadsCnt"))
+            else if (name == QString("SubDownloadsCnt"))
             {
                 if (subtitle->downloadsCount.isEmpty())
                    subtitle->downloadsCount = xml.readElementText();
             }
-            else if (name == QStringLiteral("SubDate"))
+            else if (name == QString("SubDate"))
             {
                 if (subtitle->subDate.isEmpty())
                     subtitle->subDate = xml.readElementText();
             }
-            else if (name == QStringLiteral("MovieYear"))
+            else if (name == QString("MovieYear"))
             {
                 if (subtitle->movieYear.isEmpty())
                     subtitle->movieYear = xml.readElementText();
             }
-            else if (name == QStringLiteral("MovieID"))
+            else if (name == QString("MovieID"))
             {
                 if (subtitle->movieUrl.isEmpty())
-                    subtitle->movieUrl = getBaseUrl() + attrs.value(QStringLiteral("Link")).toString();
+                    subtitle->movieUrl = getBaseUrl() + attrs.value(QString("Link")).toString();
             }
-            else if (name == QStringLiteral("SeriesSeason"))
+            else if (name == QString("SeriesSeason"))
             {
                 if (subtitle->seriesSeason.isEmpty())
                     subtitle->seriesSeason = xml.readElementText();
             }
-            else if (name == QStringLiteral("SeriesSubtitles"))
+            else if (name == QString("SeriesSubtitles"))
             {
                 if (subtitle->seriesSubtitles.isEmpty())
                     subtitle->seriesSubtitles = xml.readElementText();
             }
 #if 0
-            else if (name == QStringLiteral("SeriesDownloadsCnt"))
+            else if (name == QString("SeriesDownloadsCnt"))
             {
                 if (subtitle->downloadUrl.isEmpty())
-                    subtitle->downloadUrl = attrs.value(QStringLiteral("LinkDownload")).toString();
+                    subtitle->downloadUrl = attrs.value(QString("LinkDownload")).toString();
                 if (subtitle->downloadsCount.isEmpty())
                     subtitle->downloadsCount = xml.readElementText();
             }
-            else if (name == QStringLiteral("Newest"))
+            else if (name == QString("Newest"))
             {
                 if (subtitle->subDate.isEmpty())
                     subtitle->subDate = xml.readElementText();
             }
 #endif
-            else if (name == QStringLiteral("Subtitle"))
+            else if (name == QString("Subtitle"))
             {
                 if (subtitle->downloadUrl.isEmpty())
-                    subtitle->downloadUrl = attrs.value(QStringLiteral("LinkDownload")).toString();
+                    subtitle->downloadUrl = attrs.value(QString("LinkDownload")).toString();
                 if (subtitle->subDate.isEmpty())
-                    subtitle->subDate = attrs.value(QStringLiteral("SubAddDate")).toString();
+                    subtitle->subDate = attrs.value(QString("SubAddDate")).toString();
             }
         }
         else if (isResultsFound)
@@ -469,7 +402,7 @@ void OpenSubtitles::parseXml(const QByteArray &data, QTreeWidgetItem *parentTwi)
             return new QTreeWidgetItem(parentTwi);
         return new QTreeWidgetItem(m_results);
     };
-    for (auto &&subtitle : std::as_const(subtitles))
+    for (auto &&subtitle : subtitles)
     {
         if (!subtitle.episodeUrl.isEmpty())
         {
@@ -482,7 +415,7 @@ void OpenSubtitles::parseXml(const QByteArray &data, QTreeWidgetItem *parentTwi)
             if (subtitle.seriesSeason.toInt() > 0)
                 twi->setText(0, tr("%1 (Season %2)").arg(subtitle.episodeName, subtitle.seriesSeason));
             else
-                twi->setText(0, QStringLiteral("%1").arg(subtitle.episodeName));
+                twi->setText(0, QString("%1").arg(subtitle.episodeName));
 
             new QTreeWidgetItem(twi);
             twi->setData(0, SubItemUrlRole, subtitle.episodeUrl);
@@ -501,9 +434,9 @@ void OpenSubtitles::parseXml(const QByteArray &data, QTreeWidgetItem *parentTwi)
 
             auto twi = createTwi();
             if (!subtitle.movieYear.isEmpty())
-                twi->setText(0, QStringLiteral("%1 (%2)").arg(name, subtitle.movieYear));
+                twi->setText(0, QString("%1 (%2)").arg(name, subtitle.movieYear));
             else
-                twi->setText(0, QStringLiteral("%1").arg(name));
+                twi->setText(0, QString("%1").arg(name));
             twi->setText(1, subtitle.subDate);
             twi->setData(2, Qt::UserRole + 0, subtitle.downloadsCount);
             twi->setData(2, Qt::UserRole + 1, subtitle.format);
@@ -530,18 +463,17 @@ void OpenSubtitles::parseXml(const QByteArray &data, QTreeWidgetItem *parentTwi)
             auto twi = createTwi();
             twi->setText(0, subtitle.name + " (" + subtitle.movieYear + ")");
 
-            if (subtitle.thumbUrl.startsWith(QStringLiteral("http")))
+            if (subtitle.thumbUrl.startsWith(QString("http")))
             {
                 auto reply = m_net->start(subtitle.thumbUrl);
                 m_loadThumbnailReplies.push_back(reply);
-                connect(reply, &NetworkReply::finished, this, [this, reply, twi] {
-                    if (!reply->hasError() && m_results->indexOfTopLevelItem(twi) > -1)
-                    {
-                        twi->setIcon(0, QIcon(QPixmap::fromImage(QImage::fromData(reply->readAll()))));
-                    }
-                    m_loadThumbnailReplies.removeOne(reply);
-                    reply->deleteLater();
-                });
+
+                ReplyData data;
+                data.reply = reply;
+                data.twi = twi;
+                m_replyDataMap[reply] = data;
+
+                connect(reply, SIGNAL(finished()), this, SLOT(onThumbnailReplyFinished()));
             }
 
             new QTreeWidgetItem(twi);
@@ -575,7 +507,7 @@ void OpenSubtitles::parseXml(const QByteArray &data, QTreeWidgetItem *parentTwi)
             downloadCountFieldWidth = qMax(downloadCountFieldWidth , twi->data(2, Qt::UserRole + 0).toString().size());
         });
         iterateItems([&downloadCountFieldWidth](QTreeWidgetItem *twi) {
-            twi->setText(2, QStringLiteral("%1x (%2)").arg(twi->data(2, Qt::UserRole + 0).toString(), downloadCountFieldWidth).arg(twi->data(2, Qt::UserRole + 1).toString()));
+            twi->setText(2, QString("%1x (%2)").arg(twi->data(2, Qt::UserRole + 0).toString(), downloadCountFieldWidth).arg(twi->data(2, Qt::UserRole + 1).toString()));
         });
     }
 
@@ -593,19 +525,17 @@ void OpenSubtitles::complete()
     if (m_completerReply)
     {
         m_completerReply->abort();
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         m_completerReply.clear();
+#else
+        m_completerReply = nullptr;
+#endif
     }
 
     if (const auto text = m_searchEdit->text().simplified(); !text.isEmpty())
     {
         m_completerReply = m_net->start(getSuggestUrl(text, getLanguage()));
-        connect(m_completerReply, &NetworkReply::finished, this, [this, completerReply = m_completerReply] {
-            if (!completerReply->hasError())
-            {
-                parseCompleterJson(completerReply->readAll());
-            }
-            completerReply->deleteLater();
-        });
+        connect(m_completerReply, SIGNAL(finished()), this, SLOT(onCompleterReplyFinished()));
     }
     else
     {
@@ -648,16 +578,20 @@ void OpenSubtitles::search()
     if (m_searchReply)
     {
         m_searchReply->abort();
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         m_searchReply.clear();
+#else
+        m_searchReply = nullptr;
+#endif
     }
 
-    for (auto &&reply : std::as_const(m_loadSubItemReplies))
+    for (auto &&reply : m_loadSubItemReplies)
     {
         reply->abort();
     }
     m_loadSubItemReplies.clear();
 
-    for (auto &&reply : std::as_const(m_loadThumbnailReplies))
+    for (auto &&reply : m_loadThumbnailReplies)
     {
         reply->abort();
     }
@@ -668,22 +602,7 @@ void OpenSubtitles::search()
         m_searchReply = m_net->start(getSearchUrl(text, getLanguage()));
         setBusyCursor(true);
         m_results->setEnabled(false);
-        connect(m_searchReply, &NetworkReply::finished, this, [this, searchReply = m_searchReply] {
-            m_results->clear();
-            if (!searchReply->hasError())
-            {
-                parseXml(searchReply->readAll());
-                m_results->scrollToTop();
-            }
-            else
-            {
-                auto twi = new QTreeWidgetItem(m_results);
-                twi->setText(0, tr("Error"));
-            }
-            setBusyCursor(false);
-            m_results->setEnabled(true);
-            searchReply->deleteLater();
-        });
+        connect(m_searchReply, SIGNAL(finished()), this, SLOT(onSearchReplyFinished()));
     }
     else
     {
@@ -698,19 +617,16 @@ void OpenSubtitles::searchNext()
     if (m_searchReply)
     {
         m_searchReply->abort();
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         m_searchReply.clear();
+#else
+        m_searchReply = nullptr;
+#endif
     }
 
     m_searchReply = m_net->start(m_toUrl);
     setBusyCursor(true);
-    connect(m_searchReply, &NetworkReply::finished, this, [this, searchReply = m_searchReply] {
-        if (!searchReply->hasError())
-        {
-            parseXml(searchReply->readAll());
-        }
-        setBusyCursor(false);
-        searchReply->deleteLater();
-    });
+    connect(m_searchReply, SIGNAL(finished()), this, SLOT(onSearchNextReplyFinished()));
 
     m_toUrl.clear();
 }
@@ -719,11 +635,233 @@ void OpenSubtitles::loadSubItem(const QString &url, const QPersistentModelIndex
     auto reply = m_net->start(url);
     setBusyCursor(true);
     m_loadSubItemReplies.push_back(reply);
-    connect(reply, &NetworkReply::finished, this, [this, reply, index, next] {
-        const auto twi = m_results->itemFromIndex(index);
-        if (Q_LIKELY(twi) && (next || Q_LIKELY(twi->childCount() == 1)))
+
+    ReplyData data;
+    data.reply = reply;
+    data.index = index;
+    data.next = next;
+    m_replyDataMap[reply] = data;
+
+    connect(reply, SIGNAL(finished()), this, SLOT(onLoadSubItemReplyFinished()));
+}
+
+void OpenSubtitles::setBusyCursor(bool set)
+{
+    if (set)
+    {
+        if (++m_busyCursor == 1)
+        {
+            setCursor(Qt::BusyCursor);
+        }
+    }
+    else if (m_busyCursor > 0 && --m_busyCursor == 0)
+    {
+        setCursor(QCursor());
+    }
+}
+
+// Qt4 slot implementations
+
+void OpenSubtitles::onCompleterTimeout()
+{
+    complete();
+}
+
+void OpenSubtitles::onSearchEditClearButtonClicked()
+{
+    clearCompleter();
+    search();
+}
+
+void OpenSubtitles::onSearchEditTextEdited(const QString &text)
+{
+    Q_UNUSED(text);
+    m_dontComplete = false;
+    m_completerTimer->start();
+}
+
+void OpenSubtitles::onSearchEditReturnPressed()
+{
+    m_dontComplete = true;
+    search();
+}
+
+void OpenSubtitles::onLanguageChanged(int idx)
+{
+    Q_UNUSED(idx)
+    sets().set(QString("Language"), getLanguage());
+    search();
+}
+
+void OpenSubtitles::onResultsCustomContextMenuRequested(const QPoint &p)
+{
+    Q_UNUSED(p);
+    if (const auto twi = m_results->currentItem())
+    {
+        const auto url = twi->data(0, DownloadUrlRole).toString();
+        if (!url.isEmpty())
+        {
+            QDesktopServices::openUrl(QUrl(url));
+        }
+    }
+}
+
+void OpenSubtitles::onResultsItemExpanded(QTreeWidgetItem *twi)
+{
+    if (Q_UNLIKELY(!twi))
+        return;
+
+    if (twi->childCount() == 1)
+    {
+        const auto subItem = twi->child(0);
+        const auto url = twi->data(0, SubItemUrlRole).toString();
+        if (!url.isEmpty())
+        {
+            subItem->setText(0, tr("Loading..."));
+            twi->setData(0, SubItemUrlRole, QVariant());
+            twi->setData(0, SubItemUrlRoleBak, url);
+            loadSubItem(url, m_results->indexFromItem(twi), false);
+        }
+    }
+}
+
+void OpenSubtitles::onResultsItemDoubleClicked(QTreeWidgetItem *twi, int column)
+{
+    Q_UNUSED(column);
+
+    if (Q_UNLIKELY(!twi))
+        return;
+
+    if (!twi->parent() || Q_UNLIKELY(twi->childCount() != 0))
+        return;
+
+    const auto url = twi->data(0, DownloadUrlRole).toString();
+    if (url.isEmpty())
+        return;
+
+    QDesktopServices::openUrl(QUrl(url));
+}
+
+void OpenSubtitles::onScrollBarRangeChanged(int min, int max)
+{
+    Q_UNUSED(min)
+    Q_UNUSED(max)
+    scanForToUrl();
+}
+
+void OpenSubtitles::onScrollBarValueChanged(int value)
+{
+    Q_UNUSED(value)
+    scanForToUrl();
+}
+
+void OpenSubtitles::onLanguageActionTriggered(bool checked)
+{
+    Q_UNUSED(checked)
+    QAction *act = qobject_cast<QAction*>(sender());
+    if (!act)
+        return;
+
+    QString name = act->data().toString();
+    clearCompleter();
+    m_searchEdit->setText(name);
+    search();
+    m_dw->raise();
+}
+
+void OpenSubtitles::onThumbnailReplyFinished()
+{
+    NetworkReply *reply = qobject_cast<NetworkReply*>(sender());
+    if (!reply)
+        return;
+
+    auto it = m_replyDataMap.find(reply);
+    if (it != m_replyDataMap.end())
+    {
+        ReplyData data = it.value();
+        m_replyDataMap.erase(it);
+
+        if (!reply->hasError() && m_results->indexOfTopLevelItem(data.twi) > -1)
+        {
+            data.twi->setIcon(0, QIcon(QPixmap::fromImage(QImage::fromData(reply->readAll()))));
+        }
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+        m_loadThumbnailReplies.removeOne(reply);
+#else
+        // Qt4: QVector doesn't have removeOne, use indexOf + remove
+        int idx = m_loadThumbnailReplies.indexOf(reply);
+        if (idx >= 0)
+            m_loadThumbnailReplies.remove(idx);
+#endif
+        reply->deleteLater();
+    }
+}
+
+void OpenSubtitles::onCompleterReplyFinished()
+{
+    NetworkReply *completerReply = m_completerReply;
+    if (!completerReply)
+        return;
+
+    if (!completerReply->hasError())
+    {
+        parseCompleterJson(completerReply->readAll());
+    }
+    completerReply->deleteLater();
+}
+
+void OpenSubtitles::onSearchReplyFinished()
+{
+    NetworkReply *searchReply = m_searchReply;
+    if (!searchReply)
+        return;
+
+    m_results->clear();
+    if (!searchReply->hasError())
+    {
+        parseXml(searchReply->readAll());
+        m_results->scrollToTop();
+    }
+    else
+    {
+        auto twi = new QTreeWidgetItem(m_results);
+        twi->setText(0, tr("Error"));
+    }
+    setBusyCursor(false);
+    m_results->setEnabled(true);
+    searchReply->deleteLater();
+}
+
+void OpenSubtitles::onSearchNextReplyFinished()
+{
+    NetworkReply *searchReply = m_searchReply;
+    if (!searchReply)
+        return;
+
+    if (!searchReply->hasError())
+    {
+        parseXml(searchReply->readAll());
+    }
+    setBusyCursor(false);
+    searchReply->deleteLater();
+}
+
+void OpenSubtitles::onLoadSubItemReplyFinished()
+{
+    NetworkReply *reply = qobject_cast<NetworkReply*>(sender());
+    if (!reply)
+        return;
+
+    auto it = m_replyDataMap.find(reply);
+    if (it != m_replyDataMap.end())
+    {
+        ReplyData data = it.value();
+        m_replyDataMap.erase(it);
+
+        const auto twi = m_results->itemFromIndex(data.index);
+        if (Q_LIKELY(twi) && (data.next || Q_LIKELY(twi->childCount() == 1)))
         {
-            auto loadingTwi = next
+            auto loadingTwi = data.next
                 ? nullptr
                 : twi->child(0)
             ;
@@ -745,23 +883,15 @@ void OpenSubtitles::loadSubItem(const QString &url, const QPersistentModelIndex
             }
         }
         setBusyCursor(false);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         m_loadSubItemReplies.removeOne(reply);
+#else
+        // Qt4: QVector doesn't have removeOne, use indexOf + remove
+        int idx = m_loadSubItemReplies.indexOf(reply);
+        if (idx >= 0)
+            m_loadSubItemReplies.remove(idx);
+#endif
         reply->deleteLater();
-    });
-}
-
-void OpenSubtitles::setBusyCursor(bool set)
-{
-    if (set)
-    {
-        if (++m_busyCursor == 1)
-        {
-            setCursor(Qt::BusyCursor);
-        }
-    }
-    else if (m_busyCursor > 0 && --m_busyCursor == 0)
-    {
-        setCursor(QCursor());
     }
 }
 
@@ -772,5 +902,9 @@ inline void OpenSubtitles::clearCompleter()
 
 inline QString OpenSubtitles::getLanguage() const
 {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     return m_languages->currentData().toString();
+#else
+    return m_languages->itemData(m_languages->currentIndex()).toString();
+#endif
 }
diff --git a/src/modules/Extensions/OpenSubtitles.hpp b/src/modules/Extensions/OpenSubtitles.hpp
index 9186abc9..bdc5c82c 100644
--- a/src/modules/Extensions/OpenSubtitles.hpp
+++ b/src/modules/Extensions/OpenSubtitles.hpp
@@ -21,6 +21,7 @@
 #include <QMPlay2Extensions.hpp>
 
 #include <QPointer>
+#include <QPersistentModelIndex>
 
 class QTreeWidgetItem;
 class NetworkAccess;
@@ -29,14 +30,14 @@ class QComboBox;
 class LineEdit;
 class QTimer;
 
-#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+#if QT_VERSION < 0x060000
 class TreeWidget;
 #else
 class QTreeWidget;
 using TreeWidget = QTreeWidget;
 #endif
 
-class OpenSubtitles final : public QWidget, public QMPlay2Extensions
+class OpenSubtitles : public QWidget, public QMPlay2Extensions
 {
     Q_OBJECT
 
@@ -46,9 +47,9 @@ public:
     OpenSubtitles(Module &module, const QIcon &icon);
     ~OpenSubtitles();
 
-    DockWidget *getDockWidget() override;
+    DockWidget *getDockWidget();
 
-    QVector<QAction *> getActions(const QString &name, double, const QString &url, const QString &, const QString &) override;
+    QVector<QAction *> getActions(const QString &name, double, const QString &url, const QString &, const QString &);
 
 private:
     void parseCompleterJson(const QByteArray &data);
@@ -68,7 +69,41 @@ private:
 
     inline QString getLanguage() const;
 
+private slots:
+    void onCompleterTimeout();
+    void onSearchEditClearButtonClicked();
+    void onSearchEditTextEdited(const QString &text);
+    void onSearchEditReturnPressed();
+    void onLanguageChanged(int idx);
+    void onResultsCustomContextMenuRequested(const QPoint &p);
+    void onResultsItemExpanded(QTreeWidgetItem *twi);
+    void onResultsItemDoubleClicked(QTreeWidgetItem *twi, int column);
+    void onScrollBarRangeChanged(int min, int max);
+    void onScrollBarValueChanged(int value);
+    void onLanguageActionTriggered(bool checked);
+    void onThumbnailReplyFinished();
+    void onCompleterReplyFinished();
+    void onSearchReplyFinished();
+    void onSearchNextReplyFinished();
+    void onLoadSubItemReplyFinished();
+
 private:
+    struct ReplyData {
+        QPointer<NetworkReply> reply;
+        QTreeWidgetItem *twi;
+        QPersistentModelIndex index;
+        bool next;
+
+        ReplyData() : twi(nullptr), next(false) {}
+#if QT_VERSION >= 0x050000
+        ReplyData(const ReplyData &) = default;
+        ReplyData &operator=(const ReplyData &) = default;
+#endif
+    };
+
+    QHash<QAction*, QString> m_languageActions;
+    QHash<QObject*, ReplyData> m_replyDataMap;
+
     const QIcon m_icon;
 
     DockWidget *const m_dw;
diff --git a/src/modules/Extensions/Radio.cpp b/src/modules/Extensions/Radio.cpp
index 6242be97..0e590969 100644
--- a/src/modules/Extensions/Radio.cpp
+++ b/src/modules/Extensions/Radio.cpp
@@ -24,13 +24,13 @@
 #include <Playlist.hpp>
 
 #include <QDesktopServices>
-#include <QJsonDocument>
+#include <QJsonDocument.h>
 #include <QInputDialog>
 #include <QMessageBox>
 #include <QFileDialog>
-#include <QJsonObject>
+#include <QJsonObject.h>
 #include <QScrollBar>
-#include <QJsonArray>
+#include <QJsonArray.h>
 #include <qevent.h>
 #include <QBuffer>
 #include <QTimer>
@@ -111,7 +111,7 @@ Radio::Radio(Module &module) :
     connect(m_dw, SIGNAL(dockVisibilityChanged(bool)), this, SLOT(visibilityChanged(bool)));
     connect(m_radioBrowserModel, SIGNAL(radiosAdded()), m_loadIconsTimer, SLOT(start()));
     connect(m_radioBrowserModel, SIGNAL(searchFinished()), this, SLOT(searchFinished()));
-    connect(m_radioBrowserModel, &RadioBrowserModel::connectionError, this, &Radio::connectionError);
+    connect(m_radioBrowserModel, SIGNAL(connectionError()), this, SLOT(connectionError()));
     connect(m_radioView->verticalScrollBar(), SIGNAL(valueChanged(int)), m_loadIconsTimer, SLOT(start()));
     connect(m_loadIconsTimer, SIGNAL(timeout()), this, SLOT(loadIcons()));
     connect(m_filterEdit, SIGNAL(textEdited(QString)), m_radioBrowserModel, SLOT(setFiltrText(QString)));
@@ -120,16 +120,16 @@ Radio::Radio(Module &module) :
     connect(m_searchComboBox, SIGNAL(activated(int)), this, SLOT(searchData()));
     connect(m_net, SIGNAL(finished(NetworkReply *)), this, SLOT(replyFinished(NetworkReply *)));
 
-    connect(m_addMyRadioStationButton, &QToolButton::clicked, this, &Radio::addMyRadioStationButtonClicked);
-    connect(m_editMyRadioStationButton, &QToolButton::clicked, this, &Radio::editMyRadioStationButtonClicked);
-    connect(m_removeMyRadioStationButton, &QToolButton::clicked, this, &Radio::removeMyRadioStationButtonClicked);
-    connect(m_loadMyRadioStationButton, &QToolButton::clicked, this, &Radio::loadMyRadioStationButtonClicked);
-    connect(m_saveMyRadioStationButton, &QToolButton::clicked, this, &Radio::saveMyRadioStationButtonClicked);
-    connect(m_myRadioListWidget, &QListWidget::itemDoubleClicked, this, &Radio::myRadioListWidgetItemDoubleClicked);
-    connect(m_searchByComboBox, qOverload<int>(&QComboBox::activated), this, &Radio::searchByComboBoxActivated);
-    connect(m_addRadioBrowserStationButton, &QToolButton::clicked, this, &Radio::addRadioBrowserStationButtonClicked);
-    connect(m_radioView, &QTreeView::doubleClicked, this, &Radio::radioViewDoubleClicked);
-    connect(m_radioView, &QTreeView::customContextMenuRequested, this, &Radio::radioViewCustomContextMenuRequested);
+    connect(m_addMyRadioStationButton, SIGNAL(clicked()), this, SLOT(addMyRadioStationButtonClicked()));
+    connect(m_editMyRadioStationButton, SIGNAL(clicked()), this, SLOT(editMyRadioStationButtonClicked()));
+    connect(m_removeMyRadioStationButton, SIGNAL(clicked()), this, SLOT(removeMyRadioStationButtonClicked()));
+    connect(m_loadMyRadioStationButton, SIGNAL(clicked()), this, SLOT(loadMyRadioStationButtonClicked()));
+    connect(m_saveMyRadioStationButton, SIGNAL(clicked()), this, SLOT(saveMyRadioStationButtonClicked()));
+    connect(m_myRadioListWidget, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT(myRadioListWidgetItemDoubleClicked(QListWidgetItem *)));
+    connect(m_searchByComboBox, SIGNAL(activated(int)), this, SLOT(searchByComboBoxActivated(int)));
+    connect(m_addRadioBrowserStationButton, SIGNAL(clicked()), this, SLOT(addRadioBrowserStationButtonClicked()));
+    connect(m_radioView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(radioViewDoubleClicked(QModelIndex)));
+    connect(m_radioView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(radioViewCustomContextMenuRequested(QPoint)));
 }
 Radio::~Radio()
 {
@@ -201,8 +201,7 @@ void Radio::ensureTrayMenu()
     {
         auto act = m_menu->addAction(item->text());
         act->setData(item->data(Qt::UserRole));
-        connect(act, &QAction::triggered,
-                this, &Radio::trayActionTriggered);
+        connect(act, SIGNAL(triggered(bool)), this, SLOT(trayActionTriggered(bool)));
     }
 
     m_recreateTrayMenu = false;
@@ -231,21 +230,26 @@ void Radio::searchFinished()
         s += header->sectionSize(i);
     if (s < header->width()) // It's inaccurate comparison
     {
-        header->setSectionResizeMode(0, QHeaderView::Stretch);
+        header->
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+setSectionResizeMode(
+#else
+setResizeMode(
+#endif
+0, QHeaderView::Stretch);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         if (!m_sectionResizedConn)
         {
-            m_sectionResizedConn = connect(header, &QHeaderView::sectionResized,
-                                           header, [this, header](int logicalIndex, int oldSize, int newSize) {
-                Q_UNUSED(oldSize)
-                Q_UNUSED(newSize)
-
-                if (logicalIndex != 0)
-                    return;
-
-                disconnect(m_sectionResizedConn);
-                header->setSectionResizeMode(0, QHeaderView::Interactive);
-            }, Qt::QueuedConnection);
+            m_sectionResizedConn = connect(header, SIGNAL(sectionResized(int,int,int)),
+                                           this, SLOT(onHeaderSectionResized(int,int,int)),
+                                           Qt::QueuedConnection);
         }
+#else
+        // Qt4: Always connect, we'll handle disconnect manually
+        connect(header, SIGNAL(sectionResized(int,int,int)),
+                this, SLOT(onHeaderSectionResized(int,int,int)),
+                Qt::QueuedConnection);
+#endif
     }
     m_radioView->setEnabled(true);
 }
@@ -287,7 +291,8 @@ void Radio::replyFinished(NetworkReply *reply)
                     if (!data.isObject())
                         continue;
 
-                    const auto name = data["name"].toString();
+                    const QJsonObject dataObj = data.toObject();
+                    const auto name = dataObj["name"].toString();
                     if (name.trimmed().isEmpty())
                         continue;
 
@@ -478,11 +483,13 @@ void Radio::saveMyRadioStationButtonClicked()
     if (idx < 0)
         return;
 
-    const auto suffix = QStringView(filter).mid(idx + 2).chopped(1);
+    // Qt4: No QStringView, use QString methods directly
+    // chopped(1) is equivalent to left(length - 1)
+    const auto suffix = filter.mid(idx + 2, filter.length() - idx - 3);
     if (!filePath.endsWith(suffix, Qt::CaseInsensitive))
         filePath += suffix;
 
-    if (suffix == QStringLiteral(".qmplay2radio"))
+    if (suffix == QString(".qmplay2radio"))
     {
         QSettings(filePath, QSettings::IniFormat).setValue("Radia", getMyRadios());
     }
@@ -659,10 +666,10 @@ void Radio::setSearchInfo(const QStringList &list)
         if (m_searchByComboBox->currentIndex() == Country)
         {
             const auto country = QLocale::countryToString(QLocale::system().country());
-            auto it = std::find_if(list.crbegin(), list.crend(), [&](const QString &str) {
+            auto it = std::find_if(list.constBegin(), list.constEnd(), [&](const QString &str) {
                 return str.contains(country, Qt::CaseInsensitive);
             });
-            const int idx = list.size() - std::distance(list.crbegin(), it) - 1;
+            const int idx = list.size() - std::distance(list.constBegin(), it) - 1;
             if (!country.isEmpty() && idx >= 0 && idx < list.size())
             {
                 m_searchComboBox->setCurrentIndex(idx);
@@ -759,6 +766,33 @@ void Radio::connectionError()
     emit QMPlay2Core.sendMessage(tr("Connection error"), RadioName, 3);
 }
 
+void Radio::onHeaderSectionResized(int logicalIndex, int oldSize, int newSize)
+{
+    Q_UNUSED(oldSize)
+    Q_UNUSED(newSize)
+
+    if (logicalIndex != 0)
+        return;
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+    disconnect(m_sectionResizedConn);
+    m_sectionResizedConn = QMetaObject::Connection(); // Reset connection
+#else
+    // Qt4: Disconnect all sectionResized signals from this slot
+    disconnect(m_radioView->header(), SIGNAL(sectionResized(int, int, int)),
+               this, SLOT(onHeaderSectionResized(int, int, int)));
+#endif
+
+    QHeaderView *header = m_radioView->header();
+    header->
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+setSectionResizeMode(
+#else
+setResizeMode(
+#endif
+0, QHeaderView::Interactive);
+}
+
 bool Radio::eventFilter(QObject *watched, QEvent *event)
 {
     if (watched == m_myRadioListWidget)
diff --git a/src/modules/Extensions/Radio.hpp b/src/modules/Extensions/Radio.hpp
index 053ce77b..c731eb35 100644
--- a/src/modules/Extensions/Radio.hpp
+++ b/src/modules/Extensions/Radio.hpp
@@ -20,9 +20,13 @@
 
 #include <QMPlay2Extensions.hpp>
 
+#include <QWidget>
 #include <QPointer>
 #include <QIcon>
 #include <QMap>
+#include <QMenu>
+#include <QTimer>
+#include <QModelIndex>
 
 class QSplitter;
 class QComboBox;
@@ -36,9 +40,8 @@ class RadioBrowserModel;
 class QListWidgetItem;
 class NetworkAccess;
 class NetworkReply;
-class QTimer;
 
-class Radio final : public QWidget, public QMPlay2Extensions
+class Radio : public QWidget, public QMPlay2Extensions
 {
     Q_OBJECT
 
@@ -46,10 +49,10 @@ public:
     Radio(Module &);
     ~Radio();
 
-    DockWidget *getDockWidget() override;
+    DockWidget *getDockWidget();
 
-    QMenu *getTrayMenu() override;
-    void ensureTrayMenu() override;
+    QMenu *getTrayMenu();
+    void ensureTrayMenu();
 
 private slots:
     void visibilityChanged(const bool v);
@@ -66,9 +69,13 @@ private slots:
     void radioBrowserEnqueue();
     void radioBrowserOpenHomePage();
 
+    void onHeaderSectionResized(int logicalIndex, int oldSize, int newSize);
+
 private:
     void setupUi();
 
+private slots:
+    // Qt4: Connected using the old signal/slot syntax
     void addMyRadioStationButtonClicked();
     void editMyRadioStationButtonClicked();
     void removeMyRadioStationButtonClicked();
@@ -82,6 +89,7 @@ private:
     void radioViewDoubleClicked(const QModelIndex &index);
     void radioViewCustomContextMenuRequested(const QPoint &pos);
 
+private:
     QString getFileFilters(bool all) const;
 
     void radioBrowserPlayOrEnqueue(const QModelIndex &index, const QString &param);
@@ -95,14 +103,17 @@ private:
     QStringList getMyRadios() const;
     void loadMyRadios(const QStringList &radios);
 
+private slots:
     void trayActionTriggered(bool checked);
 
+private:
     void play(const QString &url, const QString &name);
 
+private slots:
     void connectionError();
 
 private:
-    bool eventFilter(QObject *watched, QEvent *event) override;
+    bool eventFilter(QObject *watched, QEvent *event);
 
 private:
     QSplitter *m_radioBrowserSplitter;
@@ -129,7 +140,9 @@ private:
     DockWidget *m_dw;
     QMenu *m_menu = nullptr;
 
+#if QT_VERSION >= 0x050000
     QMetaObject::Connection m_sectionResizedConn;
+#endif
 
     QMap<int, QPair<QStringList, QPointer<NetworkReply>>> m_searchInfo;
     RadioBrowserModel *m_radioBrowserModel;
diff --git a/src/modules/Extensions/Radio/RadioBrowserModel.cpp b/src/modules/Extensions/Radio/RadioBrowserModel.cpp
index 1b6deb78..a97c347c 100644
--- a/src/modules/Extensions/Radio/RadioBrowserModel.cpp
+++ b/src/modules/Extensions/Radio/RadioBrowserModel.cpp
@@ -21,9 +21,9 @@
 #include <NetworkAccess.hpp>
 #include <Functions.hpp>
 
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QJsonArray>
+#include <QJsonDocument.h>
+#include <QJsonObject.h>
+#include <QJsonArray.h>
 #include <QPainter>
 #include <QWidget>
 #include <QUrl>
@@ -62,7 +62,7 @@ RadioBrowserModel::~RadioBrowserModel()
 
 void RadioBrowserModel::clear()
 {
-    for (const std::shared_ptr<Column> &column : std::as_const(m_rows))
+    for (const std::shared_ptr<Column> &column : m_rows)
         delete column->iconReply;
     delete m_replySearch;
 
@@ -88,7 +88,7 @@ void RadioBrowserModel::loadIcons(const int first, const int last)
         if (!column->iconReply && !column->iconUrl.isEmpty())
         {
             column->iconReply = m_net->start(column->iconUrl);
-            for (const std::shared_ptr<Column> &c : std::as_const(m_rows))
+            for (const std::shared_ptr<Column> &c : m_rows)
             {
                 if (c.get() == column)
                     continue;
@@ -296,7 +296,7 @@ void RadioBrowserModel::setFiltrText(const QString &text)
     else
     {
         m_rowsToDisplay.clear();
-        for (const std::shared_ptr<Column> &column : std::as_const(m_rows))
+        for (const std::shared_ptr<Column> &column : m_rows)
         {
             if (column->name.contains(text, Qt::CaseInsensitive))
                 m_rowsToDisplay.append(column);
@@ -323,11 +323,12 @@ void RadioBrowserModel::replyFinished(NetworkReply *reply)
 
             const QPixmap radioIcon = QIcon(":/radio.svgz").pixmap(elementHeight(), elementHeight());
 
-            for (const QJsonValue item : arrayItems)
+            for (const QJsonValue &itemVal : arrayItems)
             {
-                if (!item.isObject())
+                if (!itemVal.isObject())
                     continue;
 
+                const QJsonObject item = itemVal.toObject();
                 QString streamInfo = item["codec"].toString();
                 if (!streamInfo.isEmpty())
                 {
@@ -407,10 +408,13 @@ void RadioBrowserModel::replyFinished(NetworkReply *reply)
                     if (!image.isNull())
                     {
                         const int s = elementHeight();
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
                         const qreal dpr = m_widget->devicePixelRatioF();
-
                         column->icon = QPixmap(s * dpr, s * dpr);
                         column->icon.setDevicePixelRatio(dpr);
+#else
+                        column->icon = QPixmap(s, s);
+#endif
                         column->icon.fill(Qt::transparent);
 
                         column->hasIcon = true;
diff --git a/src/modules/Extensions/Radio/RadioBrowserModel.hpp b/src/modules/Extensions/Radio/RadioBrowserModel.hpp
index c5c36986..1b048706 100644
--- a/src/modules/Extensions/Radio/RadioBrowserModel.hpp
+++ b/src/modules/Extensions/Radio/RadioBrowserModel.hpp
@@ -32,7 +32,7 @@ class NetworkAccess;
 class NetworkReply;
 struct Column;
 
-class RadioBrowserModel final : public QAbstractItemModel
+class RadioBrowserModel : public QAbstractItemModel
 {
     Q_OBJECT
 
@@ -54,14 +54,14 @@ public:
     QUrl getHomePageUrl(const QModelIndex &index) const;
     QString getUUID(const QModelIndex &index) const;
 
-    QModelIndex index(int row, int column, const QModelIndex &parent) const override;
-    QModelIndex parent(const QModelIndex &child) const override;
-    int rowCount(const QModelIndex &parent) const override;
-    int columnCount(const QModelIndex &parent) const override;
-    QVariant data(const QModelIndex &index, int role) const override;
-    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
-    Qt::ItemFlags flags(const QModelIndex &index) const override;
-    void sort(int columnIdx, Qt::SortOrder order = Qt::AscendingOrder) override;
+    QModelIndex index(int row, int column, const QModelIndex &parent) const;
+    QModelIndex parent(const QModelIndex &child) const;
+    int rowCount(const QModelIndex &parent) const;
+    int columnCount(const QModelIndex &parent) const;
+    QVariant data(const QModelIndex &index, int role) const;
+    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+    Qt::ItemFlags flags(const QModelIndex &index) const;
+    void sort(int columnIdx, Qt::SortOrder order = Qt::AscendingOrder);
 
 public slots:
     void setFiltrText(const QString &text = QString());
diff --git a/src/modules/Extensions/YouTube.cpp b/src/modules/Extensions/YouTube.cpp
index 2812302f..8656faf0 100644
--- a/src/modules/Extensions/YouTube.cpp
+++ b/src/modules/Extensions/YouTube.cpp
@@ -21,20 +21,24 @@
 #include <YouTubeDL.hpp>
 #include <LineEdit.hpp>
 
-#include <QLoggingCategory>
+#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
+    #include <QLoggingCategory>
+#else
+    #include <QDebug>
+#endif
 #include <QStringListModel>
 #include <QDesktopServices>
-#include <QJsonParseError>
+#include <QJsonParseError.h>
 #include <QActionGroup>
 #include <QTextDocument>
-#include <QJsonDocument>
+#include <QJsonDocument.h>
 #include <QProgressBar>
 #include <QApplication>
-#include <QJsonObject>
+#include <QJsonObject.h>
 #include <QHeaderView>
 #include <QGridLayout>
 #include <QToolButton>
-#include <QJsonArray>
+#include <QJsonArray.h>
 #include <QCompleter>
 #include <QClipboard>
 #include <QMimeData>
@@ -44,7 +48,12 @@
 #include <QMenu>
 #include <QUrl>
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
 Q_LOGGING_CATEGORY(youtube, "Extensions/YouTube")
+#else
+    #define qCWarning(category) qWarning() << #category ":"
+    #define qCDebug(category) qDebug() << #category ":"
+#endif
 
 #define YOUTUBE_URL "https://www.youtube.com"
 
@@ -90,8 +99,20 @@ ResultsYoutube::ResultsYoutube()
     headerItem()->setText(2, tr("User"));
 
     header()->setStretchLastSection(false);
-    header()->setSectionResizeMode(0, QHeaderView::Stretch);
-    header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
+    header()->
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+setSectionResizeMode(
+#else
+setResizeMode(
+#endif
+0, QHeaderView::Stretch);
+    header()->
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+setSectionResizeMode(
+#else
+setResizeMode(
+#endif
+1, QHeaderView::ResizeToContents);
 
     connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(playEntry(QTreeWidgetItem *)));
     connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(contextMenu(const QPoint &)));
@@ -139,6 +160,26 @@ void ResultsYoutube::showRelated()
     }
 }
 
+void ResultsYoutube::playAction()
+{
+    QAction *act = qobject_cast<QAction*>(sender());
+    if (act)
+    {
+        QString param = act->data().toString();
+        playOrEnqueue("open", currentItem(), param);
+    }
+}
+
+void ResultsYoutube::enqueueAction()
+{
+    QAction *act = qobject_cast<QAction*>(sender());
+    if (act)
+    {
+        QString param = act->data().toString();
+        playOrEnqueue("enqueue", currentItem(), param);
+    }
+}
+
 void ResultsYoutube::contextMenu(const QPoint &point)
 {
     QTreeWidgetItem *tWI = currentItem();
@@ -149,8 +190,7 @@ void ResultsYoutube::contextMenu(const QPoint &point)
     const QString url = tWI->data(0, Qt::UserRole).toString();
 
     auto menu = new QMenu(this);
-    connect(menu, &QMenu::aboutToHide,
-            menu, &QMenu::deleteLater);
+    connect(menu, SIGNAL(aboutToHide()), menu, SLOT(deleteLater()));
 
     for (int i = 0; i < 2; ++i)
     {
@@ -162,12 +202,14 @@ void ResultsYoutube::contextMenu(const QPoint &point)
         if (!tWI->isDisabled())
         {
             const auto param = i == 0 ? QString() : QString("audio");
-            subMenu->addAction(tr("Play"), this, [=] {
-                playOrEnqueue("open", currentItem(), param);
-            });
-            subMenu->addAction(tr("Enqueue"), this, [=] {
-                playOrEnqueue("enqueue", currentItem(), param);
-            });
+            QAction *playAct = subMenu->addAction(tr("Play"));
+            playAct->setData(param);
+            connect(playAct, SIGNAL(triggered()), this, SLOT(playAction()));
+
+            QAction *enqueueAct = subMenu->addAction(tr("Enqueue"));
+            enqueueAct->setData(param);
+            connect(enqueueAct, SIGNAL(triggered()), this, SLOT(enqueueAction()));
+
             subMenu->addSeparator();
         }
 
@@ -207,8 +249,7 @@ PageSwitcher::PageSwitcher(YouTube *youTubeW)
     currPageB = new QLabel;
 
     nextB = new QToolButton;
-    connect(nextB, &QToolButton::clicked,
-            youTubeW, &YouTube::chPage);
+    connect(nextB, SIGNAL(clicked()), youTubeW, SLOT(chPage()));
     nextB->setAutoRaise(true);
     nextB->setArrowType(Qt::RightArrow);
 
@@ -266,9 +307,7 @@ YouTube::YouTube(Module &module) :
     searchB->setAutoRaise(true);
 
     QToolButton *showSettingsB = new QToolButton;
-    connect(showSettingsB, &QToolButton::clicked, this, [] {
-        emit QMPlay2Core.showSettings("Extensions");
-    });
+    connect(showSettingsB, SIGNAL(clicked()), this, SLOT(showSettings()));
     showSettingsB->setIcon(QMPlay2Core.getIconFromTheme("configure"));
     showSettingsB->setToolTip(tr("Settings"));
     showSettingsB->setAutoRaise(true);
@@ -281,13 +320,9 @@ YouTube::YouTube(Module &module) :
     int qualityIdx = 0;
     for (QAction *act : m_qualityGroup->actions())
     {
-        connect(act, &QAction::triggered, this, [=] {
-            sets().set("YouTube/QualityPreset", act->text());
-        });
-        connect(act, &QAction::toggled, this, [=](bool checked) {
-            if (checked)
-                setItags(qualityIdx);
-        });
+        m_qualityActionToIndex[act] = qualityIdx;
+        connect(act, SIGNAL(triggered()), this, SLOT(onQualityActionTriggered()));
+        connect(act, SIGNAL(toggled(bool)), this, SLOT(onQualityActionToggled(bool)));
         act->setCheckable(true);
         qualityMenu->addAction(act);
         ++qualityIdx;
@@ -311,14 +346,8 @@ YouTube::YouTube(Module &module) :
     int sortByIdx = 0;
     for (QAction *act : m_sortByGroup->actions())
     {
-        connect(act, &QAction::triggered, this, [=] {
-            if (m_sortByIdx != sortByIdx)
-            {
-                m_sortByIdx = sortByIdx;
-                sets().set("YouTube/SortBy", m_sortByIdx);
-                search();
-            }
-        });
+        m_sortByActionToIndex[act] = sortByIdx;
+        connect(act, SIGNAL(triggered()), this, SLOT(onSortByActionTriggered()));
         act->setCheckable(true);
         sortByMenu->addAction(act);
         ++sortByIdx;
@@ -339,7 +368,7 @@ YouTube::YouTube(Module &module) :
     sortByB->setAutoRaise(true);
 
     resultsW = new ResultsYoutube;
-    connect(resultsW, &ResultsYoutube::requestRelated, this, &YouTube::fetchRelated);
+    connect(resultsW, SIGNAL(requestRelated(QString)), this, SLOT(fetchRelated(QString)));
 
     progressB = new QProgressBar;
     progressB->hide();
@@ -594,9 +623,22 @@ void YouTube::netFinished(NetworkReply *reply)
         }
         else if (reply == searchReply)
         {
-            m_apiKey = QRegularExpression(R"|("INNERTUBE_API_KEY"\s*:\s*"(.+?)")|").match(replyData).captured(1);
-            m_clientName = QRegularExpression(R"|("INNERTUBE_CLIENT_NAME"\s*:\s*"(.+?)")|").match(replyData).captured(1);
-            m_clientVersion = QRegularExpression(R"|("INNERTUBE_CLIENT_VERSION"\s*:\s*"(.+?)")|").match(replyData).captured(1);
+            // Qt4: Use QRegExp instead of QRegularExpression
+            QRegExp apiKeyRe("\"INNERTUBE_API_KEY\"\\s*:\\s*\"(.+?)\"");
+            apiKeyRe.setMinimal(true);
+            if (apiKeyRe.indexIn(replyData) != -1)
+                m_apiKey = apiKeyRe.cap(1);
+
+            QRegExp clientNameRe("\"INNERTUBE_CLIENT_NAME\"\\s*:\\s*\"(.+?)\"");
+            clientNameRe.setMinimal(true);
+            if (clientNameRe.indexIn(replyData) != -1)
+                m_clientName = clientNameRe.cap(1);
+
+            QRegExp clientVersionRe("\"INNERTUBE_CLIENT_VERSION\"\\s*:\\s*\"(.+?)\"");
+            clientVersionRe.setMinimal(true);
+            if (clientVersionRe.indexIn(replyData) != -1)
+                m_clientVersion = clientVersionRe.cap(1);
+
             setSearchResults(getYtInitialData(replyData).object(), false, false);
         }
         else if (reply == relatedReply)
@@ -988,52 +1030,48 @@ void YouTube::setSearchResults(const QJsonObject &jsonObj, bool isContinuation,
     {
         Q_ASSERT(!isContinuation);
 
-        items = jsonObj
-            ["contents"]
-            ["twoColumnWatchNextResults"]
-            ["secondaryResults"]
-            ["secondaryResults"]
-            ["results"].toArray()
-        ;
+        const QJsonObject contents = jsonObj["contents"].toObject();
+        const QJsonObject twoColumnWatchNextResults = contents["twoColumnWatchNextResults"].toObject();
+        const QJsonObject secondaryResults1 = twoColumnWatchNextResults["secondaryResults"].toObject();
+        const QJsonObject secondaryResults2 = secondaryResults1["secondaryResults"].toObject();
+        items = secondaryResults2["results"].toArray();
     }
     else if (isContinuation)
     {
         const auto onResponseReceivedCommands = jsonObj
             ["onResponseReceivedCommands"].toArray()
         ;
-        for (const QJsonValue val : onResponseReceivedCommands)
+        for (const QJsonValue &val : onResponseReceivedCommands)
         {
-            items = val
-                ["appendContinuationItemsAction"]
-                ["continuationItems"].toArray()
-            ;
+            const QJsonObject valObj = val.toObject();
+            const QJsonObject appendContinuationItemsAction = valObj["appendContinuationItemsAction"].toObject();
+            items = appendContinuationItemsAction["continuationItems"].toArray();
             if (!items.isEmpty())
                 break;
         }
     }
     else
     {
-        items = jsonObj
-            ["contents"]
-            ["twoColumnSearchResultsRenderer"]
-            ["primaryContents"]
-            ["sectionListRenderer"]
-            ["contents"].toArray()
-        ;
+        const QJsonObject contents = jsonObj["contents"].toObject();
+        const QJsonObject twoColumnSearchResultsRenderer = contents["twoColumnSearchResultsRenderer"].toObject();
+        const QJsonObject primaryContents = twoColumnSearchResultsRenderer["primaryContents"].toObject();
+        const QJsonObject sectionListRenderer = primaryContents["sectionListRenderer"].toObject();
+        items = sectionListRenderer["contents"].toArray();
     }
 
-    for (const QJsonValue obj : items)
+    for (const QJsonValue &obj : items)
     {
         if (isRelatedResults)
         {
             bool radioRenderer = false;
 
-            const auto videoRenderer = obj["compactVideoRenderer"].toObject();
+            const QJsonObject objObj = obj.toObject();
+            const auto videoRenderer = objObj["compactVideoRenderer"].toObject();
             const auto playlistRenderer = [&] {
-                auto r = obj["compactPlaylistRenderer"].toObject();
+                auto r = objObj["compactPlaylistRenderer"].toObject();
                 if (r.isEmpty())
                 {
-                    r = obj["compactRadioRenderer"].toObject();
+                    r = objObj["compactRadioRenderer"].toObject();
                     radioRenderer = !r.isEmpty();
                 }
                 return r;
@@ -1043,16 +1081,20 @@ void YouTube::setSearchResults(const QJsonObject &jsonObj, bool isContinuation,
 
             if (isVideo)
             {
-                title = videoRenderer["title"]["simpleText"].toString();
+                title = videoRenderer["title"].toObject()["simpleText"].toString();
                 contentId = videoRenderer["videoId"].toString();
                 if (title.isEmpty() || contentId.isEmpty())
                     continue;
 
-                length = videoRenderer["lengthText"]["simpleText"].toString();
-                user = videoRenderer["longBylineText"]["runs"].toArray().at(0)["text"].toString();
-                publishTime = videoRenderer["publishedTimeText"]["simpleText"].toString();
-                viewCount = videoRenderer["shortViewCountText"]["simpleText"].toString();
-                thumbnail = videoRenderer["thumbnail"]["thumbnails"].toArray().at(0)["url"].toString();
+                length = videoRenderer["lengthText"].toObject()["simpleText"].toString();
+                const QJsonArray longBylineTextRuns = videoRenderer["longBylineText"].toObject()["runs"].toArray();
+                if (!longBylineTextRuns.isEmpty())
+                    user = longBylineTextRuns.at(0).toObject()["text"].toString();
+                publishTime = videoRenderer["publishedTimeText"].toObject()["simpleText"].toString();
+                viewCount = videoRenderer["shortViewCountText"].toObject()["simpleText"].toString();
+                const QJsonArray thumbnailsArray = videoRenderer["thumbnail"].toObject()["thumbnails"].toArray();
+                if (!thumbnailsArray.isEmpty())
+                    thumbnail = thumbnailsArray.at(0).toObject()["url"].toString();
 
                 url = YOUTUBE_URL "/watch?v=" + contentId;
             }
@@ -1060,22 +1102,25 @@ void YouTube::setSearchResults(const QJsonObject &jsonObj, bool isContinuation,
             {
                 QString videoId;
 
-                title = playlistRenderer["title"]["simpleText"].toString();
+                title = playlistRenderer["title"].toObject()["simpleText"].toString();
                 contentId = playlistRenderer["playlistId"].toString();
                 if (title.isEmpty() || contentId.isEmpty())
                     continue;
 
                 if (radioRenderer)
                 {
-                    videoId = playlistRenderer["navigationEndpoint"]["watchEndpoint"]["videoId"].toString();
+                    const QJsonObject navigationEndpoint = playlistRenderer["navigationEndpoint"].toObject();
+                    videoId = navigationEndpoint["watchEndpoint"].toObject()["videoId"].toString();
                     if (videoId.isEmpty())
                         continue;
                 }
                 else
                 {
-                    user = playlistRenderer["longBylineText"]["simpleText"].toString();
+                    user = playlistRenderer["longBylineText"].toObject()["simpleText"].toString();
                 }
-                thumbnail = playlistRenderer["thumbnail"]["thumbnails"].toArray().at(0)["url"].toString();
+                const QJsonArray thumbnailsArray = playlistRenderer["thumbnail"].toObject()["thumbnails"].toArray();
+                if (!thumbnailsArray.isEmpty())
+                    thumbnail = thumbnailsArray.at(0).toObject()["url"].toString();
 
                 if (radioRenderer)
                     url = YOUTUBE_URL "/watch?v=" + videoId + "&list=" + contentId;
@@ -1087,25 +1132,23 @@ void YouTube::setSearchResults(const QJsonObject &jsonObj, bool isContinuation,
         }
         else
         {
-            const auto contents = obj
-                ["itemSectionRenderer"]
-                ["contents"].toArray()
-            ;
-
-            const auto token = obj
-                ["continuationItemRenderer"]
-                ["continuationEndpoint"]
-                ["continuationCommand"]
-                ["token"].toString()
-            ;
+            const QJsonObject objObj = obj.toObject();
+            const QJsonObject itemSectionRenderer = objObj["itemSectionRenderer"].toObject();
+            const auto contents = itemSectionRenderer["contents"].toArray();
+
+            const QJsonObject continuationItemRenderer = objObj["continuationItemRenderer"].toObject();
+            const QJsonObject continuationEndpoint = continuationItemRenderer["continuationEndpoint"].toObject();
+            const QJsonObject continuationCommand = continuationEndpoint["continuationCommand"].toObject();
+            const auto token = continuationCommand["token"].toString();
             if (!token.isEmpty())
                 m_continuationToken = token;
 
-            for (const QJsonValue obj : contents)
+            for (const QJsonValue &objVal : contents)
             {
                 bool radioRenderer = false;
                 bool lockupViewModel = false;
 
+                const QJsonObject obj = objVal.toObject();
                 const auto videoRenderer = obj["videoRenderer"].toObject();
                 const auto playlistRenderer = [&] {
                     auto r = obj["playlistRenderer"].toObject();
@@ -1126,16 +1169,22 @@ void YouTube::setSearchResults(const QJsonObject &jsonObj, bool isContinuation,
 
                 if (isVideo)
                 {
-                    title = videoRenderer["title"]["runs"].toArray().at(0)["text"].toString();
+                    const QJsonArray titleRuns = videoRenderer["title"].toObject()["runs"].toArray();
+                    if (!titleRuns.isEmpty())
+                        title = titleRuns.at(0).toObject()["text"].toString();
                     contentId = videoRenderer["videoId"].toString();
                     if (title.isEmpty() || contentId.isEmpty())
                         continue;
 
-                    length = videoRenderer["lengthText"]["simpleText"].toString();
-                    user = videoRenderer["ownerText"]["runs"].toArray().at(0)["text"].toString();
-                    publishTime = videoRenderer["publishedTimeText"]["simpleText"].toString();
-                    viewCount = videoRenderer["shortViewCountText"]["simpleText"].toString();
-                    thumbnail = videoRenderer["thumbnail"]["thumbnails"].toArray().at(0)["url"].toString();
+                    length = videoRenderer["lengthText"].toObject()["simpleText"].toString();
+                    const QJsonArray ownerTextRuns = videoRenderer["ownerText"].toObject()["runs"].toArray();
+                    if (!ownerTextRuns.isEmpty())
+                        user = ownerTextRuns.at(0).toObject()["text"].toString();
+                    publishTime = videoRenderer["publishedTimeText"].toObject()["simpleText"].toString();
+                    viewCount = videoRenderer["shortViewCountText"].toObject()["simpleText"].toString();
+                    const QJsonArray thumbnailsArray = videoRenderer["thumbnail"].toObject()["thumbnails"].toArray();
+                    if (!thumbnailsArray.isEmpty())
+                        thumbnail = thumbnailsArray.at(0).toObject()["url"].toString();
 
                     url = YOUTUBE_URL "/watch?v=" + contentId;
                 }
@@ -1143,22 +1192,28 @@ void YouTube::setSearchResults(const QJsonObject &jsonObj, bool isContinuation,
                 {
                     QString videoId;
 
-                    title = playlistRenderer["metadata"]["lockupMetadataViewModel"]["title"]["content"].toString();
-                    contentId = playlistRenderer["rendererContext"]["commandContext"]["onTap"]["innertubeCommand"]["watchEndpoint"]["playlistId"].toString();
-                    videoId = playlistRenderer["rendererContext"]["commandContext"]["onTap"]["innertubeCommand"]["watchEndpoint"]["videoId"].toString();
+                    const QJsonObject metadata = playlistRenderer["metadata"].toObject();
+                    const QJsonObject lockupMetadataViewModel = metadata["lockupMetadataViewModel"].toObject();
+                    title = lockupMetadataViewModel["title"].toObject()["content"].toString();
+
+                    const QJsonObject rendererContext = playlistRenderer["rendererContext"].toObject();
+                    const QJsonObject commandContext = rendererContext["commandContext"].toObject();
+                    const QJsonObject onTap = commandContext["onTap"].toObject();
+                    const QJsonObject innertubeCommand = onTap["innertubeCommand"].toObject();
+                    const QJsonObject watchEndpoint = innertubeCommand["watchEndpoint"].toObject();
+                    contentId = watchEndpoint["playlistId"].toString();
+                    videoId = watchEndpoint["videoId"].toString();
                     if (title.isEmpty() || contentId.isEmpty() || videoId.isEmpty())
                         continue;
 
-                    const auto thumbnails = playlistRenderer
-                        ["contentImage"]
-                        ["collectionThumbnailViewModel"]
-                        ["primaryThumbnail"]
-                        ["thumbnailViewModel"]
-                        ["image"]
-                        ["sources"].toArray()
-                    ;
+                    const QJsonObject contentImage = playlistRenderer["contentImage"].toObject();
+                    const QJsonObject collectionThumbnailViewModel = contentImage["collectionThumbnailViewModel"].toObject();
+                    const QJsonObject primaryThumbnail = collectionThumbnailViewModel["primaryThumbnail"].toObject();
+                    const QJsonObject thumbnailViewModel = primaryThumbnail["thumbnailViewModel"].toObject();
+                    const QJsonObject image = thumbnailViewModel["image"].toObject();
+                    const auto thumbnails = image["sources"].toArray();
                     if (!thumbnails.isEmpty())
-                        thumbnail = thumbnails[0]["url"].toString();
+                        thumbnail = thumbnails[0].toObject()["url"].toString();
 
                     url = YOUTUBE_URL "/watch?v=" + videoId + "&list=" + contentId;
                 }
@@ -1166,28 +1221,31 @@ void YouTube::setSearchResults(const QJsonObject &jsonObj, bool isContinuation,
                 {
                     QString videoId;
 
-                    title = playlistRenderer["title"]["simpleText"].toString();
+                    title = playlistRenderer["title"].toObject()["simpleText"].toString();
                     contentId = playlistRenderer["playlistId"].toString();
                     if (title.isEmpty() || contentId.isEmpty())
                         continue;
 
                     if (radioRenderer)
                     {
-                        videoId = playlistRenderer["navigationEndpoint"]["watchEndpoint"]["videoId"].toString();
+                        const QJsonObject navigationEndpoint = playlistRenderer["navigationEndpoint"].toObject();
+                        videoId = navigationEndpoint["watchEndpoint"].toObject()["videoId"].toString();
                         if (videoId.isEmpty())
                             continue;
                     }
                     else
                     {
-                        user = playlistRenderer["longBylineText"]["runs"].toArray().at(0)["text"].toString();
+                        const QJsonArray longBylineTextRuns = playlistRenderer["longBylineText"].toObject()["runs"].toArray();
+                        if (!longBylineTextRuns.isEmpty())
+                            user = longBylineTextRuns.at(0).toObject()["text"].toString();
                     }
-                    thumbnail = playlistRenderer
-                        ["thumbnailRenderer"]
-                        ["playlistVideoThumbnailRenderer"]
-                        ["thumbnail"]
-                        ["thumbnails"].toArray().at(0)
-                        ["url"].toString()
-                    ;
+
+                    const QJsonObject thumbnailRenderer = playlistRenderer["thumbnailRenderer"].toObject();
+                    const QJsonObject playlistVideoThumbnailRenderer = thumbnailRenderer["playlistVideoThumbnailRenderer"].toObject();
+                    const QJsonObject thumbnailObj = playlistVideoThumbnailRenderer["thumbnail"].toObject();
+                    const QJsonArray thumbnailsArray = thumbnailObj["thumbnails"].toArray();
+                    if (!thumbnailsArray.isEmpty())
+                        thumbnail = thumbnailsArray.at(0).toObject()["url"].toString();
 
                     if (radioRenderer)
                         url = YOUTUBE_URL "/watch?v=" + videoId + "&list=" + contentId;
@@ -1236,7 +1294,7 @@ QStringList YouTube::getYouTubeVideo(const QString &param, const QString &url, I
     const bool hasTitle = !rawErrOutput.contains("Unable to extract video title", Qt::CaseInsensitive);
     const auto title = hasTitle ? o["title"].toString() : QString();
 
-    if (hasTitle && o["_type"].toString() == QStringLiteral("playlist"))
+    if (hasTitle && o["_type"].toString() == QString("playlist"))
     {
         PlaylistEntries entries;
         for (const QJsonValue &entryVal : o["entries"].toArray())
@@ -1285,17 +1343,17 @@ QStringList YouTube::getYouTubeVideo(const QString &param, const QString &url, I
         }
 
         const auto itagStr = format["format_id"].toString().split('-');
-        const auto note = format[QStringLiteral("format_note")].toString();
+        const auto note = format[QString("format_note")].toString();
         const auto itag = itagStr.value(0).toInt();
         const auto url = format["url"].toString();
         const auto ext = format["ext"].toString();
-        if (itag != 0 && !url.isEmpty() && !ext.isEmpty() && (!note.contains(QStringLiteral("DRC")) || sets().getBool(QStringLiteral("YouTube/AllowDRC"))))
+        if (itag != 0 && !url.isEmpty() && !ext.isEmpty() && (!note.contains(QString("DRC")) || sets().getBool(QString("YouTube/AllowDRC"))))
         {
             itagsData[itag].first += url;
             itagsData[itag].second += "." + ext;
             if (audioItags.contains(itag))
             {
-                urlLanguages[url] = format[QStringLiteral("language")].toString();
+                urlLanguages[url] = format[QString("language")].toString();
                 urlNotes[url] = note;
             }
         }
@@ -1305,7 +1363,7 @@ QStringList YouTube::getYouTubeVideo(const QString &param, const QString &url, I
         for (auto &&itag : itags)
         {
             auto it = itagsData.constFind(itag);
-            if (it != itagsData.cend())
+            if (it != itagsData.constEnd())
             {
                 urls += it->first;
                 exts += it->second;
@@ -1361,7 +1419,7 @@ QStringList YouTube::getYouTubeVideo(const QString &param, const QString &url, I
         if (subtitlesForLang.isEmpty())
             subtitlesForLang = subtitles["en"].toArray();
 
-        for (auto &&subtitlesFmtVal : std::as_const(subtitlesForLang))
+        for (auto &&subtitlesFmtVal : subtitlesForLang)
         {
             const auto subtitlesFmt = subtitlesFmtVal.toObject();
             if (subtitlesFmt.isEmpty())
@@ -1384,22 +1442,22 @@ QStringList YouTube::getYouTubeVideo(const QString &param, const QString &url, I
     Q_ASSERT(!urls.isEmpty());
     Q_ASSERT(urls.count() == exts.count());
 
-    for (auto &&url : std::as_const(urls))
+    for (auto &&url : urls)
     {
         const auto urlNote = urlNotes.value(url).trimmed();
         const auto urlLanguage = urlLanguages.value(url).trimmed();
         QList<QMPlay2Tag> tags;
         if (!urlNote.isEmpty())
         {
-            tags += {{QString::number(QMPLAY2_TAG_DEFAULT), QString::number(urlNote.contains(QStringLiteral("(default)")))}};
-            if (urlNote.contains(QStringLiteral("DRC")))
+            tags += {{QString::number(QMPLAY2_TAG_DEFAULT), QString::number(urlNote.contains(QString("(default)")) ? 1 : 0)}};
+            if (urlNote.contains(QString("DRC")))
                 tags += {{QString::number(QMPLAY2_TAG_DRC), tr("yes")}};
         }
         if (!urlLanguage.isEmpty())
         {
             QString lang;
-            const int idx1 = urlLanguage.indexOf(QStringLiteral(","));
-            const int idx2 = urlLanguage.indexOf(QStringLiteral(" "));
+            const int idx1 = urlLanguage.indexOf(QString(","));
+            const int idx2 = urlLanguage.indexOf(QString(" "));
             int idx = qMin(idx1, idx2);
             if (idx < 0)
                 idx = qMax(idx1, idx2);
@@ -1421,12 +1479,12 @@ QStringList YouTube::getYouTubeVideo(const QString &param, const QString &url, I
     else
     {
         QString url = "FFmpeg://{";
-        for (auto &&urlPart : std::as_const(urls))
+        for (auto &&urlPart : urls)
             url += "[" + urlPart + "]";
         url += "}";
 
         QString ext;
-        for (auto &&extPart : std::as_const(exts))
+        for (auto &&extPart : exts)
             ext += "[" + extPart + "]";
 
         result += url;
@@ -1460,3 +1518,48 @@ QJsonDocument YouTube::getYtInitialData(const QByteArray &data)
 
     return QJsonDocument();
 }
+
+void YouTube::showSettings()
+{
+    emit QMPlay2Core.showSettings("Extensions");
+}
+
+void YouTube::onQualityActionTriggered()
+{
+    QAction *act = qobject_cast<QAction*>(sender());
+    if (act)
+        sets().set("YouTube/QualityPreset", act->text());
+}
+
+void YouTube::onQualityActionToggled(bool checked)
+{
+    if (!checked)
+        return;
+
+    QAction *act = qobject_cast<QAction*>(sender());
+    if (act)
+    {
+        auto it = m_qualityActionToIndex.find(act);
+        if (it != m_qualityActionToIndex.end())
+            setItags(it.value());
+    }
+}
+
+void YouTube::onSortByActionTriggered()
+{
+    QAction *act = qobject_cast<QAction*>(sender());
+    if (act)
+    {
+        auto it = m_sortByActionToIndex.find(act);
+        if (it != m_sortByActionToIndex.end())
+        {
+            int sortByIdx = it.value();
+            if (m_sortByIdx != sortByIdx)
+            {
+                m_sortByIdx = sortByIdx;
+                sets().set("YouTube/SortBy", m_sortByIdx);
+                search();
+            }
+        }
+    }
+}
diff --git a/src/modules/Extensions/YouTube.hpp b/src/modules/Extensions/YouTube.hpp
index 4c83d8f4..4d7535e5 100644
--- a/src/modules/Extensions/YouTube.hpp
+++ b/src/modules/Extensions/YouTube.hpp
@@ -26,6 +26,9 @@
 #include <QPointer>
 #include <QMutex>
 
+#include <QJsonDocument.h>
+#include <QJsonObject.h>
+
 class NetworkReply;
 class QProgressBar;
 class QActionGroup;
@@ -45,7 +48,7 @@ enum class PreferredCodec
 
 /**/
 
-class ResultsYoutube final : public QTreeWidget
+class ResultsYoutube : public QTreeWidget
 {
     Q_OBJECT
 public:
@@ -70,13 +73,16 @@ private slots:
     void showRelated();
 
     void contextMenu(const QPoint &p);
+
+    void playAction();
+    void enqueueAction();
 };
 
 /**/
 
 class YouTube;
 
-class PageSwitcher final : public QWidget
+class PageSwitcher : public QWidget
 {
     Q_OBJECT
 public:
@@ -90,7 +96,7 @@ public:
 
 using ItagNames = QPair<QStringList, QList<int>>;
 
-class YouTube final : public QWidget, public QMPlay2Extensions
+class YouTube : public QWidget, public QMPlay2Extensions
 {
     Q_OBJECT
 
@@ -101,19 +107,19 @@ public:
     YouTube(Module &module);
     ~YouTube();
 
-    bool set() override;
+    bool set();
 
-    DockWidget *getDockWidget() override;
+    DockWidget *getDockWidget();
 
-    bool canConvertAddress() const override;
+    bool canConvertAddress() const;
 
-    QString matchAddress(const QString &url) const override;
-    QList<AddressPrefix> addressPrefixList(bool) const override;
-    void convertAddress(const QString &, const QString &, const QString &, QString *, QString *, QIcon *, QString *, IOController<> *ioCtrl) override;
+    QString matchAddress(const QString &url) const;
+    QList<AddressPrefix> addressPrefixList(bool) const;
+    void convertAddress(const QString &, const QString &, const QString &, QString *, QString *, QIcon *, QString *, IOController<> *ioCtrl);
 
-    QVector<QAction *> getActions(const QString &, double, const QString &, const QString &, const QString &) override;
+    QVector<QAction *> getActions(const QString &, double, const QString &, const QString &, const QString &);
 
-public:
+public slots:
     void chPage();
 
 private slots:
@@ -128,6 +134,11 @@ private slots:
 
     void searchMenu();
 
+    void showSettings();
+    void onQualityActionTriggered();
+    void onQualityActionToggled(bool checked);
+    void onSortByActionTriggered();
+
 private:
     void prepareSearch();
 
@@ -166,6 +177,8 @@ private:
     bool m_allowSubtitles;
 
     QActionGroup *m_qualityGroup = nullptr, *m_sortByGroup = nullptr;
+    QHash<QAction*, int> m_qualityActionToIndex;
+    QHash<QAction*, int> m_sortByActionToIndex;
 
     int m_sortByIdx = 0;
 
diff --git a/src/modules/FFmpeg/D3D11VAVulkan.hpp b/src/modules/FFmpeg/D3D11VAVulkan.hpp
index ba8af7c4..202f5bb8 100644
--- a/src/modules/FFmpeg/D3D11VAVulkan.hpp
+++ b/src/modules/FFmpeg/D3D11VAVulkan.hpp
@@ -46,12 +46,12 @@ public:
     D3D11VAVulkan(AVBufferRef *hwDeviceBufferRef, bool zeroCopyAllowed);
     ~D3D11VAVulkan();
 
-    QString name() const override;
+    QString name() const;
 
-    void map(Frame &frame) override;
-    void clear() override;
+    void map(Frame &frame);
+    void clear();
 
-    SyncDataPtr sync(const std::vector<Frame> &frames, vk::SubmitInfo *submitInfo) override;
+    SyncDataPtr sync(const std::vector<Frame> &frames, vk::SubmitInfo *submitInfo);
 
 public:
     bool init();
diff --git a/src/modules/FFmpeg/DXVA2OpenGL.cpp b/src/modules/FFmpeg/DXVA2OpenGL.cpp
index 02f9cc8d..4da94095 100644
--- a/src/modules/FFmpeg/DXVA2OpenGL.cpp
+++ b/src/modules/FFmpeg/DXVA2OpenGL.cpp
@@ -21,7 +21,7 @@
 #include <QMPlay2Core.hpp>
 #include <Frame.hpp>
 
-#include <QOpenGLFunctions>
+#include <QtOpenGL/QGLContext>
 #include <QLibrary>
 
 extern "C"
@@ -64,7 +64,8 @@ DXVA2OpenGL::~DXVA2OpenGL()
     {
         for (int i = 0; i < s_numRenderTargets; ++i)
         {
-            for (auto &&renderTarget : std::as_const(m_renderTargets[i].surfaces))
+            // Qt4: No std::as_const
+            foreach (auto renderTarget, m_renderTargets[i].surfaces)
                 renderTarget->Release();
         }
     }
@@ -92,7 +93,8 @@ bool DXVA2OpenGL::init(const int *widths, const int *heights, const SetTexturePa
         m_width = widths[0];
         m_height = heights[0];
 
-        QOpenGLContext::currentContext()->functions()->glGenTextures(2, m_textures);
+        // Qt4: Use OpenGL functions directly
+        glGenTextures(2, m_textures);
     }
 
     for (int i = 0; i < 2; ++i)
@@ -106,7 +108,8 @@ bool DXVA2OpenGL::init(const int *widths, const int *heights, const SetTexturePa
         return ret;
     }
 
-    auto context = QOpenGLContext::currentContext();
+    // Qt4: Use QGLContext instead of QOpenGLContext
+    auto context = QGLContext::currentContext();
     if (!context)
     {
         QMPlay2Core.logError("DXVA2 :: Unable to get OpenGL context");
@@ -339,7 +342,7 @@ bool DXVA2OpenGL::checkCodec(const QByteArray &codecName, bool b10)
 
         for (UINT i = 0; i < count; ++i)
         {
-            for (const GUID *guid : std::as_const(neededCodecGUIDs))
+            for (const GUID *guid : neededCodecGUIDs)
             {
                 if (IsEqualGUID(codecGUIDs[i], *guid))
                 {
@@ -498,7 +501,8 @@ void DXVA2OpenGL::clearTextures()
         }
     }
 
-    QOpenGLContext::currentContext()->functions()->glDeleteTextures(2, m_textures);
+    // Qt4: Use OpenGL functions directly
+    glDeleteTextures(2, m_textures);
     memset(m_textures, 0, sizeof(m_textures));
     m_width = m_height = 0;
 }
diff --git a/src/modules/FFmpeg/DXVA2OpenGL.hpp b/src/modules/FFmpeg/DXVA2OpenGL.hpp
index cd968292..2c6d0099 100644
--- a/src/modules/FFmpeg/DXVA2OpenGL.hpp
+++ b/src/modules/FFmpeg/DXVA2OpenGL.hpp
@@ -20,7 +20,7 @@
 
 #include <opengl/OpenGLHWInterop.hpp>
 
-#include <QOpenGLContext>
+#include <QtOpenGL/QGLContext>
 #include <QHash>
 
 #include <functional>
@@ -41,20 +41,20 @@ public:
     DXVA2OpenGL(AVBufferRef *hwDeviceBufferRef);
     ~DXVA2OpenGL();
 
-    QString name() const override;
+    QString name() const;
 
-    Format getFormat() const override;
+    Format getFormat() const;
 
-    bool init(const int *widths, const int *heights, const SetTextureParamsFn &setTextureParamsFn) override;
-    void clear() override;
+    bool init(const int *widths, const int *heights, const SetTextureParamsFn &setTextureParamsFn);
+    void clear();
 
-    bool mapFrame(Frame &videoFrame) override;
-    quint32 getTexture(int plane) override;
+    bool mapFrame(Frame &videoFrame);
+    quint32 getTexture(int plane);
 
-    Frame getCpuFrame(const Frame &videoFrame) override;
+    Frame getCpuFrame(const Frame &videoFrame);
 
-    void getVideAdjustmentCap(VideoAdjustment &videoAdjustmentCap) override;
-    void setVideoAdjustment(const VideoAdjustment &videoAdjustment) override;
+    void getVideAdjustmentCap(VideoAdjustment &videoAdjustmentCap);
+    void setVideoAdjustment(const VideoAdjustment &videoAdjustment);
 
     /**/
 
diff --git a/src/modules/FFmpeg/FFCommon.cpp b/src/modules/FFmpeg/FFCommon.cpp
index 351d590c..07348858 100644
--- a/src/modules/FFmpeg/FFCommon.cpp
+++ b/src/modules/FFmpeg/FFCommon.cpp
@@ -18,7 +18,7 @@
 
 #include <FFCommon.hpp>
 
-#include <QRegularExpression>
+#include <QRegExp>
 #include <QProcess>
 #include <QSysInfo>
 #include <QDebug>
@@ -57,9 +57,13 @@ void FFCommon::setDriversPath(const QString &dirName, const QByteArray &envVar)
         if (maps.open(QFile::ReadOnly | QFile::Text))
         {
             const auto lines = maps.readAll().split('\n');
+            // Qt4: Use QRegExp instead of QRegularExpression
+            QRegExp rx("(/usr/lib/\\S+-linux-gnu)/.+\\.so($|\\.)");
             for (auto &&line : lines)
             {
-                const auto dir = QRegularExpression(R"((\/usr\/lib\/\S+-linux-gnu)\/.+\.so($|\.))").match(line).captured(1).toUtf8();
+                QByteArray dir;
+                if (rx.indexIn(line) != -1)
+                    dir = rx.cap(1).toUtf8();
                 if (!dir.isEmpty())
                 {
                     if (!dirs.contains(dir))
diff --git a/src/modules/FFmpeg/FFDec.hpp b/src/modules/FFmpeg/FFDec.hpp
index 3cca114b..ed40f042 100644
--- a/src/modules/FFmpeg/FFDec.hpp
+++ b/src/modules/FFmpeg/FFDec.hpp
@@ -46,9 +46,9 @@ protected:
     FFDec();
     ~FFDec();
 
-    void setSupportedPixelFormats(const AVPixelFormats &pixelFormats) override;
+    void setSupportedPixelFormats(const AVPixelFormats &pixelFormats);
 
-    int pendingFrames() const override;
+    int pendingFrames() const;
 
     /**/
 
diff --git a/src/modules/FFmpeg/FFDecD3D11VA.hpp b/src/modules/FFmpeg/FFDecD3D11VA.hpp
index decc75bc..01b29051 100644
--- a/src/modules/FFmpeg/FFDecD3D11VA.hpp
+++ b/src/modules/FFmpeg/FFDecD3D11VA.hpp
@@ -27,19 +27,19 @@
 class D3D11VAVulkan;
 struct SwsContext;
 
-class FFMPEG_EXPORT FFDecD3D11VA final : public FFDecHWAccel
+class FFMPEG_EXPORT FFDecD3D11VA : public FFDecHWAccel
 {
 public:
     FFDecD3D11VA(Module &module);
     ~FFDecD3D11VA();
 
-    bool set() override;
+    bool set();
 
-    QString name() const override;
+    QString name() const;
 
-    int decodeVideo(const Packet &encodedPacket, Frame &decoded, AVPixelFormat &newPixFmt, bool flush, unsigned hurryUp) override;
+    int decodeVideo(const Packet &encodedPacket, Frame &decoded, AVPixelFormat &newPixFmt, bool flush, unsigned hurryUp);
 
-    bool open(StreamInfo &streamInfo) override;
+    bool open(StreamInfo &streamInfo);
 
 private:
     bool comparePrimaryDevice() const;
diff --git a/src/modules/FFmpeg/FFDecDXVA2.hpp b/src/modules/FFmpeg/FFDecDXVA2.hpp
index 38846856..7f326348 100644
--- a/src/modules/FFmpeg/FFDecDXVA2.hpp
+++ b/src/modules/FFmpeg/FFDecDXVA2.hpp
@@ -20,19 +20,19 @@
 
 #include <FFDecHWAccel.hpp>
 
-class FFMPEG_EXPORT FFDecDXVA2 final : public FFDecHWAccel
+class FFMPEG_EXPORT FFDecDXVA2 : public FFDecHWAccel
 {
 public:
     FFDecDXVA2(Module &module);
     ~FFDecDXVA2();
 
-    bool set() override;
+    bool set();
 
-    QString name() const override;
+    QString name() const;
 
-    std::shared_ptr<VideoFilter> hwAccelFilter() const override;
+    std::shared_ptr<VideoFilter> hwAccelFilter() const;
 
-    bool open(StreamInfo &streamInfo) override;
+    bool open(StreamInfo &streamInfo);
 
 private:
     AVBufferRef *m_hwDeviceBufferRef = nullptr;
diff --git a/src/modules/FFmpeg/FFDecHWAccel.hpp b/src/modules/FFmpeg/FFDecHWAccel.hpp
index e3ffcada..b73c91d5 100644
--- a/src/modules/FFmpeg/FFDecHWAccel.hpp
+++ b/src/modules/FFmpeg/FFDecHWAccel.hpp
@@ -28,13 +28,13 @@ protected:
 
     static bool hasHWAccel(const char *hwaccelName);
 
-    AVCodec *init(StreamInfo &streamInfo) override;
+    AVCodec *init(StreamInfo &streamInfo);
 
     bool hasHWDecContext() const override final;
 
-    int decodeVideo(const Packet &encodedPacket, Frame &decoded, AVPixelFormat &newPixFmt, bool flush, unsigned hurryUp) override;
+    int decodeVideo(const Packet &encodedPacket, Frame &decoded, AVPixelFormat &newPixFmt, bool flush, unsigned hurryUp);
 
-    bool hasCriticalError() const override;
+    bool hasCriticalError() const;
 
 protected:
     bool m_hasCriticalError = false;
diff --git a/src/modules/FFmpeg/FFDecSW.cpp b/src/modules/FFmpeg/FFDecSW.cpp
index 8928daef..4bf618e4 100644
--- a/src/modules/FFmpeg/FFDecSW.cpp
+++ b/src/modules/FFmpeg/FFDecSW.cpp
@@ -24,6 +24,8 @@
 #include <StreamInfo.hpp>
 #include <Functions.hpp>
 
+#include <cmath>
+
 #ifdef USE_VULKAN
 #   include "../qmvk/MemoryPropertyFlags.hpp"
 #   include "../qmvk/PhysicalDevice.hpp"
@@ -266,14 +268,14 @@ int FFDecSW::decodeAudio(const Packet &encodedPacket, QByteArray &decoded, doubl
             ts = frame->best_effort_timestamp * av_q2d(m_timeBase);
         else if (encodedPacket.hasDts() || encodedPacket.hasPts())
             ts = encodedPacket.ts();
-        else if (codec_ctx->codec_id == AV_CODEC_ID_APE && !qIsNaN(m_lastTs))
+        else if (codec_ctx->codec_id == AV_CODEC_ID_APE && !std::isnan(m_lastTs))
             ts = m_lastTs + static_cast<double>(frame->nb_samples) / static_cast<double>(sampleRate);
         else
-            ts = qQNaN();
+            ts = std::numeric_limits<double>::quiet_NaN();
     }
     else
     {
-        ts = qQNaN();
+        ts = std::numeric_limits<double>::quiet_NaN();
     }
 
     m_lastTs = ts;
@@ -420,7 +422,7 @@ bool FFDecSW::decodeSubtitle(const QVector<Packet> &encodedPackets, double pos,
         }
     }
 
-    return qIsNaN(pos)
+    return std::isnan(pos)
         ? true
         : getFromBitmapSubsBuffer(osd, pos)
     ;
@@ -548,7 +550,7 @@ void FFDecSW::setPixelFormat()
 
     const QByteArray srcPixFmtName = srcPixDesc->name;
 
-    for (const AVPixelFormat pixFmt : std::as_const(m_supportedPixelFormats))
+    for (const AVPixelFormat pixFmt : m_supportedPixelFormats)
     {
         if (!sws_isSupportedOutput(pixFmt))
             continue;
diff --git a/src/modules/FFmpeg/FFDecSW.hpp b/src/modules/FFmpeg/FFDecSW.hpp
index 8e8c5c9c..0b3f215a 100644
--- a/src/modules/FFmpeg/FFDecSW.hpp
+++ b/src/modules/FFmpeg/FFDecSW.hpp
@@ -18,6 +18,8 @@
 
 #pragma once
 
+#include <limits>
+
 #include <FFDec.hpp>
 
 #include <deque>
@@ -49,7 +51,7 @@ public:
 
 struct SwsContext;
 
-class FFMPEG_EXPORT FFDecSW final : public FFDec
+class FFMPEG_EXPORT FFDecSW : public FFDec
 {
 public:
     FFDecSW(Module &);
@@ -57,20 +59,20 @@ public:
 private:
     ~FFDecSW();
 
-    bool set() override;
+    bool set();
 
-    QString name() const override;
+    QString name() const;
 
-    QByteArray subtitleHeader() const override;
+    QByteArray subtitleHeader() const;
 
-    void setSupportedPixelFormats(const AVPixelFormats &pixelFormats) override;
+    void setSupportedPixelFormats(const AVPixelFormats &pixelFormats);
 
-    int  decodeAudio(const Packet &encodedPacket, QByteArray &decoded, double &ts, quint8 &channels, quint32 &sampleRate, bool flush) override;
-    int  decodeVideo(const Packet &encodedPacket, Frame &decoded, AVPixelFormat &newPixFmt, bool flush, unsigned hurry_up) override;
-    bool decodeSubtitle(const QVector<Packet> &encodedPackets, double pos, std::shared_ptr<QMPlay2OSD> &osd, const QSize &size, bool flush) override;
-    QList<QByteArray> decodeSubtitle(const Packet &encodedPacket) override;
+    int  decodeAudio(const Packet &encodedPacket, QByteArray &decoded, double &ts, quint8 &channels, quint32 &sampleRate, bool flush);
+    int  decodeVideo(const Packet &encodedPacket, Frame &decoded, AVPixelFormat &newPixFmt, bool flush, unsigned hurry_up);
+    bool decodeSubtitle(const QVector<Packet> &encodedPackets, double pos, std::shared_ptr<QMPlay2OSD> &osd, const QSize &size, bool flush);
+    QList<QByteArray> decodeSubtitle(const Packet &encodedPacket);
 
-    bool open(StreamInfo &) override;
+    bool open(StreamInfo &);
 
     /**/
 
@@ -98,7 +100,7 @@ private:
 
     std::deque<Subtitle> m_subtitles;
 
-    double m_lastTs = qQNaN();
+    double m_lastTs = std::numeric_limits<double>::quiet_NaN();
 
 #ifdef USE_VULKAN
     std::shared_ptr<QmVk::BufferPool> m_vkBufferPool;
diff --git a/src/modules/FFmpeg/FFDecVAAPI.cpp b/src/modules/FFmpeg/FFDecVAAPI.cpp
index b27333db..dc2a1322 100644
--- a/src/modules/FFmpeg/FFDecVAAPI.cpp
+++ b/src/modules/FFmpeg/FFDecVAAPI.cpp
@@ -156,7 +156,7 @@ bool FFDecVAAPI::open(StreamInfo &streamInfo)
 
 #if defined(USE_OPENGL) || defined(USE_VULKAN)
     auto maybeResetVaapi = [this, codec] {
-        if (m_vaapi && m_vaapi->m_codecId != codec->id && m_vaapi->m_isMesaRadeon && m_vaapi->m_driverVersion >= QVersionNumber(22, 0, 0))
+        if (m_vaapi && m_vaapi->m_codecId != codec->id && m_vaapi->m_isMesaRadeon && m_vaapi->m_driverVersion >= SimpleVersion(22, 0, 0))
             m_vaapi.reset();
     };
 #endif
diff --git a/src/modules/FFmpeg/FFDecVAAPI.hpp b/src/modules/FFmpeg/FFDecVAAPI.hpp
index dcc6b6f2..16df855f 100644
--- a/src/modules/FFmpeg/FFDecVAAPI.hpp
+++ b/src/modules/FFmpeg/FFDecVAAPI.hpp
@@ -28,21 +28,21 @@ class VAAPIOpenGL;
 class VAAPIVulkan;
 #endif
 
-class FFMPEG_EXPORT FFDecVAAPI final : public FFDecHWAccel
+class FFMPEG_EXPORT FFDecVAAPI : public FFDecHWAccel
 {
 public:
     FFDecVAAPI(Module &);
     ~FFDecVAAPI();
 
-    bool set() override;
+    bool set();
 
-    QString name() const override;
+    QString name() const;
 
-    std::shared_ptr<VideoFilter> hwAccelFilter() const override;
+    std::shared_ptr<VideoFilter> hwAccelFilter() const;
 
-    int decodeVideo(const Packet &encodedPacket, Frame &decoded, AVPixelFormat &newPixFmt, bool flush, unsigned hurryUp) override;
+    int decodeVideo(const Packet &encodedPacket, Frame &decoded, AVPixelFormat &newPixFmt, bool flush, unsigned hurryUp);
 
-    bool open(StreamInfo &streamInfo) override;
+    bool open(StreamInfo &streamInfo);
 
 private:
     void maybeClearHwSurfaces();
diff --git a/src/modules/FFmpeg/FFDecVTB.hpp b/src/modules/FFmpeg/FFDecVTB.hpp
index 52cb1bab..82c08ab1 100644
--- a/src/modules/FFmpeg/FFDecVTB.hpp
+++ b/src/modules/FFmpeg/FFDecVTB.hpp
@@ -20,17 +20,17 @@
 
 #include <FFDecHWAccel.hpp>
 
-class FFMPEG_EXPORT FFDecVTB final : public FFDecHWAccel
+class FFMPEG_EXPORT FFDecVTB : public FFDecHWAccel
 {
 public:
     FFDecVTB(Module &module);
     ~FFDecVTB();
 
-    bool set() override;
+    bool set();
 
-    QString name() const override;
+    QString name() const;
 
-    bool open(StreamInfo &streamInfo) override;
+    bool open(StreamInfo &streamInfo);
 
 private:
     AVBufferRef *m_hwDeviceBufferRef = nullptr;
diff --git a/src/modules/FFmpeg/FFDecVkVideo.hpp b/src/modules/FFmpeg/FFDecVkVideo.hpp
index 1ec6c304..cc0e32f9 100644
--- a/src/modules/FFmpeg/FFDecVkVideo.hpp
+++ b/src/modules/FFmpeg/FFDecVkVideo.hpp
@@ -33,19 +33,19 @@ class Device;
 
 }
 
-class FFMPEG_EXPORT FFDecVkVideo final : public FFDecHWAccel
+class FFMPEG_EXPORT FFDecVkVideo : public FFDecHWAccel
 {
 public:
     FFDecVkVideo(Module &module, bool hwDownload);
     ~FFDecVkVideo();
 
-    bool set() override;
+    bool set();
 
-    QString name() const override;
+    QString name() const;
 
-    int decodeVideo(const Packet &encodedPacket, Frame &decoded, AVPixelFormat &newPixFmt, bool flush, unsigned hurryUp) override;
+    int decodeVideo(const Packet &encodedPacket, Frame &decoded, AVPixelFormat &newPixFmt, bool flush, unsigned hurryUp);
 
-    bool open(StreamInfo &streamInfo) override;
+    bool open(StreamInfo &streamInfo);
 
 private:
     bool initHw();
diff --git a/src/modules/FFmpeg/FFDemux.cpp b/src/modules/FFmpeg/FFDemux.cpp
index f250a158..a0081939 100644
--- a/src/modules/FFmpeg/FFDemux.cpp
+++ b/src/modules/FFmpeg/FFDemux.cpp
@@ -35,7 +35,7 @@ FFDemux::FFDemux(Module &module) :
 FFDemux::~FFDemux()
 {
     streams_info.clear();
-    for (const FormatContext *fmtCtx : std::as_const(formatContexts))
+    for (const FormatContext *fmtCtx : formatContexts)
         delete fmtCtx;
 }
 
@@ -63,7 +63,7 @@ bool FFDemux::set()
 bool FFDemux::metadataChanged() const
 {
     bool isMetadataChanged = false;
-    for (const FormatContext *fmtCtx : std::as_const(formatContexts))
+    for (const FormatContext *fmtCtx : formatContexts)
         isMetadataChanged |= fmtCtx->metadataChanged();
     return isMetadataChanged;
 }
@@ -71,7 +71,7 @@ bool FFDemux::metadataChanged() const
 bool FFDemux::isStillImage() const
 {
     bool stillImage = true;
-    for (const FormatContext *fmtCtx : std::as_const(formatContexts))
+    for (const FormatContext *fmtCtx : formatContexts)
         stillImage &= fmtCtx->isStillImage();
     return stillImage;
 }
@@ -92,7 +92,7 @@ QList<ChapterInfo> FFDemux::getChapters() const
 QString FFDemux::name() const
 {
     QString name;
-    for (const FormatContext *fmtCtx : std::as_const(formatContexts))
+    for (const FormatContext *fmtCtx : formatContexts)
     {
         const QString fmtCtxName = fmtCtx->name();
         if (!name.contains(fmtCtxName))
@@ -122,7 +122,7 @@ bool FFDemux::getReplayGain(bool album, float &gain_db, float &peak) const
 qint64 FFDemux::size() const
 {
     qint64 bytes = -1;
-    for (const FormatContext *fmtCtx : std::as_const(formatContexts))
+    for (const FormatContext *fmtCtx : formatContexts)
     {
         const qint64 s = fmtCtx->size();
         if (s < 0)
@@ -134,14 +134,14 @@ qint64 FFDemux::size() const
 double FFDemux::length() const
 {
     double length = -1.0;
-    for (const FormatContext *fmtCtx : std::as_const(formatContexts))
+    for (const FormatContext *fmtCtx : formatContexts)
         length = qMax(length, fmtCtx->length());
     return length;
 }
 int FFDemux::bitrate() const
 {
     int bitrate = 0;
-    for (const FormatContext *fmtCtx : std::as_const(formatContexts))
+    for (const FormatContext *fmtCtx : formatContexts)
         bitrate += fmtCtx->bitrate();
     return bitrate;
 }
@@ -154,7 +154,7 @@ QByteArray FFDemux::image(bool forceCopy) const
 
 bool FFDemux::localStream() const
 {
-    for (const FormatContext *fmtCtx : std::as_const(formatContexts))
+    for (const FormatContext *fmtCtx : formatContexts)
         if (!fmtCtx->isLocal)
             return false;
     return true;
@@ -164,7 +164,7 @@ void FFDemux::selectStreams(const QSet<int> &selectedStreams)
 {
     bool first = true;
     int baseIdx = 0;
-    for (FormatContext *fmtCtx : std::as_const(formatContexts))
+    for (FormatContext *fmtCtx : formatContexts)
     {
         if (first)
         {
@@ -187,7 +187,7 @@ void FFDemux::selectStreams(const QSet<int> &selectedStreams)
 bool FFDemux::seek(double pos, bool backward)
 {
     bool seeked = false;
-    for (FormatContext *fmtCtx : std::as_const(formatContexts))
+    for (FormatContext *fmtCtx : formatContexts)
     {
         if (fmtCtx->seek(pos, backward))
             seeked |= true;
@@ -237,13 +237,13 @@ bool FFDemux::read(Packet &encoded, int &idx)
 }
 void FFDemux::pause()
 {
-    for (FormatContext *fmtCtx : std::as_const(formatContexts))
+    for (FormatContext *fmtCtx : formatContexts)
         fmtCtx->pause();
 }
 void FFDemux::abort()
 {
     QMutexLocker mL(&mutex);
-    for (FormatContext *fmtCtx : std::as_const(formatContexts))
+    for (FormatContext *fmtCtx : formatContexts)
         fmtCtx->abort();
     abortFetchTracks = true;
 }
@@ -257,7 +257,7 @@ bool FFDemux::open(const QString &entireUrl)
     {
         if (!param.isEmpty())
             addFormatContext(url, param);
-        else for (QString stream : url.split("][", Qt::SkipEmptyParts))
+        else for (QString stream : url.split("][", QString::SkipEmptyParts))
         {
             stream.remove('[');
             stream.remove(']');
@@ -455,7 +455,7 @@ Playlist::Entries FFDemux::fetchTracks(const QString &url, bool &ok)
                 const double end = indexes.value(i + 1, {-1.0, -1.0}).first;
                 if (entry.url.isEmpty() || start < 0.0 || (end <= 0.0 && !lastItem))
                 {
-                    entries.removeAt(i);
+                    entries.remove(i);
                     continue;
                 }
                 const QString param = QString("CUE:%1:%2").arg(start).arg(end);
diff --git a/src/modules/FFmpeg/FFDemux.hpp b/src/modules/FFmpeg/FFDemux.hpp
index 9d7a3531..b0ff3ce0 100644
--- a/src/modules/FFmpeg/FFDemux.hpp
+++ b/src/modules/FFmpeg/FFDemux.hpp
@@ -22,7 +22,7 @@
 
 class FormatContext;
 
-class FFDemux final : public Demuxer
+class FFDemux : public Demuxer
 {
     Q_DECLARE_TR_FUNCTIONS(FFDemux)
 public:
@@ -30,36 +30,36 @@ public:
 private:
     ~FFDemux();
 
-    bool set() override;
+    bool set();
 
-    bool metadataChanged() const override;
+    bool metadataChanged() const;
 
-    bool isStillImage() const override;
+    bool isStillImage() const;
 
-    QList<ProgramInfo> getPrograms() const override;
-    QList<ChapterInfo> getChapters() const override;
+    QList<ProgramInfo> getPrograms() const;
+    QList<ChapterInfo> getChapters() const;
 
-    QString name() const override;
-    QString title() const override;
-    QList<QMPlay2Tag> tags() const override;
-    bool getReplayGain(bool album, float &gain_db, float &peak) const override;
-    qint64 size() const override;
-    double length() const override;
-    int bitrate() const override;
-    QByteArray image(bool forceCopy) const override;
+    QString name() const;
+    QString title() const;
+    QList<QMPlay2Tag> tags() const;
+    bool getReplayGain(bool album, float &gain_db, float &peak) const;
+    qint64 size() const;
+    double length() const;
+    int bitrate() const;
+    QByteArray image(bool forceCopy) const;
 
-    bool localStream() const override;
+    bool localStream() const;
 
-    void selectStreams(const QSet<int> &selectedStreams) override;
+    void selectStreams(const QSet<int> &selectedStreams);
 
-    bool seek(double pos, bool backward) override;
-    bool read(Packet &encoded, int &idx) override;
-    void pause() override;
-    void abort() override;
+    bool seek(double pos, bool backward);
+    bool read(Packet &encoded, int &idx);
+    void pause();
+    void abort();
 
-    bool open(const QString &entireUrl) override;
+    bool open(const QString &entireUrl);
 
-    Playlist::Entries fetchTracks(const QString &url, bool &ok) override;
+    Playlist::Entries fetchTracks(const QString &url, bool &ok);
 
     /**/
 
diff --git a/src/modules/FFmpeg/FFReader.cpp b/src/modules/FFmpeg/FFReader.cpp
index c538d101..880c833b 100644
--- a/src/modules/FFmpeg/FFReader.cpp
+++ b/src/modules/FFmpeg/FFReader.cpp
@@ -49,7 +49,7 @@ public:
     }
 
 private:
-    void run() override
+    void run()
     {
         const AVIOInterruptCB interruptCB = {(int(*)(void*))::interruptCB, &m_abortCtx->isAborted};
         avio_open2(&m_avioCtx, m_url, AVIO_FLAG_READ, &interruptCB, &m_options);
diff --git a/src/modules/FFmpeg/FFReader.hpp b/src/modules/FFmpeg/FFReader.hpp
index c707ed9a..0aa4a8a1 100644
--- a/src/modules/FFmpeg/FFReader.hpp
+++ b/src/modules/FFmpeg/FFReader.hpp
@@ -24,25 +24,25 @@
 
 struct AVIOContext;
 
-class FFReader final : public Reader
+class FFReader : public Reader
 {
 public:
     FFReader();
 private:
-    bool readyRead() const override;
-    bool canSeek() const override;
+    bool readyRead() const;
+    bool canSeek() const;
 
-    bool seek(qint64) override;
-    QByteArray read(qint64) override;
-    void pause() override;
-    bool atEnd() const override;
-    void abort() override;
+    bool seek(qint64);
+    QByteArray read(qint64);
+    void pause();
+    bool atEnd() const;
+    void abort();
 
-    qint64 size() const override;
-    qint64 pos() const override;
-    QString name() const override;
+    qint64 size() const;
+    qint64 pos() const;
+    QString name() const;
 
-    bool open() override;
+    bool open();
 
     /**/
 
diff --git a/src/modules/FFmpeg/FFmpeg.cpp b/src/modules/FFmpeg/FFmpeg.cpp
index 8b45a0bd..6c3b72ce 100644
--- a/src/modules/FFmpeg/FFmpeg.cpp
+++ b/src/modules/FFmpeg/FFmpeg.cpp
@@ -45,8 +45,7 @@ extern "C"
 #endif
 }
 
-#include <QOperatingSystemVersion>
-#include <QGuiApplication>
+#include <QApplication>
 #include <QComboBox>
 
 FFmpeg::FFmpeg() :
@@ -67,7 +66,21 @@ FFmpeg::FFmpeg() :
 #if defined(QMPlay2_DXVA2) || defined(QMPlay2_D3D11VA)
     dxIcon = QIcon(":/DXVA2.svgz");
     if (QMPlay2Core.isVulkanRenderer())
+    {
+#ifdef Q_OS_WIN
+        // D3D11VA requires Windows 8 or later
+        // In Qt4, we use QSysInfo for Windows version detection
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         d3d11vaSupported = (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8);
+#else
+        // Qt4: Check Windows version using QSysInfo
+        // Windows 8 is version 6.2
+        d3d11vaSupported = (QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS8);
+#endif
+#else
+        d3d11vaSupported = false; // Not Windows
+#endif
+    }
     else
         dxva2Supported = true;
 #endif
@@ -80,6 +93,7 @@ FFmpeg::FFmpeg() :
     init("AllowExperimental", false);
     init("DecoderEnabled", true);
 #ifdef QMPlay2_VKVIDEO
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     switch (QOperatingSystemVersion::currentType())
     {
         case QOperatingSystemVersion::Windows:
@@ -89,6 +103,16 @@ FFmpeg::FFmpeg() :
             init("DecoderVkVideoEnabled", true);
             break;
     }
+#else
+    // Qt4 compatibility: Use preprocessor to determine OS
+#ifdef Q_OS_WIN
+    // VkVideo requires Windows 10 or later in Qt4
+    // Windows 10 is version 6.4 (QSysInfo doesn't have WV_WINDOWS10, so enable for Windows 8+)
+    init("DecoderVkVideoEnabled", QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS8);
+#else
+    init("DecoderVkVideoEnabled", true);
+#endif
+#endif
 #endif
 #ifdef QMPlay2_VAAPI
     init("DecoderVAAPIEnabled", true);
diff --git a/src/modules/FFmpeg/FFmpeg.hpp b/src/modules/FFmpeg/FFmpeg.hpp
index 25de8ce1..b75dd268 100644
--- a/src/modules/FFmpeg/FFmpeg.hpp
+++ b/src/modules/FFmpeg/FFmpeg.hpp
@@ -24,19 +24,19 @@
 
 class QComboBox;
 
-class FFmpeg final : public Module
+class FFmpeg : public Module
 {
     Q_DECLARE_TR_FUNCTIONS(FFmpeg)
 public:
     FFmpeg();
     ~FFmpeg();
 private:
-    QList<Info> getModulesInfo(const bool) const override;
-    void *createInstance(const QString &) override;
+    QList<Info> getModulesInfo(const bool) const;
+    void *createInstance(const QString &);
 
-    SettingsWidget *getSettingsWidget() override;
+    SettingsWidget *getSettingsWidget();
 
-    void videoDeintSave() override;
+    void videoDeintSave();
 
     /**/
 
@@ -70,13 +70,13 @@ class QGroupBox;
 class QSpinBox;
 class Slider;
 
-class ModuleSettingsWidget final : public Module::SettingsWidget
+class ModuleSettingsWidget : public Module::SettingsWidget
 {
     Q_DECLARE_TR_FUNCTIONS(ModuleSettingsWidget)
 public:
     ModuleSettingsWidget(Module &module, bool vkVideo, bool dxva2, bool d3d11va);
 private:
-    void saveSettings() override;
+    void saveSettings();
 
     QGroupBox *demuxerB;
     QCheckBox *reconnectNetworkB;
diff --git a/src/modules/FFmpeg/FormatContext.cpp b/src/modules/FFmpeg/FormatContext.cpp
index 1809fd2e..5fad479b 100644
--- a/src/modules/FFmpeg/FormatContext.cpp
+++ b/src/modules/FFmpeg/FormatContext.cpp
@@ -25,6 +25,8 @@
 #include <Settings.hpp>
 #include <Packet.hpp>
 
+#include <cmath>
+
 #ifdef Q_OS_ANDROID
 #   include <QFile>
 #endif
@@ -226,7 +228,7 @@ public:
     }
 
 private:
-    void run() override
+    void run()
     {
         avformat_open_input(&m_formatCtx, m_url, m_inputFmt, &m_options);
         if (!wakeIfNotAborted() && m_formatCtx)
@@ -262,7 +264,7 @@ FormatContext::~FormatContext()
         av_packet_free(&packet);
     }
     delete oggHelper;
-    for (StreamInfo *streamInfo : std::as_const(streamsInfo))
+    for (StreamInfo *streamInfo : streamsInfo)
         delete streamInfo;
 }
 
@@ -1001,7 +1003,7 @@ bool FormatContext::open(const QString &_url, const QString &param)
                 {
                     if (firstAudioIdx > -1)
                     {
-                        for (auto &&streamInfo : std::as_const(streamsInfo))
+                        for (auto &&streamInfo : streamsInfo)
                         {
                             if (streamInfo->params->codec_type == AVMEDIA_TYPE_AUDIO)
                                 streamInfo->is_default = false;
@@ -1034,7 +1036,7 @@ void FormatContext::setStreamOffset(double offset)
 void FormatContext::selectStreams(const QSet<int> &selectedStreams)
 {
     m_allDiscarded = true;
-    for (AVStream *stream : std::as_const(streams))
+    for (AVStream *stream : streams)
     {
         if (stream->codecpar->codec_type == AVMEDIA_TYPE_DATA || stream->codecpar->codec_type == AVMEDIA_TYPE_ATTACHMENT)
         {
@@ -1092,7 +1094,7 @@ StreamInfo *FormatContext::getStreamInfo(AVStream *stream) const
 
     bool hasUrlTagDefault = false;
     bool hasUrlTagLanguage = false;
-    for (auto &&urlTag : std::as_const(m_urlTags))
+    for (auto &&urlTag : m_urlTags)
     {
         const auto tag = StreamInfo::getTag(urlTag.first);
         if (tag == QMPLAY2_TAG_DEFAULT)
@@ -1170,7 +1172,7 @@ StreamInfo *FormatContext::getStreamInfo(AVStream *stream) const
             {
                 const auto rotation = av_display_rotation_get(reinterpret_cast<const int32_t *>(sideData));
 #endif
-                if (!qIsNaN(rotation))
+                if (!std::isnan(rotation))
                 {
                     switch(qRound(rotation))
                     {
diff --git a/src/modules/FFmpeg/VAAPI.cpp b/src/modules/FFmpeg/VAAPI.cpp
index 4ceed4bb..21ea7f84 100644
--- a/src/modules/FFmpeg/VAAPI.cpp
+++ b/src/modules/FFmpeg/VAAPI.cpp
@@ -34,9 +34,8 @@ extern "C"
     #include <libavutil/buffer.h>
 }
 
-#include <QGuiApplication>
+#include <QApplication>
 #include <QVarLengthArray>
-#include <QVersionNumber>
 #include <QFileInfo>
 #include <QDebug>
 #include <QDir>
@@ -148,16 +147,17 @@ bool VAAPI::open()
         // Some issues discussed here: https://gitlab.freedesktop.org/mesa/mesa/-/issues/8740
 
         const auto parts = m_vendor.simplified().split(" ");
-        for (auto &&part : parts)
+        // Qt4: No std::as_const, no for-range with auto&&
+        foreach (const QString &part, parts)
         {
-            const auto ver = QVersionNumber::fromString(part);
+            const auto ver = SimpleVersion::fromString(part);
             if (!ver.isNull())
             {
                 m_driverVersion = ver;
                 break;
             }
         }
-        if (m_driverVersion >= QVersionNumber(22, 0, 0) && m_driverVersion < QVersionNumber(23, 3, 0))
+        if (m_driverVersion >= SimpleVersion(22, 0, 0) && m_driverVersion < SimpleVersion(23, 3, 0))
         {
             m_mutex = std::make_unique<QMutex>();
         }
diff --git a/src/modules/FFmpeg/VAAPI.hpp b/src/modules/FFmpeg/VAAPI.hpp
index 6cfbce48..229d8476 100644
--- a/src/modules/FFmpeg/VAAPI.hpp
+++ b/src/modules/FFmpeg/VAAPI.hpp
@@ -19,7 +19,6 @@
 #pragma once
 
 #include <QCoreApplication>
-#include <QVersionNumber>
 #include <QVector>
 #include <QMutex>
 #include <QHash>
@@ -34,6 +33,44 @@ struct AVFrame;
 class QImage;
 class Frame;
 
+// Qt4: Simple version number replacement
+struct SimpleVersion
+{
+    int major = 0, minor = 0, patch = 0;
+
+    SimpleVersion() = default;
+    SimpleVersion(int maj, int min, int pat) : major(maj), minor(min), patch(pat) {}
+
+    bool isNull() const { return major == 0 && minor == 0 && patch == 0; }
+
+    bool operator>=(const SimpleVersion &other) const
+    {
+        if (major != other.major) return major > other.major;
+        if (minor != other.minor) return minor > other.minor;
+        return patch >= other.patch;
+    }
+
+    bool operator<(const SimpleVersion &other) const
+    {
+        return !(*this >= other) && !(*this == other);
+    }
+
+    bool operator==(const SimpleVersion &other) const
+    {
+        return major == other.major && minor == other.minor && patch == other.patch;
+    }
+
+    static SimpleVersion fromString(const QString &str)
+    {
+        SimpleVersion ver;
+        QStringList parts = str.split('.');
+        if (parts.size() >= 1) ver.major = parts[0].toInt();
+        if (parts.size() >= 2) ver.minor = parts[1].toInt();
+        if (parts.size() >= 3) ver.patch = parts[2].toInt();
+        return ver;
+    }
+};
+
 class VAAPI
 {
     Q_DECLARE_TR_FUNCTIONS(VAAPI)
@@ -69,7 +106,7 @@ public:
 
     QString m_vendor;
     bool m_isMesaRadeon = false;
-    QVersionNumber m_driverVersion; // Used for Mesa Radeon workarounds
+    SimpleVersion m_driverVersion; // Used for Mesa Radeon workarounds
 
     bool ok = false;
 
diff --git a/src/modules/FFmpeg/VAAPIOpenGL.cpp b/src/modules/FFmpeg/VAAPIOpenGL.cpp
index bcabebad..da5a8966 100644
--- a/src/modules/FFmpeg/VAAPIOpenGL.cpp
+++ b/src/modules/FFmpeg/VAAPIOpenGL.cpp
@@ -22,7 +22,7 @@
 #include <QMPlay2Core.hpp>
 #include <Frame.hpp>
 
-#include <QOpenGLContext>
+#include <QtOpenGL/QGLContext>
 
 #include <unistd.h>
 
@@ -90,7 +90,8 @@ bool VAAPIOpenGL::init(const int *widths, const int *heights, const SetTexturePa
     if (m_egl->eglDpy != EGL_NO_DISPLAY && m_egl->eglCreateImageKHR && m_egl->eglDestroyImageKHR && m_egl->glEGLImageTargetTexture2DOES)
         return true;
 
-    const auto context = QOpenGLContext::currentContext();
+    // Qt4: Use QGLContext instead of QOpenGLContext
+    const auto context = QGLContext::currentContext();
     if (!context)
     {
         QMPlay2Core.logError("VA-API :: Unable to get OpenGL context");
diff --git a/src/modules/FFmpeg/VAAPIOpenGL.hpp b/src/modules/FFmpeg/VAAPIOpenGL.hpp
index 71982f09..41927153 100644
--- a/src/modules/FFmpeg/VAAPIOpenGL.hpp
+++ b/src/modules/FFmpeg/VAAPIOpenGL.hpp
@@ -36,18 +36,18 @@ public:
     VAAPIOpenGL();
     ~VAAPIOpenGL();
 
-    QString name() const override;
+    QString name() const;
 
-    Format getFormat() const override;
-    bool isCopy() const override;
+    Format getFormat() const;
+    bool isCopy() const;
 
-    bool init(const int *widths, const int *heights, const SetTextureParamsFn &setTextureParamsFn) override;
-    void clear() override;
+    bool init(const int *widths, const int *heights, const SetTextureParamsFn &setTextureParamsFn);
+    void clear();
 
-    bool mapFrame(Frame &videoFrame) override;
-    quint32 getTexture(int plane) override;
+    bool mapFrame(Frame &videoFrame);
+    quint32 getTexture(int plane);
 
-    Frame getCpuFrame(const Frame &videoFrame) override;
+    Frame getCpuFrame(const Frame &videoFrame);
 
     /**/
 
diff --git a/src/modules/FFmpeg/VAAPIVulkan.cpp b/src/modules/FFmpeg/VAAPIVulkan.cpp
index af322676..6ece8742 100644
--- a/src/modules/FFmpeg/VAAPIVulkan.cpp
+++ b/src/modules/FFmpeg/VAAPIVulkan.cpp
@@ -101,7 +101,7 @@ void VAAPIVulkan::map(Frame &frame)
 
     auto &vkImage = m_images[id];
 
-    if (!vkImage || (vaField != VA_FRAME_PICTURE && m_vaapi->m_isMesaRadeon && m_vaapi->m_driverVersion >= QVersionNumber(23, 3, 0)))
+    if (!vkImage || (vaField != VA_FRAME_PICTURE && m_vaapi->m_isMesaRadeon && m_vaapi->m_driverVersion >= SimpleVersion(23, 3, 0)))
     {
         if (m_vaapi->m_mutex)
             m_vaapi->m_mutex->lock();
diff --git a/src/modules/FFmpeg/VAAPIVulkan.hpp b/src/modules/FFmpeg/VAAPIVulkan.hpp
index f9b8630c..344c9f4c 100644
--- a/src/modules/FFmpeg/VAAPIVulkan.hpp
+++ b/src/modules/FFmpeg/VAAPIVulkan.hpp
@@ -14,7 +14,7 @@ class Image;
 struct AVFrame;
 class VAAPI;
 
-class VAAPIVulkan final : public QmVk::HWInterop
+class VAAPIVulkan : public QmVk::HWInterop
 {
 public:
     VAAPIVulkan();
@@ -29,12 +29,12 @@ public:
         return m_vaapi;
     }
 
-    QString name() const override;
+    QString name() const;
 
-    void map(Frame &frame) override;
-    void clear() override;
+    void map(Frame &frame);
+    void clear();
 
-    SyncDataPtr sync(const std::vector<Frame> &frames, vk::SubmitInfo *submitInfo = nullptr) override;
+    SyncDataPtr sync(const std::vector<Frame> &frames, vk::SubmitInfo *submitInfo = nullptr);
 
 public:
     void insertAvailableSurface(uintptr_t id);
diff --git a/src/modules/FFmpeg/VTBOpenGL.cpp b/src/modules/FFmpeg/VTBOpenGL.cpp
index dc325ed7..145cf333 100644
--- a/src/modules/FFmpeg/VTBOpenGL.cpp
+++ b/src/modules/FFmpeg/VTBOpenGL.cpp
@@ -20,7 +20,7 @@
 
 #include <Frame.hpp>
 
-#include <QOpenGLContext>
+#include <QtOpenGL/QGLContext>
 #include <QImage>
 
 extern "C"
diff --git a/src/modules/FFmpeg/VTBOpenGL.hpp b/src/modules/FFmpeg/VTBOpenGL.hpp
index 19f44f1f..5d58ce92 100644
--- a/src/modules/FFmpeg/VTBOpenGL.hpp
+++ b/src/modules/FFmpeg/VTBOpenGL.hpp
@@ -22,25 +22,25 @@
 
 struct AVBufferRef;
 
-class VTBOpenGL final : public OpenGLHWInterop
+class VTBOpenGL : public OpenGLHWInterop
 {
 public:
     VTBOpenGL(AVBufferRef *hwDeviceBufferRef);
     ~VTBOpenGL();
 
-    QString name() const override;
+    QString name() const;
 
-    Format getFormat() const override;
-    bool isTextureRectangle() const override;
-    bool isCopy() const override;
+    Format getFormat() const;
+    bool isTextureRectangle() const;
+    bool isCopy() const;
 
-    bool init(const int *widths, const int *heights, const SetTextureParamsFn &setTextureParamsFn) override;
-    void clear() override;
+    bool init(const int *widths, const int *heights, const SetTextureParamsFn &setTextureParamsFn);
+    void clear();
 
-    bool mapFrame(Frame &videoFrame) override;
-    quint32 getTexture(int plane) override;
+    bool mapFrame(Frame &videoFrame);
+    quint32 getTexture(int plane);
 
-    Frame getCpuFrame(const Frame &videoFrame) override;
+    Frame getCpuFrame(const Frame &videoFrame);
 
 public:
     AVBufferRef *m_hwDeviceBufferRef = nullptr;
diff --git a/src/modules/FFmpeg/VkVideoVulkan.hpp b/src/modules/FFmpeg/VkVideoVulkan.hpp
index 906028fb..6a7da6ed 100644
--- a/src/modules/FFmpeg/VkVideoVulkan.hpp
+++ b/src/modules/FFmpeg/VkVideoVulkan.hpp
@@ -29,20 +29,20 @@ class Instance;
 class Image;
 }
 
-class VkVideoVulkan final : public QmVk::HWInterop
+class VkVideoVulkan : public QmVk::HWInterop
 {
 public:
     VkVideoVulkan(const std::shared_ptr<QmVk::Instance> &device);
     ~VkVideoVulkan();
 
-    QString name() const override;
+    QString name() const;
 
-    void map(Frame &frame) override;
-    void clear() override;
+    void map(Frame &frame);
+    void clear();
 
-    SyncDataPtr sync(const std::vector<Frame> &frames, vk::SubmitInfo *submitInfo = nullptr) override;
+    SyncDataPtr sync(const std::vector<Frame> &frames, vk::SubmitInfo *submitInfo = nullptr);
 
-    void updateInfo(const std::vector<Frame> &frames) override;
+    void updateInfo(const std::vector<Frame> &frames);
 
 public:
     void insertAvailableAvVkFrames(uintptr_t avVkFramePtr, int codedWidth, int codedHeight);
diff --git a/src/modules/FileAssociation/FileAssociation.hpp b/src/modules/FileAssociation/FileAssociation.hpp
index 3299a592..de139b7d 100644
--- a/src/modules/FileAssociation/FileAssociation.hpp
+++ b/src/modules/FileAssociation/FileAssociation.hpp
@@ -20,16 +20,16 @@
 
 #include <Module.hpp>
 
-class FileAssociation final : public Module
+class FileAssociation : public Module
 {
     Q_OBJECT
 public:
     FileAssociation();
 private:
-    QList<Info> getModulesInfo(const bool) const override;
-    void *createInstance(const QString &) override;
+    QList<Info> getModulesInfo(const bool) const;
+    void *createInstance(const QString &);
 
-    SettingsWidget *getSettingsWidget() override;
+    SettingsWidget *getSettingsWidget();
 
     bool reallyFirsttime;
 private slots:
@@ -43,14 +43,14 @@ class QPushButton;
 class QGroupBox;
 class QCheckBox;
 
-class ModuleSettingsWidget final : public Module::SettingsWidget
+class ModuleSettingsWidget : public Module::SettingsWidget
 {
     Q_OBJECT
     friend class FileAssociation;
 public:
     ModuleSettingsWidget(Module &);
 private:
-    void saveSettings() override;
+    void saveSettings();
     void addExtension(const QString &, const bool, const bool isPlaylist = false);
 private slots:
     void selectAll();
diff --git a/src/modules/Inputs/Inputs.cpp b/src/modules/Inputs/Inputs.cpp
index 503d76cf..514869a3 100644
--- a/src/modules/Inputs/Inputs.cpp
+++ b/src/modules/Inputs/Inputs.cpp
@@ -305,7 +305,7 @@ void ModuleSettingsWidget::saveSettings()
     if (pcmExtsE->text().isEmpty())
         pcmExtsE->setText(g_standartExts);
     sets().set("PCM", pcmB->isChecked());
-    sets().set("PCM/extensions", pcmExtsE->text().split(';', Qt::SkipEmptyParts));
+    sets().set("PCM/extensions", pcmExtsE->text().split(';', QString::SkipEmptyParts));
     for (int i = 0; i < formatB.size(); ++i)
         if (formatB[i]->isChecked())
         {
diff --git a/src/modules/Inputs/Inputs.hpp b/src/modules/Inputs/Inputs.hpp
index 19606b72..065a2f65 100644
--- a/src/modules/Inputs/Inputs.hpp
+++ b/src/modules/Inputs/Inputs.hpp
@@ -20,18 +20,18 @@
 
 #include <Module.hpp>
 
-class Inputs final : public Module
+class Inputs : public Module
 {
     Q_OBJECT
 public:
     Inputs();
 private:
-    QList<Info> getModulesInfo(const bool) const override;
-    void *createInstance(const QString &) override;
+    QList<Info> getModulesInfo(const bool) const;
+    void *createInstance(const QString &);
 
-    QList<QAction *> getAddActions() override;
+    QList<QAction *> getAddActions();
 
-    SettingsWidget *getSettingsWidget() override;
+    SettingsWidget *getSettingsWidget();
 
     QIcon toneIcon, pcmIcon;
 private slots:
@@ -43,7 +43,7 @@ private slots:
 #include <QSpinBox>
 #include <QDialog>
 
-class HzW final : public QWidget
+class HzW : public QWidget
 {
 public:
     HzW(int, const QStringList &);
@@ -60,7 +60,7 @@ private:
 
 class QGridLayout;
 
-class AddD final : public QDialog
+class AddD : public QDialog
 {
     Q_OBJECT
 public:
@@ -95,7 +95,7 @@ class QGroupBox;
 class QCheckBox;
 class QLineEdit;
 
-class ModuleSettingsWidget final : public Module::SettingsWidget
+class ModuleSettingsWidget : public Module::SettingsWidget
 {
     Q_OBJECT
 public:
@@ -103,7 +103,7 @@ public:
 private slots:
     void applyFreqs();
 private:
-    void saveSettings() override;
+    void saveSettings();
 
     AddD *toneGenerator;
     QGroupBox *pcmB;
diff --git a/src/modules/Inputs/PCM.hpp b/src/modules/Inputs/PCM.hpp
index 044876ea..98258e69 100644
--- a/src/modules/Inputs/PCM.hpp
+++ b/src/modules/Inputs/PCM.hpp
@@ -24,25 +24,25 @@
 
 class Reader;
 
-class PCM final : public Demuxer
+class PCM : public Demuxer
 {
 public:
     enum FORMAT {PCM_U8, PCM_S8, PCM_S16, PCM_S24, PCM_S32, PCM_FLT, FORMAT_COUNT};
 
     PCM(Module &);
 private:
-    bool set() override;
+    bool set();
 
-    QString name() const override;
-    QString title() const override;
-    double length() const override;
-    int bitrate() const override;
+    QString name() const;
+    QString title() const;
+    double length() const;
+    int bitrate() const;
 
-    bool seek(double, bool) override;
-    bool read(Packet &, int &) override;
-    void abort() override;
+    bool seek(double, bool);
+    bool read(Packet &, int &);
+    void abort();
 
-    bool open(const QString &) override;
+    bool open(const QString &);
 
     /**/
 
diff --git a/src/modules/Inputs/ToneGenerator.cpp b/src/modules/Inputs/ToneGenerator.cpp
index 701b99e8..59457943 100644
--- a/src/modules/Inputs/ToneGenerator.cpp
+++ b/src/modules/Inputs/ToneGenerator.cpp
@@ -21,7 +21,11 @@
 #include <Functions.hpp>
 #include <Packet.hpp>
 
-#include <QUrlQuery>
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+    #include <QUrlQuery>
+#else
+    #include <QUrl>
+#endif
 
 #include <cmath>
 
@@ -131,12 +135,22 @@ bool ToneGenerator::open(const QString &entireUrl)
         return true;
     }
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     srate = QUrlQuery(url).queryItemValue("samplerate").toUInt();
     if (!srate)
         srate = 44100;
 
     freqs.clear();
-    for (const QString &freq : QUrlQuery(url).queryItemValue("freqs").split(',', Qt::SkipEmptyParts))
+    for (const QString &freq : QUrlQuery(url).queryItemValue("freqs").split(',', QString::SkipEmptyParts))
+#else
+    // Qt4: No QUrlQuery, use QUrl methods directly
+    srate = url.queryItemValue("samplerate").toUInt();
+    if (!srate)
+        srate = 44100;
+
+    freqs.clear();
+    for (const QString &freq : url.queryItemValue("freqs").split(',', QString::SkipEmptyParts))
+#endif
         freqs += freq.toInt();
     if (freqs.isEmpty())
     {
diff --git a/src/modules/Inputs/ToneGenerator.hpp b/src/modules/Inputs/ToneGenerator.hpp
index cdda11ba..982db75e 100644
--- a/src/modules/Inputs/ToneGenerator.hpp
+++ b/src/modules/Inputs/ToneGenerator.hpp
@@ -22,28 +22,28 @@
 
 #include <atomic>
 
-class ToneGenerator final : public Demuxer
+class ToneGenerator : public Demuxer
 {
     Q_DECLARE_TR_FUNCTIONS(ToneGenerator)
 public:
     ToneGenerator(Module &);
 
-    bool set() override;
+    bool set();
 private:
-    bool metadataChanged() const override;
+    bool metadataChanged() const;
 
-    QString name() const override;
-    QString title() const override;
-    double length() const override;
-    int bitrate() const override;
+    QString name() const;
+    QString title() const;
+    double length() const;
+    int bitrate() const;
 
-    bool dontUseBuffer() const override;
+    bool dontUseBuffer() const;
 
-    bool seek(double, bool) override;
-    bool read(Packet &, int &) override;
-    void abort() override;
+    bool seek(double, bool);
+    bool read(Packet &, int &);
+    void abort();
 
-    bool open(const QString &) override;
+    bool open(const QString &);
 
     /**/
 
diff --git a/src/modules/Notify/Notify.hpp b/src/modules/Notify/Notify.hpp
index ddd58164..28038492 100644
--- a/src/modules/Notify/Notify.hpp
+++ b/src/modules/Notify/Notify.hpp
@@ -20,16 +20,16 @@
 
 #include <Module.hpp>
 
-class Notify final : public Module
+class Notify : public Module
 {
 public:
     Notify();
 
 private:
-    QList<Info> getModulesInfo(const bool) const override;
-    void *createInstance(const QString &) override;
+    QList<Info> getModulesInfo(const bool) const;
+    void *createInstance(const QString &);
 
-    SettingsWidget *getSettingsWidget() override;
+    SettingsWidget *getSettingsWidget();
 };
 
 /**/
@@ -41,7 +41,7 @@ class QCheckBox;
 class QGroupBox;
 class QLineEdit;
 
-class ModuleSettingsWidget final : public Module::SettingsWidget
+class ModuleSettingsWidget : public Module::SettingsWidget
 {
     Q_DECLARE_TR_FUNCTIONS(ModuleSettingsWidget)
 
@@ -49,7 +49,7 @@ public:
     ModuleSettingsWidget(Module &module);
 
 private:
-    void saveSettings() override;
+    void saveSettings();
 
     QGroupBox *m_notify;
     QDoubleSpinBox *m_timeoutSB;
diff --git a/src/modules/Notify/NotifyExtension.cpp b/src/modules/Notify/NotifyExtension.cpp
index 1015e2b3..a7c66992 100644
--- a/src/modules/Notify/NotifyExtension.cpp
+++ b/src/modules/Notify/NotifyExtension.cpp
@@ -35,7 +35,7 @@ NotifyService::NotifyService(Settings &settings) :
 {
     if (settings.getBool("ShowTitle"))
     {
-        connect(&QMPlay2Core, &QMPlay2CoreClass::updatePlaying, this, &NotifyService::updatePlaying);
+        connect(&QMPlay2Core, SIGNAL(updatePlaying(bool, const QString &, const QString &, const QString &, int, bool, const QString &, const QString &)), this, SLOT(updatePlaying(bool, const QString &, const QString &, const QString &, int, bool, const QString &, const QString &)));
 
         //"coverDataFromMediaFile()" and "coverFile()" are emited before "updatePlaying()"
         connect(&QMPlay2Core, SIGNAL(coverDataFromMediaFile(QByteArray)), this, SLOT(coverDataFromMediaFile(QByteArray)));
diff --git a/src/modules/Notify/NotifyExtension.hpp b/src/modules/Notify/NotifyExtension.hpp
index 1a86ad97..6244b304 100644
--- a/src/modules/Notify/NotifyExtension.hpp
+++ b/src/modules/Notify/NotifyExtension.hpp
@@ -22,7 +22,7 @@
 
 #include <memory>
 
-class NotifyService final : public QObject
+class NotifyService : public QObject
 {
     Q_OBJECT
 
@@ -30,10 +30,9 @@ public:
     NotifyService(Settings &settings);
     ~NotifyService();
 
-private:
+private slots:
     void updatePlaying(bool play, const QString &title, const QString &artist, const QString &album, int, bool, const QString &fileName, const QString &url);
 
-private slots:
     void coverDataFromMediaFile(const QByteArray &cover);
     void coverFile(const QString &fileName);
 
@@ -48,14 +47,14 @@ private:
 
 /**/
 
-class NotifyExtension final : public QMPlay2Extensions
+class NotifyExtension : public QMPlay2Extensions
 {
 public:
     NotifyExtension(Module &module);
     ~NotifyExtension();
 
 private:
-    bool set() override;
+    bool set();
 
     std::unique_ptr<NotifyService> m_notifyService;
 };
diff --git a/src/modules/OpenMPT/OpenMPT.cpp b/src/modules/OpenMPT/OpenMPT.cpp
index c4665e91..0a38bedb 100644
--- a/src/modules/OpenMPT/OpenMPT.cpp
+++ b/src/modules/OpenMPT/OpenMPT.cpp
@@ -84,7 +84,7 @@ ModuleSettingsWidget::ModuleSettingsWidget(Module &module)
     m_stereoSeparation = new QSpinBox;
     m_stereoSeparation->setRange(0, 200);
     m_stereoSeparation->setValue(sets().getInt("StereoSeparation"));
-    m_stereoSeparation->setSuffix(QStringLiteral("%"));
+    m_stereoSeparation->setSuffix(QString("%"));
 
     m_interpolationFilter = new QComboBox;
     m_interpolationFilter->addItem(tr("Default"), 0);
@@ -124,7 +124,11 @@ ModuleSettingsWidget::ModuleSettingsWidget(Module &module)
     auto updateVisSettingsVisibility = [this](int index) {
         m_visSettingsWidget->setVisible(index == 1);
     };
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     connect(m_visMode, qOverload<int>(&QComboBox::currentIndexChanged), this, updateVisSettingsVisibility);
+#else
+    connect(m_visMode, SIGNAL(currentIndexChanged(int)), this, SLOT(visModeChanged(int)));
+#endif
     updateVisSettingsVisibility(m_visMode->currentIndex());
 
     auto *layout = new QFormLayout(this);
@@ -141,8 +145,17 @@ void ModuleSettingsWidget::saveSettings()
     sets().set("Enabled", m_enabled->isChecked());
     sets().set("SubsongMode", m_subsongMode->currentIndex());
     sets().set("StereoSeparation", m_stereoSeparation->value());
-    sets().set("InterpolationFilter", m_interpolationFilter->currentData().toInt());
+    sets().set("InterpolationFilter", m_interpolationFilter->itemData(m_interpolationFilter->currentIndex()).toInt());
     sets().set("VisMode", m_visMode->currentIndex());
     sets().set("VisChannels", m_visChannels->value());
     sets().set("VisRows", m_visRows->value());
 }
+
+// Qt4: Helper slot implementations
+
+#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
+void ModuleSettingsWidget::visModeChanged(int index)
+{
+    m_visSettingsWidget->setVisible(index == 1);
+}
+#endif
diff --git a/src/modules/OpenMPT/OpenMPT.hpp b/src/modules/OpenMPT/OpenMPT.hpp
index 0445bcff..d98ad1d9 100644
--- a/src/modules/OpenMPT/OpenMPT.hpp
+++ b/src/modules/OpenMPT/OpenMPT.hpp
@@ -20,15 +20,15 @@
 
 #include <Module.hpp>
 
-class OpenMPT final : public Module
+class OpenMPT : public Module
 {
 public:
     OpenMPT();
 
 private:
-    QList<Info> getModulesInfo(const bool) const override;
-    void *createInstance(const QString &) override;
-    SettingsWidget *getSettingsWidget() override;
+    QList<Info> getModulesInfo(const bool) const;
+    void *createInstance(const QString &);
+    SettingsWidget *getSettingsWidget();
 
 private:
     QIcon m_modIcon{":/MOD.svg"};
@@ -36,21 +36,28 @@ private:
 
 /**/
 
-#include <QCoreApplication>
-
 class QCheckBox;
 class QComboBox;
 class QSpinBox;
 
-class ModuleSettingsWidget final : public Module::SettingsWidget
+class ModuleSettingsWidget : public Module::SettingsWidget
 {
-    Q_DECLARE_TR_FUNCTIONS(ModuleSettingsWidget)
+    // XXX(qt4-port): Changed from Q_DECLARE_TR_FUNCTIONS to Q_OBJECT (which also
+    // provides tr()) because this class now needs a private slot for Qt4's
+    // old-style SIGNAL/SLOT connect (see visModeChanged() below); Q_OBJECT is
+    // required for moc to generate the slot dispatch under Qt4.
+    Q_OBJECT
 
 public:
     ModuleSettingsWidget(Module &);
 
 private:
-    void saveSettings() override;
+    void saveSettings();
+
+#if QT_VERSION < 0x050000
+private slots:
+    void visModeChanged(int index);
+#endif
 
 private:
     QCheckBox *m_enabled{nullptr};
diff --git a/src/modules/OpenMPT/OpenMPTDemux.cpp b/src/modules/OpenMPT/OpenMPTDemux.cpp
index 68f42187..990ab023 100644
--- a/src/modules/OpenMPT/OpenMPTDemux.cpp
+++ b/src/modules/OpenMPT/OpenMPTDemux.cpp
@@ -214,7 +214,7 @@ Playlist::Entries OpenMPDemux::fetchTracks(const QString &url, bool &ok)
         m_module->select_subsong(i);
 
         Playlist::Entry entry;
-        entry.url = QString::fromLatin1(DemuxerName) + QStringLiteral("://") + QChar('{') + url + QStringLiteral("}") + QString::number(i);
+        entry.url = QString::fromLatin1(DemuxerName) + QString("://") + QChar('{') + url + QString("}") + QString::number(i);
         entry.name = formatSubsongTitle(i, subsongNames, m_moduleTitle).first;
         entry.length = m_module->get_duration_seconds();
 
@@ -382,5 +382,5 @@ std::pair<QString, QString> OpenMPDemux::formatSubsongTitle(int subsong, const s
         subsongTitle = QString::fromStdString(subsongNames[subsong]).trimmed();
     if (subsongTitle.isEmpty())
         subsongTitle = rawTitle;
-    return {tr("Track %1").arg(subsong + 1, 2, 10, QChar('0')) + QStringLiteral(" - ") + subsongTitle, subsongTitle};
+    return {tr("Track %1").arg(subsong + 1, 2, 10, QChar('0')) + QString(" - ") + subsongTitle, subsongTitle};
 }
diff --git a/src/modules/OpenMPT/OpenMPTDemux.hpp b/src/modules/OpenMPT/OpenMPTDemux.hpp
index 09f6c920..3bd43a1e 100644
--- a/src/modules/OpenMPT/OpenMPTDemux.hpp
+++ b/src/modules/OpenMPT/OpenMPTDemux.hpp
@@ -31,7 +31,7 @@ namespace openmpt {
     class module;
 }
 
-class OpenMPDemux final : public Demuxer
+class OpenMPDemux : public Demuxer
 {
     Q_DECLARE_TR_FUNCTIONS(OpenMPDemux)
 
@@ -46,25 +46,25 @@ public:
     OpenMPDemux(Module &);
 
 private:
-    ~OpenMPDemux() override;
+    ~OpenMPDemux();
 
-    bool set() override;
+    bool set();
 
-    QString name() const override;
-    QString title() const override;
-    QList<QMPlay2Tag> tags() const override;
-    double length() const override;
-    int bitrate() const override;
+    QString name() const;
+    QString title() const;
+    QList<QMPlay2Tag> tags() const;
+    double length() const;
+    int bitrate() const;
 
-    bool dontUseBuffer() const override;
+    bool dontUseBuffer() const;
 
-    bool seek(double, bool) override;
-    bool read(Packet &, int &) override;
-    void abort() override;
+    bool seek(double, bool);
+    bool read(Packet &, int &);
+    void abort();
 
-    Playlist::Entries fetchTracks(const QString &url, bool &ok) override;
+    Playlist::Entries fetchTracks(const QString &url, bool &ok);
 
-    bool open(const QString &urlArg) override;
+    bool open(const QString &urlArg);
 
     void openModule(const QString &url);
 
diff --git a/src/modules/OpenMPT/OpenMPTVisBase.cpp b/src/modules/OpenMPT/OpenMPTVisBase.cpp
index 997e1526..7874bd96 100644
--- a/src/modules/OpenMPT/OpenMPTVisBase.cpp
+++ b/src/modules/OpenMPT/OpenMPTVisBase.cpp
@@ -29,7 +29,7 @@ int OpenMPTVisBase::height() const
 
 QFont OpenMPTVisBase::visFont(int pt)
 {
-    QFont font(QStringLiteral("Monospace"), pt);
+    QFont font(QString("Monospace"), pt);
     font.setStyleHint(QFont::Monospace);
     font.setFixedPitch(true);
     return font;
diff --git a/src/modules/OpenMPT/OpenMPTVisPatterns.cpp b/src/modules/OpenMPT/OpenMPTVisPatterns.cpp
index d1fdff5e..64357f18 100644
--- a/src/modules/OpenMPT/OpenMPTVisPatterns.cpp
+++ b/src/modules/OpenMPT/OpenMPTVisPatterns.cpp
@@ -34,7 +34,12 @@ static int computeMaxCellWidth(openmpt::module *module, int pattern, const QFont
     for (int ch = 0; ch < numChannels; ++ch)
     {
         const std::string text = module->format_pattern_row_channel(pattern, 0, ch);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
         const int w = fm.horizontalAdvance(QString::fromStdString(text)) + fm.horizontalAdvance("X");
+#else
+        // Qt4: QFontMetrics::horizontalAdvance() was added in Qt 5.11; use width() instead
+        const int w = fm.width(QString::fromStdString(text)) + fm.width("X");
+#endif
         if (w > maxW)
             maxW = w;
     }
@@ -80,7 +85,12 @@ OpenMPTVisPatterns::OpenMPTVisPatterns(openmpt::module *module, int channels, in
     m_rowHeight = fm.height() + 2;
     m_height = m_rows * m_rowHeight;
     m_maxCellWidth = maxCellWidth;
+#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
     m_charWidth = fm.horizontalAdvance("X");
+#else
+    // Qt4: QFontMetrics::horizontalAdvance() was added in Qt 5.11; use width() instead
+    m_charWidth = fm.width("X");
+#endif
 
     for (int i = 33; i <= 126; ++i)
     {
diff --git a/src/modules/OpenMPT/OpenMPTVisPatterns.hpp b/src/modules/OpenMPT/OpenMPTVisPatterns.hpp
index 99150c77..8a00690a 100644
--- a/src/modules/OpenMPT/OpenMPTVisPatterns.hpp
+++ b/src/modules/OpenMPT/OpenMPTVisPatterns.hpp
@@ -27,8 +27,8 @@ class OpenMPTVisPatterns : public OpenMPTVisBase
 public:
     OpenMPTVisPatterns(openmpt::module *module, int channels, int rows);
 
-    Frame render(openmpt::module *module) override;
-    void reset() override;
+    Frame render(openmpt::module *module);
+    void reset();
 
 private:
     int m_channels{0};
diff --git a/src/modules/OpenMPT/OpenMPTVisSamples.cpp b/src/modules/OpenMPT/OpenMPTVisSamples.cpp
index 0f84632f..49c867e7 100644
--- a/src/modules/OpenMPT/OpenMPTVisSamples.cpp
+++ b/src/modules/OpenMPT/OpenMPTVisSamples.cpp
@@ -20,6 +20,8 @@
 
 #include <QPainter>
 
+#include <cmath>
+
 #include <libopenmpt/libopenmpt.hpp>
 
 extern "C" {
@@ -43,7 +45,12 @@ static QFont computeFitFont(int nameWidth, int &outCharWidth, int &outAscent)
         fm = QFontMetrics(font);
     }
 
-    outCharWidth = fm.horizontalAdvance(QStringLiteral("X"));
+#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
+    outCharWidth = fm.horizontalAdvance(QString("X"));
+#else
+    // Qt4: QFontMetrics::horizontalAdvance() was added in Qt 5.11; use width() instead
+    outCharWidth = fm.width(QString("X"));
+#endif
     outAscent = fm.ascent();
     return font;
 }
diff --git a/src/modules/OpenMPT/OpenMPTVisSamples.hpp b/src/modules/OpenMPT/OpenMPTVisSamples.hpp
index 2a2f3280..f4f43974 100644
--- a/src/modules/OpenMPT/OpenMPTVisSamples.hpp
+++ b/src/modules/OpenMPT/OpenMPTVisSamples.hpp
@@ -29,8 +29,8 @@ class OpenMPTVisSamples : public OpenMPTVisBase
 public:
     OpenMPTVisSamples(openmpt::module *module);
 
-    Frame render(openmpt::module *module) override;
-    void reset() override;
+    Frame render(openmpt::module *module);
+    void reset();
 
 private:
     int m_numSamples{0};
diff --git a/src/modules/OpenSLES/OpenSLESWriter.hpp b/src/modules/OpenSLES/OpenSLESWriter.hpp
index 13f6b775..673cadab 100644
--- a/src/modules/OpenSLES/OpenSLESWriter.hpp
+++ b/src/modules/OpenSLES/OpenSLESWriter.hpp
@@ -24,24 +24,24 @@
 
 #include <SLES/OpenSLES.h>
 
-class OpenSLESWriter final : public Writer
+class OpenSLESWriter : public Writer
 {
 public:
     OpenSLESWriter(Module &);
 private:
         ~OpenSLESWriter();
 
-    bool set() override;
+    bool set();
 
-    bool readyWrite() const override;
+    bool readyWrite() const;
 
-    bool processParams(bool *paramsCorrected) override;
-    qint64 write(const QByteArray &) override;
-    void pause() override;
+    bool processParams(bool *paramsCorrected);
+    qint64 write(const QByteArray &);
+    void pause();
 
-    QString name() const override;
+    QString name() const;
 
-    bool open() override;
+    bool open();
 
     /**/
 
diff --git a/src/modules/PipeWire/PipeWire.hpp b/src/modules/PipeWire/PipeWire.hpp
index ee5b717e..9dafbe98 100644
--- a/src/modules/PipeWire/PipeWire.hpp
+++ b/src/modules/PipeWire/PipeWire.hpp
@@ -20,17 +20,17 @@
 
 #include <Module.hpp>
 
-class PipeWire final : public Module
+class PipeWire : public Module
 {
 public:
     PipeWire();
     ~PipeWire();
 
 private:
-    QList<Info> getModulesInfo(const bool) const override;
-    void *createInstance(const QString &) override;
+    QList<Info> getModulesInfo(const bool) const;
+    void *createInstance(const QString &);
 
-    SettingsWidget *getSettingsWidget() override;
+    SettingsWidget *getSettingsWidget();
 };
 
 /**/
@@ -39,7 +39,7 @@ private:
 
 class QCheckBox;
 
-class ModuleSettingsWidget final : public Module::SettingsWidget
+class ModuleSettingsWidget : public Module::SettingsWidget
 {
     Q_DECLARE_TR_FUNCTIONS(ModuleSettingsWidget)
 
@@ -48,7 +48,7 @@ public:
     ~ModuleSettingsWidget();
 
 private:
-    void saveSettings() override;
+    void saveSettings();
 
     QCheckBox *m_enabledCheckBox;
 };
diff --git a/src/modules/PipeWire/PipeWireWriter.hpp b/src/modules/PipeWire/PipeWireWriter.hpp
index e061c20f..2bb46fcc 100644
--- a/src/modules/PipeWire/PipeWireWriter.hpp
+++ b/src/modules/PipeWire/PipeWireWriter.hpp
@@ -28,7 +28,7 @@
 #include <spa/param/audio/format-utils.h>
 #include <pipewire/pipewire.h>
 
-class PipeWireWriter final : public Writer
+class PipeWireWriter : public Writer
 {
     Q_DECLARE_TR_FUNCTIONS(PipeWireWriter)
 
@@ -37,18 +37,18 @@ public:
     ~PipeWireWriter();
 
 private:
-    bool set() override;
+    bool set();
 
-    bool readyWrite() const override;
+    bool readyWrite() const;
 
-    bool processParams(bool *paramsCorrected) override;
-    qint64 write(const QByteArray &) override;
+    bool processParams(bool *paramsCorrected);
+    qint64 write(const QByteArray &);
 
-    QString name() const override;
+    QString name() const;
 
-    bool open() override;
+    bool open();
 
-    void pause() override;
+    void pause();
 
 private:
     void onCoreEventDone(uint32_t id, int seq);
diff --git a/src/modules/Playlists/M3U.cpp b/src/modules/Playlists/M3U.cpp
index 1ac38d83..fa969990 100644
--- a/src/modules/Playlists/M3U.cpp
+++ b/src/modules/Playlists/M3U.cpp
@@ -100,19 +100,19 @@ bool M3U::write(const Entries &list)
 #endif
             }
             writer->write(QString("#EXTINF:" + length + "," + QString(entry.name).replace('\n', '\001') + "\r\n").toUtf8());
-            for (auto it = entry.params.cbegin(), itEnd = entry.params.cend(); it != itEnd; ++it)
+            for (auto it = entry.params.constBegin(), itEnd = entry.params.constEnd(); it != itEnd; ++it)
             {
                 const auto &key = it.key();
                 if (key == Playlist::Entry::UserAgentParam)
                 {
-                    writer->write(QStringLiteral("#EXTVLCOPT:http-user-agent=%1\r\n").arg(QString(it.value().trimmed())).toUtf8());
+                    writer->write(QString("#EXTVLCOPT:http-user-agent=%1\r\n").arg(QString(it.value().trimmed())).toUtf8());
                 }
                 else if (key == Playlist::Entry::ReferrerParam)
                 {
-                    writer->write(QStringLiteral("#EXTVLCOPT:http-referrer=%1\r\n").arg(QString(it.value().trimmed())).toUtf8());
+                    writer->write(QString("#EXTVLCOPT:http-referrer=%1\r\n").arg(QString(it.value().trimmed())).toUtf8());
                 }
             }
-            writer->write((url + "\r\n").toUtf8());
+            writer->write(QString(url + "\r\n").toUtf8());
         }
     }
     return true;
diff --git a/src/modules/Playlists/M3U.hpp b/src/modules/Playlists/M3U.hpp
index 15ffb7c0..6c03f5b3 100644
--- a/src/modules/Playlists/M3U.hpp
+++ b/src/modules/Playlists/M3U.hpp
@@ -20,10 +20,10 @@
 
 #include <Playlist.hpp>
 
-class M3U final : public Playlist
+class M3U : public Playlist
 {
-    Entries read() override;
-    bool write(const Entries &) override;
+    Entries read();
+    bool write(const Entries &);
 };
 
 #define M3UName "M3U"
diff --git a/src/modules/Playlists/PLS.cpp b/src/modules/Playlists/PLS.cpp
index 3bfd3692..4f7688bc 100644
--- a/src/modules/Playlists/PLS.cpp
+++ b/src/modules/Playlists/PLS.cpp
@@ -157,8 +157,8 @@ bool PLS::write(const Entries &list)
             writer->write(QString("QMPlay_GID" + idx + "=" + QString::number(entry.GID) + "\r\n").toUtf8());
         if (entry.parent)
             writer->write(QString("QMPlay_parent" + idx + "=" + QString::number(entry.parent) + "\r\n").toUtf8());
-        for (auto it = entry.params.cbegin(), itEnd = entry.params.cend(); it != itEnd; ++it)
-            writer->write(QStringLiteral("QMPlay_param_%1%2=%3\r\n").arg(QString(it.key())).arg(idx).arg(QString(it.value().trimmed())).toUtf8());
+        for (auto it = entry.params.constBegin(), itEnd = entry.params.constEnd(); it != itEnd; ++it)
+            writer->write(QString("QMPlay_param_%1%2=%3\r\n").arg(QString(it.key())).arg(idx).arg(QString(it.value().trimmed())).toUtf8());
     }
     return true;
 }
diff --git a/src/modules/Playlists/PLS.hpp b/src/modules/Playlists/PLS.hpp
index f1194ea0..3377e446 100644
--- a/src/modules/Playlists/PLS.hpp
+++ b/src/modules/Playlists/PLS.hpp
@@ -20,10 +20,10 @@
 
 #include <Playlist.hpp>
 
-class PLS final : public Playlist
+class PLS : public Playlist
 {
-    Entries read() override;
-    bool write(const Entries &) override;
+    Entries read();
+    bool write(const Entries &);
 };
 
 #define PLSName "PLS"
diff --git a/src/modules/Playlists/Playlists.hpp b/src/modules/Playlists/Playlists.hpp
index e38b8a0d..3b3a3e15 100644
--- a/src/modules/Playlists/Playlists.hpp
+++ b/src/modules/Playlists/Playlists.hpp
@@ -20,15 +20,15 @@
 
 #include <Module.hpp>
 
-class Playlists final : public Module
+class Playlists : public Module
 {
 public:
     Playlists();
 private:
-    QList<Info> getModulesInfo(const bool) const override;
-    void *createInstance(const QString &) override;
+    QList<Info> getModulesInfo(const bool) const;
+    void *createInstance(const QString &);
 
-    SettingsWidget *getSettingsWidget() override;
+    SettingsWidget *getSettingsWidget();
 };
 
 /**/
@@ -37,13 +37,13 @@ private:
 
 class QCheckBox;
 
-class ModuleSettingsWidget final : public Module::SettingsWidget
+class ModuleSettingsWidget : public Module::SettingsWidget
 {
     Q_DECLARE_TR_FUNCTIONS(ModuleSettingsWidget)
 public:
     ModuleSettingsWidget(Module &);
 private:
-    void saveSettings() override;
+    void saveSettings();
 
     QCheckBox *m3uEnabledB, *xspfEnabledB;
 };
diff --git a/src/modules/Playlists/XSPF.cpp b/src/modules/Playlists/XSPF.cpp
index e829d80b..bed8766c 100644
--- a/src/modules/Playlists/XSPF.cpp
+++ b/src/modules/Playlists/XSPF.cpp
@@ -52,42 +52,42 @@ Playlist::Entries XSPF::read()
         switch (token)
         {
             case QXmlStreamReader::StartElement:
-                if (name == QStringLiteral("track"))
+                if (name == QString("track"))
                     inTrack = true;
                 else if (inTrack)
                 {
-                    if (name == QStringLiteral("extension"))
+                    if (name == QString("extension"))
                     {
-                        if (xmlReader.attributes().value("application") == QStringLiteral("QMPlay2"))
+                        if (xmlReader.attributes().value("application") == QString("QMPlay2"))
                             inExtension = true;
                     }
                     else
                     {
                         const QString value = xmlReader.readElementText();
-                        if (name == QStringLiteral("location"))
+                        if (name == QString("location"))
                             curr.url = Functions::Url(QUrl::fromPercentEncoding(value.toUtf8()), playlistPath);
-                        else if (name == QStringLiteral("title"))
+                        else if (name == QString("title"))
                             curr.name = value;
-                        else if (name == QStringLiteral("duration"))
+                        else if (name == QString("duration"))
                             curr.length = value.toInt() / 1000.0;
                         if (inExtension)
                         {
-                            if (name == QStringLiteral("f"))
+                            if (name == QString("f"))
                                 curr.flags = value.toInt();
-                            else if (name == QStringLiteral("q"))
+                            else if (name == QString("q"))
                                 curr.queue = value.toInt();
-                            else if (name == QStringLiteral("g"))
+                            else if (name == QString("g"))
                                 curr.GID = value.toInt();
-                            else if (name == QStringLiteral("p"))
+                            else if (name == QString("p"))
                                 curr.parent = value.toInt();
                         }
                     }
                 }
                 break;
             case QXmlStreamReader::EndElement:
-                if (name == QStringLiteral("extension"))
+                if (name == QString("extension"))
                     inExtension = false;
-                else if (name == QStringLiteral("track"))
+                else if (name == QString("track"))
                 {
                     if (inTrack)
                     {
diff --git a/src/modules/Playlists/XSPF.hpp b/src/modules/Playlists/XSPF.hpp
index 57dd6c51..5b58cf94 100644
--- a/src/modules/Playlists/XSPF.hpp
+++ b/src/modules/Playlists/XSPF.hpp
@@ -20,10 +20,10 @@
 
 #include <Playlist.hpp>
 
-class XSPF final : public Playlist
+class XSPF : public Playlist
 {
-    Entries read() override;
-    bool write(const Entries &) override;
+    Entries read();
+    bool write(const Entries &);
 };
 
 #define XSPFName "XSPF"
diff --git a/src/modules/PortAudio/3rdparty/CoreAudio/AudioDevice.h b/src/modules/PortAudio/3rdparty/CoreAudio/AudioDevice.h
index 8ddf0b10..6f90cf14 100644
--- a/src/modules/PortAudio/3rdparty/CoreAudio/AudioDevice.h
+++ b/src/modules/PortAudio/3rdparty/CoreAudio/AudioDevice.h
@@ -59,7 +59,12 @@
 #        define __MAC_10_11 101100
 #    endif
 #    ifndef QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE
-#        define QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE
+#        if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+#            define QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE
+#        else
+#            // Qt4: Always use deprecated API on older systems
+#            define QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(x) 0
+#        endif
 #    endif
 #    if !QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_11)
 #        define DEPRECATED_LISTENER_API
diff --git a/src/modules/PortAudio/PortAudio.cpp b/src/modules/PortAudio/PortAudio.cpp
index 7bf87004..def696b8 100644
--- a/src/modules/PortAudio/PortAudio.cpp
+++ b/src/modules/PortAudio/PortAudio.cpp
@@ -25,7 +25,7 @@ PortAudio::PortAudio() :
     m_icon = QIcon(":/PortAudio.svgz");
 
     init("WriterEnabled", true);
-#if defined(Q_OS_MACOS)
+#if defined(Q_OS_MAC)
     init("Delay", 0.03);
     init("BitPerfect", false);
 #elif defined(Q_OS_WIN)
@@ -92,7 +92,7 @@ ModuleSettingsWidget::ModuleSettingsWidget(Module &module) :
     m_exclusive->setChecked(sets().getBool("Exclusive"));
 #endif
 
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     bitPerfect = new QCheckBox(tr("Bit-perfect audio"));
     bitPerfect->setChecked(sets().getBool("BitPerfect"));
     bitPerfect->setToolTip(tr("This sets the selected output device to the sample rate of the content being played"));
@@ -105,7 +105,7 @@ ModuleSettingsWidget::ModuleSettingsWidget(Module &module) :
 #ifdef Q_OS_WIN
     layout->addRow(m_exclusive);
 #endif
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     layout->addRow(bitPerfect);
 #endif
 }
@@ -118,7 +118,7 @@ void ModuleSettingsWidget::saveSettings()
 #ifdef Q_OS_WIN
     sets().set("Exclusive", m_exclusive->isChecked());
 #endif
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     sets().set("BitPerfect", bitPerfect->isChecked());
 #endif
 }
diff --git a/src/modules/PortAudio/PortAudio.hpp b/src/modules/PortAudio/PortAudio.hpp
index fd8c821b..9c16e890 100644
--- a/src/modules/PortAudio/PortAudio.hpp
+++ b/src/modules/PortAudio/PortAudio.hpp
@@ -20,17 +20,17 @@
 
 #include <Module.hpp>
 
-class PortAudio final : public Module
+class PortAudio : public Module
 {
 public:
     PortAudio();
     ~PortAudio();
 
 private:
-    QList<Info> getModulesInfo(const bool) const override;
-    void *createInstance(const QString &) override;
+    QList<Info> getModulesInfo(const bool) const;
+    void *createInstance(const QString &);
 
-    SettingsWidget *getSettingsWidget() override;
+    SettingsWidget *getSettingsWidget();
 };
 
 /**/
@@ -41,7 +41,7 @@ class QDoubleSpinBox;
 class QCheckBox;
 class QComboBox;
 
-class ModuleSettingsWidget final : public Module::SettingsWidget
+class ModuleSettingsWidget : public Module::SettingsWidget
 {
     Q_DECLARE_TR_FUNCTIONS(ModuleSettingsWidget)
 
@@ -49,7 +49,7 @@ public:
     ModuleSettingsWidget(Module &);
 
 private:
-    void saveSettings() override;
+    void saveSettings();
 
     QCheckBox *enabledB;
     QComboBox *devicesB;
@@ -57,7 +57,7 @@ private:
 #ifdef Q_OS_WIN
     QCheckBox *m_exclusive;
 #endif
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     QCheckBox *bitPerfect;
 #endif
 };
diff --git a/src/modules/PortAudio/PortAudioWriter.cpp b/src/modules/PortAudio/PortAudioWriter.cpp
index e2438c2e..8da43333 100644
--- a/src/modules/PortAudio/PortAudioWriter.cpp
+++ b/src/modules/PortAudio/PortAudioWriter.cpp
@@ -27,7 +27,7 @@
 #   include <audioclient.h>
 #endif
 
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     #include "3rdparty/CoreAudio/AudioDeviceList.h"
     #include "3rdparty/CoreAudio/AudioDevice.h"
 #endif
@@ -124,7 +124,7 @@ PortAudioWriter::~PortAudioWriter()
 #ifdef Q_OS_WIN
     delete m_wasapiNotifications;
 #endif
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     delete m_coreAudioDevice;
 #endif
     if (m_initialized)
@@ -154,7 +154,7 @@ bool PortAudioWriter::set()
         restartPlaying = true;
     }
 #endif
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     const bool bitPerfect = sets().getBool("BitPerfect");
 #endif
     if (m_outputDevice != newOutputDevice)
@@ -167,7 +167,7 @@ bool PortAudioWriter::set()
         m_outputParameters.suggestedLatency = delay;
         restartPlaying = true;
     }
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     if (m_bitPerfect != bitPerfect)
     {
         m_bitPerfect = bitPerfect;
@@ -297,9 +297,9 @@ QString PortAudioWriter::name() const
         if (m_exclusive)
             name += QString(", %1Hz").arg(m_sampleRate);
 #endif
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
         if (m_coreAudioDevice)
-            name += QStringLiteral(", %1Hz").arg(m_coreAudioDevice->CurrentNominalSampleRate());
+            name += QString(", %1Hz").arg(m_coreAudioDevice->CurrentNominalSampleRate());
 #endif
     }
     return name;
@@ -389,7 +389,7 @@ bool PortAudioWriter::openStream()
         }
 #endif
 
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
         if (m_bitPerfect)
         {
             const QString devName(Pa_GetDeviceInfo(m_outputParameters.device)->name);
@@ -496,7 +496,7 @@ void PortAudioWriter::close()
         Pa_CloseStream(m_stream);
         m_stream = nullptr;
     }
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     if (m_coreAudioDevice)
         m_coreAudioDevice->ResetNominalSampleRate();
 #endif
diff --git a/src/modules/PortAudio/PortAudioWriter.hpp b/src/modules/PortAudio/PortAudioWriter.hpp
index 1fc3f311..564c0a10 100644
--- a/src/modules/PortAudio/PortAudioWriter.hpp
+++ b/src/modules/PortAudio/PortAudioWriter.hpp
@@ -35,23 +35,23 @@
 #ifdef Q_OS_WIN
 class PortAudioWriter;
 
-class WASAPINotifications final : public IMMNotificationClient
+class WASAPINotifications : public IMMNotificationClient
 {
 public:
     WASAPINotifications(PortAudioWriter *writer);
     ~WASAPINotifications();;
 
 private:
-    HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) override;
-    HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId) override;
-    HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId) override;
-    HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) override;
-    HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) override;
+    HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState);
+    HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId);
+    HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId);
+    HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId);
+    HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key);
 
 private:
-    HRESULT STDMETHODCALLTYPE QueryInterface(const IID &iid, void **ppUnk) override;
-    ULONG STDMETHODCALLTYPE AddRef() override;
-    ULONG STDMETHODCALLTYPE Release() override;
+    HRESULT STDMETHODCALLTYPE QueryInterface(const IID &iid, void **ppUnk);
+    ULONG STDMETHODCALLTYPE AddRef();
+    ULONG STDMETHODCALLTYPE Release();
 
 private:
     PortAudioWriter *const m_writer;
@@ -59,11 +59,11 @@ private:
 };
 #endif
 
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
 class AudioDevice;
 #endif
 
-class PortAudioWriter final : public Writer
+class PortAudioWriter : public Writer
 {
     Q_DECLARE_TR_FUNCTIONS(PortAudioWriter)
 
@@ -72,17 +72,17 @@ public:
 private:
     ~PortAudioWriter();
 
-    bool set() override;
+    bool set();
 
-    bool readyWrite() const override;
+    bool readyWrite() const;
 
-    bool processParams(bool *paramsCorrected) override;
-    qint64 write(const QByteArray &) override;
-    void pause() override;
+    bool processParams(bool *paramsCorrected);
+    qint64 write(const QByteArray &);
+    void pause();
 
-    QString name() const override;
+    QString name() const;
 
-    bool open() override;
+    bool open();
 
 private:
     bool deviceNeedsChangeParams(int *newChn = nullptr, int *newRate = nullptr);
@@ -124,7 +124,7 @@ private:
     QString m_paDefaultDeviceId;
     QString m_defaultDeviceId;
 #endif
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     bool m_bitPerfect = false;
     AudioDevice *m_coreAudioDevice = nullptr;
 #endif
diff --git a/src/modules/PulseAudio/PulseAudio.hpp b/src/modules/PulseAudio/PulseAudio.hpp
index f875e2b8..3847ca4f 100644
--- a/src/modules/PulseAudio/PulseAudio.hpp
+++ b/src/modules/PulseAudio/PulseAudio.hpp
@@ -20,15 +20,15 @@
 
 #include <Module.hpp>
 
-class PulseAudio final : public Module
+class PulseAudio : public Module
 {
 public:
     PulseAudio();
 private:
-    QList<Info> getModulesInfo(const bool) const override;
-    void *createInstance(const QString &) override;
+    QList<Info> getModulesInfo(const bool) const;
+    void *createInstance(const QString &);
 
-    SettingsWidget *getSettingsWidget() override;
+    SettingsWidget *getSettingsWidget();
 };
 
 /**/
@@ -38,13 +38,13 @@ private:
 class QDoubleSpinBox;
 class QCheckBox;
 
-class ModuleSettingsWidget final : public Module::SettingsWidget
+class ModuleSettingsWidget : public Module::SettingsWidget
 {
     Q_DECLARE_TR_FUNCTIONS(ModuleSettingsWidget)
 public:
     ModuleSettingsWidget(Module &);
 private:
-    void saveSettings() override;
+    void saveSettings();
 
     QCheckBox *enabledB;
     QDoubleSpinBox *delayB;
diff --git a/src/modules/PulseAudio/PulseAudioWriter.hpp b/src/modules/PulseAudio/PulseAudioWriter.hpp
index a82e60ad..3a7fda45 100644
--- a/src/modules/PulseAudio/PulseAudioWriter.hpp
+++ b/src/modules/PulseAudio/PulseAudioWriter.hpp
@@ -23,23 +23,23 @@
 
 #include <QCoreApplication>
 
-class PulseAudioWriter final : public Writer
+class PulseAudioWriter : public Writer
 {
     Q_DECLARE_TR_FUNCTIONS(PulseAudioWriter)
 public:
     PulseAudioWriter(Module &);
     ~PulseAudioWriter();
 private:
-    bool set() override;
+    bool set();
 
-    bool readyWrite() const override;
+    bool readyWrite() const;
 
-    bool processParams(bool *paramsCorrected) override;
-    qint64 write(const QByteArray &) override;
+    bool processParams(bool *paramsCorrected);
+    qint64 write(const QByteArray &);
 
-    QString name() const override;
+    QString name() const;
 
-    bool open() override;
+    bool open();
 
     /**/
 
diff --git a/src/modules/QPainter/QPainter.hpp b/src/modules/QPainter/QPainter.hpp
index e4f93511..e942feb6 100644
--- a/src/modules/QPainter/QPainter.hpp
+++ b/src/modules/QPainter/QPainter.hpp
@@ -20,15 +20,15 @@
 
 #include <Module.hpp>
 
-class QPainterSW final : public Module
+class QPainterSW : public Module
 {
 public:
     QPainterSW();
 private:
-    QList<Info> getModulesInfo(const bool) const override;
-    void *createInstance(const QString &) override;
+    QList<Info> getModulesInfo(const bool) const;
+    void *createInstance(const QString &);
 
-    SettingsWidget *getSettingsWidget() override;
+    SettingsWidget *getSettingsWidget();
 };
 
 /**/
@@ -37,13 +37,13 @@ private:
 
 class QCheckBox;
 
-class ModuleSettingsWidget final : public Module::SettingsWidget
+class ModuleSettingsWidget : public Module::SettingsWidget
 {
     Q_DECLARE_TR_FUNCTIONS(ModuleSettingsWidget)
 public:
     ModuleSettingsWidget(Module &);
 private:
-    void saveSettings() override;
+    void saveSettings();
 
     QCheckBox *enabledB;
 };
diff --git a/src/modules/QPainter/QPainterWriter.cpp b/src/modules/QPainter/QPainterWriter.cpp
index b3e2c8e2..ba0ccd7e 100644
--- a/src/modules/QPainter/QPainterWriter.cpp
+++ b/src/modules/QPainter/QPainterWriter.cpp
@@ -39,7 +39,12 @@ Drawable::Drawable(QPainterWriter &writer) :
     setPalette(Qt::black);
 }
 Drawable::~Drawable()
-{}
+{
+#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
+    if (imgData)
+        av_free(imgData);
+#endif
+}
 
 void Drawable::draw(const Frame &newVideoFrame, bool canRepaint, bool entireScreen)
 {
@@ -54,10 +59,18 @@ void Drawable::draw(const Frame &newVideoFrame, bool canRepaint, bool entireScre
     if (imgScaler.create(videoFrame, m_scaleByQt ? -1 : imgW, m_scaleByQt ? -1 : imgH))
     {
         auto createImg = [this](int w, int h) {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
             auto imgData = reinterpret_cast<uint8_t *>(av_malloc(w * h * 4 + av_cpu_max_align()));
             img = QImage(imgData, w, h, QImage::Format_RGB32, [](void *ptr) {
                 av_free(ptr);
             }, imgData);
+#else
+            // Qt4: Manual memory management
+            if (imgData)
+                av_free(imgData);
+            imgData = reinterpret_cast<uint8_t *>(av_malloc(w * h * 4 + av_cpu_max_align()));
+            img = QImage(imgData, w, h, QImage::Format_RGB32);
+#endif
         };
         if (m_scaleByQt && (img.width() != videoFrame.width() || img.height() != videoFrame.height()))
         {
@@ -81,7 +94,13 @@ void Drawable::draw(const Frame &newVideoFrame, bool canRepaint, bool entireScre
 
 void Drawable::resizeEvent(QResizeEvent *e)
 {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
     const qreal dpr = devicePixelRatioF();
+#elif QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+    const qreal dpr = devicePixelRatio();
+#else
+    const qreal dpr = 1.0; // Qt4: No device pixel ratio support
+#endif
     Functions::getImageSize(writer.aspect_ratio, writer.zoom, width(), height(), W, H, &X, &Y);
     Functions::getImageSize(writer.aspect_ratio, writer.zoom, width() * dpr, height() * dpr, imgW, imgH);
 
@@ -101,7 +120,13 @@ void Drawable::paintEvent(QPaintEvent *)
 
     if (!osd_list.isEmpty())
     {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
         const qreal dpr = devicePixelRatioF();
+#elif QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+        const qreal dpr = devicePixelRatio();
+#else
+        const qreal dpr = 1.0; // Qt4: No device pixel ratio support
+#endif
         if (!qFuzzyCompare(dpr, 1.0))
             p.scale(1.0 / dpr, 1.0 / dpr);
         p.setClipRect(0, 0, imgW, imgH);
diff --git a/src/modules/QPainter/QPainterWriter.hpp b/src/modules/QPainter/QPainterWriter.hpp
index 0d7dea63..b2944d94 100644
--- a/src/modules/QPainter/QPainterWriter.hpp
+++ b/src/modules/QPainter/QPainterWriter.hpp
@@ -27,7 +27,7 @@
 class QPainterWriter;
 class QMPlay2OSD;
 
-class Drawable final : public QWidget
+class Drawable : public QWidget
 {
 public:
     Drawable(class QPainterWriter &);
@@ -35,25 +35,28 @@ public:
 
     void draw(const Frame &newVideoFrame, bool, bool);
 
-    void resizeEvent(QResizeEvent *) override;
+    void resizeEvent(QResizeEvent *);
 
     Frame videoFrame;
     QMPlay2OSDList osd_list;
     int Brightness, Contrast;
 private:
-    void paintEvent(QPaintEvent *) override;
-    bool event(QEvent *) override;
+    void paintEvent(QPaintEvent *);
+    bool event(QEvent *);
 
     int X, Y, W, H, imgW, imgH;
     QPainterWriter &writer;
     QImage img;
     ImgScaler imgScaler;
     bool m_scaleByQt = false;
+#if QT_VERSION < 0x050000
+    uint8_t *imgData = nullptr; // Qt4: Manual memory management
+#endif
 };
 
 /**/
 
-class QPainterWriter final : public VideoWriter
+class QPainterWriter : public VideoWriter
 {
     friend class Drawable;
 public:
@@ -61,19 +64,19 @@ public:
 private:
     ~QPainterWriter();
 
-    bool set() override;
+    bool set();
 
-    bool readyWrite() const override;
+    bool readyWrite() const;
 
-    bool processParams(bool *paramsCorrected) override;
+    bool processParams(bool *paramsCorrected);
 
-    AVPixelFormats supportedPixelFormats() const override;
+    AVPixelFormats supportedPixelFormats() const;
 
-    void writeVideo(const Frame &videoFrame, QMPlay2OSDList &&osdList) override;
+    void writeVideo(const Frame &videoFrame, QMPlay2OSDList &&osdList);
 
-    QString name() const override;
+    QString name() const;
 
-    bool open() override;
+    bool open();
 
     /**/
 
diff --git a/src/modules/Subtitles/Classic.cpp b/src/modules/Subtitles/Classic.cpp
index 3f219901..edb67f61 100644
--- a/src/modules/Subtitles/Classic.cpp
+++ b/src/modules/Subtitles/Classic.cpp
@@ -21,7 +21,7 @@
 #include <Functions.hpp>
 #include <LibASS.hpp>
 
-#include <QRegularExpression>
+#include <QRegExp>
 #include <QStringList>
 
 #include <algorithm>
@@ -70,7 +70,7 @@ static inline void initOnce(bool &ok, LibASS *ass)
     }
 }
 
-static inline QString convertLine(const QRegularExpression &rx, const QString &line)
+static inline QString convertLine(const QRegExp &rx, const QString &line)
 {
     return line.section(rx, 1, 1, QString::SectionIncludeTrailingSep).replace('|', '\n');
 }
@@ -100,14 +100,16 @@ bool Classic::toASS(const QByteArray &txt, LibASS *ass, double fps)
 
     bool ok = false, use_mDVD_FPS = Use_mDVD_FPS;
 
-    const QRegularExpression TMPRegExp(R"(\d{1,2}:\d{1,2}:\d{1,2}\D\s?)");
-    const QRegularExpression MPL2RegExp(R"(\[\d+\]\[\d*\]\s?)");
-    const QRegularExpression MicroDVDRegExp(R"(\{\d+\}\{\d*\}\s?)");
-    const QRegularExpression MicroDVDStylesRegExp(R"(\{(\w):(.*)\})", QRegularExpression::InvertedGreedinessOption);
+    // Qt4: Use QRegExp instead of QRegularExpression
+    const QRegExp TMPRegExp("\\d{1,2}:\\d{1,2}:\\d{1,2}\\D\\s?");
+    const QRegExp MPL2RegExp("\\[\\d+\\]\\[\\d*\\]\\s?");
+    const QRegExp MicroDVDRegExp("\\{\\d+\\}\\{\\d*\\}\\s?");
+    QRegExp MicroDVDStylesRegExp("\\{(\\w):(.*)\\}");
+    MicroDVDStylesRegExp.setMinimal(true);
 
     QList<SubWithoutEnd> subsWithoutEnd;
 
-    for (const QString &line : QString(txt).remove('\r').split('\n', Qt::SkipEmptyParts))
+    for (const QString &line : QString(txt).remove('\r').split('\n', QString::SkipEmptyParts))
     {
         double start = 0.0, duration = 0.0;
         QString sub;
@@ -166,7 +168,8 @@ bool Classic::toASS(const QByteArray &txt, LibASS *ass, double fps)
                 if (use_mDVD_FPS && (s == 0 || s == 1))
                 {
                     use_mDVD_FPS = false;
-                    const double newFPS = QStringView(sub).left(qMin(6, sub.size())).toDouble();
+                    // Qt4: No QStringView, use QString::left() directly
+                    const double newFPS = sub.left(qMin(6, sub.size())).toDouble();
                     if (newFPS > 0.0 && newFPS < 100.0)
                     {
                         fps = newFPS;
@@ -177,15 +180,14 @@ bool Classic::toASS(const QByteArray &txt, LibASS *ass, double fps)
                 int pos = 0;
                 for (;;)
                 {
-                    const auto match = MicroDVDStylesRegExp.match(sub, pos);
-                    if (!match.hasMatch())
+                    // Qt4: Use QRegExp::indexIn instead of match()
+                    pos = MicroDVDStylesRegExp.indexIn(sub, pos);
+                    if (pos == -1)
                         break;
 
-                    pos = match.capturedStart();
-
-                    const int matchedLength = match.capturedLength();
-                    const QString styleText = match.captured(2);
-                    const QChar s = match.captured(1).at(0);
+                    const int matchedLength = MicroDVDStylesRegExp.matchedLength();
+                    const QString styleText = MicroDVDStylesRegExp.cap(2);
+                    const QChar s = MicroDVDStylesRegExp.cap(1).at(0);
                     const bool singleLine = s.isLower();
                     switch (s.toLower().toLatin1())
                     {
@@ -251,7 +253,7 @@ bool Classic::toASS(const QByteArray &txt, LibASS *ass, double fps)
         }
 
         initOnce(ok, ass);
-        for (const SubWithoutEnd &sub : std::as_const(subsWithoutEnd))
+        for (const SubWithoutEnd &sub : subsWithoutEnd)
             ass->addASSEvent(Functions::convertToASS(sub.sub), sub.start, sub.duration);
     }
 
diff --git a/src/modules/Subtitles/Classic.hpp b/src/modules/Subtitles/Classic.hpp
index 2f5ca8ad..68e23ce8 100644
--- a/src/modules/Subtitles/Classic.hpp
+++ b/src/modules/Subtitles/Classic.hpp
@@ -20,12 +20,12 @@
 
 #include <SubsDec.hpp>
 
-class Classic final : public SubsDec
+class Classic : public SubsDec
 {
 public:
     Classic(bool, double);
 private:
-    bool toASS(const QByteArray &, class LibASS *, double) override;
+    bool toASS(const QByteArray &, class LibASS *, double);
 
     /**/
 
diff --git a/src/modules/Subtitles/SRT.cpp b/src/modules/Subtitles/SRT.cpp
index 96ee9459..fe209c7f 100644
--- a/src/modules/Subtitles/SRT.cpp
+++ b/src/modules/Subtitles/SRT.cpp
@@ -20,7 +20,7 @@
 #include <Functions.hpp>
 #include <LibASS.hpp>
 
-#include <QRegularExpression>
+#include <QRegExp>
 #include <QStringList>
 
 #include <cstdio>
@@ -33,7 +33,7 @@ bool SRT::toASS(const QByteArray &srt, LibASS *ass, double)
     bool ok = false;
     const char *scanfFmt = (srt.left(11 /* Including BOM */).contains("WEBVTT")) ? "%d:%d:%d.%d" : "%d:%d:%d,%d";
 
-    for (const QString &entry : QString(QString("\n\n") + srt).remove('\r').split(QRegularExpression("\n\n+(\\d+\n)?"), Qt::SkipEmptyParts))
+    for (const QString &entry : QString(QString("\n\n") + srt).remove('\r').split(QRegExp("\\n\\n+(\\d+\\n)?"), QString::SkipEmptyParts))
     {
         int idx = entry.indexOf('\n');
         if (idx > -1)
diff --git a/src/modules/Subtitles/SRT.hpp b/src/modules/Subtitles/SRT.hpp
index 946054b0..c8e74374 100644
--- a/src/modules/Subtitles/SRT.hpp
+++ b/src/modules/Subtitles/SRT.hpp
@@ -20,9 +20,9 @@
 
 #include <SubsDec.hpp>
 
-class SRT final : public SubsDec
+class SRT : public SubsDec
 {
-    bool toASS(const QByteArray &, class LibASS *, double) override;
+    bool toASS(const QByteArray &, class LibASS *, double);
 };
 
 #define SRTSubsName "SRT Subtitles"
diff --git a/src/modules/Subtitles/Subtitles.hpp b/src/modules/Subtitles/Subtitles.hpp
index a9ca6e0f..d3a9555a 100644
--- a/src/modules/Subtitles/Subtitles.hpp
+++ b/src/modules/Subtitles/Subtitles.hpp
@@ -20,15 +20,15 @@
 
 #include <Module.hpp>
 
-class Subtitles final : public Module
+class Subtitles : public Module
 {
 public:
     Subtitles();
 private:
-    QList<Info> getModulesInfo(const bool) const override;
-    void *createInstance(const QString &) override;
+    QList<Info> getModulesInfo(const bool) const;
+    void *createInstance(const QString &);
 
-    SettingsWidget *getSettingsWidget() override;
+    SettingsWidget *getSettingsWidget();
 };
 
 /**/
@@ -38,13 +38,13 @@ private:
 class QCheckBox;
 class QDoubleSpinBox;
 
-class ModuleSettingsWidget final : public Module::SettingsWidget
+class ModuleSettingsWidget : public Module::SettingsWidget
 {
     Q_DECLARE_TR_FUNCTIONS(ModuleSettingsWidget)
 public:
     ModuleSettingsWidget(Module &);
 private:
-    void saveSettings() override;
+    void saveSettings();
 
     QCheckBox *srtEB, *classicEB, *mEB;
     QDoubleSpinBox *maxLenB;
diff --git a/src/modules/VideoFilters/BlendDeint.hpp b/src/modules/VideoFilters/BlendDeint.hpp
index e084f691..4ad47e1e 100644
--- a/src/modules/VideoFilters/BlendDeint.hpp
+++ b/src/modules/VideoFilters/BlendDeint.hpp
@@ -20,14 +20,14 @@
 
 #include <VideoFilter.hpp>
 
-class BlendDeint final : public VideoFilter
+class BlendDeint : public VideoFilter
 {
 public:
     BlendDeint();
 
-    bool filter(QQueue<Frame> &framesQueue) override;
+    bool filter(QQueue<Frame> &framesQueue);
 
-    bool processParams(bool *paramsCorrected) override;
+    bool processParams(bool *paramsCorrected);
 };
 
 #define BlendDeintName "Blend"
diff --git a/src/modules/VideoFilters/BobDeint.hpp b/src/modules/VideoFilters/BobDeint.hpp
index 0ee037e9..bc875259 100644
--- a/src/modules/VideoFilters/BobDeint.hpp
+++ b/src/modules/VideoFilters/BobDeint.hpp
@@ -20,14 +20,14 @@
 
 #include <VideoFilter.hpp>
 
-class BobDeint final : public VideoFilter
+class BobDeint : public VideoFilter
 {
 public:
     BobDeint();
 
-    bool filter(QQueue<Frame> &framesQueue) override;
+    bool filter(QQueue<Frame> &framesQueue);
 
-    bool processParams(bool *paramsCorrected) override;
+    bool processParams(bool *paramsCorrected);
 };
 
 #define BobDeintName "Bob"
diff --git a/src/modules/VideoFilters/DiscardDeint.hpp b/src/modules/VideoFilters/DiscardDeint.hpp
index af767d71..a00ebf29 100644
--- a/src/modules/VideoFilters/DiscardDeint.hpp
+++ b/src/modules/VideoFilters/DiscardDeint.hpp
@@ -20,14 +20,14 @@
 
 #include <VideoFilter.hpp>
 
-class DiscardDeint final : public VideoFilter
+class DiscardDeint : public VideoFilter
 {
 public:
     DiscardDeint();
 
-    bool filter(QQueue<Frame> &framesQueue) override;
+    bool filter(QQueue<Frame> &framesQueue);
 
-    bool processParams(bool *paramsCorrected) override;
+    bool processParams(bool *paramsCorrected);
 };
 
 #define DiscardDeintName "Discard"
diff --git a/src/modules/VideoFilters/FPSDoubler.cpp b/src/modules/VideoFilters/FPSDoubler.cpp
index fedf45d9..e43ae02e 100644
--- a/src/modules/VideoFilters/FPSDoubler.cpp
+++ b/src/modules/VideoFilters/FPSDoubler.cpp
@@ -20,6 +20,8 @@
 
 #include <QDebug>
 
+#include <cmath>
+
 FPSDoubler::FPSDoubler(Module &module, bool &fullScreen)
     : VideoFilter(false)
     , m_fullScreen(fullScreen)
@@ -48,7 +50,7 @@ bool FPSDoubler::filter(QQueue<Frame> &framesQueue)
         auto frame = m_internalQueue.dequeue();
         framesQueue.enqueue(frame);
         const auto frameTs = frame.ts();
-        if (!qIsNaN(m_lastTS))
+        if (!std::isnan(m_lastTS))
         {
             m_frameTimeSum += frameTs - m_lastTS;
             ++m_frames;
@@ -76,7 +78,7 @@ bool FPSDoubler::processParams(bool *)
     m_frameTimeSum = 0.0;
     m_frames = 0;
 
-    m_lastTS = qQNaN();
+    m_lastTS = std::numeric_limits<double>::quiet_NaN();
 
     return true;
 }
diff --git a/src/modules/VideoFilters/FPSDoubler.hpp b/src/modules/VideoFilters/FPSDoubler.hpp
index 204ba261..616deb45 100644
--- a/src/modules/VideoFilters/FPSDoubler.hpp
+++ b/src/modules/VideoFilters/FPSDoubler.hpp
@@ -20,17 +20,17 @@
 
 #include <VideoFilter.hpp>
 
-class FPSDoubler final : public VideoFilter
+class FPSDoubler : public VideoFilter
 {
 public:
     FPSDoubler(Module &module, bool &fullScreen);
     ~FPSDoubler();
 
-    bool set() override;
+    bool set();
 
-    bool filter(QQueue<Frame> &framesQueue) override;
+    bool filter(QQueue<Frame> &framesQueue);
 
-    bool processParams(bool *paramsCorrected) override;
+    bool processParams(bool *paramsCorrected);
 
 private:
     bool &m_fullScreen;
diff --git a/src/modules/VideoFilters/MotionBlur.hpp b/src/modules/VideoFilters/MotionBlur.hpp
index a45d1968..957bdc06 100644
--- a/src/modules/VideoFilters/MotionBlur.hpp
+++ b/src/modules/VideoFilters/MotionBlur.hpp
@@ -20,14 +20,14 @@
 
 #include <VideoFilter.hpp>
 
-class MotionBlur final : public VideoFilter
+class MotionBlur : public VideoFilter
 {
 public:
     MotionBlur();
 
-    bool filter(QQueue<Frame> &framesQueue) override;
+    bool filter(QQueue<Frame> &framesQueue);
 
-    bool processParams(bool *paramsCorrected) override;
+    bool processParams(bool *paramsCorrected);
 };
 
 #define MotionBlurName "Motion Blur"
diff --git a/src/modules/VideoFilters/VFilters.cpp b/src/modules/VideoFilters/VFilters.cpp
index 9657955b..c1cb875e 100644
--- a/src/modules/VideoFilters/VFilters.cpp
+++ b/src/modules/VideoFilters/VFilters.cpp
@@ -35,10 +35,15 @@ VFilters::VFilters() :
     init("FPSDoubler/MaxFPS", 29.990);
     init("FPSDoubler/OnlyFullScreen", true);
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     connect(&QMPlay2Core, &QMPlay2CoreClass::fullScreenChanged,
             this, [this](bool fs) {
         m_fullScreen = fs;
     });
+#else
+    connect(&QMPlay2Core, SIGNAL(fullScreenChanged(bool)),
+            this, SLOT(onFullScreenChanged(bool)));
+#endif
 }
 
 QList<VFilters::Info> VFilters::getModulesInfo(const bool) const
diff --git a/src/modules/VideoFilters/VFilters.hpp b/src/modules/VideoFilters/VFilters.hpp
index 62f69315..25914ee4 100644
--- a/src/modules/VideoFilters/VFilters.hpp
+++ b/src/modules/VideoFilters/VFilters.hpp
@@ -22,16 +22,20 @@
 
 #include <QCoreApplication>
 
-class VFilters final : public Module
+class VFilters : public Module
 {
-    Q_DECLARE_TR_FUNCTIONS(VFilters)
+    Q_OBJECT
 public:
     VFilters();
+private slots:
+#if QT_VERSION < 0x050000 || defined(Q_MOC_RUN)
+    void onFullScreenChanged(bool fs) { m_fullScreen = fs; }
+#endif
 private:
-    QList<Info> getModulesInfo(const bool) const override;
-    void *createInstance(const QString &) override;
+    QList<Info> getModulesInfo(const bool) const;
+    void *createInstance(const QString &);
 
-    SettingsWidget *getSettingsWidget() override;
+    SettingsWidget *getSettingsWidget();
 
 private:
     bool m_fullScreen = false;
@@ -42,7 +46,7 @@ private:
 class QDoubleSpinBox;
 class QCheckBox;
 
-class ModuleSettingsWidget final : public Module::SettingsWidget
+class ModuleSettingsWidget : public Module::SettingsWidget
 {
     Q_DECLARE_TR_FUNCTIONS(ModuleSettingsWidget)
 
@@ -50,7 +54,7 @@ public:
     ModuleSettingsWidget(Module &module);
 
 private:
-    void saveSettings() override;
+    void saveSettings();
 
 private:
     QDoubleSpinBox *const m_minFpsSpinBox;
diff --git a/src/modules/VideoFilters/YadifDeint.cpp b/src/modules/VideoFilters/YadifDeint.cpp
index b5ff29df..6c6ef7a0 100644
--- a/src/modules/VideoFilters/YadifDeint.cpp
+++ b/src/modules/VideoFilters/YadifDeint.cpp
@@ -25,7 +25,11 @@
 
 #include <QMPlay2Core.hpp>
 
-#include <QtConcurrent/QtConcurrentRun>
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+    #include <QtConcurrent/QtConcurrentRun>
+#else
+    #include <QtCore/QtCore>
+#endif
 
 #include <algorithm>
 #include <vector>
@@ -266,6 +270,7 @@ bool YadifDeint::filter(QQueue<Frame> &framesQueue)
 
         const int threadsCount = min(m_threadsPool.maxThreadCount(), destFrame.height(1));
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         vector<QFuture<void>> threads;
         threads.reserve(threadsCount);
 
@@ -275,6 +280,11 @@ bool YadifDeint::filter(QQueue<Frame> &framesQueue)
 
         for (auto &&thread : threads)
             thread.waitForFinished();
+#else
+        // Qt4: Run sequentially without QtConcurrent
+        for (int i = 0; i < threadsCount; ++i)
+            doFilter(i, threadsCount);
+#endif
 
         if (m_doubler)
             deinterlaceDoublerCommon(destFrame);
diff --git a/src/modules/VideoFilters/YadifDeint.hpp b/src/modules/VideoFilters/YadifDeint.hpp
index 45fff197..85bd437b 100644
--- a/src/modules/VideoFilters/YadifDeint.hpp
+++ b/src/modules/VideoFilters/YadifDeint.hpp
@@ -22,14 +22,14 @@
 
 #include <QThreadPool>
 
-class YadifDeint final : public VideoFilter
+class YadifDeint : public VideoFilter
 {
 public:
     YadifDeint(bool m_doubler, bool m_spatialCheck);
 
-    bool filter(QQueue<Frame> &framesQueue) override;
+    bool filter(QQueue<Frame> &framesQueue);
 
-    bool processParams(bool *paramsCorrected) override;
+    bool processParams(bool *paramsCorrected);
 private:
     const bool m_doubler;
     const bool m_spatialCheck;
diff --git a/src/modules/Visualizations/FFTSpectrum.hpp b/src/modules/Visualizations/FFTSpectrum.hpp
index bafe21e8..2513b0aa 100644
--- a/src/modules/Visualizations/FFTSpectrum.hpp
+++ b/src/modules/Visualizations/FFTSpectrum.hpp
@@ -27,19 +27,19 @@
 
 class FFTSpectrum;
 
-class FFTSpectrumW final : public VisWidget
+class FFTSpectrumW : public VisWidget
 {
     friend class FFTSpectrum;
     Q_DECLARE_TR_FUNCTIONS(FFTSpectrumW)
 public:
     FFTSpectrumW(FFTSpectrum &);
 private:
-    void paint(QPainter &p) override;
+    void paint(QPainter &p);
 
-    void mouseMoveEvent(QMouseEvent *e) override;
+    void mouseMoveEvent(QMouseEvent *e);
 
-    void start() override;
-    void stop() override;
+    void start();
+    void stop();
 
     QVector<float> spectrumData;
     QVector<QPair<qreal, QPair<qreal, double>>> lastData;
@@ -54,22 +54,22 @@ private:
 
 /**/
 
-class FFTSpectrum final : public QMPlay2Extensions
+class FFTSpectrum : public QMPlay2Extensions
 {
 public:
     FFTSpectrum(Module &);
 
     void soundBuffer(const bool);
 
-    bool set() override;
+    bool set();
 private:
-    DockWidget *getDockWidget() override;
+    DockWidget *getDockWidget();
 
-    bool isVisualization() const override;
-    void connectDoubleClick(const QObject *, const char *) override;
-    void visState(bool, uchar, uint) override;
-    void sendSoundData(const QByteArray &) override;
-    void clearSoundData() override;
+    bool isVisualization() const;
+    void connectDoubleClick(const QObject *, const char *);
+    void visState(bool, uchar, uint);
+    void sendSoundData(const QByteArray &);
+    void clearSoundData();
 
     /**/
 
diff --git a/src/modules/Visualizations/SimpleVis.hpp b/src/modules/Visualizations/SimpleVis.hpp
index b4688732..e959ecf8 100644
--- a/src/modules/Visualizations/SimpleVis.hpp
+++ b/src/modules/Visualizations/SimpleVis.hpp
@@ -26,7 +26,7 @@
 
 class SimpleVis;
 
-class SimpleVisW final : public VisWidget
+class SimpleVisW : public VisWidget
 {
     friend class SimpleVis;
 
@@ -34,12 +34,12 @@ class SimpleVisW final : public VisWidget
 public:
     SimpleVisW(SimpleVis &);
 private:
-    void paint(QPainter &p) override;
+    void paint(QPainter &p);
 
-    void resizeEvent(QResizeEvent *) override;
+    void resizeEvent(QResizeEvent *);
 
-    void start() override;
-    void stop() override;
+    void start();
+    void stop();
 
     QByteArray soundData;
     quint8 chn;
@@ -54,22 +54,22 @@ private:
 
 /**/
 
-class SimpleVis final : public QMPlay2Extensions
+class SimpleVis : public QMPlay2Extensions
 {
 public:
     SimpleVis(Module &);
 
     void soundBuffer(const bool);
 
-    bool set() override;
+    bool set();
 private:
-    DockWidget *getDockWidget() override;
+    DockWidget *getDockWidget();
 
-    bool isVisualization() const override;
-    void connectDoubleClick(const QObject *, const char *) override;
-    void visState(bool, uchar, uint) override;
-    void sendSoundData(const QByteArray &) override;
-    void clearSoundData() override;
+    bool isVisualization() const;
+    void connectDoubleClick(const QObject *, const char *);
+    void visState(bool, uchar, uint);
+    void sendSoundData(const QByteArray &);
+    void clearSoundData();
 
     /**/
 
diff --git a/src/modules/Visualizations/VisWidget.cpp b/src/modules/Visualizations/VisWidget.cpp
index c956435f..6de153f0 100644
--- a/src/modules/Visualizations/VisWidget.cpp
+++ b/src/modules/Visualizations/VisWidget.cpp
@@ -21,7 +21,7 @@
 #include <DockWidget.hpp>
 #include <Functions.hpp>
 
-#include <QGuiApplication>
+#include <QApplication>
 #include <QMouseEvent>
 #include <QPainter>
 #include <QMenu>
@@ -58,7 +58,8 @@ VisWidget::VisWidget()
 
     connect(&tim, SIGNAL(timeout()), this, SLOT(updateVisualization()));
     connect(dw, SIGNAL(dockVisibilityChanged(bool)), this, SLOT(visibilityChanged(bool)));
-    connect(&QMPlay2Core, qOverload<const QPixmap &>(&QMPlay2CoreClass::wallpaperChanged), this, &VisWidget::wallpaperChanged);
+    // Qt4: Use SIGNAL/SLOT syntax instead of function pointers
+    connect(&QMPlay2Core, SIGNAL(wallpaperChanged(const QPixmap &)), this, SLOT(wallpaperChanged(const QPixmap &)));
     connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(contextMenu(const QPoint &)));
 }
 
@@ -82,7 +83,7 @@ void VisWidget::setUseOpenGL(bool b)
     m_pendingUpdate = false;
     if (b && !glW)
     {
-        glW = new QOpenGLWidget(this);
+        glW = new QGLWidget(this);
         glW->setAttribute(Qt::WA_TransparentForMouseEvents);
         glW->setContextMenuPolicy(Qt::NoContextMenu);
         glW->setFocusPolicy(Qt::NoFocus);
@@ -156,8 +157,7 @@ bool VisWidget::eventFilter(QObject *watched, QEvent *event)
     if (glW && watched == glW && event->type() == QEvent::Paint)
     {
         QPainter p(glW);
-        if (QGuiApplication::platformName().contains("wayland"))
-            p.fillRect(rect(), Qt::black);
+        // Qt4: No Wayland support, skip this check
         paint(p);
         m_pendingUpdate = false;
         return true;
diff --git a/src/modules/Visualizations/VisWidget.hpp b/src/modules/Visualizations/VisWidget.hpp
index c709a0d3..ec9cf362 100644
--- a/src/modules/Visualizations/VisWidget.hpp
+++ b/src/modules/Visualizations/VisWidget.hpp
@@ -22,7 +22,7 @@
 #include <QTimer>
 
 #ifdef USE_OPENGL
-#   include <QOpenGLWidget>
+#   include <QtOpenGL/QGLWidget>
 #endif
 
 class DockWidget;
@@ -45,7 +45,7 @@ protected:
 
     void setUseOpenGL(bool b);
 
-    void resizeEvent(QResizeEvent *e) override;
+    void resizeEvent(QResizeEvent *e);
 
     QTimer tim;
     bool stopped;
@@ -60,7 +60,7 @@ private:
     bool eventFilter(QObject *watched, QEvent *event) override final;
 
 #ifdef USE_OPENGL
-    QOpenGLWidget *glW = nullptr;
+    QGLWidget *glW = nullptr;
     bool m_pendingUpdate = false;
 #endif
     bool dockWidgetVisible = false;
diff --git a/src/modules/Visualizations/Visualizations.cpp b/src/modules/Visualizations/Visualizations.cpp
index b4f35226..3257d208 100644
--- a/src/modules/Visualizations/Visualizations.cpp
+++ b/src/modules/Visualizations/Visualizations.cpp
@@ -140,5 +140,5 @@ void ModuleSettingsWidget::saveSettings()
     sets().set("SimpleVis/SoundLength", sndLenB->value());
     sets().set("FFTSpectrum/Size", fftSizeB->value());
     sets().set("FFTSpectrum/LinearScale", m_fftLinearScaleB->isChecked());
-    sets().set("FFTSpectrum/LimitFreq", m_fftLimitFreqB->currentData().toInt());
+    sets().set("FFTSpectrum/LimitFreq", m_fftLimitFreqB->itemData(m_fftLimitFreqB->currentIndex()).toInt());
 }
diff --git a/src/modules/Visualizations/Visualizations.hpp b/src/modules/Visualizations/Visualizations.hpp
index b4150d35..00287dea 100644
--- a/src/modules/Visualizations/Visualizations.hpp
+++ b/src/modules/Visualizations/Visualizations.hpp
@@ -20,15 +20,15 @@
 
 #include <Module.hpp>
 
-class Visualizations final : public Module
+class Visualizations : public Module
 {
 public:
     Visualizations();
 private:
-    QList<Info> getModulesInfo(const bool) const override;
-    void *createInstance(const QString &) override;
+    QList<Info> getModulesInfo(const bool) const;
+    void *createInstance(const QString &);
 
-    SettingsWidget *getSettingsWidget() override;
+    SettingsWidget *getSettingsWidget();
 };
 
 /**/
@@ -39,13 +39,13 @@ class QCheckBox;
 class QComboBox;
 class QSpinBox;
 
-class ModuleSettingsWidget final : public Module::SettingsWidget
+class ModuleSettingsWidget : public Module::SettingsWidget
 {
     Q_DECLARE_TR_FUNCTIONS(ModuleSettingsWidget)
 public:
     ModuleSettingsWidget(Module &);
 private:
-    void saveSettings() override;
+    void saveSettings();
 
     QSpinBox *refTimeB = nullptr, *sndLenB, *fftSizeB;
     QComboBox *m_fftLimitFreqB;
diff --git a/src/modules/XVideo/XVideo.cpp b/src/modules/XVideo/XVideo.cpp
index cfd84296..7d5c50ba 100644
--- a/src/modules/XVideo/XVideo.cpp
+++ b/src/modules/XVideo/XVideo.cpp
@@ -19,8 +19,6 @@
 #include <XVideo.hpp>
 #include <XVideoWriter.hpp>
 
-#include <QGuiApplication>
-
 XVideo::XVideo() :
     Module("XVideo")
 {
@@ -33,14 +31,24 @@ XVideo::XVideo() :
 QList<XVideo::Info> XVideo::getModulesInfo(const bool showDisabled) const
 {
     QList<Info> modulesInfo;
-    if (showDisabled || (QGuiApplication::platformName() == "xcb" && getBool("Enabled")))
+    // Qt4: Use Q_WS_X11 instead of platformName check
+#ifdef Q_WS_X11
+    if (showDisabled || getBool("Enabled"))
         modulesInfo += Info(XVideoWriterName, WRITER, QStringList{"video"});
+#else
+    Q_UNUSED(showDisabled)
+#endif
     return modulesInfo;
 }
 void *XVideo::createInstance(const QString &name)
 {
-    if (name == XVideoWriterName && QGuiApplication::platformName() == "xcb" && getBool("Enabled"))
+    // Qt4: Use Q_WS_X11 instead of platformName check
+#ifdef Q_WS_X11
+    if (name == XVideoWriterName && getBool("Enabled"))
         return new XVideoWriter(*this);
+#else
+    Q_UNUSED(name)
+#endif
     return nullptr;
 }
 
diff --git a/src/modules/XVideo/XVideo.hpp b/src/modules/XVideo/XVideo.hpp
index a85c1eaa..fb03aebf 100644
--- a/src/modules/XVideo/XVideo.hpp
+++ b/src/modules/XVideo/XVideo.hpp
@@ -20,15 +20,15 @@
 
 #include <Module.hpp>
 
-class XVideo final : public Module
+class XVideo : public Module
 {
 public:
     XVideo();
 private:
-    QList<Info> getModulesInfo(const bool) const override;
-    void *createInstance(const QString &) override;
+    QList<Info> getModulesInfo(const bool) const;
+    void *createInstance(const QString &);
 
-    SettingsWidget *getSettingsWidget() override;
+    SettingsWidget *getSettingsWidget();
 };
 
 /**/
@@ -38,13 +38,13 @@ private:
 class QCheckBox;
 class QComboBox;
 
-class ModuleSettingsWidget final : public Module::SettingsWidget
+class ModuleSettingsWidget : public Module::SettingsWidget
 {
     Q_DECLARE_TR_FUNCTIONS(ModuleSettingsWidget)
 public:
     ModuleSettingsWidget(Module &);
 private:
-    void saveSettings() override;
+    void saveSettings();
 
     QCheckBox *enabledB, *useSHMB;
     QComboBox *adaptorsB;
diff --git a/src/modules/XVideo/XVideoWriter.hpp b/src/modules/XVideo/XVideoWriter.hpp
index d5a6fb02..38bfb9e3 100644
--- a/src/modules/XVideo/XVideoWriter.hpp
+++ b/src/modules/XVideo/XVideoWriter.hpp
@@ -24,18 +24,18 @@
 
 #include <QWidget>
 
-class Drawable final : public QWidget
+class Drawable : public QWidget
 {
     friend class XVideoWriter;
 public:
     Drawable(class XVideoWriter &);
     ~Drawable() final = default;
 private:
-    void resizeEvent(QResizeEvent *) override;
-    void paintEvent(QPaintEvent *) override;
-    bool event(QEvent *) override;
+    void resizeEvent(QResizeEvent *);
+    void paintEvent(QPaintEvent *);
+    bool event(QEvent *);
 
-    QPaintEngine *paintEngine() const override;
+    QPaintEngine *paintEngine() const;
 
     int X, Y, W, H;
     QRect dstRect, srcRect;
@@ -46,7 +46,7 @@ private:
 
 class QMPlay2OSD;
 
-class XVideoWriter final : public VideoWriter
+class XVideoWriter : public VideoWriter
 {
     friend class Drawable;
 public:
@@ -54,16 +54,16 @@ public:
 private:
     ~XVideoWriter();
 
-    bool set() override;
+    bool set();
 
-    bool readyWrite() const override;
+    bool readyWrite() const;
 
-    bool processParams(bool *paramsCorrected) override;
-    void writeVideo(const Frame &videoFrame, QMPlay2OSDList &&osdList) override;
+    bool processParams(bool *paramsCorrected);
+    void writeVideo(const Frame &videoFrame, QMPlay2OSDList &&osdList);
 
-    QString name() const override;
+    QString name() const;
 
-    bool open() override;
+    bool open();
 
     /**/
 
diff --git a/src/qmplay2/CMakeLists.txt b/src/qmplay2/CMakeLists.txt
index 2d3a5aef..7eacdcba 100644
--- a/src/qmplay2/CMakeLists.txt
+++ b/src/qmplay2/CMakeLists.txt
@@ -125,18 +125,10 @@ if(USE_OPENGL)
         opengl/opengl.qrc
     )
 
-    if(BUILD_WITH_QT6)
-        list(APPEND OPENGL_LIBS Qt6::OpenGL Qt6::OpenGLWidgets)
-        get_target_property(QtGui_Enabled_Features Qt6::Gui QT_ENABLED_PUBLIC_FEATURES)
-    endif()
-
-    if((${QT_PREFIX}Gui_OPENGL_IMPLEMENTATION STREQUAL GLESv2) OR (QtGui_Enabled_Features MATCHES "opengles"))
-        if(NOT WIN32)
-            list(APPEND OPENGL_LIBS GLESv2)
-        endif()
-    elseif(${QT_PREFIX}Gui_OPENGL_IMPLEMENTATION STREQUAL GLES)
-        message(SEND_ERROR "OpenGL|ES 1.0 is not supported!")
-    endif()
+    # Qt4 has OpenGL support built into QtOpenGL (already linked via QT_USE_FILE)
+    # For macOS 10.6.8, we use OpenGL 2.0
+    find_package(OpenGL REQUIRED)
+    list(APPEND OPENGL_LIBS ${OPENGL_LIBRARIES})
 endif()
 
 if(USE_VULKAN)
@@ -220,11 +212,8 @@ endif()
 if(USE_FREEDESKTOP_NOTIFICATIONS)
     list(APPEND QMPLAY2_HDR NotifiesFreedesktop.hpp)
     list(APPEND QMPLAY2_SRC NotifiesFreedesktop.cpp)
-    if(BUILD_WITH_QT6)
-        qt6_add_dbus_interface(QMPLAY2_SRC org.freedesktop.Notifications.xml notifications_interface)
-    else()
-        qt5_add_dbus_interface(QMPLAY2_SRC org.freedesktop.Notifications.xml notifications_interface)
-    endif()
+    # Qt4 DBus interface generation
+    qt4_add_dbus_interface(QMPLAY2_SRC org.freedesktop.Notifications.xml notifications_interface)
     add_definitions(-DNOTIFIES_FREEDESKTOP)
 elseif(APPLE)
     list(APPEND QMPLAY2_HDR NotifiesMacOS.hpp)
@@ -233,22 +222,16 @@ elseif(APPLE)
 endif()
 
 if(USE_FREEDESKTOP_NOTIFICATIONS OR USE_DBUS_PM OR USE_MPRIS2)
-    set(DBUS ${QT_PREFIX}::DBus)
+    # Qt4 DBus support (QtDBus was already included in find_package if needed)
+    find_package(Qt4 COMPONENTS QtDBus)
+    if(QT_QTDBUS_FOUND)
+        set(DBUS ${QT_QTDBUS_LIBRARY})
+    endif()
 endif()
 
+# QML is not supported in Qt4 - USE_QML should be OFF
 if(USE_QML)
-    list(APPEND QMPLAY2_HDR
-        CommonJS.hpp
-        TreeWidgetJS.hpp
-        NetworkAccessJS.hpp
-    )
-    list(APPEND QMPLAY2_SRC
-        CommonJS.cpp
-        TreeWidgetJS.cpp
-        NetworkAccessJS.cpp
-    )
-    add_definitions(-DUSE_QML)
-    set(QML ${QT_PREFIX}::Qml)
+    message(FATAL_ERROR "QML is not supported in Qt4")
 endif()
 
 if(USE_YOUTUBEDL)
@@ -361,10 +344,7 @@ endif()
 
 target_link_libraries(${PROJECT_NAME}
     PUBLIC
-    ${QT_PREFIX}::Core
-    ${QT_PREFIX}::Gui
-    ${QT_PREFIX}::Widgets
-    ${QT_PREFIX}::Svg
+    ${QT_LIBRARIES}
     ${LIBAVFORMAT_LIBRARIES}
     ${LIBAVCODEC_LIBRARIES}
     ${LIBSWSCALE_LIBRARIES}
@@ -372,37 +352,26 @@ target_link_libraries(${PROJECT_NAME}
     ${LIBSWRESAMPLE_LIBRARIES}
     ${OPENGL_LIBS}
     ${DBUS}
+    ${QJSON4_LIBRARY}
     PRIVATE
-    ${QML}
     ${LIBQMPLAY2_LIBS}
 )
-if(USE_VULKAN)
-    target_link_libraries(${PROJECT_NAME}
-        PRIVATE
-        $<FILTER:$<TARGET_OBJECTS:QmVk>,EXCLUDE,.*\\.[gp]ch>
-    )
-    target_compile_definitions(${PROJECT_NAME}
-        PUBLIC
-        $<TARGET_PROPERTY:QmVk,INTERFACE_COMPILE_DEFINITIONS>
-    )
-    target_include_directories(${PROJECT_NAME}
-        PUBLIC
-        $<TARGET_PROPERTY:QmVk,INTERFACE_INCLUDE_DIRECTORIES>
-    )
-endif()
-if(BUILD_WITH_QT6)
-    target_link_libraries(${PROJECT_NAME}
-        PUBLIC
-        Qt6::Core5Compat # QTextCodec
-    )
-endif()
-
-if(USE_VULKAN AND NOT WIN32 AND NOT ANDROID)
-    target_link_libraries(${PROJECT_NAME}
-        PRIVATE
-        dl
-    )
-endif()
+# Vulkan support is disabled for Qt4/macOS 10.6.8
+# if(USE_VULKAN)
+#     target_link_libraries(${PROJECT_NAME}
+#         PRIVATE
+#         $<FILTER:$<TARGET_OBJECTS:QmVk>,EXCLUDE,.*\\.[gp]ch>
+#     )
+# endif()
+# Qt4 has QTextCodec built-in, no need for Core5Compat
+
+# Vulkan support disabled for Qt4/macOS 10.6.8
+# if(USE_VULKAN AND NOT WIN32 AND NOT ANDROID)
+#     target_link_libraries(${PROJECT_NAME}
+#         PRIVATE
+#         dl
+#     )
+# endif()
 
 macro(libqmplay2_set_target_params)
     target_link_libraries(${PROJECT_NAME}
diff --git a/src/qmplay2/ChapterProgramInfo.hpp b/src/qmplay2/ChapterProgramInfo.hpp
index e6042d56..728b5c44 100644
--- a/src/qmplay2/ChapterProgramInfo.hpp
+++ b/src/qmplay2/ChapterProgramInfo.hpp
@@ -18,6 +18,8 @@
 
 #pragma once
 
+#include <QPair>
+
 #include <QString>
 #include <QVector>
 
diff --git a/src/qmplay2/ColorButton.cpp b/src/qmplay2/ColorButton.cpp
index 55aedaf6..4976cbd8 100644
--- a/src/qmplay2/ColorButton.cpp
+++ b/src/qmplay2/ColorButton.cpp
@@ -26,7 +26,10 @@ ColorButton::ColorButton(QWidget *parent) :
 {
     setCursor(Qt::PointingHandCursor);
     setAttribute(Qt::WA_OpaquePaintEvent);
-    connect(this, &ColorButton::clicked, this, &ColorButton::openColorDialog);
+    connect(this, SIGNAL(clicked()), this, SLOT(openColorDialog()));
+}
+ColorButton::~ColorButton()
+{
 }
 
 void ColorButton::setAlphaAllowed(bool alpha)
@@ -43,7 +46,7 @@ void ColorButton::setColor(const QColor &color)
                .arg(m_color.red(), 2, 16, QChar('0'))
                .arg(m_color.green(), 2, 16, QChar('0'))
                .arg(m_color.blue(), 2, 16, QChar('0'))
-               .arg(m_alphaChannel ? QStringLiteral("%1").arg(m_color.alpha(), 2, 16, QChar('0')) : QString())
+               .arg(m_alphaChannel ? QString("%1").arg(m_color.alpha(), 2, 16, QChar('0')) : QString())
                .toUpper()
     );
     update();
diff --git a/src/qmplay2/ColorButton.hpp b/src/qmplay2/ColorButton.hpp
index feb1b943..ac2f8dae 100644
--- a/src/qmplay2/ColorButton.hpp
+++ b/src/qmplay2/ColorButton.hpp
@@ -22,11 +22,12 @@
 
 #include <QPushButton>
 
-class QMPLAY2SHAREDLIB_EXPORT ColorButton final : public QPushButton
+class QMPLAY2SHAREDLIB_EXPORT ColorButton : public QPushButton
 {
     Q_OBJECT
 public:
     ColorButton(QWidget *parent = nullptr);
+    ~ColorButton();
 
     void setAlphaAllowed(bool alpha);
 
@@ -37,9 +38,9 @@ public:
     }
 
 protected:
-    void paintEvent(QPaintEvent *) override;
+    void paintEvent(QPaintEvent *);
 
-private:
+private slots:
     void openColorDialog();
 
 signals:
diff --git a/src/qmplay2/CommonJS.cpp b/src/qmplay2/CommonJS.cpp
index 9c87026b..42b2c204 100644
--- a/src/qmplay2/CommonJS.cpp
+++ b/src/qmplay2/CommonJS.cpp
@@ -27,11 +27,9 @@ int CommonJS::insertNetworkReply(NetworkReply *networkReply)
     QMutexLocker locker(&m_networkReplyMutex);
     const auto id = ++m_networkReplyId;
     m_networkReplies[id] = networkReply;
+    m_networkReplyToId[networkReply] = id;
 
-    connect(networkReply, &NetworkReply::destroyed,
-            this, [=] {
-        removeNetworkReply(id);
-    });
+    connect(networkReply, SIGNAL(destroyed(QObject*)), this, SLOT(onNetworkReplyDestroyed()));
 
     return id;
 }
@@ -43,7 +41,12 @@ NetworkReply *CommonJS::getNetworkReply(const int id) const
 void CommonJS::removeNetworkReply(const int id)
 {
     QMutexLocker locker(&m_networkReplyMutex);
-    m_networkReplies.remove(id);
+    auto it = m_networkReplies.find(id);
+    if (it != m_networkReplies.end())
+    {
+        m_networkReplyToId.remove(it.value());
+        m_networkReplies.erase(it);
+    }
 }
 
 int CommonJS::insertIOController(IOController<> *ioCtrl)
@@ -97,12 +100,13 @@ int CommonJS::startTimer(const int ms, const bool singleShot, QJSValue onTimeout
     const auto id = ++m_timerId;
     m_timers[id] = timer;
 
-    connect(timer, &QTimer::timeout,
-            this, [=]() mutable {
-        onTimeout.call();
-        if (timer->isSingleShot())
-            stopTimer(id);
-    });
+    TimerData data;
+    data.id = id;
+    data.callback = onTimeout;
+    data.singleShot = singleShot;
+    m_timerData[timer] = data;
+
+    connect(timer, SIGNAL(timeout()), this, SLOT(onTimerTimeout()));
 
     return id;
 }
@@ -112,7 +116,9 @@ void CommonJS::stopTimer(const int id)
     auto it = m_timers.find(id);
     if (it != m_timers.end())
     {
-        delete it.value();
+        QTimer *timer = it.value();
+        m_timerData.remove(timer);
+        delete timer;
         m_timers.erase(it);
     }
 }
@@ -216,3 +222,39 @@ QString CommonJS::getNameForUrl(const QString &url)
 {
     return QMPlay2Core.getNameForUrl(url);
 }
+
+void CommonJS::onNetworkReplyDestroyed()
+{
+    QObject *obj = sender();
+    if (!obj)
+        return;
+
+    QMutexLocker locker(&m_networkReplyMutex);
+    auto it = m_networkReplyToId.find(obj);
+    if (it != m_networkReplyToId.end())
+    {
+        int id = it.value();
+        m_networkReplyToId.erase(it);
+        m_networkReplies.remove(id);
+    }
+}
+
+void CommonJS::onTimerTimeout()
+{
+    QTimer *timer = qobject_cast<QTimer*>(sender());
+    if (!timer)
+        return;
+
+    QMutexLocker locker(&m_timerMutex);
+    auto it = m_timerData.find(timer);
+    if (it != m_timerData.end())
+    {
+        TimerData data = it.value();
+        locker.unlock();
+
+        data.callback.call();
+
+        if (data.singleShot)
+            stopTimer(data.id);
+    }
+}
diff --git a/src/qmplay2/CommonJS.hpp b/src/qmplay2/CommonJS.hpp
index 5cf35103..c4157d4e 100644
--- a/src/qmplay2/CommonJS.hpp
+++ b/src/qmplay2/CommonJS.hpp
@@ -67,10 +67,21 @@ public:
     Q_INVOKABLE void addNameForUrl(const QString &url, const QString &name, const bool removeAfterUse = true);
     Q_INVOKABLE  QString getNameForUrl(const QString &url);
 
+private slots:
+    void onNetworkReplyDestroyed();
+    void onTimerTimeout();
+
 private:
+    struct TimerData {
+        int id;
+        QJSValue callback;
+        bool singleShot;
+    };
+
     mutable QMutex m_networkReplyMutex;
     int m_networkReplyId = 0;
     QHash<int, NetworkReply *> m_networkReplies;
+    QHash<QObject*, int> m_networkReplyToId;
 
     mutable QMutex m_ioControllerMutex;
     int m_ioControllerId = 0;
@@ -79,4 +90,5 @@ private:
     QMutex m_timerMutex;
     int m_timerId = 0;
     QHash<int, QTimer *> m_timers;
+    QHash<QTimer*, TimerData> m_timerData;
 };
diff --git a/src/qmplay2/Decoder.cpp b/src/qmplay2/Decoder.cpp
index 41304505..74f5a957 100644
--- a/src/qmplay2/Decoder.cpp
+++ b/src/qmplay2/Decoder.cpp
@@ -23,17 +23,17 @@
 
 class QMPlay2DummyDecoder : public Decoder
 {
-    QString name() const override
+    QString name() const
     {
         return QString();
     }
 
-    bool open(StreamInfo &) override
+    bool open(StreamInfo &)
     {
         return true;
     }
 
-    int decodeVideo(const Packet &encodedPacket, Frame &decoded, AVPixelFormat &newPixFmt, bool flush, unsigned hurry_up) override
+    int decodeVideo(const Packet &encodedPacket, Frame &decoded, AVPixelFormat &newPixFmt, bool flush, unsigned hurry_up)
     {
         Q_UNUSED(newPixFmt)
         Q_UNUSED(flush)
@@ -47,7 +47,7 @@ class QMPlay2DummyDecoder : public Decoder
         return 0;
     }
 
-    int decodeAudio(const Packet &encodedPacket, QByteArray &decoded, double &ts, quint8 &channels, quint32 &sampleRate, bool flush) override
+    int decodeAudio(const Packet &encodedPacket, QByteArray &decoded, double &ts, quint8 &channels, quint32 &sampleRate, bool flush)
     {
         Q_UNUSED(channels)
         Q_UNUSED(sampleRate)
@@ -57,7 +57,7 @@ class QMPlay2DummyDecoder : public Decoder
         return decoded.size();
     }
 
-    bool isDummyDecoder() const override
+    bool isDummyDecoder() const
     {
         return true;
     }
diff --git a/src/qmplay2/DeintHWPrepareFilter.cpp b/src/qmplay2/DeintHWPrepareFilter.cpp
index 3f2911af..0b99e4ec 100644
--- a/src/qmplay2/DeintHWPrepareFilter.cpp
+++ b/src/qmplay2/DeintHWPrepareFilter.cpp
@@ -39,7 +39,7 @@ bool DeintHWPrepareFilter::filter(QQueue<Frame> &framesQueue)
     addFramesToInternalQueue(framesQueue);
     if (!m_internalQueue.isEmpty())
     {
-        Frame frame = m_internalQueue.constFirst();
+        Frame frame = m_internalQueue.first();
 
         if (!m_deinterlace)
             frame.setNoInterlaced();
diff --git a/src/qmplay2/DeintHWPrepareFilter.hpp b/src/qmplay2/DeintHWPrepareFilter.hpp
index 62beb529..b2d41867 100644
--- a/src/qmplay2/DeintHWPrepareFilter.hpp
+++ b/src/qmplay2/DeintHWPrepareFilter.hpp
@@ -27,9 +27,9 @@ public:
     DeintHWPrepareFilter();
     ~DeintHWPrepareFilter();
 
-    bool filter(QQueue<Frame> &framesQueue) override;
+    bool filter(QQueue<Frame> &framesQueue);
 
-    bool processParams(bool *paramsCorrected) override;
+    bool processParams(bool *paramsCorrected);
 
 private:
     bool m_deinterlace = false;
diff --git a/src/qmplay2/Demuxer.cpp b/src/qmplay2/Demuxer.cpp
index d94b8e39..aeb2d16a 100644
--- a/src/qmplay2/Demuxer.cpp
+++ b/src/qmplay2/Demuxer.cpp
@@ -69,7 +69,7 @@ bool Demuxer::create(const QString &url, IOController<Demuxer> &demuxer, FetchTr
 
 Demuxer::~Demuxer()
 {
-    for (StreamInfo *streamInfo : std::as_const(streams_info))
+    for (StreamInfo *streamInfo : streams_info)
         delete streamInfo;
 }
 
diff --git a/src/qmplay2/DockWidget.cpp b/src/qmplay2/DockWidget.cpp
index 4f06ce24..a3e9bc4e 100644
--- a/src/qmplay2/DockWidget.cpp
+++ b/src/qmplay2/DockWidget.cpp
@@ -23,7 +23,7 @@
 
 class EmptyW final : public QWidget
 {
-    QSize sizeHint() const override
+    QSize sizeHint() const
     {
         return QSize(0, 0);
     }
@@ -37,30 +37,37 @@ DockWidget::DockWidget()
 {
     m_visibilityTimer->setSingleShot(true);
     m_visibilityTimer->setInterval(0);
-    connect(m_visibilityTimer, &QTimer::timeout, this, [this] {
-        if (m_lastVisible != m_visible)
-        {
-            emit dockVisibilityChanged(m_visible);
-            m_lastVisible = m_visible;
-        }
-    });
-
-    connect(this, &QDockWidget::visibilityChanged, this, [this](bool visible) {
-        m_visible = visible;
-        if (!m_visibilityTimer->isActive())
-            m_visibilityTimer->start();
-    });
-
-    connect(this, &QDockWidget::dockLocationChanged, this, [this](Qt::DockWidgetArea area) {
-        Q_UNUSED(area)
-        emit shouldStoreSizes();
-    });
+    connect(m_visibilityTimer, SIGNAL(timeout()), this, SLOT(onVisibilityTimeout()));
+    connect(this, SIGNAL(visibilityChanged(bool)), this, SLOT(onVisibilityChanged(bool)));
+    connect(this, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(onDockLocationChanged(Qt::DockWidgetArea)));
 }
 DockWidget::~DockWidget()
 {
     delete m_emptyW;
 }
 
+void DockWidget::onVisibilityTimeout()
+{
+    if (m_lastVisible != m_visible)
+    {
+        emit dockVisibilityChanged(m_visible);
+        m_lastVisible = m_visible;
+    }
+}
+
+void DockWidget::onVisibilityChanged(bool visible)
+{
+    m_visible = visible;
+    if (!m_visibilityTimer->isActive())
+        m_visibilityTimer->start();
+}
+
+void DockWidget::onDockLocationChanged(Qt::DockWidgetArea area)
+{
+    Q_UNUSED(area)
+    emit shouldStoreSizes();
+}
+
 void DockWidget::setTitleBarVisible(bool v)
 {
     m_titleBarVisible = v;
diff --git a/src/qmplay2/DockWidget.hpp b/src/qmplay2/DockWidget.hpp
index c3d34d73..ef2180e7 100644
--- a/src/qmplay2/DockWidget.hpp
+++ b/src/qmplay2/DockWidget.hpp
@@ -38,14 +38,19 @@ public:
     void setResizingByMainWindow(bool r);
 
 protected:
-    void resizeEvent(QResizeEvent *e) override;
-    void showEvent(QShowEvent *e) override;
-    void closeEvent(QCloseEvent *e) override;
+    void resizeEvent(QResizeEvent *e);
+    void showEvent(QShowEvent *e);
+    void closeEvent(QCloseEvent *e);
 
 signals:
     void dockVisibilityChanged(bool visible);
     void shouldStoreSizes();
 
+private slots:
+    void onVisibilityTimeout();
+    void onVisibilityChanged(bool visible);
+    void onDockLocationChanged(Qt::DockWidgetArea area);
+
 private:
     QWidget *const m_emptyW;
     QTimer *const m_visibilityTimer;
diff --git a/src/qmplay2/Functions.cpp b/src/qmplay2/Functions.cpp
index f6f68b88..d134d405 100644
--- a/src/qmplay2/Functions.cpp
+++ b/src/qmplay2/Functions.cpp
@@ -34,20 +34,19 @@
 #include <QPainter>
 #include <QDir>
 #include <QUrl>
-#include <QWindow>
+// Qt4: No QWindow (use QWidget instead)
+#include <QWidget>
 #include <QLibrary>
 #include <QTextCodec>
 #include <QMessageBox>
 #include <QStyleOption>
 #include <QInputDialog>
-#include <QGuiApplication>
+// Qt4: No QGuiApplication (use QApplication)
+#include <QApplication>
 #include <QVarLengthArray>
-#include <QRegularExpression>
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
-# include <QInputDevice>
-#else
-# include <QTouchDevice>
-#endif
+#include <QRegExp>
+// Qt4: No QInputDevice/QTouchDevice in Qt4
+// Touch device APIs are Qt5+
 
 extern "C"
 {
@@ -60,6 +59,7 @@ extern "C"
 
 using namespace std;
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
 static inline QWindow *getNativeWindow(const QWidget *w)
 {
     if (w)
@@ -69,12 +69,13 @@ static inline QWindow *getNativeWindow(const QWidget *w)
     }
     return nullptr;
 }
+#endif
 
 /**/
 
 QDate Functions::parseVersion(const QString &dateTxt)
 {
-    const QStringList l = dateTxt.split(QRegularExpression(R"(\D)"));
+    const QStringList l = dateTxt.split(QRegExp("\\D"));
     int y = 0, m = 0, d = 0;
     if (l.count() >= 3)
     {
@@ -146,7 +147,8 @@ QString Functions::Url(QString url, const QString &pth)
             url.prepend(addPth);
         }
 #ifndef Q_OS_WIN
-        if (hasBackslash && !QFileInfo::exists(url))
+        // Qt4: QFileInfo::exists() is not a static method
+        if (hasBackslash && !QFileInfo(url).exists())
             url.replace("\\", "/");
 #endif
         url.prepend("file://");
@@ -196,7 +198,7 @@ QString Functions::fileName(QString f, bool extension)
         if (real_url.startsWith("file://"))
             return fileName(real_url, extension);
         if (!extension && isResourcePlaylist(f))
-            return fileName(QStringLiteral("QMPlay2://") + real_url, false);
+            return fileName(QString("QMPlay2://") + real_url, false);
         return real_url;
     }
     /**/
@@ -318,10 +320,16 @@ QPixmap Functions::getPixmapFromIcon(const QIcon &icon, QSize size, QWidget *w)
     else
     {
         imgSize = icon.availableSizes().value(0);
+        // Qt4: QSize::scale() returns void, not QSize
         imgSize.scale(size, size.isEmpty() ? Qt::KeepAspectRatioByExpanding : Qt::KeepAspectRatio);
     }
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     return icon.pixmap(getNativeWindow(w), imgSize);
+#else
+    // Qt4: icon.pixmap() doesn't take QWindow*, just use size
+    return icon.pixmap(imgSize);
+#endif
 }
 void Functions::drawPixmap(QPainter &p, const QPixmap &pixmap, const QWidget *w, Qt::TransformationMode transformationMode, Qt::AspectRatioMode aRatioMode, QSize size, qreal scale, bool alwaysAllowEnlarge)
 {
@@ -353,7 +361,11 @@ void Functions::drawPixmap(QPainter &p, const QPixmap &pixmap, const QWidget *w,
         if (pixmapSize.height() > pixmap.height())
             pixmapSize.setHeight(pixmap.height());
     }
-    pixmapSize = pixmap.size().scaled(pixmapSize, aRatioMode);
+    // Qt4/Qt5: QSize::scaled() returns QSize in Qt5, but is void in Qt4
+    // Create a copy and scale it
+    QSize originalSize = pixmap.size();
+    originalSize.scale(pixmapSize, aRatioMode);
+    pixmapSize = originalSize;
 
     const bool isSmooth = (transformationMode == Qt::SmoothTransformation);
     const QPoint pixmapPos {
@@ -362,9 +374,14 @@ void Functions::drawPixmap(QPainter &p, const QPixmap &pixmap, const QWidget *w,
     };
     if (isSmooth && (pixmapSize.width() < pixmap.width() / 2 || pixmapSize.height() < pixmap.height() / 2))
     {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         const qreal dpr = w->devicePixelRatioF();
         pixmapToDraw = pixmapToDraw.scaled(pixmapSize * dpr, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
         pixmapToDraw.setDevicePixelRatio(dpr);
+#else
+        // Qt4: No HiDPI support, just scale normally
+        pixmapToDraw = pixmapToDraw.scaled(pixmapSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+#endif
         p.drawPixmap(pixmapPos, pixmapToDraw);
     }
     else
@@ -426,7 +443,12 @@ void Functions::paintOSD(bool rgbSwapped, const QMPlay2OSDList &osd_list, const
                 (const uchar *)img.rgba.constData(),
                 img.size.width(),
                 img.size.height(),
+#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
                 rgbSwapped ? QImage::Format_RGBA8888 : QImage::Format_ARGB32
+#else
+                // Qt4: Use Format_ARGB32 or Format_RGB888 (no RGBA8888)
+                rgbSwapped ? QImage::Format_RGB888 : QImage::Format_ARGB32
+#endif
             );
             if (osd->needsRescale())
             {
@@ -595,25 +617,22 @@ QByteArray Functions::convertToASS(QString txt)
     txt.replace('\n', "\\N", Qt::CaseInsensitive);
 
     // Colors
-    const QRegularExpression colorRegExp(
-        R"(<font\s+color\s*=\s*\"?\#?(\w{6})\"?\s*>(.*)<\/font\s*>)",
-        QRegularExpression::CaseInsensitiveOption | QRegularExpression::InvertedGreedinessOption
-    );
+    // Qt4: Use QRegExp instead of QRegularExpression
+    QRegExp colorRegExp("<font\\s+color\\s*=\\s*\"?\\#?(\\w{6})\"?\\s*>(.*)</font\\s*>", Qt::CaseInsensitive);
+    colorRegExp.setMinimal(true);
     int pos = 0;
     for (;;)
     {
-        const auto match = colorRegExp.match(txt, pos);
-        if (!match.hasMatch())
+        pos = colorRegExp.indexIn(txt, pos);
+        if (pos == -1)
             break;
 
-        pos = match.capturedStart();
-
-        QString rgb = match.captured(1);
+        QString rgb = colorRegExp.cap(1);
         rgb = rgb.mid(4, 2) + rgb.mid(2, 2) + rgb.mid(0, 2);
 
-        const QString replaced = "{\\1c&" + rgb + "&}" + match.captured(2) + "{\\1c}";
+        const QString replaced = "{\\1c&" + rgb + "&}" + colorRegExp.cap(2) + "{\\1c}";
 
-        txt.replace(pos, match.capturedLength(), replaced);
+        txt.replace(pos, colorRegExp.matchedLength(), replaced);
         pos += replaced.length();
     }
 
@@ -641,7 +660,7 @@ QStringList Functions::getUrlsFromMimeData(const QMimeData *mimeData, const bool
     }
     else if (mimeData->hasText())
     {
-        urls = mimeData->text().remove('\r').split('\n', Qt::SkipEmptyParts);
+        urls = mimeData->text().remove('\r').split('\n', QString::SkipEmptyParts);
     }
     if (checkExtensionsForUrl)
     {
@@ -727,7 +746,7 @@ bool Functions::isResourcePlaylist(const QString &url)
     QString realUrl, addressPrefixName;
     if (!Functions::splitPrefixAndUrlIfHasPluginPrefix(url, &addressPrefixName, &realUrl))
         return false;
-    return (addressPrefixName == QStringLiteral("QMPlay2")) && realUrl.endsWith(".pls");
+    return (addressPrefixName == QString("QMPlay2")) && realUrl.endsWith(".pls");
 }
 
 void Functions::hFlip(quint8 *data, int linesize, int height, int width)
@@ -799,11 +818,15 @@ quint32 Functions::getBestSampleRate()
 
 bool Functions::wrapMouse(QWidget *widget, QPoint &mousePos, int margin)
 {
+    // Qt4 compatibility: Use platform macros instead of QGuiApplication::platformName()
+    // Mouse wrapping is primarily for Windows and X11, not macOS
     static bool allowed =
-            QGuiApplication::platformName() == QStringLiteral("windows")
-         || QGuiApplication::platformName() == QStringLiteral("xcb")
-#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
-         || QGuiApplication::platformName().contains(QStringLiteral("wayland"))
+#if defined(Q_OS_WIN)
+            true
+#elif defined(Q_OS_LINUX) || defined(Q_OS_UNIX)
+            true  // X11/Wayland on Linux
+#else
+            false // macOS and other platforms
 #endif
     ;
     if (!allowed)
@@ -987,30 +1010,32 @@ QMatrix4x4 Functions::getColorPrimariesToSpecifiedMatrix(const QVector2D &wp, ar
 
     auto getRgbToXyzMatrix = [](const QVector2D &wp, const QVector2D &r, const QVector2D &g, const QVector2D &b) {
         auto xyToXyz = [](const QVector2D &v) {
+            // Qt4: Use .x() and .y() instead of subscript operator
             return QVector3D(
-                v[0] / v[1],
+                v.x() / v.y(),
                 1.0f,
-                (1.0f - v[0] - v[1]) / v[1]
+                (1.0f - v.x() - v.y()) / v.y()
             );
         };
 
         const auto wpXyz = xyToXyz(wp);
 
-        const auto ry = r[1];
-        const auto gy = g[1];
-        const auto by = b[1];
+        // Qt4: Use .x() and .y() instead of subscript operator
+        const auto ry = r.y();
+        const auto gy = g.y();
+        const auto by = b.y();
 
-        const auto Xr = r[0] / ry;
+        const auto Xr = r.x() / ry;
         const auto Yr = 1.0f;
-        const auto Zr = (1.0f - r[0] - r[1]) / ry;
+        const auto Zr = (1.0f - r.x() - r.y()) / ry;
 
-        const auto Xg = g[0] / gy;
+        const auto Xg = g.x() / gy;
         const auto Yg = 1.0f;
-        const auto Zg = (1.0f - g[0] - g[1]) / gy;
+        const auto Zg = (1.0f - g.x() - g.y()) / gy;
 
-        const auto Xb = b[0] / by;
+        const auto Xb = b.x() / by;
         const auto Yb = 1.0f;
-        const auto Zb = (1.0f - b[0] - b[1]) / by;
+        const auto Zb = (1.0f - b.x() - b.y()) / by;
 
         const auto S = QMatrix4x4(
             Xr,   Xg,   Xb,   0.0f,
@@ -1019,11 +1044,12 @@ QMatrix4x4 Functions::getColorPrimariesToSpecifiedMatrix(const QVector2D &wp, ar
             0.0f, 0.0f, 0.0f, 1.0f
         ).inverted().map(wpXyz);
 
+        // Qt4: Use .x(), .y(), .z() instead of subscript operator
         return QMatrix4x4(
-            S[0] * Xr, S[1] * Xg, S[2] * Xb, 0.0f,
-            S[0] * Yr, S[1] * Yg, S[2] * Yb, 0.0f,
-            S[0] * Zr, S[1] * Zg, S[2] * Zb, 0.0f,
-            0.0f,      0.0f,      0.0f,      1.0f
+            S.x() * Xr, S.y() * Xg, S.z() * Xb, 0.0f,
+            S.x() * Yr, S.y() * Yg, S.z() * Yb, 0.0f,
+            S.x() * Zr, S.y() * Zg, S.z() * Zb, 0.0f,
+            0.0f,       0.0f,       0.0f,       1.0f
         );
     };
 
@@ -1057,19 +1083,21 @@ QMatrix4x4 Functions::getColorPrimariesToSpecifiedMatrix(AVColorPrimaries colorP
 
 bool Functions::compareText(const QString &a, const QString &b)
 {
-    QRegularExpression rx(R"(\d+)");
+    // Qt4: Use QRegExp instead of QRegularExpression
+    QRegExp rx("\\d+");
 
-    auto fillMatches = [](auto &&matchIt, auto &&matches) {
-        while (matchIt.hasNext())
+    auto fillMatches = [&rx](const QString &text, std::vector<std::pair<int, int>> &matches) {
+        int pos = 0;
+        while ((pos = rx.indexIn(text, pos)) != -1)
         {
-            const auto match = matchIt.next();
-            matches.emplace_back(match.capturedStart(), match.captured().length());
+            matches.emplace_back(pos, rx.matchedLength());
+            pos += rx.matchedLength();
         }
     };
 
     std::vector<std::pair<int, int>> matchesA, matchesB;
-    fillMatches(rx.globalMatch(a), matchesA);
-    fillMatches(rx.globalMatch(b), matchesB);
+    fillMatches(a, matchesA);
+    fillMatches(b, matchesB);
 
     const int n = qMin(matchesA.size(), matchesB.size());
     if (n > 0)
@@ -1116,7 +1144,7 @@ bool Functions::hasTouchScreen()
             return true;
         }
     }
-#else
+#elif QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     const auto touchDevs = QTouchDevice::devices();
     for (auto &&touchDev : touchDevs)
     {
@@ -1125,6 +1153,9 @@ bool Functions::hasTouchScreen()
             return true;
         }
     }
+#else
+    // Qt4: No touch device detection available
+    return false;
 #endif
     return false;
 }
@@ -1145,7 +1176,8 @@ QString Functions::getSeqFile(const QString &dir, const QString &ext, const QStr
     quint16 num = 0;
     for (const QString &f : QDir(dir).entryList({QString("QMPlay2_%1_?????%2").arg(frag, ext)}, QDir::Files, QDir::Name))
     {
-        const quint16 n = QStringView(f).mid(8 + frag.size() + 1, 5).toUShort();
+        // Qt4: No QStringView, use QString::mid() directly
+        const quint16 n = f.mid(8 + frag.size() + 1, 5).toUShort();
         if (n > num)
             num = n;
     }
@@ -1189,6 +1221,31 @@ std::pair<QString, QString> Functions::determineExtFmt(const QList<StreamInfo *>
     return {ext, fmt};
 }
 
+// Qt4 helper for getUserDoubleValue
+namespace {
+class DoubleValueHelper : public QObject
+{
+    Q_OBJECT
+public:
+    DoubleValueHelper(const function<void(double)> &callback, QObject *parent = nullptr)
+        : QObject(parent), m_callback(callback) {}
+
+public slots:
+    void onValueChanged(double newValue)
+    {
+        if (qFuzzyIsNull(newValue))
+            m_callback(0.0);
+        else if (qFuzzyCompare(newValue, 1.0))
+            m_callback(1.0);
+        else
+            m_callback(newValue);
+    }
+
+private:
+    function<void(double)> m_callback;
+};
+}
+
 void Functions::getUserDoubleValue(QWidget *parent, const QString &title, const QString &label, double value, double min, double max, int decimals, double step, const function<void(double)> &onChanged)
 {
     Q_ASSERT(onChanged);
@@ -1198,18 +1255,17 @@ void Functions::getUserDoubleValue(QWidget *parent, const QString &title, const
     input.setWindowTitle(title);
     input.setTextValue(label);
     input.setDoubleDecimals(decimals);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
     input.setDoubleStep(step);
+#else
+    // Qt4: setDoubleStep() doesn't exist, step is handled automatically
+    Q_UNUSED(step)
+#endif
     input.setDoubleRange(min, max);
     input.setDoubleValue(value);
 
-    QObject::connect(&input, &QInputDialog::doubleValueChanged, parent, [&onChanged](double newValue) {
-        if (qFuzzyIsNull(newValue))
-            onChanged(0.0);
-        else if (qFuzzyCompare(newValue, 1.0))
-            onChanged(1.0);
-        else
-            onChanged(newValue);
-    });
+    DoubleValueHelper helper(onChanged, &input);
+    QObject::connect(&input, SIGNAL(doubleValueChanged(double)), &helper, SLOT(onValueChanged(double)));
 
     onChanged(value);
 
@@ -1218,3 +1274,5 @@ void Functions::getUserDoubleValue(QWidget *parent, const QString &title, const
         onChanged(value);
     }
 }
+
+#include "Functions.moc"
diff --git a/src/qmplay2/Functions.hpp b/src/qmplay2/Functions.hpp
index 7ee0962b..c76bf59a 100644
--- a/src/qmplay2/Functions.hpp
+++ b/src/qmplay2/Functions.hpp
@@ -48,7 +48,7 @@ class QRect;
 #ifdef Q_OS_WIN
     #include <windows.h>
 #else
-    #ifdef Q_OS_MACOS
+    #ifdef Q_OS_MAC
         #include <mach/mach_time.h>
     #else
         #include <time.h>
@@ -90,7 +90,7 @@ namespace Functions
         QueryPerformanceFrequency(&Frequency);
         QueryPerformanceCounter(&Counter);
         return (double)Counter.QuadPart / (double)Frequency.QuadPart;
-#elif defined Q_OS_MACOS
+#elif defined Q_OS_MAC
         mach_timebase_info_data_t mach_base_info;
         mach_timebase_info(&mach_base_info);
         return ((double)mach_absolute_time() * (double)mach_base_info.numer) / (1000000000.0 * (double)mach_base_info.denom);
diff --git a/src/qmplay2/GPUInstance.cpp b/src/qmplay2/GPUInstance.cpp
index 9a58f46a..e2575149 100644
--- a/src/qmplay2/GPUInstance.cpp
+++ b/src/qmplay2/GPUInstance.cpp
@@ -21,8 +21,8 @@
 
 #ifdef USE_OPENGL
 #   include <opengl/OpenGLInstance.hpp>
-#   include <QOffscreenSurface>
-#   include <QOpenGLFunctions>
+#   include <QtOpenGL/QGLPixelBuffer>
+#   include <QtOpenGL/QGLContext>
 #endif
 #ifdef USE_VULKAN
 #   include <vulkan/VulkanInstance.hpp>
@@ -45,15 +45,13 @@ shared_ptr<GPUInstance> GPUInstance::create()
 #   ifdef USE_OPENGL
         if (!vkInstance->isPhysicalDeviceGpu() && !sets.getBool("Vulkan/UserApplied"))
         {
-            QOffscreenSurface surface;
-            QOpenGLContext glCtx;
-
-            surface.create();
-            if (!glCtx.create() || !glCtx.makeCurrent(&surface))
+            // Qt4: Use QGLPixelBuffer instead of QOffscreenSurface
+            QGLPixelBuffer pbuffer(1, 1);
+            if (!pbuffer.isValid() || !pbuffer.makeCurrent())
                 return vkInstance;
 
-            const auto currentRenderer = QByteArray(reinterpret_cast<const char *>(glCtx.functions()->glGetString(GL_RENDERER))).toLower();
-            for (auto &&swRendererName : {"llvmpipe", "software", "swrast", "softpipe"})
+            const auto currentRenderer = QByteArray(reinterpret_cast<const char *>(glGetString(GL_RENDERER))).toLower();
+            foreach (const char *swRendererName, {"llvmpipe", "software", "swrast", "softpipe"})
             {
                 if (currentRenderer.contains(swRendererName))
                     return vkInstance;
diff --git a/src/qmplay2/IPC.hpp b/src/qmplay2/IPC.hpp
index 8341e743..f3b0b4c9 100644
--- a/src/qmplay2/IPC.hpp
+++ b/src/qmplay2/IPC.hpp
@@ -31,7 +31,7 @@ class IPCServerPriv;
 
 class QSocketNotifier;
 
-class QMPLAY2SHAREDLIB_EXPORT IPCSocket final : public QIODevice
+class QMPLAY2SHAREDLIB_EXPORT IPCSocket : public QIODevice
 {
     Q_OBJECT
     friend class IPCServer;
@@ -41,8 +41,8 @@ public:
 
     bool isConnected() const;
 
-    bool open(OpenMode mode) override;
-    void close() override;
+    bool open(OpenMode mode);
+    void close();
 
 private slots:
     void socketReadActive();
@@ -54,15 +54,15 @@ private:
     IPCSocket(int socket, QObject *parent);
 #endif
 
-    qint64 readData(char *data, qint64 maxSize) override;
-    qint64 writeData(const char *data, qint64 maxSize) override;
+    qint64 readData(char *data, qint64 maxSize);
+    qint64 writeData(const char *data, qint64 maxSize);
 
     IPCSocketPriv *m_priv;
 };
 
 /**/
 
-class QMPLAY2SHAREDLIB_EXPORT IPCServer final : public QObject
+class QMPLAY2SHAREDLIB_EXPORT IPCServer : public QObject
 {
     Q_OBJECT
 public:
diff --git a/src/qmplay2/IPC_Unix.cpp b/src/qmplay2/IPC_Unix.cpp
index 21f0d80e..a89242d2 100644
--- a/src/qmplay2/IPC_Unix.cpp
+++ b/src/qmplay2/IPC_Unix.cpp
@@ -30,7 +30,7 @@ static sockaddr_un getSockAddr(const QString &fileName)
     sockaddr_un sockAddr;
     sockAddr.sun_family = AF_UNIX;
     strncpy(sockAddr.sun_path, fileName.toLocal8Bit(), sizeof sockAddr.sun_path - 1);
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
     sockAddr.sun_len = SUN_LEN(&sockAddr);
 #endif
     return sockAddr;
diff --git a/src/qmplay2/IPC_Windows.cpp b/src/qmplay2/IPC_Windows.cpp
index aa252c2e..b7fc31f5 100644
--- a/src/qmplay2/IPC_Windows.cpp
+++ b/src/qmplay2/IPC_Windows.cpp
@@ -37,7 +37,7 @@ public:
     }
 
 private:
-    void run() override
+    void run()
     {
         WaitForSingleObject(m_event, INFINITE);
     }
diff --git a/src/qmplay2/InDockW.cpp b/src/qmplay2/InDockW.cpp
index eee4850a..d0b576b5 100644
--- a/src/qmplay2/InDockW.cpp
+++ b/src/qmplay2/InDockW.cpp
@@ -43,6 +43,9 @@ InDockW::InDockW(const QColor &grad1, const QColor &grad2, const QColor &qmpTxt)
     setMouseTracking(true);
     updateSettings();
 }
+InDockW::~InDockW()
+{
+}
 
 QWidget *InDockW::getWidget()
 {
diff --git a/src/qmplay2/InDockW.hpp b/src/qmplay2/InDockW.hpp
index b08942e6..225f0d42 100644
--- a/src/qmplay2/InDockW.hpp
+++ b/src/qmplay2/InDockW.hpp
@@ -23,11 +23,12 @@
 #include <QPointer>
 #include <QWidget>
 
-class QMPLAY2SHAREDLIB_EXPORT InDockW final : public QWidget
+class QMPLAY2SHAREDLIB_EXPORT InDockW : public QWidget
 {
     Q_OBJECT
 public:
     InDockW(const QColor &, const QColor &, const QColor &);
+    ~InDockW();
 
     QWidget *getWidget();
     void setLoseHeight(int lh);
@@ -49,11 +50,11 @@ private slots:
 public:
     void setWidget(QWidget *newW);
 protected:
-    void resizeEvent(QResizeEvent *) override;
-    void paintEvent(QPaintEvent *) override;
-    void enterEvent(Q_ENTER_EVENT *) override;
-    void leaveEvent(QEvent *) override;
-    bool event(QEvent *) override;
+    void resizeEvent(QResizeEvent *);
+    void paintEvent(QPaintEvent *);
+    void enterEvent(Q_ENTER_EVENT *);
+    void leaveEvent(QEvent *);
+    bool event(QEvent *);
 signals:
     void resized(const QSize &size);
     void itemDropped(const QString &);
diff --git a/src/qmplay2/LibASS.cpp b/src/qmplay2/LibASS.cpp
index 39090c72..c36d35b8 100644
--- a/src/qmplay2/LibASS.cpp
+++ b/src/qmplay2/LibASS.cpp
@@ -492,9 +492,10 @@ bool LibASS::getASS(shared_ptr<QMPlay2OSD> &osd, double pos)
     if (!ass_sub_track || !ass_sub_renderer || !W || !H)
         return false;
 
-    if (qIsNaN(pos))
+    // Qt4/Qt5: Use std::isnan instead of std::isnan
+    if (std::isnan(pos))
         pos = m_lastPos;
-    if (qIsNaN(pos))
+    if (std::isnan(pos))
         return false;
 
     double _fontScale = fontScale;
@@ -573,7 +574,7 @@ void LibASS::closeASS()
     ass_sub_track = nullptr;
     ass_sub_renderer = nullptr;
     ass_clear_fonts(m_subsAss);
-    m_lastPos = qQNaN();
+    m_lastPos = std::numeric_limits<double>::quiet_NaN();
     m_assIDs.clear();
 }
 
diff --git a/src/qmplay2/LibASS.hpp b/src/qmplay2/LibASS.hpp
index 52ff969a..de970b00 100644
--- a/src/qmplay2/LibASS.hpp
+++ b/src/qmplay2/LibASS.hpp
@@ -18,6 +18,8 @@
 
 #pragma once
 
+#include <limits>
+
 #include <QMPlay2Lib.hpp>
 
 #include <QByteArray>
@@ -72,7 +74,7 @@ public:
     void addASSEvents(const QList<QByteArray> &events, double start, double duration);
     void addASSEvent(const QByteArray &, double, double);
     void flushASSEvents();
-    bool getASS(std::shared_ptr<QMPlay2OSD> &osd, double pos = qQNaN());
+    bool getASS(std::shared_ptr<QMPlay2OSD> &osd, double pos = std::numeric_limits<double>::quiet_NaN());
     void closeASS();
 
 private:
@@ -98,7 +100,7 @@ private:
     ass_renderer *ass_sub_renderer;
     QList<ass_style *> ass_sub_styles_copy;
     bool hasASSData;
-    double m_lastPos = qQNaN();
+    double m_lastPos = std::numeric_limits<double>::quiet_NaN();
     std::set<int> m_assIDs;
 
 #ifdef USE_VULKAN
diff --git a/src/qmplay2/LineEdit.cpp b/src/qmplay2/LineEdit.cpp
index 3691d3a3..06cbaa9d 100644
--- a/src/qmplay2/LineEdit.cpp
+++ b/src/qmplay2/LineEdit.cpp
@@ -25,13 +25,20 @@
 LineEdit::LineEdit(QWidget *parent)
     : QLineEdit(parent)
 {
-    QAction *clearAct = addAction(QMPlay2Core.getIconFromTheme("edit-clear"), TrailingPosition);
-    connect(clearAct, &QAction::triggered, this, &LineEdit::clearText);
-    connect(this, &LineEdit::textChanged, this, [=](const QString &text) {
-        clearAct->setVisible(!text.isEmpty());
-    });
-    clearAct->setToolTip(tr("Clear"));
-    clearAct->setVisible(false);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
+    m_clearAct = addAction(QMPlay2Core.getIconFromTheme("edit-clear"), TrailingPosition);
+#else
+    // Qt4: QLineEdit::addAction() doesn't exist, use a QAction with no position
+    m_clearAct = new QAction(QMPlay2Core.getIconFromTheme("edit-clear"), tr("Clear"), this);
+    // Note: In Qt4, we can't add actions directly to QLineEdit, so this will be less integrated
+#endif
+    connect(m_clearAct, SIGNAL(triggered()), this, SLOT(clearText()));
+    connect(this, SIGNAL(textChanged(QString)), this, SLOT(onTextChanged(QString)));
+    m_clearAct->setToolTip(tr("Clear"));
+    m_clearAct->setVisible(false);
+}
+LineEdit::~LineEdit()
+{
 }
 
 void LineEdit::clearText()
@@ -39,3 +46,8 @@ void LineEdit::clearText()
     clear();
     emit clearButtonClicked();
 }
+
+void LineEdit::onTextChanged(const QString &text)
+{
+    m_clearAct->setVisible(!text.isEmpty());
+}
diff --git a/src/qmplay2/LineEdit.hpp b/src/qmplay2/LineEdit.hpp
index 7c20a4a4..9e28b2f5 100644
--- a/src/qmplay2/LineEdit.hpp
+++ b/src/qmplay2/LineEdit.hpp
@@ -22,15 +22,25 @@
 
 #include <QLineEdit>
 
-class QMPLAY2SHAREDLIB_EXPORT LineEdit final : public QLineEdit
+class QAction;
+
+class QMPLAY2SHAREDLIB_EXPORT LineEdit : public QLineEdit
 {
     Q_OBJECT
 
 public:
     LineEdit(QWidget *parent = nullptr);
+    ~LineEdit();
 
+public slots:
     void clearText();
 
 signals:
     void clearButtonClicked();
+
+private slots:
+    void onTextChanged(const QString &text);
+
+private:
+    QAction *m_clearAct;
 };
diff --git a/src/qmplay2/Module.cpp b/src/qmplay2/Module.cpp
index 750d8677..04a0db78 100644
--- a/src/qmplay2/Module.cpp
+++ b/src/qmplay2/Module.cpp
@@ -35,7 +35,7 @@ void Module::videoDeintSave()
 void Module::setInstances(bool &restartPlaying)
 {
     QMutexLocker locker(&mutex);
-    for (ModuleCommon *mc : std::as_const(instances))
+    for (ModuleCommon *mc : instances)
         if (!mc->set())
             restartPlaying = true;
 }
diff --git a/src/qmplay2/Module.hpp b/src/qmplay2/Module.hpp
index f858aa96..84342929 100644
--- a/src/qmplay2/Module.hpp
+++ b/src/qmplay2/Module.hpp
@@ -127,7 +127,8 @@ template<typename T>
 void Module::setInstance()
 {
     QMutexLocker locker(&mutex);
-    for (ModuleCommon *mc : std::as_const(instances))
+    // Qt4: No std::as_const, iterate directly
+    foreach (ModuleCommon *mc, instances)
     {
         T *t = dynamic_cast<T *>(mc);
         if (t)
diff --git a/src/qmplay2/NetworkAccess.cpp b/src/qmplay2/NetworkAccess.cpp
index 8a80b1b3..fa0da099 100644
--- a/src/qmplay2/NetworkAccess.cpp
+++ b/src/qmplay2/NetworkAccess.cpp
@@ -72,7 +72,7 @@ public:
         m_retries(params.retries),
         m_retryInterval(params.retryInterval),
         m_ctx(nullptr),
-        m_error(NetworkReply::Error::Ok),
+        m_error(NetworkReply::ErrorOk),
         m_aborted(false),
         m_afterOpen(false)
     {}
@@ -107,11 +107,11 @@ public:
     };
 
 private:
-    void run() override
+    void run()
     {
         const QString scheme = Functions::getUrlScheme(m_url);
         if (scheme.isEmpty() || scheme == "file")
-            m_error = NetworkReply::Error::UnsupportedScheme;
+            m_error = NetworkReply::ErrorUnsupportedScheme;
         else
         {
             AVIOInterruptCB interruptCB = {(int(*)(void*))::interruptCB, m_status};
@@ -133,37 +133,37 @@ private:
                 const int ret = avio_open2(&m_ctx, url, AVIO_FLAG_READ | AVIO_FLAG_DIRECT, &interruptCB, &options);
                 if (ret >= 0)
                 {
-                    m_error = NetworkReply::Error::Ok;
+                    m_error = NetworkReply::ErrorOk;
                     break;
                 }
                 switch (ret)
                 {
                     case AVERROR_HTTP_BAD_REQUEST:
-                        m_error = NetworkReply::Error::Connection400;
+                        m_error = NetworkReply::ErrorConnection400;
                         break;
                     case AVERROR_HTTP_UNAUTHORIZED:
-                        m_error = NetworkReply::Error::Connection401;
+                        m_error = NetworkReply::ErrorConnection401;
                         break;
                     case AVERROR_HTTP_FORBIDDEN:
-                        m_error = NetworkReply::Error::Connection403;
+                        m_error = NetworkReply::ErrorConnection403;
                         break;
                     case AVERROR_HTTP_NOT_FOUND:
-                        m_error = NetworkReply::Error::Connection404;
+                        m_error = NetworkReply::ErrorConnection404;
                         break;
                     case AVERROR_HTTP_OTHER_4XX:
-                        m_error = NetworkReply::Error::Connection4XX;
+                        m_error = NetworkReply::ErrorConnection4XX;
                         break;
                     case AVERROR_HTTP_SERVER_ERROR:
-                        m_error = NetworkReply::Error::Connection5XX;
+                        m_error = NetworkReply::ErrorConnection5XX;
                         continue; // Continue if server error (e.g. Service Temporarily Unavailable)
                     default:
-                        m_error = NetworkReply::Error::Connection;
+                        m_error = NetworkReply::ErrorConnection;
                         continue; // Continue if connection error
                 }
                 break;
             } while (--m_retries > 0 && doSleep(m_aborted, m_retryInterval));
 
-            if (m_error == NetworkReply::Error::Ok)
+            if (m_error == NetworkReply::ErrorOk)
             {
                 char *cookies = nullptr;
                 if (av_opt_get(m_ctx, "cookies", AV_OPT_SEARCH_CHILDREN, (uint8_t **)&cookies) >= 0)
@@ -182,7 +182,7 @@ private:
 
                 int64_t size = avio_size(m_ctx);
                 if (size >= m_maxSize)
-                    m_error = NetworkReply::Error::FileTooLarge;
+                    m_error = NetworkReply::ErrorFileTooLarge;
                 else
                 {
                     if (size < -1)
@@ -199,7 +199,7 @@ private:
                         if (received < 0) //Error
                         {
                             if (received != AVERROR_EOF)
-                                m_error = NetworkReply::Error::Download;
+                                m_error = NetworkReply::ErrorDownload;
                             break;
                         }
 
@@ -224,7 +224,7 @@ private:
 
                         if (pos + chunkSize >= m_maxSize)
                         {
-                            m_error = NetworkReply::Error::FileTooLarge;
+                            m_error = NetworkReply::ErrorFileTooLarge;
                             break;
                         }
                     }
@@ -257,11 +257,11 @@ void NetworkReply::abort()
 
 bool NetworkReply::hasError() const
 {
-    return (error() != Error::Ok);
+    return (error() != ErrorOk);
 }
 NetworkReply::Error NetworkReply::error() const
 {
-    return m_priv->m_aborted ? Error::Aborted : m_priv->m_error;
+    return m_priv->m_aborted ? ErrorAborted : m_priv->m_error;
 }
 
 QByteArray NetworkReply::getCookies() const
@@ -300,7 +300,7 @@ NetworkReply::Wait NetworkReply::waitForFinished(int ms)
             }
         }
     }
-    return ret ? (hasError() ? Wait::Error : Wait::Ok) : Wait::Timeout;
+    return ret ? (hasError() ? WaitError : WaitOk) : WaitTimeout;
 }
 
 NetworkReply::NetworkReply(const QString &url, const QByteArray &postData, const QByteArray &rawHeaders, const NetworkAccessParams &params) :
@@ -378,7 +378,7 @@ bool NetworkAccess::startAndWait(IOController<NetworkReply> &ioCtrl, const QStri
     m_params->retries = oldRetries;
     if (ok)
     {
-        if (ioCtrl->waitForFinished() == NetworkReply::Wait::Ok)
+        if (ioCtrl->waitForFinished() == NetworkReply::WaitOk)
             return true;
         ioCtrl.reset();
     }
diff --git a/src/qmplay2/NetworkAccess.hpp b/src/qmplay2/NetworkAccess.hpp
index dd020baa..669eb5a0 100644
--- a/src/qmplay2/NetworkAccess.hpp
+++ b/src/qmplay2/NetworkAccess.hpp
@@ -26,7 +26,7 @@
 class NetworkReplyPriv;
 struct NetworkAccessParams;
 
-class QMPLAY2SHAREDLIB_EXPORT NetworkReply final : public QObject, public BasicIO
+class QMPLAY2SHAREDLIB_EXPORT NetworkReply : public QObject, public BasicIO
 {
     Q_OBJECT
 
@@ -34,36 +34,36 @@ class QMPLAY2SHAREDLIB_EXPORT NetworkReply final : public QObject, public BasicI
     friend class NetworkAccess;
 
 public:
-    enum class Error
+    enum Error
     {
-        Ok = 0,
-        UnsupportedScheme,
-        Connection,
-        Connection400,
-        Connection401,
-        Connection403,
-        Connection404,
-        Connection4XX,
-        Connection5XX,
-        FileTooLarge,
-        Download,
-        Aborted
+        ErrorOk = 0,
+        ErrorUnsupportedScheme,
+        ErrorConnection,
+        ErrorConnection400,
+        ErrorConnection401,
+        ErrorConnection403,
+        ErrorConnection404,
+        ErrorConnection4XX,
+        ErrorConnection5XX,
+        ErrorFileTooLarge,
+        ErrorDownload,
+        ErrorAborted
     };
-    Q_ENUM(Error)
 
-    enum class Wait
+    enum Wait
     {
-        Ok = 0,
-        Timeout,
-        Error
+        WaitOk = 0,
+        WaitTimeout,
+        WaitError
     };
-    Q_ENUM(Wait)
+
+    Q_ENUMS(Error Wait)
 
     ~NetworkReply();
 
     QString url() const;
 
-    void abort() override;
+    void abort();
 
     bool hasError() const;
     Error error() const;
diff --git a/src/qmplay2/NetworkAccessJS.cpp b/src/qmplay2/NetworkAccessJS.cpp
index 16a9f829..06215b94 100644
--- a/src/qmplay2/NetworkAccessJS.cpp
+++ b/src/qmplay2/NetworkAccessJS.cpp
@@ -83,25 +83,16 @@ int NetworkAccessJS::start(QJSValue args, QJSValue onFinished, QJSValue onProgre
     auto reply = m_net->start(url, postData, rawHeaders);
     const auto id = QMPlay2Core.getCommonJS()->insertNetworkReply(reply);
 
-    connect(reply, &NetworkReply::finished,
-            reply, [=]() mutable {
-        if (onFinished.isCallable())
-        {
-            onFinished.call({
-                static_cast<int>(reply->error()),
-                QString(reply->readAll()),
-                QString(reply->getCookies()),
-                id,
-            });
-        }
-        reply->deleteLater();
-    });
+    ReplyCallbacks callbacks;
+    callbacks.id = id;
+    callbacks.onFinished = onFinished;
+    callbacks.onProgress = onProgress;
+    m_replyCallbacks[reply] = callbacks;
+
+    connect(reply, SIGNAL(finished()), this, SLOT(onReplyFinished()));
     if (onProgress.isCallable())
     {
-        connect(reply, &NetworkReply::downloadProgress,
-                this, [=](int pos, int total) mutable {
-            onProgress.call({pos, total});
-        });
+        connect(reply, SIGNAL(downloadProgress(int,int)), this, SLOT(onReplyDownloadProgress(int,int)));
     }
 
     return id;
@@ -131,3 +122,45 @@ QVariantMap NetworkAccessJS::startAndWait(QJSValue args, const int ioControllerI
         {"ok", false}
     };
 }
+
+void NetworkAccessJS::onReplyFinished()
+{
+    NetworkReply *reply = qobject_cast<NetworkReply*>(sender());
+    if (!reply)
+        return;
+
+    auto it = m_replyCallbacks.find(reply);
+    if (it != m_replyCallbacks.end())
+    {
+        ReplyCallbacks callbacks = it.value();
+        m_replyCallbacks.erase(it);
+
+        if (callbacks.onFinished.isCallable())
+        {
+            callbacks.onFinished.call({
+                static_cast<int>(reply->error()),
+                QString(reply->readAll()),
+                QString(reply->getCookies()),
+                callbacks.id,
+            });
+        }
+        reply->deleteLater();
+    }
+}
+
+void NetworkAccessJS::onReplyDownloadProgress(int pos, int total)
+{
+    NetworkReply *reply = qobject_cast<NetworkReply*>(sender());
+    if (!reply)
+        return;
+
+    auto it = m_replyCallbacks.find(reply);
+    if (it != m_replyCallbacks.end())
+    {
+        ReplyCallbacks &callbacks = it.value();
+        if (callbacks.onProgress.isCallable())
+        {
+            callbacks.onProgress.call({pos, total});
+        }
+    }
+}
diff --git a/src/qmplay2/NetworkAccessJS.hpp b/src/qmplay2/NetworkAccessJS.hpp
index 8cc77097..4d8586b6 100644
--- a/src/qmplay2/NetworkAccessJS.hpp
+++ b/src/qmplay2/NetworkAccessJS.hpp
@@ -29,6 +29,17 @@ public:
     Q_INVOKABLE int start(QJSValue args, QJSValue onFinished = {}, QJSValue onProgress = {});
     Q_INVOKABLE QVariantMap startAndWait(QJSValue args, const int ioControllerId);
 
+private slots:
+    void onReplyFinished();
+    void onReplyDownloadProgress(int pos, int total);
+
 private:
+    struct ReplyCallbacks {
+        int id;
+        QJSValue onFinished;
+        QJSValue onProgress;
+    };
+
     NetworkAccess *const m_net;
+    QHash<QObject*, ReplyCallbacks> m_replyCallbacks;
 };
diff --git a/src/qmplay2/NotifiesAndroid.hpp b/src/qmplay2/NotifiesAndroid.hpp
index 81791835..b962090b 100644
--- a/src/qmplay2/NotifiesAndroid.hpp
+++ b/src/qmplay2/NotifiesAndroid.hpp
@@ -22,10 +22,10 @@
 
 #include <QObject>
 
-class NotifiesAndroid final : public QObject, public Notifies
+class NotifiesAndroid : public QObject, public Notifies
 {
     Q_OBJECT
 
-    bool doNotify(const QString &title, const QString &message, const int ms, const QPixmap &pixmap, const int iconId = 0) override;
-    bool doNotify(const QString &title, const QString &message, const int ms, const QImage &image, const int iconId = 0) override;
+    bool doNotify(const QString &title, const QString &message, const int ms, const QPixmap &pixmap, const int iconId = 0);
+    bool doNotify(const QString &title, const QString &message, const int ms, const QImage &image, const int iconId = 0);
 };
diff --git a/src/qmplay2/NotifiesFreedesktop.hpp b/src/qmplay2/NotifiesFreedesktop.hpp
index 4a26d5fa..ed89d3b9 100644
--- a/src/qmplay2/NotifiesFreedesktop.hpp
+++ b/src/qmplay2/NotifiesFreedesktop.hpp
@@ -26,7 +26,7 @@
 class OrgFreedesktopNotificationsInterface;
 class QDBusPendingCallWatcher;
 
-class QMPLAY2SHAREDLIB_EXPORT NotifiesFreedesktop final : public QObject, public Notifies
+class QMPLAY2SHAREDLIB_EXPORT NotifiesFreedesktop : public QObject, public Notifies
 {
     Q_OBJECT
 
@@ -35,8 +35,8 @@ public:
     ~NotifiesFreedesktop();
 
 private:
-    bool doNotify(const QString &title, const QString &message, const int ms, const QPixmap &pixmap, const int iconId = 0) override;
-    bool doNotify(const QString &title, const QString &message, const int ms, const QImage &image, const int iconId = 0) override;
+    bool doNotify(const QString &title, const QString &message, const int ms, const QPixmap &pixmap, const int iconId = 0);
+    bool doNotify(const QString &title, const QString &message, const int ms, const QImage &image, const int iconId = 0);
 
 private slots:
     void callFinished(QDBusPendingCallWatcher *watcher);
diff --git a/src/qmplay2/NotifiesMacOS.hpp b/src/qmplay2/NotifiesMacOS.hpp
index 64ff1d52..007cdd62 100644
--- a/src/qmplay2/NotifiesMacOS.hpp
+++ b/src/qmplay2/NotifiesMacOS.hpp
@@ -22,15 +22,15 @@
 
 #include <QList>
 
-class NotifiesMacOS final : public Notifies
+class NotifiesMacOS : public Notifies
 {
 public:
     NotifiesMacOS();
     ~NotifiesMacOS();
 
 private:
-    bool doNotify(const QString &title, const QString &message, const int ms, const QPixmap &pixmap, const int iconId) override;
-    bool doNotify(const QString &title, const QString &message, const int ms, const QImage &image, const int iconId) override;
+    bool doNotify(const QString &title, const QString &message, const int ms, const QPixmap &pixmap, const int iconId);
+    bool doNotify(const QString &title, const QString &message, const int ms, const QImage &image, const int iconId);
 
     void *m_notificationItem;
     QList<void *> m_notifications;
diff --git a/src/qmplay2/NotifiesMacOS.mm b/src/qmplay2/NotifiesMacOS.mm
index e4147435..54cd511e 100644
--- a/src/qmplay2/NotifiesMacOS.mm
+++ b/src/qmplay2/NotifiesMacOS.mm
@@ -57,11 +57,29 @@ bool NotifiesMacOS::doNotify(const QString &title, const QString &message, const
 
     NSUserNotification *notification = [[NSUserNotification alloc] init];
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
     notification.title = title.toNSString();
     notification.informativeText = message.toNSString();
+#else
+    // Qt4: No toNSString(), use UTF8 C string
+    notification.title = [NSString stringWithUTF8String:title.toUtf8().constData()];
+    notification.informativeText = [NSString stringWithUTF8String:message.toUtf8().constData()];
+#endif
     if (!pixmap.isNull())
     {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
         auto cgimage = pixmap.toImage().toCGImage();
+#else
+        // Qt4: No toCGImage(), manual conversion
+        QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32);
+        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+        CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, img.bits(), img.byteCount(), NULL);
+        CGImageRef cgimage = CGImageCreate(img.width(), img.height(), 8, 32, img.bytesPerLine(),
+                                           colorSpace, kCGImageAlphaFirst | kCGBitmapByteOrder32Host,
+                                           dataProvider, NULL, false, kCGRenderingIntentDefault);
+        CGDataProviderRelease(dataProvider);
+        CGColorSpaceRelease(colorSpace);
+#endif
         auto bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
         auto image = [[NSImage alloc] init];
         [image addRepresentation:bitmapRep];
@@ -76,6 +94,7 @@ bool NotifiesMacOS::doNotify(const QString &title, const QString &message, const
         [notification release];
     else
     {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
         QTimer::singleShot(ms + 500 /* Add 500 ms for animation */, [this, notification] {
             const int idx = m_notifications.indexOf(notification);
             if (idx > -1)
@@ -85,6 +104,12 @@ bool NotifiesMacOS::doNotify(const QString &title, const QString &message, const
             }
         });
         m_notifications.append(notification);
+#else
+        // Qt4: QTimer::singleShot doesn't support lambda, and NotifiesMacOS is not a QObject.
+        // For Qt4, we simply let the notification persist. The system will handle cleanup.
+        // Store it so we can clean up in the destructor.
+        m_notifications.append(notification);
+#endif
     }
 
     return true;
diff --git a/src/qmplay2/NotifiesTray.hpp b/src/qmplay2/NotifiesTray.hpp
index df0f5616..0756b26b 100644
--- a/src/qmplay2/NotifiesTray.hpp
+++ b/src/qmplay2/NotifiesTray.hpp
@@ -20,16 +20,16 @@
 
 #include <Notifies.hpp>
 
-class QMPLAY2SHAREDLIB_EXPORT NotifiesTray final : public Notifies
+class QMPLAY2SHAREDLIB_EXPORT NotifiesTray : public Notifies
 {
 public:
     NotifiesTray(QSystemTrayIcon *tray);
     ~NotifiesTray() final = default;
 
 private:
-    bool doNotify(const QString &title, const QString &message, const int ms, const QPixmap &pixmap, const int iconId) override;
-    bool doNotify(const QString &title, const QString &message, const int ms, const QImage &image, const int iconId) override;
-    bool doNotify(const QString &title, const QString &message, const int ms, const int iconId) override;
+    bool doNotify(const QString &title, const QString &message, const int ms, const QPixmap &pixmap, const int iconId);
+    bool doNotify(const QString &title, const QString &message, const int ms, const QImage &image, const int iconId);
+    bool doNotify(const QString &title, const QString &message, const int ms, const int iconId);
 
     QSystemTrayIcon *m_tray;
 };
diff --git a/src/qmplay2/QMPlay2Core.cpp b/src/qmplay2/QMPlay2Core.cpp
index ab97b7dc..7e8df9f3 100644
--- a/src/qmplay2/QMPlay2Core.cpp
+++ b/src/qmplay2/QMPlay2Core.cpp
@@ -28,8 +28,7 @@
 #include <Version.hpp>
 #include <Module.hpp>
 
-#include <QLoggingCategory>
-#include <QStandardPaths>
+#include <QDesktopServices>
 #include <QApplication>
 #include <QLibraryInfo>
 #include <QTranslator>
@@ -37,14 +36,18 @@
 #include <QLibrary>
 #include <QPointer>
 #include <QLocale>
-#include <QWindow>
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+    #include <QWindow>
+#endif
 #include <QFile>
 #include <QDir>
 #if defined Q_OS_WIN
     #include <windows.h>
     #include <powrprof.h>
-#elif defined Q_OS_MACOS
-    #include <QOperatingSystemVersion>
+#elif defined Q_OS_MAC
+    #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+        #include <QOperatingSystemVersion>
+    #endif
 #elif !defined Q_OS_ANDROID && !defined Q_OS_HAIKU && defined DBUS_PM
     #include <QDBusConnection>
     #include <QDBusInterface>
@@ -93,14 +96,19 @@ private:
 
 /**/
 
-Q_LOGGING_CATEGORY(ffmpeglog, "FFmpegLog")
-
 static void avQMPlay2LogHandler(void *avcl, int level, const char *fmt, va_list vl)
 {
     if (level <= AV_LOG_FATAL)
     {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
         const QByteArray msg = QString::vasprintf(fmt, vl).trimmed().toUtf8();
-        qCCritical(ffmpeglog) << msg.constData();
+#else
+        // Qt4: QString::vasprintf doesn't exist, use vsprintf manually
+        char buffer[4096];
+        vsnprintf(buffer, sizeof(buffer), fmt, vl);
+        const QByteArray msg = QString::fromUtf8(buffer).trimmed().toUtf8();
+#endif
+        qCritical() << "FFmpegLog:" << msg.constData();
     }
     else
     {
@@ -212,8 +220,9 @@ int QMPlay2CoreClass::getCPUMaxAlign()
 #ifdef USE_OPENGL
 bool QMPlay2CoreClass::isGlOnWindowForced()
 {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     static bool forced = [] {
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
 #   if defined(MAC_OS_X_VERSION_10_14) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_14)
         return (QOperatingSystemVersion::current() <= QOperatingSystemVersion::MacOSHighSierra);
 #   else
@@ -225,6 +234,31 @@ bool QMPlay2CoreClass::isGlOnWindowForced()
 #endif
     }();
     return forced;
+#else
+    // Qt4: No lambda initialization support, use static variable
+    static bool forced = false;
+    static bool initialized = false;
+    if (!initialized)
+    {
+        initialized = true;
+#ifdef Q_OS_MAC
+#   if defined(MAC_OS_X_VERSION_10_14) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_14)
+        // Qt4 doesn't have QOperatingSystemVersion, assume older macOS
+        forced = true;
+#   else
+        forced = true;
+#   endif
+#else
+        // Qt4: No QApplication::platformName(), check for Android at compile time
+#ifdef Q_OS_ANDROID
+        forced = true;
+#else
+        forced = false;
+#endif
+#endif
+    }
+    return forced;
+#endif
 }
 #endif
 
@@ -245,10 +279,20 @@ void QMPlay2CoreClass::init(bool loadModulesAndGpu, bool modulesInSubdirs, const
     {
 #if defined(Q_OS_WIN) || defined(Q_OS_HAIKU)
         settingsDir = QFileInfo(QSettings(QSettings::IniFormat, QSettings::UserScope, QString()).fileName()).absolutePath() + "/QMPlay2/";
-#elif defined(Q_OS_MACOS)
-        settingsDir = Functions::cleanPath(QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).value(0));
+#elif defined(Q_OS_MAC)
+        #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+            settingsDir = Functions::cleanPath(QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).value(0));
+        #else
+            // Qt4: Use QDesktopServices::storageLocation
+            settingsDir = Functions::cleanPath(QDesktopServices::storageLocation(QDesktopServices::DataLocation));
+        #endif
 #else
-        const QString configSettingsDir(QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).value(0) + "/QMPlay2/");
+        #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+            const QString configSettingsDir(QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).value(0) + "/QMPlay2/");
+        #else
+            // Qt4: Use QDesktopServices::storageLocation for config location
+            const QString configSettingsDir(QDesktopServices::storageLocation(QDesktopServices::DataLocation) + "/QMPlay2/");
+        #endif
         const QString homeSettingsDir(QDir::homePath() + "/.qmplay2/");
         if (QFileInfo(configSettingsDir).isDir() || !QFileInfo(homeSettingsDir).isDir())
         {
@@ -347,7 +391,7 @@ void QMPlay2CoreClass::init(bool loadModulesAndGpu, bool modulesInSubdirs, const
         }
 
         QStringList pluginsName;
-        for (const QFileInfo &fInfo : std::as_const(pluginsList))
+        for (const QFileInfo &fInfo : pluginsList)
         {
             if (QLibrary::isLibrary(fInfo.filePath()))
             {
@@ -371,7 +415,10 @@ void QMPlay2CoreClass::init(bool loadModulesAndGpu, bool modulesInSubdirs, const
                         }
                         const quint8 qtMajorVersion = ((v >> 24) & 0xFF);
                         const quint8 qtMinorVersion = ((v >> 16) & 0xFF);
-                        if (qtMajorVersion != QT_VERSION_MAJOR || qtMinorVersion < QT_VERSION_MINOR)
+                        // Qt4: Extract major/minor from QT_VERSION
+                        const quint8 currentQtMajor = ((QT_VERSION >> 16) & 0xFF);
+                        const quint8 currentQtMinor = ((QT_VERSION >> 8) & 0xFF);
+                        if (qtMajorVersion != currentQtMajor || qtMinorVersion < currentQtMinor)
                         {
                             log(fInfo.fileName() + " - " + tr("mismatch module Qt version"), AddTimeToLog | ErrorLog | SaveLog);
                             return false;
@@ -418,7 +465,7 @@ void QMPlay2CoreClass::quit()
 {
     if (settingsDir.isEmpty())
         return;
-    for (Module *pluginInstance : std::as_const(pluginsInstance))
+    for (Module *pluginInstance : pluginsInstance)
         delete pluginInstance;
     pluginsInstance.clear();
     videoFilters.clear();
@@ -441,7 +488,7 @@ void QMPlay2CoreClass::quit()
 
 bool QMPlay2CoreClass::canSuspend()
 {
-#if defined Q_OS_WIN || defined Q_OS_MACOS
+#if defined Q_OS_WIN || defined Q_OS_MAC
     m_suspend = 1;
     return true;
 #elif defined DBUS_PM
@@ -466,7 +513,7 @@ void QMPlay2CoreClass::suspend()
 
 #if defined Q_OS_WIN
     SetSuspendState(false, false, false);
-#elif defined Q_OS_MACOS
+#elif defined Q_OS_MAC
     Q_UNUSED(system("pmset sleepnow > /dev/null 2>&1 &"));
 #elif defined DBUS_PM
     auto connection = QDBusConnection::systemBus();
@@ -515,6 +562,7 @@ QStringList QMPlay2CoreClass::getModules(const QString &type, int typeLen) const
         const int idx = availableModules.indexOf(module);
         if (idx > -1)
         {
+            // Qt4: QStringList::remove() is removeAt()
             availableModules.removeAt(idx);
             modules += module;
         }
@@ -524,7 +572,12 @@ QStringList QMPlay2CoreClass::getModules(const QString &type, int typeLen) const
 
 qreal QMPlay2CoreClass::getVideoDevicePixelRatio() const
 {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     return getVideoDock()->devicePixelRatioF();
+#else
+    // Qt4: No HiDPI support, always return 1.0
+    return 1.0;
+#endif
 }
 
 QIcon QMPlay2CoreClass::getIconFromTheme(const QString &iconName, const QIcon &fallback)
@@ -592,7 +645,13 @@ void QMPlay2CoreClass::setLanguage()
         lang = QLocale::system().name();
     if (translator->load(lang, langDir))
     {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         lang = QFileInfo(translator->filePath()).baseName();
+#else
+        // Qt4: QTranslator::filePath() doesn't exist, extract locale from lang string
+        // Keep only the locale part (e.g., "en_US" or "en")
+        lang = lang.left(lang.indexOf('_') > 0 ? lang.indexOf('_') : lang.length());
+#endif
     }
     else
     {
diff --git a/src/qmplay2/QMPlay2Extensions.cpp b/src/qmplay2/QMPlay2Extensions.cpp
index a8875079..969e1a49 100644
--- a/src/qmplay2/QMPlay2Extensions.cpp
+++ b/src/qmplay2/QMPlay2Extensions.cpp
@@ -32,7 +32,7 @@ void QMPlay2Extensions::openExtensions()
                 if (QMPlay2Ext)
                     guiExtensionsList.append(QMPlay2Ext);
             }
-    for (QMPlay2Extensions *QMPlay2Ext : std::as_const(guiExtensionsList))
+    for (QMPlay2Extensions *QMPlay2Ext : guiExtensionsList)
         QMPlay2Ext->init();
 }
 
diff --git a/src/qmplay2/Reader.cpp b/src/qmplay2/Reader.cpp
index 07bd386c..ac65434a 100644
--- a/src/qmplay2/Reader.cpp
+++ b/src/qmplay2/Reader.cpp
@@ -59,7 +59,7 @@ class IODeviceReader : public Reader
     }
 
 protected:
-    bool open() override
+    bool open()
     {
         return m_io->open(QIODevice::ReadOnly);
     }
diff --git a/src/qmplay2/Settings.cpp b/src/qmplay2/Settings.cpp
index a93f4047..b64d8777 100644
--- a/src/qmplay2/Settings.cpp
+++ b/src/qmplay2/Settings.cpp
@@ -90,7 +90,7 @@ QVariant Settings::get(const QString &key, const QVariant &def) const
 
 void Settings::flushCache()
 {
-    for (const QString &key : std::as_const(toRemove))
+    for (const QString &key : toRemove)
         QSettings::remove(key);
     toRemove.clear();
 
diff --git a/src/qmplay2/Slider.cpp b/src/qmplay2/Slider.cpp
index a6b96c3b..90c434ba 100644
--- a/src/qmplay2/Slider.cpp
+++ b/src/qmplay2/Slider.cpp
@@ -31,6 +31,9 @@ Slider::Slider() :
 {
     setMouseTracking(true);
 }
+Slider::~Slider()
+{
+}
 
 void Slider::setValue(int val)
 {
@@ -149,9 +152,15 @@ void Slider::mouseMoveEvent(QMouseEvent *e)
 }
 void Slider::wheelEvent(QWheelEvent *e)
 {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     const auto angleDelta  = e->angleDelta();
+    const int delta = (angleDelta.y() != 0 ? angleDelta.y() : angleDelta.x());
+#else
+    // Qt4: Use delta() which returns an int
+    const int delta = e->delta();
+#endif
     int v;
-    if ((angleDelta.y() != 0 ? angleDelta.y() : angleDelta.x()) > 0)
+    if (delta > 0)
         v = value() + wheelStep;
     else
         v = value() - wheelStep;
diff --git a/src/qmplay2/Slider.hpp b/src/qmplay2/Slider.hpp
index b06e0dcc..ef749b59 100644
--- a/src/qmplay2/Slider.hpp
+++ b/src/qmplay2/Slider.hpp
@@ -22,12 +22,12 @@
 
 #include <QSlider>
 
-class QMPLAY2SHAREDLIB_EXPORT Slider final : public QSlider
+class QMPLAY2SHAREDLIB_EXPORT Slider : public QSlider
 {
     Q_OBJECT
 public:
     Slider();
-    ~Slider() final = default;
+    ~Slider();
 
     inline bool ignoringValueChanged() const
     {
@@ -45,12 +45,12 @@ public slots:
     }
     void drawRange(int first, int second);
 protected:
-    void paintEvent(QPaintEvent *) override;
-    void mousePressEvent(QMouseEvent *) override;
-    void mouseReleaseEvent(QMouseEvent *) override;
-    void mouseMoveEvent(QMouseEvent *) override;
-    void wheelEvent(QWheelEvent *) override;
-    void enterEvent(Q_ENTER_EVENT *) override;
+    void paintEvent(QPaintEvent *);
+    void mousePressEvent(QMouseEvent *);
+    void mouseReleaseEvent(QMouseEvent *);
+    void mouseMoveEvent(QMouseEvent *);
+    void wheelEvent(QWheelEvent *);
+    void enterEvent(Q_ENTER_EVENT *);
 private:
     int getMousePos(const QPoint &pos);
 
diff --git a/src/qmplay2/SndResampler.cpp b/src/qmplay2/SndResampler.cpp
index d7e6a3d8..951ce8a4 100644
--- a/src/qmplay2/SndResampler.cpp
+++ b/src/qmplay2/SndResampler.cpp
@@ -182,7 +182,7 @@ void SndResampler::convert(const QByteArray &src, QByteArray &dst, bool flush)
         if (!m_rubberBandStretcher)
         {
             RubberBandStretcher::Options options = RubberBandStretcher::OptionProcessRealTime | RubberBandStretcher::OptionChannelsTogether;
-            if (QStringLiteral(RUBBERBAND_VERSION) != QStringLiteral("3.0.0") || m_dstSamplerate >= 40000)
+            if (QString(RUBBERBAND_VERSION) != QString("3.0.0") || m_dstSamplerate >= 40000)
             {
                 // Workaround for RubberBand 3.0.0 bug:
                 // - low sample rates - bad audio quality
diff --git a/src/qmplay2/StreamInfo.hpp b/src/qmplay2/StreamInfo.hpp
index 59a033b0..51bb95c7 100644
--- a/src/qmplay2/StreamInfo.hpp
+++ b/src/qmplay2/StreamInfo.hpp
@@ -25,6 +25,8 @@
 #include <QVector>
 #include <QPair>
 
+#include <limits>
+
 extern "C" {
     #include <libavcodec/avcodec.h>
 }
@@ -122,7 +124,7 @@ public:
 
     // Video only
     AVRational fps = {0, 1};
-    double rotation = qQNaN();
+    double rotation = std::numeric_limits<double>::quiet_NaN();
     bool spherical = false;
     bool custom_sar = false;
 
diff --git a/src/qmplay2/StreamMuxer.cpp b/src/qmplay2/StreamMuxer.cpp
index 1660072f..ecf62729 100644
--- a/src/qmplay2/StreamMuxer.cpp
+++ b/src/qmplay2/StreamMuxer.cpp
@@ -21,12 +21,23 @@
 #include <StreamInfo.hpp>
 #include <Packet.hpp>
 
-#include <QLoggingCategory>
+#include <QDebug>
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
+    #include <QLoggingCategory>
+    Q_LOGGING_CATEGORY(mux, "StreamMuxer")
+    #define LOG_DEBUG() LOG_DEBUG()
+    #define LOG_WARNING() LOG_WARNING()
+    #define LOG_CRITICAL() LOG_CRITICAL()
+#else
+    // Qt4: No QLoggingCategory, use qDebug/qWarning/qCritical with category prefix
+    #define LOG_DEBUG() qDebug() << "StreamMuxer:"
+    #define LOG_WARNING() qWarning() << "StreamMuxer:"
+    #define LOG_CRITICAL() qCritical() << "StreamMuxer:"
+#endif
 
 #include <unordered_map>
 
-Q_LOGGING_CATEGORY(mux, "StreamMuxer")
-
 extern "C"
 {
     #include <libavformat/avformat.h>
@@ -70,7 +81,7 @@ StreamMuxer::StreamMuxer(const QString &fileName, const QList<StreamInfo *> &str
         {
             if (streamInfo->params->codec_type == AVMEDIA_TYPE_AUDIO)
             {
-                Q_ASSERT(format == QStringLiteral("wav"));
+                Q_ASSERT(format == QString("wav"));
                 codecId = AV_CODEC_ID_PCM_F32LE;
             }
             else
@@ -179,7 +190,7 @@ bool StreamMuxer::write(const Packet &packet, const int idx)
     {
         if (!packet.hasKeyFrame())
         {
-            qCDebug(mux) << "Skipping first packet, because it is not key frame, stream:" << idx;
+            LOG_DEBUG() << "Skipping first packet, because it is not key frame, stream:" << idx;
             return true; // Skip packet if we can't start with key frame
         }
 
@@ -213,7 +224,7 @@ bool StreamMuxer::write(const Packet &packet, const int idx)
     }
     if (!dtsOk)
     {
-        qCWarning(mux) << "Skipping packet with invalid dts in stream" << idx;
+        LOG_WARNING() << "Skipping packet with invalid dts in stream" << idx;
         return true;
     }
 
diff --git a/src/qmplay2/VideoFilter.cpp b/src/qmplay2/VideoFilter.cpp
index 12b40389..78191d91 100644
--- a/src/qmplay2/VideoFilter.cpp
+++ b/src/qmplay2/VideoFilter.cpp
@@ -19,6 +19,7 @@
 #include <VideoFilter.hpp>
 
 #include <QDebug>
+#include <cmath>
 
 #ifdef USE_VULKAN
 #   include <QMPlay2Core.hpp>
@@ -65,7 +66,7 @@ void VideoFilter::clearBuffer()
     m_internalQueue.clear();
 
     m_secondFrame = false;
-    m_lastTS = qQNaN();
+    m_lastTS = std::numeric_limits<double>::quiet_NaN();
 }
 
 bool VideoFilter::removeLastFromInternalBuffer()
@@ -81,7 +82,7 @@ bool VideoFilter::removeLastFromInternalBuffer()
 void VideoFilter::processParamsDeint()
 {
     m_secondFrame = false;
-    m_lastTS = qQNaN();
+    m_lastTS = std::numeric_limits<double>::quiet_NaN();
 
     m_deintFlags = getParam("DeinterlaceFlags").toInt();
 }
@@ -90,7 +91,7 @@ void VideoFilter::addFramesToInternalQueue(QQueue<Frame> &framesQueue)
 {
     while (!framesQueue.isEmpty())
     {
-        const Frame &videoFrame = framesQueue.constFirst();
+        const Frame &videoFrame = framesQueue.first();
         if (videoFrame.isEmpty() || (!m_supportedPixelFormats.isEmpty() && !m_supportedPixelFormats.contains(videoFrame.pixelFormat())))
             break;
         m_internalQueue.enqueue(framesQueue.dequeue());
@@ -100,7 +101,7 @@ void VideoFilter::addFramesToDeinterlace(QQueue<Frame> &framesQueue)
 {
     while (!framesQueue.isEmpty())
     {
-        const Frame &videoFrame = framesQueue.constFirst();
+        const Frame &videoFrame = framesQueue.first();
         if (videoFrame.isEmpty() || (!m_supportedPixelFormats.isEmpty() && !m_supportedPixelFormats.contains(videoFrame.pixelFormat())))
             break;
         if ((m_deintFlags & AutoDeinterlace) && !videoFrame.isInterlaced())
@@ -120,7 +121,8 @@ void VideoFilter::deinterlaceDoublerCommon(Frame &frame)
         m_internalQueue.removeFirst();
     }
 
-    if (m_secondFrame || qIsNaN(m_lastTS))
+    // Qt4/Qt5: Use std::isnan instead of std::isnan
+    if (m_secondFrame || std::isnan(m_lastTS))
         m_lastTS = initialTS;
 
     m_secondFrame = !m_secondFrame;
diff --git a/src/qmplay2/VideoFilter.hpp b/src/qmplay2/VideoFilter.hpp
index 7bf62102..938da4e5 100644
--- a/src/qmplay2/VideoFilter.hpp
+++ b/src/qmplay2/VideoFilter.hpp
@@ -18,6 +18,8 @@
 
 #pragma once
 
+#include <limits>
+
 #include <ModuleCommon.hpp>
 #include <ModuleParams.hpp>
 #include <Frame.hpp>
@@ -98,7 +100,7 @@ protected:
 
     // For doubler
     bool m_secondFrame = false;
-    double m_lastTS = qQNaN();
+    double m_lastTS = std::numeric_limits<double>::quiet_NaN();
 
 #ifdef USE_VULKAN
     std::shared_ptr<QmVk::HWInterop> m_vkHwInterop;
diff --git a/src/qmplay2/VideoFilters.cpp b/src/qmplay2/VideoFilters.cpp
index c96a71cc..d056308d 100644
--- a/src/qmplay2/VideoFilters.cpp
+++ b/src/qmplay2/VideoFilters.cpp
@@ -72,7 +72,7 @@ public:
 
     QMutex bufferMutex;
 private:
-    void run() override
+    void run()
     {
         while (!br)
         {
@@ -90,7 +90,7 @@ private:
             bool pending = false;
             do
             {
-                for (const std::shared_ptr<VideoFilter> &vFilter : std::as_const(videoFilters.filters))
+                for (const std::shared_ptr<VideoFilter> &vFilter : videoFilters.filters)
                     pending |= vFilter->filter(queue);
 
                 if (queue.isEmpty())
@@ -201,7 +201,7 @@ void VideoFilters::clearBuffers()
     if (!filters.isEmpty())
     {
         filtersThr.waitForFinished(true);
-        for (auto &&vFilter : std::as_const(filters))
+        for (auto &&vFilter : filters)
             vFilter->clearBuffer();
     }
     outputQueue.clear();
diff --git a/src/qmplay2/VideoOutputCommon.cpp b/src/qmplay2/VideoOutputCommon.cpp
index 7b6dbb42..bbcfcd7a 100644
--- a/src/qmplay2/VideoOutputCommon.cpp
+++ b/src/qmplay2/VideoOutputCommon.cpp
@@ -28,19 +28,48 @@
 using namespace std;
 using namespace placeholders;
 
+// Qt4 helper: QSizeF::scaled() doesn't exist
+#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
+static QSizeF scaledSizeF(const QSizeF &size, const QSizeF &target, Qt::AspectRatioMode mode)
+{
+    QSizeF result = size;
+    if (mode == Qt::KeepAspectRatio || mode == Qt::KeepAspectRatioByExpanding)
+    {
+        qreal widthRatio = target.width() / size.width();
+        qreal heightRatio = target.height() / size.height();
+        qreal ratio;
+        if (mode == Qt::KeepAspectRatio)
+            ratio = qMin(widthRatio, heightRatio);
+        else
+            ratio = qMax(widthRatio, heightRatio);
+        result = QSizeF(size.width() * ratio, size.height() * ratio);
+    }
+    else if (mode == Qt::IgnoreAspectRatio)
+    {
+        result = target;
+    }
+    return result;
+}
+#endif
+
 VideoOutputCommon::VideoOutputCommon(bool yInverted)
     : m_yMultiplier(yInverted ? -1.0f : 1.0f)
 {
     m_rotAnimation.setEasingCurve(QEasingCurve::OutQuint);
     m_rotAnimation.setDuration(1000.0);
-
-    QObject::connect(&m_rotAnimation, &QVariantAnimation::valueChanged,
-                     bind(&VideoOutputCommon::rotValueUpdated, this, _1));
 }
 VideoOutputCommon::~VideoOutputCommon()
 {
 }
 
+void VideoOutputCommon::setupAnimationConnection(QObject *receiver)
+{
+    // Derived classes that have QObject should call this and provide themselves
+    // The signal will be connected via a slot in the derived class
+    Q_UNUSED(receiver);
+    // Connection is actually done in derived classes that have Q_OBJECT
+}
+
 QWidget *VideoOutputCommon::widget() const
 {
     return m_widget;
@@ -78,7 +107,12 @@ bool VideoOutputCommon::setSphericalView(bool sphericalView)
 
 QSize VideoOutputCommon::getRealWidgetSize(bool lh) const
 {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     const auto widgetSizeF = m_widget->devicePixelRatioF() * QSizeF(m_widget->size() + QSize(0, lh ? m_widget->property("loseHeight").toInt() : 0));
+#else
+    // Qt4: No HiDPI support, devicePixelRatio is always 1.0
+    const auto widgetSizeF = QSizeF(m_widget->size() + QSize(0, lh ? m_widget->property("loseHeight").toInt() : 0));
+#endif
     return QSize(
         widgetSizeF.width(),
         widgetSizeF.height()
@@ -99,16 +133,28 @@ void VideoOutputCommon::updateSizes(bool transpose)
 {
     const auto size = getRealWidgetSize(true);
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     const auto scaledSize = m_zoom * (transpose
         ? QSizeF(1.0, m_aRatio).scaled(size, Qt::KeepAspectRatio)
         : QSizeF(m_aRatio, 1.0).scaled(size, Qt::KeepAspectRatio)
     );
+#else
+    // Qt4: Use helper function
+    const auto scaledSize = m_zoom * (transpose
+        ? scaledSizeF(QSizeF(1.0, m_aRatio), size, Qt::KeepAspectRatio)
+        : scaledSizeF(QSizeF(m_aRatio, 1.0), size, Qt::KeepAspectRatio)
+    );
+#endif
     m_scaledSize = QSize( // round down to int
         scaledSize.width(),
         scaledSize.height()
     );
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     const auto subsScaledSize = m_zoom * QSizeF(m_aRatio, 1.0).scaled(size, Qt::KeepAspectRatio);
+#else
+    const auto subsScaledSize = m_zoom * scaledSizeF(QSizeF(m_aRatio, 1.0), size, Qt::KeepAspectRatio);
+#endif
     const auto subsScaledPos = (size - subsScaledSize) / 2.0;
     m_subsRect.setRect( // round down to int
         subsScaledPos.width(),
@@ -134,7 +180,12 @@ void VideoOutputCommon::updateMatrix()
     }
     if (int loseHeight = m_widget->property("loseHeight").toInt())
     {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         m_matrix.translate(0, -loseHeight * m_widget->devicePixelRatioF() * m_yMultiplier / m_scaledSize.height());
+#else
+        // Qt4: No HiDPI support, devicePixelRatio is always 1.0
+        m_matrix.translate(0, -loseHeight * m_yMultiplier / m_scaledSize.height());
+#endif
     }
 
     if (m_sphericalView)
@@ -211,7 +262,11 @@ void VideoOutputCommon::mouseMove(QMouseEvent *e)
 
         if (m_moveVideo)
         {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
             const auto dpr = m_widget->devicePixelRatioF();
+#else
+            const auto dpr = 1.0;
+#endif
             m_videoOffset += QPointF(
                 mouseDiff.x() * dpr * 2.0 / m_scaledSize.width(),
                 mouseDiff.y() * dpr * 2.0 / m_scaledSize.height()
@@ -274,7 +329,12 @@ void VideoOutputCommon::mouseMove360(QMouseEvent *e)
         m_mouseTime = currTime;
 
         m_mousePos = newMousePos;
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         if (e->source() == Qt::MouseEventNotSynthesized)
+#else
+        // Qt4: No QMouseEvent::source(), assume all mouse events are real
+        if (true)
+#endif
         {
             m_mouseWrapped = m_canWrapMouse
                 ? Functions::wrapMouse(m_widget, m_mousePos, 1)
diff --git a/src/qmplay2/VideoOutputCommon.hpp b/src/qmplay2/VideoOutputCommon.hpp
index 6ef8d379..2d7dcc06 100644
--- a/src/qmplay2/VideoOutputCommon.hpp
+++ b/src/qmplay2/VideoOutputCommon.hpp
@@ -30,6 +30,21 @@ class QMouseEvent;
 class QVariant;
 class QWidget;
 
+// Qt4 compatibility: QVariantAnimation is abstract in Qt4
+class Qt4VariantAnimation : public QVariantAnimation
+{
+    Q_OBJECT
+public:
+    Qt4VariantAnimation(QObject *parent = nullptr) : QVariantAnimation(parent) {}
+
+protected:
+    void updateCurrentValue(const QVariant &value)
+    {
+        // Emit valueChanged signal for compatibility
+        emit valueChanged(value);
+    }
+};
+
 class VideoOutputCommon : public X11BypassCompositor
 {
 protected:
@@ -43,6 +58,9 @@ public:
 
     virtual bool setSphericalView(bool sphericalView);
 
+    // For derived classes with QObject to call to set up animation connection
+    void setupAnimationConnection(QObject *receiver);
+
 protected:
     QSize getRealWidgetSize(bool lh = false) const;
 
@@ -62,9 +80,10 @@ protected:
     void mouseMove360(QMouseEvent *e);
     void mouseRelease360(QMouseEvent *e);
 
-private:
     void rotValueUpdated(const QVariant &value);
 
+private:
+
 protected:
     const float m_yMultiplier;
 
@@ -79,7 +98,7 @@ protected:
 
     QMatrix4x4 m_matrix;
 
-    QVariantAnimation m_rotAnimation;
+    Qt4VariantAnimation m_rotAnimation;
 
     QPointF m_videoOffset;
     QPointF m_osdOffset;
diff --git a/src/qmplay2/Writer.cpp b/src/qmplay2/Writer.cpp
index 7adfd95f..f48c17a2 100644
--- a/src/qmplay2/Writer.cpp
+++ b/src/qmplay2/Writer.cpp
@@ -20,7 +20,12 @@
 
 #include <Functions.hpp>
 
-#include <QSaveFile>
+#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
+    #include <QSaveFile>
+#else
+    // Qt4: No QSaveFile, use QFile instead
+    #include <QFile>
+#endif
 #include <QBuffer>
 #ifdef Q_OS_ANDROID
 #   include <QFile>
@@ -41,7 +46,7 @@ class IODeviceWriter : public Writer
     }
 
 protected:
-    bool open() override
+    bool open()
     {
         return m_io->open(QIODevice::WriteOnly);
     }
@@ -55,8 +60,14 @@ class QMPlay2FileWriter : public IODeviceWriter
 {
     ~QMPlay2FileWriter()
     {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
         if (auto f = static_cast<QSaveFile *>(m_io.get()))
             f->commit();
+#else
+        // Qt4: Using QFile, no commit needed
+        if (auto f = static_cast<QFile *>(m_io.get()))
+            f->close();
+#endif
     }
 
     QString name() const override final
@@ -72,7 +83,12 @@ class QMPlay2FileWriter : public IODeviceWriter
             m_io.reset(new QFile(url));
         else
 #endif
+#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
             m_io.reset(new QSaveFile(url.mid(7)));
+#else
+            // Qt4: Use QFile instead of QSaveFile
+            m_io.reset(new QFile(url.mid(7)));
+#endif
         return IODeviceWriter::open();
     }
 };
diff --git a/src/qmplay2/X11BypassCompositor.cpp b/src/qmplay2/X11BypassCompositor.cpp
index e09b43a0..80966597 100644
--- a/src/qmplay2/X11BypassCompositor.cpp
+++ b/src/qmplay2/X11BypassCompositor.cpp
@@ -28,23 +28,30 @@ X11BypassCompositor::X11BypassCompositor()
 }
 X11BypassCompositor::~X11BypassCompositor()
 {
-    if (m_fullScreenChangedConn)
-    {
-        setX11BypassCompositor(false);
-        QObject::disconnect(m_fullScreenChangedConn);
-    }
+    setX11BypassCompositor(false);
+}
+
+void X11BypassCompositor::setFullScreenCallback(std::function<bool()> callback)
+{
+    m_fullScreenCallback = callback;
+}
+
+void X11BypassCompositor::fullScreenChanged(bool fullScreen)
+{
+    m_isFullScreen = fullScreen;
+    setX11BypassCompositor(m_bypassCompositor);
 }
 
 void X11BypassCompositor::setX11BypassCompositor(bool bypassCompositor)
 {
-    if (!m_fullScreenChangedConn)
+    if (!m_fullScreenCallback && QMPlay2Core.getMainWindow())
     {
-        m_fullScreenChangedConn = QObject::connect(&QMPlay2Core, &QMPlay2CoreClass::fullScreenChanged, [this](bool fullScreen) {
-            m_isFullScreen = fullScreen;
-            setX11BypassCompositor(m_bypassCompositor);
-        });
         m_isFullScreen = QMPlay2Core.getMainWindow()->property("fullScreen").toBool();
     }
+    else if (m_fullScreenCallback)
+    {
+        m_isFullScreen = m_fullScreenCallback();
+    }
 
     m_bypassCompositor = bypassCompositor;
 
diff --git a/src/qmplay2/X11BypassCompositor.hpp b/src/qmplay2/X11BypassCompositor.hpp
index 5a7ca81c..3a1abaa3 100644
--- a/src/qmplay2/X11BypassCompositor.hpp
+++ b/src/qmplay2/X11BypassCompositor.hpp
@@ -20,21 +20,23 @@
 
 #include <QMPlay2Lib.hpp>
 
-#include <QMetaObject>
+#include <functional>
 
 class QMPLAY2SHAREDLIB_EXPORT X11BypassCompositor
 {
-    Q_DISABLE_COPY(X11BypassCompositor)
-
 public:
     X11BypassCompositor();
     ~X11BypassCompositor();
 
     void setX11BypassCompositor(bool bypassCompositor);
+    void setFullScreenCallback(std::function<bool()> callback);
+
+protected:
+    void fullScreenChanged(bool fullScreen);
 
 private:
     bool m_bypassCompositor = false;
     bool m_compositorBypassed = false;
     bool m_isFullScreen = false;
-    QMetaObject::Connection m_fullScreenChangedConn;
+    std::function<bool()> m_fullScreenCallback;
 };
diff --git a/src/qmplay2/YouTubeDL.cpp b/src/qmplay2/YouTubeDL.cpp
index 39e6a517..897dddf4 100644
--- a/src/qmplay2/YouTubeDL.cpp
+++ b/src/qmplay2/YouTubeDL.cpp
@@ -23,11 +23,16 @@
 #include <Functions.hpp>
 #include <Settings.hpp>
 
-#include <QRegularExpression>
-#include <QStandardPaths>
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QJsonArray>
+#include <QRegExp>
+#include <QUrl>
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+    #include <QStandardPaths>
+#else
+    #include <QProcess>
+#endif
+#include <QJsonDocument.h>
+#include <QJsonObject.h>
+#include <QJsonArray.h>
 #include <QFileInfo>
 #include <QMutex>
 #include <QFile>
@@ -35,7 +40,61 @@
 
 constexpr const char *g_name = "YouTubeDL";
 static bool g_mustUpdate = true;
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
 static QRecursiveMutex g_mutex;
+#else
+static QMutex g_mutex(QMutex::Recursive);
+#endif
+
+#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
+// Qt4/older Qt5: reimplementation of QProcess::splitCommand() (added in Qt
+// 5.15), matching its documented Unix-shell-like quoting rules: single or
+// double quotes group words together, a backslash escapes the next character
+// unless inside single quotes (where everything is literal).
+static QStringList qmplay2_splitCommand(const QString &command)
+{
+    QStringList args;
+    QString tmp;
+    int quoteCount = 0;
+    QChar quoteChar;
+
+    for (int i = 0; i < command.size(); ++i)
+    {
+        const QChar c = command.at(i);
+        const bool inQuote = !quoteChar.isNull();
+        if (!inQuote && (c == '"' || c == '\''))
+        {
+            quoteChar = c;
+            ++quoteCount;
+            continue;
+        }
+        if (inQuote && c == quoteChar)
+        {
+            quoteChar = QChar();
+            continue;
+        }
+        if (quoteChar != '\'' && c == '\\' && i + 1 < command.size())
+        {
+            tmp += command.at(++i);
+            continue;
+        }
+        if (!inQuote && c.isSpace())
+        {
+            if (!tmp.isEmpty() || quoteCount)
+            {
+                args += tmp;
+                tmp.clear();
+                quoteCount = 0;
+            }
+            continue;
+        }
+        tmp += c;
+    }
+    if (!tmp.isEmpty() || quoteCount)
+        args += tmp;
+    return args;
+}
+#endif
 
 static inline QString getYtDlpFileName()
 {
@@ -159,13 +218,13 @@ void YouTubeDL::addr(const QString &url, const QString &param, QString *streamUr
         else
         {
             *streamUrl = "FFmpeg://{";
-            for (const QString &tmpUrl : std::as_const(ytdlStdout))
+            for (const QString &tmpUrl : ytdlStdout)
                 *streamUrl += "[" + tmpUrl + "]";
             *streamUrl += "}";
         }
         if (!refererHeader.isEmpty())
         {
-            for (const QString &url : std::as_const(ytdlStdout))
+            for (const QString &url : ytdlStdout)
                 QMPlay2Core.addRawHeaders(url, refererHeader);
         }
     }
@@ -174,7 +233,7 @@ void YouTubeDL::addr(const QString &url, const QString &param, QString *streamUr
     if (extension)
     {
         QStringList extensions;
-        for (const QString &tmpUrl : std::as_const(ytdlStdout))
+        for (const QString &tmpUrl : ytdlStdout)
         {
             if (tmpUrl.contains("mp4"))
                 extensions += ".mp4";
@@ -191,7 +250,7 @@ void YouTubeDL::addr(const QString &url, const QString &param, QString *streamUr
         }
         if (extensions.count() == 1)
             *extension = extensions.at(0);
-        else for (const QString &tmpExt : std::as_const(extensions))
+        else for (const QString &tmpExt : extensions)
             *extension += "[" + tmpExt + "]";
     }
 }
@@ -213,7 +272,12 @@ QStringList YouTubeDL::exec(const QString &url, const QStringList &args, QString
     {
         if (auto ap = QMPSettings.getString("YtDl/AdditionalParams").simplified(); !ap.isEmpty())
         {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
             processArgs += QProcess::splitCommand(ap);
+#else
+            // Qt4/older Qt5: QProcess::splitCommand() was added in Qt 5.15
+            processArgs += qmplay2_splitCommand(ap);
+#endif
         }
     }
 
@@ -242,11 +306,11 @@ QStringList YouTubeDL::exec(const QString &url, const QStringList &args, QString
         }
         else
         {
-            result = result.constFirst().split('\n', Qt::SkipEmptyParts);
+            result = result.first().split('\n', QString::SkipEmptyParts);
 
             // Verify if URLs has printable characters, because sometimes we
             // can get binary garbage at output (especially on Openload).
-            for (const QString &line : std::as_const(result))
+            for (const QString &line : result)
             {
                 if (line.startsWith("http"))
                 {
@@ -296,18 +360,39 @@ QStringList YouTubeDL::exec(const QString &url, const QStringList &args, QString
                 const QString url = result.at(i - 1);
 
                 const QJsonDocument json = QJsonDocument::fromJson(result.at(i).toUtf8());
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
                 for (const QJsonValue &formats : json["formats"].toArray())
+#else
+                // Qt4 QJson4: QJsonDocument doesn't have operator[], need to get object first
+                const QJsonObject rootObj = json.object();
+                for (const QJsonValue &formatsVal : rootObj["formats"].toArray())
+#endif
                 {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
                     if (url == formats["url"].toString())
                     {
                         auto cookies = formats["http_headers"]["Cookie"].toString();
                         if (cookies.isEmpty())
                             cookies = formats["cookies"].toString();
+#else
+                    // Qt4 QJson4: QJsonValue doesn't have operator[], need to convert to object
+                    const QJsonObject formats = formatsVal.toObject();
+                    if (url == formats["url"].toString())
+                    {
+                        const QJsonObject httpHeaders = formats["http_headers"].toObject();
+                        auto cookies = httpHeaders["Cookie"].toString();
+                        if (cookies.isEmpty())
+                            cookies = formats["cookies"].toString();
+#endif
                         QMPlay2Core.addCookies(url, cookies.toUtf8());
                     }
                 }
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+                result.remove(i);
+#else
                 result.removeAt(i);
+#endif
             }
         }
     }
@@ -334,7 +419,11 @@ bool YouTubeDL::prepare()
             return false;
     }
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
     if (!QFileInfo::exists(m_ytDlPath))
+#else
+    if (!QFileInfo(m_ytDlPath).exists())
+#endif
     {
         if (!download())
         {
@@ -373,7 +462,11 @@ bool YouTubeDL::download()
 
     if (!getCustomFilePath().isEmpty())
     {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
         qInfo() << "\"youtube-dl\" is not downloading to custom path";
+#else
+        qDebug() << "\"youtube-dl\" is not downloading to custom path";
+#endif
         return false;
     }
 
@@ -462,7 +555,7 @@ bool YouTubeDL::update()
         {
             qCritical() << "youtube-dl update failed:" << updateOutput;
         }
-        else if (m_process.exitCode() == 0 && !updateOutput.contains(QRegularExpression(R"(up\Wto\Wdate)")))
+        else if (m_process.exitCode() == 0 && !updateOutput.contains(QRegExp("up\\Wto\\Wdate")))
         {
             QMPlay2Core.setWorking(false);
             emit QMPlay2Core.sendMessage(tr("\"youtube-dl\" has been successfully updated!"), g_name);
@@ -514,12 +607,28 @@ void YouTubeDL::startProcess(QStringList args)
         if (shebang.startsWith("#!") && idx > -1)
         {
             const auto pythonCmd = shebang.mid(idx);
-            if (QStandardPaths::findExecutable(pythonCmd).endsWith(pythonCmd))
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+            const QString foundPython = QStandardPaths::findExecutable(pythonCmd);
+#else
+            // Qt4: No findExecutable, search PATH manually
+            QString foundPython;
+            const QStringList paths = QString::fromLocal8Bit(qgetenv("PATH")).split(':');
+            for (const QString &path : paths)
+            {
+                const QString fullPath = path + "/" + pythonCmd;
+                if (QFileInfo(fullPath).isExecutable())
+                {
+                    foundPython = fullPath;
+                    break;
+                }
+            }
+#endif
+            if (foundPython.endsWith(pythonCmd))
             {
                 args.prepend(program);
                 program = pythonCmd;
             }
-#ifdef Q_OS_MACOS
+#ifdef Q_OS_MAC
             else if (QFileInfo(QString("/usr/local/bin/" + pythonCmd)).isExecutable())
             {
                 args.prepend(program);
diff --git a/src/qmplay2/YouTubeDL.hpp b/src/qmplay2/YouTubeDL.hpp
index a7426e8f..631a107f 100644
--- a/src/qmplay2/YouTubeDL.hpp
+++ b/src/qmplay2/YouTubeDL.hpp
@@ -26,7 +26,7 @@
 
 class NetworkReply;
 
-class QMPLAY2SHAREDLIB_EXPORT YouTubeDL final : public BasicIO
+class QMPLAY2SHAREDLIB_EXPORT YouTubeDL : public BasicIO
 {
     Q_DECLARE_TR_FUNCTIONS(YouTubeDL)
     Q_DISABLE_COPY(YouTubeDL)
@@ -45,7 +45,7 @@ public:
     QStringList exec(const QString &url, const QStringList &args, QString *silentErr = nullptr, bool rawOutput = false);
 
 private:
-    void abort() override;
+    void abort();
 
 private:
     bool prepare();
diff --git a/src/qmplay2/opengl/OpenGLCommon.cpp b/src/qmplay2/opengl/OpenGLCommon.cpp
index 9dfc9563..3ad1d289 100644
--- a/src/qmplay2/opengl/OpenGLCommon.cpp
+++ b/src/qmplay2/opengl/OpenGLCommon.cpp
@@ -26,17 +26,12 @@
 #include <Frame.hpp>
 #include <Functions.hpp>
 
-#include <QOffscreenSurface>
-#include <QOpenGLContext>
-#include <QOpenGLShader>
+#include <QtOpenGL/QGLShader>
 #include <QResizeEvent>
 #include <QResource>
 #include <QPainter>
 #include <QLibrary>
 #include <QWidget>
-#ifdef Q_OS_WIN
-#   include <QOperatingSystemVersion>
-#endif
 
 /* OpenGL|ES 2.0 doesn't have those definitions */
 #ifndef GL_MAP_WRITE_BIT
@@ -82,13 +77,13 @@ OpenGLCommon::OpenGLCommon() :
     texCoordYCbCr[0] = texCoordYCbCr[4] = texCoordYCbCr[5] = texCoordYCbCr[7] = 0.0f;
     texCoordYCbCr[1] = texCoordYCbCr[3] = 1.0f;
 
-#ifndef Q_OS_MACOS
+#ifndef Q_OS_MAC
     m_gl3 = (m_glInstance->glVer >= 30);
 #endif
 
     m_matrixChangeFn = [this] {
         setMatrix = true;
-        updateGL(true);
+        requestGLUpdate(true);
     };
 }
 OpenGLCommon::~OpenGLCommon()
@@ -124,16 +119,14 @@ void OpenGLCommon::initialize(const std::shared_ptr<OpenGLHWInterop> &hwInterop)
 
     if (hwInterop)
     {
-        QOffscreenSurface surface;
-        QOpenGLContext context;
+        // Qt4: No QOffscreenSurface/QOpenGLContext
+        // Hardware interop requires active GL context - caller must ensure this
         if (!windowContext)
         {
-            surface.create();
-            if (!context.create() || !context.makeCurrent(&surface))
-            {
-                isOK = false;
-                return;
-            }
+            // Cannot create offscreen context in Qt4 easily
+            // Hardware interop will only work with active window context
+            isOK = false;
+            return;
         }
 
         switch (hwInterop->getFormat())
@@ -210,7 +203,7 @@ void OpenGLCommon::newSize(bool canUpdate)
     if (canUpdate)
     {
         if (isPaused)
-            updateGL(false);
+            requestGLUpdate(false);
         else if (!updateTimer.isActive())
             updateTimer.start(40);
     }
@@ -242,21 +235,13 @@ void OpenGLCommon::setTextureParameters(GLenum target, quint32 texture, GLint pa
 
 void OpenGLCommon::initializeGL()
 {
-    initializeOpenGLFunctions();
-
-#if !defined(QT_OPENGL_ES_2) && !defined(QT_FEATURE_opengles2)
-    if (hasPbo && !m_glInstance->hasMapBufferRange)
-    {
-        Q_ASSERT(m_glInstance->hasMapBuffer);
-        m_gl15.initializeOpenGLFunctions();
-    }
-#endif
+    // Qt4: No need to initialize GL functions explicitly
 
-    shaderProgramVideo.reset(new QOpenGLShaderProgram);
-    shaderProgramOSD.reset(new QOpenGLShaderProgram);
+    shaderProgramVideo.reset(new QGLShaderProgram);
+    shaderProgramOSD.reset(new QGLShaderProgram);
 
     /* YCbCr shader */
-    shaderProgramVideo->addShaderFromSourceCode(QOpenGLShader::Vertex, readShader(":/opengl/Video.vert"));
+    shaderProgramVideo->addShaderFromSourceCode(QGLShader::Vertex, readShader(":/opengl/Video.vert"));
     QByteArray videoFrag;
     if (numPlanes == 1)
     {
@@ -284,7 +269,7 @@ void OpenGLCommon::initializeGL()
     }
     if (target == GL_TEXTURE_RECTANGLE_ARB)
         videoFrag.prepend("#define TEXTURE_RECTANGLE\n");
-    shaderProgramVideo->addShaderFromSourceCode(QOpenGLShader::Fragment, videoFrag);
+    shaderProgramVideo->addShaderFromSourceCode(QGLShader::Fragment, videoFrag);
     if (shaderProgramVideo->bind())
     {
         texCoordYCbCrLoc = shaderProgramVideo->attributeLocation("aTexCoord");
@@ -307,8 +292,8 @@ void OpenGLCommon::initializeGL()
     }
 
     /* OSD shader */
-    shaderProgramOSD->addShaderFromSourceCode(QOpenGLShader::Vertex, readShader(":/opengl/OSD.vert"));
-    shaderProgramOSD->addShaderFromSourceCode(QOpenGLShader::Fragment, readShader(":/opengl/OSD.frag"));
+    shaderProgramOSD->addShaderFromSourceCode(QGLShader::Vertex, readShader(":/opengl/OSD.vert"));
+    shaderProgramOSD->addShaderFromSourceCode(QGLShader::Fragment, readShader(":/opengl/OSD.frag"));
     if (shaderProgramOSD->bind())
     {
         texCoordOSDLoc = shaderProgramOSD->attributeLocation("aTexCoord");
@@ -466,11 +451,13 @@ void OpenGLCommon::paintGL()
                 {
                     glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo[p + 1]);
                     quint8 *dst = nullptr;
+#if !defined(QT_OPENGL_ES_2) && !defined(QT_FEATURE_opengles2)
+    #if !defined(Q_OS_MAC)
                     if (m_glInstance->hasMapBufferRange)
                         dst = (quint8 *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, w * h * bytesMultiplier, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
-#if !defined(QT_OPENGL_ES_2) && !defined(QT_FEATURE_opengles2)
                     else
-                        dst = (quint8 *)m_gl15.glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
+    #endif
+                        dst = (quint8 *)::glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
 #endif
                     if (!dst)
                         glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
@@ -642,11 +629,13 @@ void OpenGLCommon::paintGL()
                 if (hasNewSize)
                     glBufferData(GL_PIXEL_UNPACK_BUFFER, dataSize, nullptr, GL_DYNAMIC_DRAW);
                 quint8 *dst = nullptr;
+#if !defined(QT_OPENGL_ES_2) && !defined(QT_FEATURE_opengles2)
+    #if !defined(Q_OS_MAC)
                 if (m_glInstance->hasMapBufferRange)
                     dst = (quint8 *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, dataSize, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
-#if !defined(QT_OPENGL_ES_2) && !defined(QT_FEATURE_opengles2)
                 else
-                    dst = (quint8 *)m_gl15.glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
+    #endif
+                    dst = (quint8 *)::glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
 #endif
                 if (!dst)
                     glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
@@ -726,8 +715,16 @@ QByteArray OpenGLCommon::readShader(const QString &fileName, bool pure)
             shader = "precision highp float;\n";
         shader.append("#line 1\n");
     }
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
     const auto data = res.uncompressedData();
     shader.append(data.constData(), data.size());
+#else
+    // Qt4: No uncompressedData(), use data() which returns const uchar*
+    const uchar *data = res.data();
+    const int size = res.size();
+    if (data && size > 0)
+        shader.append(reinterpret_cast<const char*>(data), size);
+#endif
     return shader;
 }
 
diff --git a/src/qmplay2/opengl/OpenGLCommon.hpp b/src/qmplay2/opengl/OpenGLCommon.hpp
index fd3393b7..d5a6903d 100644
--- a/src/qmplay2/opengl/OpenGLCommon.hpp
+++ b/src/qmplay2/opengl/OpenGLCommon.hpp
@@ -19,17 +19,15 @@
 #pragma once
 
 #include "OpenGLInstance.hpp"
+#include "Qt4OpenGLCompat.hpp"
 
 #include <Frame.hpp>
 #include <VideoAdjustment.hpp>
 #include <VideoOutputCommon.hpp>
 #include <QMPlay2OSD.hpp>
 
-#include <QOpenGLShaderProgram>
-#if !defined(QT_OPENGL_ES_2) && !defined(QT_FEATURE_opengles2)
-#   include <QOpenGLFunctions_1_5>
-#endif
-#include <QOpenGLExtraFunctions>
+#include <QtOpenGL/QGLShaderProgram>
+#include <OpenGL/gl.h>
 
 #include <QCoreApplication>
 #include <QImage>
@@ -37,7 +35,7 @@
 
 class OpenGLHWInterop;
 
-class OpenGLCommon : public VideoOutputCommon, public QOpenGLExtraFunctions
+class OpenGLCommon : public VideoOutputCommon
 {
     Q_DECLARE_TR_FUNCTIONS(OpenGLCommon)
 
@@ -67,7 +65,7 @@ public:
     void initialize(const std::shared_ptr<OpenGLHWInterop> &hwInterop);
 
     virtual void setVSync(bool enable) = 0;
-    virtual void updateGL(bool requestDelayed) = 0;
+    virtual void requestGLUpdate(bool requestDelayed) = 0;
 
 #ifdef Q_OS_WIN
     void setWindowsBypassCompositor(bool bypassCompositor);
@@ -76,7 +74,7 @@ public:
     void newSize(bool canUpdate);
     void clearImg();
 
-    bool setSphericalView(bool spherical) override;
+    bool setSphericalView(bool spherical);
 protected:
     void setTextureParameters(GLenum target, quint32 texture, GLint param);
 
@@ -85,13 +83,9 @@ protected:
 
     void contextAboutToBeDestroyed();
 
-#if !defined(QT_OPENGL_ES_2) && !defined(QT_FEATURE_opengles2)
-    QOpenGLFunctions_1_5 m_gl15;
-#endif
-
     bool vSync;
 
-    void dispatchEvent(QEvent *e, QObject *p) override;
+    void dispatchEvent(QEvent *e, QObject *p);
 private:
     inline bool isRotate90() const;
 
@@ -114,7 +108,7 @@ public:
     int m_depth = 8;
     bool m_limited = false;
 
-    std::unique_ptr<QOpenGLShaderProgram> shaderProgramVideo, shaderProgramOSD;
+    std::unique_ptr<QGLShaderProgram> shaderProgramVideo, shaderProgramOSD;
 
     qint32 texCoordYCbCrLoc, positionYCbCrLoc, texCoordOSDLoc, positionOSDLoc;
     VideoAdjustment videoAdjustment;
diff --git a/src/qmplay2/opengl/OpenGLInstance.cpp b/src/qmplay2/opengl/OpenGLInstance.cpp
index e8cef552..96102785 100644
--- a/src/qmplay2/opengl/OpenGLInstance.cpp
+++ b/src/qmplay2/opengl/OpenGLInstance.cpp
@@ -1,57 +1,107 @@
 #include "OpenGLInstance.hpp"
 #include "OpenGLWriter.hpp"
 
-#include <QOffscreenSurface>
-#include <QOpenGLFunctions>
+#include <QtOpenGL/QGLWidget>
+#include <QtOpenGL/QGLFormat>
+#include <QtOpenGL/QGLContext>
+#include <OpenGL/gl.h>
+#include <QString>
 #include <QSet>
 
 bool OpenGLInstance::init()
 {
-    QOffscreenSurface surface;
-    QOpenGLContext context;
+    // Qt4: Need an active GL context to query
+    // Create a temporary context if needed
+#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
+    QGLWidget *tempWidget = nullptr;
+    const QGLContext *existingContext = QGLContext::currentContext();
+    if (!existingContext)
+    {
+        // Create temporary offscreen widget to get a context
+        QGLFormat format;
+        format.setVersion(2, 0);
+        tempWidget = new QGLWidget(format);
+
+        // Ensure context is created - on some platforms we need this
+        if (!tempWidget->isValid())
+        {
+            delete tempWidget;
+            return false;
+        }
+
+        tempWidget->makeCurrent();
+
+        // Verify context is now current
+        if (!QGLContext::currentContext())
+        {
+            delete tempWidget;
+            return false;
+        }
+    }
+#endif
 
-    surface.create();
-    if (!context.create() || !context.makeCurrent(&surface))
+    // Get OpenGL version
+    const char *versionStr = (const char *)glGetString(GL_VERSION);
+    if (!versionStr)
+    {
+#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
+        delete tempWidget;
+#endif
         return false;
+    }
 
-    auto fns = context.functions();
+    // Parse version string (e.g., "2.1 ..." or "3.0 ...")
+    int majorVersion = 2;
+    int minorVersion = 0;
+    if (versionStr[0] >= '0' && versionStr[0] <= '9')
+    {
+        majorVersion = versionStr[0] - '0';
+        if (versionStr[1] == '.' && versionStr[2] >= '0' && versionStr[2] <= '9')
+            minorVersion = versionStr[2] - '0';
+    }
 
-    if (!fns->hasOpenGLFeature(QOpenGLFunctions::Multitexture))
-        return false;
+    // Check for OpenGL ES
+    const char *rendererStr = (const char *)glGetString(GL_RENDERER);
+    isGLES = (rendererStr && strstr(rendererStr, "OpenGL ES"));
 
-    if (!fns->hasOpenGLFeature(QOpenGLFunctions::Shaders))
-        return false;
+    // Get extensions string
+    const char *extensionsStr = (const char *)glGetString(GL_EXTENSIONS);
+    QString extensions = extensionsStr ? QString::fromLatin1(extensionsStr) : QString();
+    QSet<QString> extensionSet;
+    if (!extensions.isEmpty())
+    {
+        foreach (const QString &ext, extensions.split(' ', QString::SkipEmptyParts))
+            extensionSet.insert(ext);
+    }
 
-    if (!fns->hasOpenGLFeature(QOpenGLFunctions::NPOTTextures))
+    // OpenGL 2.0 has multitexture, shaders, and NPOT built-in
+    if (majorVersion < 2)
         return false;
 
-    isGLES = context.isOpenGLES();
-
-    const auto extensions = context.extensions();
-    const auto majorVersion = context.format().majorVersion();
+    // Check for RG texture formats (OpenGL 3.0+)
+    canUse16bitTexture = (majorVersion >= 3) || extensionSet.contains("GL_ARB_texture_rg");
 
-    if (fns->hasOpenGLFeature(QOpenGLFunctions::TextureRGFormats))
-    {
-        canUse16bitTexture = (isGLES ? extensions.contains("GL_EXT_texture_norm16") : (majorVersion >= 3));
-    }
+    // VBO support (OpenGL 2.0+)
+    hasVbo = (majorVersion >= 2) || extensionSet.contains("GL_ARB_vertex_buffer_object");
 
-    hasVbo = (majorVersion >= 2 || extensions.contains("GL_ARB_vertex_buffer_object"));
     if (hasVbo)
     {
-        if (!isGLES)
-            hasMapBufferRange = extensions.contains("GL_ARB_map_buffer_range");
-        else if (majorVersion >= 3)
-            hasMapBufferRange = true;
-
-        hasMapBuffer = extensions.contains("GL_OES_mapbuffer");
-        if (Q_UNLIKELY(!isGLES && !hasMapBuffer))
-            hasMapBuffer = (majorVersion >= 2);
-
-        hasPbo = (hasMapBuffer || hasMapBufferRange) && (majorVersion >= (isGLES ? 3 : 2) || extensions.contains("GL_ARB_pixel_buffer_object"));
+        hasMapBufferRange = extensionSet.contains("GL_ARB_map_buffer_range");
+        hasMapBuffer = (majorVersion >= 2) || extensionSet.contains("GL_OES_mapbuffer");
+        hasPbo = (hasMapBuffer || hasMapBufferRange) &&
+                 ((majorVersion >= 2) || extensionSet.contains("GL_ARB_pixel_buffer_object"));
     }
 
-#ifndef Q_OS_MACOS // On macOS I have always OpenGL 2.1...
-    glVer = majorVersion * 10 + context.format().minorVersion();
+#ifndef Q_OS_MAC
+    glVer = majorVersion * 10 + minorVersion;
+#else
+    // macOS 10.6.8 has OpenGL 2.0/2.1
+    glVer = 20;
+#endif
+
+#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
+    // Clean up temporary widget
+    delete tempWidget;
 #endif
 
     return true;
diff --git a/src/qmplay2/opengl/OpenGLInstance.hpp b/src/qmplay2/opengl/OpenGLInstance.hpp
index 759ec31a..a64fe6c6 100644
--- a/src/qmplay2/opengl/OpenGLInstance.hpp
+++ b/src/qmplay2/opengl/OpenGLInstance.hpp
@@ -20,16 +20,16 @@
 
 #include <GPUInstance.hpp>
 
-class OpenGLInstance final : public GPUInstance
+class OpenGLInstance : public GPUInstance
 {
 public:
     bool init();
 
 public:
-    QString name() const override;
-    QMPlay2CoreClass::Renderer renderer() const override;
+    QString name() const;
+    QMPlay2CoreClass::Renderer renderer() const;
 
-    VideoWriter *createOrGetVideoOutput() override;
+    VideoWriter *createOrGetVideoOutput();
 
 public:
     bool isGLES = false;
diff --git a/src/qmplay2/opengl/OpenGLWidget.cpp b/src/qmplay2/opengl/OpenGLWidget.cpp
index f7a7da05..84a945d2 100644
--- a/src/qmplay2/opengl/OpenGLWidget.cpp
+++ b/src/qmplay2/opengl/OpenGLWidget.cpp
@@ -18,12 +18,11 @@
 
 #include "OpenGLWidget.hpp"
 
-#include <QOpenGLContext>
-
 OpenGLWidget::OpenGLWidget()
 {
     m_widget = this;
     connect(&updateTimer, SIGNAL(timeout()), this, SLOT(update()));
+    connect(&m_rotAnimation, SIGNAL(valueChanged(QVariant)), this, SLOT(onRotValueUpdated(QVariant)));
 }
 OpenGLWidget::~OpenGLWidget()
 {
@@ -48,7 +47,7 @@ void OpenGLWidget::setVSync(bool enable)
     Q_UNUSED(enable)
     // Not supported
 }
-void OpenGLWidget::updateGL(bool requestDelayed)
+void OpenGLWidget::requestGLUpdate(bool requestDelayed)
 {
     if (requestDelayed)
         QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
@@ -58,7 +57,10 @@ void OpenGLWidget::updateGL(bool requestDelayed)
 
 void OpenGLWidget::initializeGL()
 {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     connect(context(), SIGNAL(aboutToBeDestroyed()), this, SLOT(aboutToBeDestroyed()), Qt::DirectConnection);
+#endif
+    // Qt4: QGLContext doesn't emit aboutToBeDestroyed(), cleanup happens in destructor
     OpenGLCommon::initializeGL();
 }
 void OpenGLWidget::paintGL()
@@ -74,8 +76,13 @@ void OpenGLWidget::aboutToBeDestroyed()
     doneCurrent();
 }
 
+void OpenGLWidget::onRotValueUpdated(const QVariant &value)
+{
+    rotValueUpdated(value);
+}
+
 bool OpenGLWidget::event(QEvent *e)
 {
     dispatchEvent(e, parent());
-    return QOpenGLWidget::event(e);
+    return QGLWidget::event(e);
 }
diff --git a/src/qmplay2/opengl/OpenGLWidget.hpp b/src/qmplay2/opengl/OpenGLWidget.hpp
index 43503855..4c41a5a4 100644
--- a/src/qmplay2/opengl/OpenGLWidget.hpp
+++ b/src/qmplay2/opengl/OpenGLWidget.hpp
@@ -20,26 +20,26 @@
 
 #include "OpenGLCommon.hpp"
 
-#include <QSurfaceFormat>
-#include <QOpenGLWidget>
+#include <QtOpenGL/QGLWidget>
 
-class OpenGLWidget final : public QOpenGLWidget, public OpenGLCommon
+class OpenGLWidget : public QGLWidget, public OpenGLCommon
 {
     Q_OBJECT
 public:
     OpenGLWidget();
     ~OpenGLWidget();
 
-    bool makeContextCurrent() override;
-    void doneContextCurrent() override;
+    bool makeContextCurrent();
+    void doneContextCurrent();
 
-    void setVSync(bool enable) override;
-    void updateGL(bool requestDelayed) override;
+    void setVSync(bool enable);
+    void requestGLUpdate(bool requestDelayed);
 
-    void initializeGL() override;
-    void paintGL() override;
+    void initializeGL();
+    void paintGL();
 private slots:
     void aboutToBeDestroyed();
+    void onRotValueUpdated(const QVariant &value);
 private:
-    bool event(QEvent *e) override;
+    bool event(QEvent *e);
 };
diff --git a/src/qmplay2/opengl/OpenGLWindow.cpp b/src/qmplay2/opengl/OpenGLWindow.cpp
index b44399ac..1c9c4b78 100644
--- a/src/qmplay2/opengl/OpenGLWindow.cpp
+++ b/src/qmplay2/opengl/OpenGLWindow.cpp
@@ -20,29 +20,37 @@
 
 #include <QMPlay2Core.hpp>
 
-#include <QGuiApplication>
-#include <QOpenGLContext>
 #include <QDockWidget>
 
 OpenGLWindow::OpenGLWindow()
-    : m_platformName(QGuiApplication::platformName())
+    : m_platformName()
     , m_passEventsToParent(
-#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
-          !m_platformName.contains(QStringLiteral("wayland")) &&
-          m_platformName != QStringLiteral("windows") &&
+#ifdef Q_OS_MAC
+          true // macOS - pass events to parent
+#elif defined(Q_OS_WIN) || defined(Q_OS_ANDROID)
+          false // Windows/Android - transparent input
+#else
+          false // Linux/X11 - transparent input
 #endif
-          m_platformName != QStringLiteral("xcb") && m_platformName != QStringLiteral("android"))
+      )
 {
     connect(&updateTimer, SIGNAL(timeout()), this, SLOT(doUpdateGL()));
+    connect(&m_rotAnimation, SIGNAL(valueChanged(QVariant)), this, SLOT(onRotValueUpdated(QVariant)));
 
-    if (!m_passEventsToParent)
-        setFlags(Qt::WindowTransparentForInput);
+    // Qt4: QGLWidget is already a widget, no need for container
+    m_widget = this;
+
+#if !defined(Q_OS_MAC) && !defined(Q_OS_ANDROID)
+    setAttribute(Qt::WA_NativeWindow);
+#endif
+
+    setAcceptDrops(false);
 
-    m_widget = QWidget::createWindowContainer(this);
-    if (!m_platformName.contains(QStringLiteral("wayland")) && m_platformName != QStringLiteral("android"))
-        m_widget->setAttribute(Qt::WA_NativeWindow);
-    m_widget->installEventFilter(this);
-    m_widget->setAcceptDrops(false);
+    if (!m_passEventsToParent)
+    {
+        // Make widget transparent to input events
+        setAttribute(Qt::WA_TransparentForMouseEvents);
+    }
 
     connect(&QMPlay2Core, SIGNAL(videoDockVisible(bool)), this, SLOT(videoVisible(bool)));
 }
@@ -71,37 +79,35 @@ void OpenGLWindow::doneContextCurrent()
 
 void OpenGLWindow::setVSync(bool enable)
 {
-    QSurfaceFormat fmt = format();
-    if (!handle())
+    // Qt4: Use QGLFormat instead of QSurfaceFormat
+    QGLFormat fmt = format();
+    if (enable != fmt.swapInterval())
     {
-        fmt.setSwapBehavior(QSurfaceFormat::DoubleBuffer); //Probably it doesn't work
-        fmt.setSwapInterval(enable);
+        fmt.setSwapInterval(enable ? 1 : 0);
+        fmt.setDoubleBuffer(true);
         setFormat(fmt);
-    }
-    else if (enable != fmt.swapInterval())
-    {
-        fmt.setSwapInterval(enable);
-        destroy();
-        setFormat(fmt);
-        create();
-        setVisible(true);
+        // Qt4: May need recreation for format changes
+        // But that can cause issues, so just set for next time
     }
     vSync = enable;
 }
-void OpenGLWindow::updateGL(bool requestDelayed)
+void OpenGLWindow::requestGLUpdate(bool requestDelayed)
 {
-    if (m_visible && isExposed())
+    // Qt4: Use isVisible() instead of isExposed()
+    if (m_visible && isVisible())
         QMetaObject::invokeMethod(this, "doUpdateGL", Qt::QueuedConnection, Q_ARG(bool, requestDelayed));
 }
 
 void OpenGLWindow::initializeGL()
 {
-    connect(context(), SIGNAL(aboutToBeDestroyed()), this, SLOT(aboutToBeDestroyed()), Qt::DirectConnection);
+    // Qt4: QGLContext doesn't have aboutToBeDestroyed signal
+    // Context destruction is handled in destructor
     OpenGLCommon::initializeGL();
 }
 void OpenGLWindow::paintGL()
 {
-    if (isExposed())
+    // Qt4: Use isVisible() instead of isExposed()
+    if (isVisible())
     {
         glClear(GL_COLOR_BUFFER_BIT);
         OpenGLCommon::paintGL();
@@ -130,10 +136,15 @@ void OpenGLWindow::videoVisible(bool v)
     m_visible = v && (m_widget->visibleRegion() != QRegion() || QMPlay2Core.getVideoDock()->visibleRegion() != QRegion());
 }
 
+void OpenGLWindow::onRotValueUpdated(const QVariant &value)
+{
+    rotValueUpdated(value);
+}
+
 bool OpenGLWindow::eventFilter(QObject *o, QEvent *e)
 {
-    if (o == m_widget)
-        dispatchEvent(e, m_widget->parent());
+    // Qt4: m_widget is now this, so handle parent dispatching
+    dispatchEvent(e, parent());
     return false;
 }
 
@@ -147,7 +158,9 @@ bool OpenGLWindow::event(QEvent *e)
         case QEvent::MouseMove:
         case QEvent::FocusIn:
         case QEvent::FocusOut:
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         case QEvent::FocusAboutToChange:
+#endif
         case QEvent::Enter:
         case QEvent::Leave:
         case QEvent::TabletMove:
@@ -158,8 +171,10 @@ bool OpenGLWindow::event(QEvent *e)
         case QEvent::TouchBegin:
         case QEvent::TouchUpdate:
         case QEvent::TouchEnd:
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
         case QEvent::InputMethodQuery:
         case QEvent::TouchCancel:
+#endif
             if (m_passEventsToParent)
                 return QCoreApplication::sendEvent(parent(), e);
         case QEvent::Wheel:
@@ -168,5 +183,5 @@ bool OpenGLWindow::event(QEvent *e)
         default:
             break;
     }
-    return QOpenGLWindow::event(e);
+    return QGLWidget::event(e);
 }
diff --git a/src/qmplay2/opengl/OpenGLWindow.hpp b/src/qmplay2/opengl/OpenGLWindow.hpp
index b61ab6e4..574da766 100644
--- a/src/qmplay2/opengl/OpenGLWindow.hpp
+++ b/src/qmplay2/opengl/OpenGLWindow.hpp
@@ -20,34 +20,36 @@
 
 #include "OpenGLCommon.hpp"
 
-#include <QOpenGLWindow>
+#include <QtOpenGL/QGLWidget>
 
-class OpenGLWindow final : private QOpenGLWindow, public OpenGLCommon
+// Qt4: No QOpenGLWindow, use QGLWidget
+class OpenGLWindow : public QGLWidget, public OpenGLCommon
 {
     Q_OBJECT
 public:
     OpenGLWindow();
     ~OpenGLWindow();
 
-    void deleteMe() override;
+    void deleteMe();
 
-    bool makeContextCurrent() override;
-    void doneContextCurrent() override;
+    bool makeContextCurrent();
+    void doneContextCurrent();
 
-    void setVSync(bool enable) override;
-    void updateGL(bool requestDelayed) override;
+    void setVSync(bool enable);
+    void requestGLUpdate(bool requestDelayed);
 
-    void initializeGL() override;
-    void paintGL() override;
+    void initializeGL();
+    void paintGL();
 
 private slots:
     void doUpdateGL(bool queued = false);
     void aboutToBeDestroyed();
     void videoVisible(bool v);
+    void onRotValueUpdated(const QVariant &value);
 private:
-    bool eventFilter(QObject *o, QEvent *e) override;
+    bool eventFilter(QObject *o, QEvent *e);
 
-    bool event(QEvent *e) override;
+    bool event(QEvent *e);
 
 private:
     const QString m_platformName;
diff --git a/src/qmplay2/opengl/OpenGLWriter.cpp b/src/qmplay2/opengl/OpenGLWriter.cpp
index 3b1afd50..b9e35e96 100644
--- a/src/qmplay2/opengl/OpenGLWriter.cpp
+++ b/src/qmplay2/opengl/OpenGLWriter.cpp
@@ -24,7 +24,7 @@
 
 #include <Frame.hpp>
 
-#include <QGuiApplication>
+#include <QApplication>
 
 using namespace std;
 
@@ -80,12 +80,11 @@ bool OpenGLWriter::set()
     {
         m_bypassCompositor = bypassCompositor;
 
-        if (QGuiApplication::platformName() == "xcb")
-        {
-            m_drawable->setX11BypassCompositor(m_bypassCompositor);
-        }
-#ifdef Q_OS_WIN
-        else if (!m_useRtt)
+        // Qt4: Use compile-time platform detection
+#if defined(Q_WS_X11)
+        m_drawable->setX11BypassCompositor(m_bypassCompositor);
+#elif defined(Q_OS_WIN)
+        if (!m_useRtt)
         {
             doReset = true;
         }
@@ -231,7 +230,7 @@ void OpenGLWriter::writeVideo(const Frame &videoFrame, QMPlay2OSDList &&osdList)
         m_drawable->doReset = true;
     }
 
-    m_drawable->updateGL(m_drawable->isSphericalView());
+    m_drawable->requestGLUpdate(m_drawable->isSphericalView());
 }
 
 void OpenGLWriter::pause()
@@ -280,7 +279,8 @@ void OpenGLWriter::addAdditionalParam(const QString &key)
 
 void OpenGLWriter::initialize(const shared_ptr<OpenGLHWInterop> &hwInterop)
 {
-    for (auto &&additionalParam : std::as_const(m_additionalParams))
+    // Qt4: No std::as_const, use const reference directly
+    foreach (const QString &additionalParam, m_additionalParams)
         removeParam(additionalParam);
     m_additionalParams.clear();
 
@@ -291,7 +291,7 @@ void OpenGLWriter::initialize(const shared_ptr<OpenGLHWInterop> &hwInterop)
     bool hasBrightness = false, hasContrast = false, hasGamma = false, hasSharpness = false;
     if (!m_drawable->videoAdjustmentKeys.isEmpty())
     {
-        for (const QString &key : std::as_const(m_drawable->videoAdjustmentKeys))
+        foreach (const QString &key, m_drawable->videoAdjustmentKeys)
         {
             if (key == "Brightness")
                 hasBrightness = true;
diff --git a/src/qmplay2/opengl/OpenGLWriter.hpp b/src/qmplay2/opengl/OpenGLWriter.hpp
index c80ca3d1..8c7c4dbb 100644
--- a/src/qmplay2/opengl/OpenGLWriter.hpp
+++ b/src/qmplay2/opengl/OpenGLWriter.hpp
@@ -27,7 +27,7 @@
 class OpenGLHWInterop;
 class OpenGLCommon;
 
-class OpenGLWriter final : public VideoWriter
+class OpenGLWriter : public VideoWriter
 {
     Q_DECLARE_TR_FUNCTIONS(OpenGLWriter)
 
@@ -35,24 +35,24 @@ public:
     OpenGLWriter();
     ~OpenGLWriter();
 
-    bool set() override;
+    bool set();
 
-    bool readyWrite() const override;
+    bool readyWrite() const;
 
-    bool processParams(bool *paramsCorrected) override;
+    bool processParams(bool *paramsCorrected);
 
-    AVPixelFormats supportedPixelFormats() const override;
+    AVPixelFormats supportedPixelFormats() const;
 
-    void writeVideo(const Frame &videoFrame, QMPlay2OSDList &&osdList) override;
+    void writeVideo(const Frame &videoFrame, QMPlay2OSDList &&osdList);
 
-    void pause() override;
+    void pause();
 
-    QString name() const override;
+    QString name() const;
 
-    bool open() override;
+    bool open();
 
-    bool setHWDecContext(const std::shared_ptr<HWDecContext> &hwDecContext) override;
-    std::shared_ptr<HWDecContext> hwDecContext() const override;
+    bool setHWDecContext(const std::shared_ptr<HWDecContext> &hwDecContext);
+    std::shared_ptr<HWDecContext> hwDecContext() const;
 
 private:
     void addAdditionalParam(const QString &key);
diff --git a/src/qmplay2/opengl/Qt4OpenGLCompat.hpp b/src/qmplay2/opengl/Qt4OpenGLCompat.hpp
new file mode 100644
index 00000000..e2102aba
--- /dev/null
+++ b/src/qmplay2/opengl/Qt4OpenGLCompat.hpp
@@ -0,0 +1,248 @@
+/*
+    Qt4 OpenGL Compatibility Layer for QMPlay2
+
+    This header provides compatibility mappings from Qt5+ OpenGL API to Qt4 equivalents.
+    Targets: macOS 10.6.8 PowerPC with OpenGL 2.0
+*/
+
+#pragma once
+
+#include <QtOpenGL/QGLWidget>
+#include <QtOpenGL/QGLShaderProgram>
+#include <QtOpenGL/QGLFramebufferObject>
+#include <QtOpenGL/QGLContext>
+#include <QtOpenGL/QGLFormat>
+
+#include <OpenGL/gl.h>
+#include <OpenGL/glext.h>
+
+// Qt5+ uses QOpenGLShaderProgram, Qt4 uses QGLShaderProgram
+#define QOpenGLShaderProgram QGLShaderProgram
+#define QOpenGLShader QGLShader
+
+// Qt5+ uses QOpenGLContext, Qt4 uses QGLContext
+#define QOpenGLContext QGLContext
+
+// Qt5+ uses QOpenGLWidget, Qt4 uses QGLWidget
+#define QOpenGLWidget QGLWidget
+
+// Qt5+ uses QOpenGLFramebufferObject, Qt4 uses QGLFramebufferObject
+#define QOpenGLFramebufferObject QGLFramebufferObject
+
+// Qt5+ uses QOpenGLFormat, Qt4 uses QGLFormat
+#define QOpenGLFormat QGLFormat
+
+// Qt5+ QOpenGLFunctions equivalent in Qt4
+// In Qt4, OpenGL functions are accessed directly or through QGLContext::getProcAddress()
+class QOpenGLFunctions
+{
+public:
+    inline void initializeOpenGLFunctions() {}
+
+    // These are standard OpenGL 2.0 functions available directly
+    inline void glActiveTexture(GLenum texture) { ::glActiveTexture(texture); }
+    inline void glAttachShader(GLuint program, GLuint shader) { ::glAttachShader(program, shader); }
+    inline void glBindBuffer(GLenum target, GLuint buffer) { ::glBindBuffer(target, buffer); }
+    inline void glBindTexture(GLenum target, GLuint texture) { ::glBindTexture(target, texture); }
+    inline void glBlendFunc(GLenum sfactor, GLenum dfactor) { ::glBlendFunc(sfactor, dfactor); }
+    inline void glBufferData(GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage) {
+        ::glBufferData(target, size, data, usage);
+    }
+    inline void glClear(GLbitfield mask) { ::glClear(mask); }
+    inline void glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) {
+        ::glClearColor(red, green, blue, alpha);
+    }
+    inline void glDeleteBuffers(GLsizei n, const GLuint *buffers) { ::glDeleteBuffers(n, buffers); }
+    inline void glDeleteTextures(GLsizei n, const GLuint *textures) { ::glDeleteTextures(n, textures); }
+    inline void glDisable(GLenum cap) { ::glDisable(cap); }
+    inline void glDrawArrays(GLenum mode, GLint first, GLsizei count) { ::glDrawArrays(mode, first, count); }
+    inline void glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices) {
+        ::glDrawElements(mode, count, type, indices);
+    }
+    inline void glEnable(GLenum cap) { ::glEnable(cap); }
+    inline void glFinish() { ::glFinish(); }
+    inline void glFlush() { ::glFlush(); }
+    inline void glGenBuffers(GLsizei n, GLuint *buffers) { ::glGenBuffers(n, buffers); }
+    inline void glGenTextures(GLsizei n, GLuint *textures) { ::glGenTextures(n, textures); }
+    inline GLenum glGetError() { return ::glGetError(); }
+    inline void glGetIntegerv(GLenum pname, GLint *params) { ::glGetIntegerv(pname, params); }
+    inline const GLubyte *glGetString(GLenum name) { return ::glGetString(name); }
+    inline void glPixelStorei(GLenum pname, GLint param) { ::glPixelStorei(pname, param); }
+    inline void glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height,
+                            GLint border, GLenum format, GLenum type, const GLvoid *pixels) {
+        ::glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
+    }
+    inline void glTexParameteri(GLenum target, GLenum pname, GLint param) {
+        ::glTexParameteri(target, pname, param);
+    }
+    inline void glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width,
+                               GLsizei height, GLenum format, GLenum type, const GLvoid *pixels) {
+        ::glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
+    }
+    inline void glViewport(GLint x, GLint y, GLsizei width, GLsizei height) {
+        ::glViewport(x, y, width, height);
+    }
+};
+
+// Qt5+ QOpenGLExtraFunctions - Maps to OpenGL 2.0+ functions
+// Some of these may need runtime checks on macOS 10.6.8
+class QOpenGLExtraFunctions : public QOpenGLFunctions
+{
+public:
+    // Vertex Buffer Objects (OpenGL 1.5+, available in OpenGL 2.0)
+    inline void glBindBuffer(GLenum target, GLuint buffer) { ::glBindBuffer(target, buffer); }
+    inline void glDeleteBuffers(GLsizei n, const GLuint *buffers) { ::glDeleteBuffers(n, buffers); }
+    inline void glGenBuffers(GLsizei n, GLuint *buffers) { ::glGenBuffers(n, buffers); }
+    inline void glBufferData(GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage) {
+        ::glBufferData(target, size, data, usage);
+    }
+    inline void glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid *data) {
+        ::glBufferSubData(target, offset, size, data);
+    }
+    inline void *glMapBuffer(GLenum target, GLenum access) {
+        return ::glMapBuffer(target, access);
+    }
+    inline GLboolean glUnmapBuffer(GLenum target) {
+        return ::glUnmapBuffer(target);
+    }
+
+    // Pixel Buffer Objects (OpenGL 2.1+, may not be available on all 10.6.8 systems)
+    // These use the same VBO functions with GL_PIXEL_UNPACK_BUFFER target
+
+    // Shader functions (OpenGL 2.0+)
+    inline GLuint glCreateShader(GLenum type) { return ::glCreateShader(type); }
+    inline void glShaderSource(GLuint shader, GLsizei count, const GLchar **string, const GLint *length) {
+        ::glShaderSource(shader, count, string, length);
+    }
+    inline void glCompileShader(GLuint shader) { ::glCompileShader(shader); }
+    inline void glGetShaderiv(GLuint shader, GLenum pname, GLint *params) {
+        ::glGetShaderiv(shader, pname, params);
+    }
+    inline void glGetShaderInfoLog(GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog) {
+        ::glGetShaderInfoLog(shader, bufSize, length, infoLog);
+    }
+    inline void glDeleteShader(GLuint shader) { ::glDeleteShader(shader); }
+
+    // Program functions (OpenGL 2.0+)
+    inline GLuint glCreateProgram() { return ::glCreateProgram(); }
+    inline void glAttachShader(GLuint program, GLuint shader) { ::glAttachShader(program, shader); }
+    inline void glLinkProgram(GLuint program) { ::glLinkProgram(program); }
+    inline void glUseProgram(GLuint program) { ::glUseProgram(program); }
+    inline void glGetProgramiv(GLuint program, GLenum pname, GLint *params) {
+        ::glGetProgramiv(program, pname, params);
+    }
+    inline void glGetProgramInfoLog(GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog) {
+        ::glGetProgramInfoLog(program, bufSize, length, infoLog);
+    }
+    inline void glDeleteProgram(GLuint program) { ::glDeleteProgram(program); }
+
+    // Uniform functions (OpenGL 2.0+)
+    inline GLint glGetUniformLocation(GLuint program, const GLchar *name) {
+        return ::glGetUniformLocation(program, name);
+    }
+    inline void glUniform1f(GLint location, GLfloat v0) { ::glUniform1f(location, v0); }
+    inline void glUniform1i(GLint location, GLint v0) { ::glUniform1i(location, v0); }
+    inline void glUniform2f(GLint location, GLfloat v0, GLfloat v1) { ::glUniform2f(location, v0, v1); }
+    inline void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2) {
+        ::glUniform3f(location, v0, v1, v2);
+    }
+    inline void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) {
+        ::glUniform4f(location, v0, v1, v2, v3);
+    }
+
+    // Vertex Attribute functions (OpenGL 2.0+)
+    inline GLint glGetAttribLocation(GLuint program, const GLchar *name) {
+        return ::glGetAttribLocation(program, name);
+    }
+    inline void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized,
+                                     GLsizei stride, const GLvoid *pointer) {
+        ::glVertexAttribPointer(index, size, type, normalized, stride, pointer);
+    }
+    inline void glEnableVertexAttribArray(GLuint index) { ::glEnableVertexAttribArray(index); }
+    inline void glDisableVertexAttribArray(GLuint index) { ::glDisableVertexAttribArray(index); }
+};
+
+// Qt5+ QOpenGLFunctions_1_5 - Legacy fixed-function pipeline
+// Note: OpenGL 2.0 still supports these, but they're deprecated
+// Only use if not using shaders (legacy path)
+#ifndef QT_OPENGL_ES_2
+class QOpenGLFunctions_1_5
+{
+public:
+    inline bool initializeOpenGLFunctions() { return true; }
+
+    // Matrix functions (deprecated in OpenGL 3.0+, but available in 2.0)
+    inline void glMatrixMode(GLenum mode) { ::glMatrixMode(mode); }
+    inline void glLoadIdentity() { ::glLoadIdentity(); }
+    inline void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top,
+                       GLdouble zNear, GLdouble zFar) {
+        ::glOrtho(left, right, bottom, top, zNear, zFar);
+    }
+
+    // Vertex arrays (deprecated in OpenGL 3.0+, but available in 2.0)
+    inline void glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer) {
+        ::glVertexPointer(size, type, stride, pointer);
+    }
+    inline void glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer) {
+        ::glTexCoordPointer(size, type, stride, pointer);
+    }
+    inline void glEnableClientState(GLenum array) { ::glEnableClientState(array); }
+    inline void glDisableClientState(GLenum array) { ::glDisableClientState(array); }
+};
+#endif // !QT_OPENGL_ES_2
+
+// Additional macros for Qt5+ compatibility
+#ifndef GL_TEXTURE0
+#define GL_TEXTURE0 0x84C0
+#endif
+
+#ifndef GL_CLAMP_TO_EDGE
+#define GL_CLAMP_TO_EDGE 0x812F
+#endif
+
+// OpenGL 2.0 guarantees these, but define them if missing
+#ifndef GL_ARRAY_BUFFER
+#define GL_ARRAY_BUFFER 0x8892
+#endif
+
+#ifndef GL_ELEMENT_ARRAY_BUFFER
+#define GL_ELEMENT_ARRAY_BUFFER 0x8893
+#endif
+
+#ifndef GL_STATIC_DRAW
+#define GL_STATIC_DRAW 0x88E4
+#endif
+
+#ifndef GL_DYNAMIC_DRAW
+#define GL_DYNAMIC_DRAW 0x88E8
+#endif
+
+#ifndef GL_STREAM_DRAW
+#define GL_STREAM_DRAW 0x88E0
+#endif
+
+// PBO support (OpenGL 2.1+)
+#ifndef GL_PIXEL_UNPACK_BUFFER
+#define GL_PIXEL_UNPACK_BUFFER 0x88EC
+#endif
+
+#ifndef GL_PIXEL_PACK_BUFFER
+#define GL_PIXEL_PACK_BUFFER 0x88EB
+#endif
+
+// Shader types
+#ifndef GL_VERTEX_SHADER
+#define GL_VERTEX_SHADER 0x8B31
+#endif
+
+#ifndef GL_FRAGMENT_SHADER
+#define GL_FRAGMENT_SHADER 0x8B30
+#endif
+
+// Notes for OpenGL 2.0 on macOS 10.6.8:
+// - All OpenGL 2.0 core features are supported
+// - Some OpenGL 2.1+ features (like PBO) may be available as extensions
+// - No geometry shaders (OpenGL 3.2+)
+// - No compute shaders (OpenGL 4.3+)
+// - GLSL version 1.20 is maximum (not 1.30+ or 3.30+)
+// - Core profile not available (only compatibility profile)
diff --git a/src/qmplay2/opengl/shaders/OSD.frag b/src/qmplay2/opengl/shaders/OSD.frag
index 8fda806c..b686c1f4 100644
--- a/src/qmplay2/opengl/shaders/OSD.frag
+++ b/src/qmplay2/opengl/shaders/OSD.frag
@@ -1,3 +1,4 @@
+// GLSL 1.20 for OpenGL 2.0
 varying vec2 vTexCoord;
 uniform sampler2D uTex;
 
diff --git a/src/qmplay2/opengl/shaders/OSD.vert b/src/qmplay2/opengl/shaders/OSD.vert
index 8be7a4ba..2edb9922 100644
--- a/src/qmplay2/opengl/shaders/OSD.vert
+++ b/src/qmplay2/opengl/shaders/OSD.vert
@@ -1,3 +1,4 @@
+// GLSL 1.20 for OpenGL 2.0
 attribute vec4 aPosition;
 attribute vec2 aTexCoord;
 varying vec2 vTexCoord;
diff --git a/src/qmplay2/opengl/shaders/Video.vert b/src/qmplay2/opengl/shaders/Video.vert
index 7ac052e1..0e1774ca 100644
--- a/src/qmplay2/opengl/shaders/Video.vert
+++ b/src/qmplay2/opengl/shaders/Video.vert
@@ -1,3 +1,4 @@
+// GLSL 1.20 for OpenGL 2.0
 attribute vec4 aPosition;
 attribute vec2 aTexCoord;
 varying vec2 vTexCoord;
diff --git a/src/qmplay2/opengl/shaders/VideoRGB.frag b/src/qmplay2/opengl/shaders/VideoRGB.frag
index 4dd4e1d8..2ec855e3 100644
--- a/src/qmplay2/opengl/shaders/VideoRGB.frag
+++ b/src/qmplay2/opengl/shaders/VideoRGB.frag
@@ -1,3 +1,4 @@
+// GLSL 1.20 for OpenGL 2.0
 #define texture texture2D
 
 varying vec2 vTexCoord;
diff --git a/src/qmplay2/opengl/shaders/VideoYCbCr.frag b/src/qmplay2/opengl/shaders/VideoYCbCr.frag
index 22cbac9f..5391dad4 100644
--- a/src/qmplay2/opengl/shaders/VideoYCbCr.frag
+++ b/src/qmplay2/opengl/shaders/VideoYCbCr.frag
@@ -1,3 +1,4 @@
+// GLSL 1.20 for OpenGL 2.0
 #ifdef TEXTURE_RECTANGLE
     #define sampler sampler2DRect
     #define texCoordY  (vTexCoord * uTextureSize)
diff --git a/src/qmplay2/vulkan/VulkanInstance.cpp b/src/qmplay2/vulkan/VulkanInstance.cpp
index 8d4196df..5867bbac 100644
--- a/src/qmplay2/vulkan/VulkanInstance.cpp
+++ b/src/qmplay2/vulkan/VulkanInstance.cpp
@@ -26,7 +26,7 @@
 #include "VulkanImagePool.hpp"
 #include "VulkanWriter.hpp"
 
-#include <QGuiApplication>
+#include <QApplication>
 #include <QVulkanInstance>
 #include <QVersionNumber>
 #include <QResource>
@@ -672,7 +672,7 @@ bool Instance::isCompatibleDevice(const shared_ptr<PhysicalDevice> &physicalDevi
     QString errorString = "Vulkan :: Discarding \"";
     errorString += properties.deviceName.data();
     errorString += "\", because:";
-    for (auto &&error : std::as_const(errors))
+    for (auto &&error : errors)
         errorString += "\n   - " + error;
     qDebug().noquote() << errorString;
 
diff --git a/src/qmplay2/vulkan/VulkanInstance.hpp b/src/qmplay2/vulkan/VulkanInstance.hpp
index afb24621..9a1eba4c 100644
--- a/src/qmplay2/vulkan/VulkanInstance.hpp
+++ b/src/qmplay2/vulkan/VulkanInstance.hpp
@@ -56,18 +56,18 @@ public:
     Instance();
     ~Instance();
 
-    void prepareDestroy() override;
+    void prepareDestroy();
 
 private:
     void init(bool doObtainPhysicalDevice);
 
 public:
-    QString name() const override;
-    QMPlay2CoreClass::Renderer renderer() const override;
+    QString name() const;
+    QMPlay2CoreClass::Renderer renderer() const;
 
-    VideoWriter *createOrGetVideoOutput() override;
+    VideoWriter *createOrGetVideoOutput();
 
-    bool checkFiltersSupported() const override;
+    bool checkFiltersSupported() const;
 
 public:
     inline QVulkanInstance *qVulkanInstance();
diff --git a/src/qmplay2/vulkan/VulkanWindow.cpp b/src/qmplay2/vulkan/VulkanWindow.cpp
index 3cf5843c..884c161f 100644
--- a/src/qmplay2/vulkan/VulkanWindow.cpp
+++ b/src/qmplay2/vulkan/VulkanWindow.cpp
@@ -40,7 +40,7 @@
 #include <Functions.hpp>
 #include <Sphere.hpp>
 
-#include <QGuiApplication>
+#include <QApplication>
 #include <QVulkanInstance>
 #include <QElapsedTimer>
 #include <qevent.h>
@@ -727,7 +727,7 @@ vector<unique_lock<mutex>> Window::prepareOSD(bool &changed)
     vector<unique_lock<mutex>> lockers;
     lockers.reserve(m_osd.size());
 
-    for (auto &&osd : std::as_const(m_osd))
+    for (auto &&osd : m_osd)
     {
         lockers.push_back(osd->lock());
 
@@ -793,7 +793,7 @@ void Window::renderOSD()
 
     vector<pair<const QMPlay2OSD::Image *, const QMPlay2OSD *>> osdImages;
 
-    for (auto &&osd : std::as_const(m_osd))
+    for (auto &&osd : m_osd)
     {
         osd->iterate([&](const QMPlay2OSD::Image &image) {
             if (image.dataBufferView->buffer()->device() != m.device)
diff --git a/src/qmplay2/vulkan/VulkanWindow.hpp b/src/qmplay2/vulkan/VulkanWindow.hpp
index 44116c0f..9147df3a 100644
--- a/src/qmplay2/vulkan/VulkanWindow.hpp
+++ b/src/qmplay2/vulkan/VulkanWindow.hpp
@@ -159,9 +159,9 @@ private:
     void resetSurface();
 
 private:
-    bool eventFilter(QObject *o, QEvent *e) override;
+    bool eventFilter(QObject *o, QEvent *e);
 
-    bool event(QEvent *e) override;
+    bool event(QEvent *e);
 
 private:
     const shared_ptr<HWInterop> &m_vkHwInterop;
diff --git a/src/qmplay2/vulkan/VulkanWriter.hpp b/src/qmplay2/vulkan/VulkanWriter.hpp
index 955396a7..94877d0f 100644
--- a/src/qmplay2/vulkan/VulkanWriter.hpp
+++ b/src/qmplay2/vulkan/VulkanWriter.hpp
@@ -37,24 +37,24 @@ public:
     Writer();
     ~Writer();
 
-    bool set() override;
+    bool set();
 
-    bool readyWrite() const override;
+    bool readyWrite() const;
 
-    bool processParams(bool *paramsCorrected) override;
+    bool processParams(bool *paramsCorrected);
 
-    AVPixelFormats supportedPixelFormats() const override;
+    AVPixelFormats supportedPixelFormats() const;
 
-    void writeVideo(const Frame &videoFrame, QMPlay2OSDList &&osdList) override;
+    void writeVideo(const Frame &videoFrame, QMPlay2OSDList &&osdList);
 
-    void pause() override;
+    void pause();
 
-    QString name() const override;
+    QString name() const;
 
-    bool open() override;
+    bool open();
 
-    bool setHWDecContext(const shared_ptr<HWDecContext> &hwDecContext) override;
-    shared_ptr<HWDecContext> hwDecContext() const override;
+    bool setHWDecContext(const shared_ptr<HWDecContext> &hwDecContext);
+    shared_ptr<HWDecContext> hwDecContext() const;
 
 private:
     Window *const m_window;
diff --git a/src/qmplay2/vulkan/VulkanYadifDeint.hpp b/src/qmplay2/vulkan/VulkanYadifDeint.hpp
index 538eaec6..a0aa5391 100644
--- a/src/qmplay2/vulkan/VulkanYadifDeint.hpp
+++ b/src/qmplay2/vulkan/VulkanYadifDeint.hpp
@@ -36,9 +36,9 @@ public:
     YadifDeint(const shared_ptr<HWInterop> &hwInterop);
     ~YadifDeint();
 
-    bool filter(QQueue<Frame> &framesQueue) override;
+    bool filter(QQueue<Frame> &framesQueue);
 
-    bool processParams(bool *paramsCorrected) override;
+    bool processParams(bool *paramsCorrected);
 
 private:
     bool ensureResources();
