diff --git a/.github/autobuild/android.sh b/.github/autobuild/android.sh index d0a9cde29e..c02273e146 100755 --- a/.github/autobuild/android.sh +++ b/.github/autobuild/android.sh @@ -69,8 +69,20 @@ setup_qt() { else echo "Installing Qt..." python3 -m pip install "aqtinstall==${AQTINSTALL_VERSION}" + local qtmultimedia=() + if [[ ! "${QT_VERSION}" =~ 5\..* ]]; then + # From Qt6 onwards, qtmultimedia is a module and cannot be installed + # as an archive anymore. + qtmultimedia=("--modules") + fi + qtmultimedia+=("qtmultimedia") + python3 -m aqt install-qt --outputdir "${QT_BASEDIR}" linux android "${QT_VERSION}" \ - --archives qtbase qttools qttranslations qtandroidextras + --archives qtbase qttools qttranslations qtandroidextras \ + "${qtmultimedia[@]}" + # Delete libraries, which we don't use, but which bloat the resulting package and might introduce unwanted dependencies. + find "${QT_BASEDIR}" -name 'libQt5*Quick*.so' -delete + rm -r "${QT_BASEDIR}/${QT_VERSION}/android/qml/" fi } diff --git a/.github/autobuild/ios.sh b/.github/autobuild/ios.sh index 34820f5fe9..14cbcbf873 100755 --- a/.github/autobuild/ios.sh +++ b/.github/autobuild/ios.sh @@ -20,7 +20,14 @@ setup() { echo "Installing Qt" python3 -m pip install "aqtinstall==${AQTINSTALL_VERSION}" # Install actual ios Qt: - python3 -m aqt install-qt --outputdir "${QT_DIR}" mac ios "${QT_VERSION}" --archives qtbase qttools qttranslations + local qtmultimedia=() + if [[ ! "${QT_VERSION}" =~ 5\..* ]]; then + # From Qt6 onwards, qtmultimedia is a module and cannot be installed + # as an archive anymore. + qtmultimedia=("--modules") + fi + qtmultimedia+=("qtmultimedia") + python3 -m aqt install-qt --outputdir "${QT_DIR}" mac ios "${QT_VERSION}" --archives qtbase qttools qttranslations "${qtmultimedia[@]}" if [[ ! "${QT_VERSION}" =~ 5\..* ]]; then # Starting with Qt6, ios' qtbase install does no longer include a real qmake binary. # Instead, it is a script which invokes the mac desktop qmake. diff --git a/.github/autobuild/linux_deb.sh b/.github/autobuild/linux_deb.sh index c85d688da0..31ff53388d 100755 --- a/.github/autobuild/linux_deb.sh +++ b/.github/autobuild/linux_deb.sh @@ -27,7 +27,7 @@ setup() { echo "Installing dependencies..." sudo apt-get -qq update - sudo apt-get -qq --no-install-recommends -y install devscripts build-essential debhelper fakeroot libjack-jackd2-dev qtbase5-dev qttools5-dev-tools + sudo apt-get -qq --no-install-recommends -y install devscripts build-essential debhelper fakeroot libjack-jackd2-dev qtbase5-dev qttools5-dev-tools qtmultimedia5-dev setup_cross_compiler } @@ -46,7 +46,7 @@ setup_cross_compiler() { return fi local GCC_VERSION=7 # 7 is the default on 18.04, there is no reason not to update once 18.04 is out of support - sudo apt install -qq -y --no-install-recommends "g++-${GCC_VERSION}-${ABI_NAME}" "qt5-qmake:${TARGET_ARCH}" "qtbase5-dev:${TARGET_ARCH}" "libjack-jackd2-dev:${TARGET_ARCH}" + sudo apt install -qq -y --no-install-recommends "g++-${GCC_VERSION}-${ABI_NAME}" "qt5-qmake:${TARGET_ARCH}" "qtbase5-dev:${TARGET_ARCH}" "libjack-jackd2-dev:${TARGET_ARCH}" "qtmultimedia5-dev:${TARGET_ARCH}" sudo update-alternatives --install "/usr/bin/${ABI_NAME}-g++" g++ "/usr/bin/${ABI_NAME}-g++-${GCC_VERSION}" 10 sudo update-alternatives --install "/usr/bin/${ABI_NAME}-gcc" gcc "/usr/bin/${ABI_NAME}-gcc-${GCC_VERSION}" 10 diff --git a/.github/autobuild/mac.sh b/.github/autobuild/mac.sh index 63f310893c..efb1fef565 100755 --- a/.github/autobuild/mac.sh +++ b/.github/autobuild/mac.sh @@ -19,7 +19,14 @@ setup() { else echo "Installing Qt..." python3 -m pip install "aqtinstall==${AQTINSTALL_VERSION}" - python3 -m aqt install-qt --outputdir "${QT_DIR}" mac desktop "${QT_VERSION}" --archives qtbase qttools qttranslations + local qtmultimedia=() + if [[ ! "${QT_VERSION}" =~ 5\..* ]]; then + # From Qt6 onwards, qtmultimedia is a module and cannot be installed + # as an archive anymore. + qtmultimedia=("--modules") + fi + qtmultimedia+=("qtmultimedia") + python3 -m aqt install-qt --outputdir "${QT_DIR}" mac desktop "${QT_VERSION}" --archives qtbase qttools qttranslations "${qtmultimedia[@]}" fi } diff --git a/.github/autobuild/windows.ps1 b/.github/autobuild/windows.ps1 index 953070cddc..2a800bbb81 100644 --- a/.github/autobuild/windows.ps1 +++ b/.github/autobuild/windows.ps1 @@ -44,6 +44,13 @@ Function Install-Qt "$QtArch", "--archives", "qtbase", "qttools", "qttranslations" ) + if ( $QtVersion -notmatch '^5\.' ) + { + # From Qt6 onwards, qtmultimedia is a module and cannot be installed + # as an archive anymore. + $Args += ("--modules") + } + $Args += ("qtmultimedia") aqt install-qt @Args if ( !$? ) { diff --git a/COMPILING.md b/COMPILING.md index b4d45c9ff4..5893000962 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -23,7 +23,7 @@ Then run `git clone git@github.com:jamulussoftware/jamulus` in Terminal to get t ### Install dependencies -On Debian 11+ you can install the dependencies by issuing the following command: `sudo apt-get -qq --no-install-recommends -y install devscripts build-essential debhelper fakeroot libjack-jackd2-dev qtbase5-dev qttools5-dev-tools` +On Debian 11+ you can install the dependencies by issuing the following command: `sudo apt-get -qq --no-install-recommends -y install devscripts build-essential debhelper fakeroot libjack-jackd2-dev qtbase5-dev qttools5-dev-tools qtmultimedia5-dev` **Note:** The exact dependencies might be different for older distributions. See [this comment by softins](https://github.com/jamulussoftware/jamulus/pull/2267#issuecomment-1022127426) @@ -34,6 +34,7 @@ On Debian 11+ you can install the dependencies by issuing the following command: - qt5-qtbase - jack-audio-connection-kit-devel - qt5-linguist +- qt5-qtmultimedia ### For all desktop distributions diff --git a/Jamulus.pro b/Jamulus.pro index 54034a0fec..3793fde7de 100644 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -39,6 +39,7 @@ contains(CONFIG, "headless") { QT -= gui } else { QT += widgets + QT += multimedia } LRELEASE_DIR = src/translation diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 5bf53539ae..7725864d27 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -13,7 +13,6 @@ - diff --git a/src/client.cpp b/src/client.cpp index 0e2a6f65d5..99f74caf10 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -62,6 +62,7 @@ CClient::CClient ( const quint16 iPortNumber, bFraSiFactSafeSupported ( false ), eGUIDesign ( GD_ORIGINAL ), eMeterStyle ( MT_LED_STRIPE ), + bEnableAudioAlerts ( false ), bEnableOPUS64 ( false ), bJitterBufferOK ( true ), bEnableIPv6 ( bNEnableIPv6 ), diff --git a/src/client.h b/src/client.h index 32641abab2..1a5d590c75 100644 --- a/src/client.h +++ b/src/client.h @@ -347,6 +347,7 @@ class CClient : public QObject EGUIDesign eGUIDesign; EMeterStyle eMeterStyle; + bool bEnableAudioAlerts; bool bEnableOPUS64; bool bJitterBufferOK; diff --git a/src/clientdlg.cpp b/src/clientdlg.cpp index b67bd75989..0ad7db99ed 100644 --- a/src/clientdlg.cpp +++ b/src/clientdlg.cpp @@ -254,6 +254,9 @@ CClientDlg::CClientDlg ( CClient* pNCliP, // set window title (with no clients connected -> "0") SetMyWindowTitle ( 0 ); + // track number of clients to detect joins/leaves for audio alerts + iClients = 0; + // prepare Mute Myself info label (invisible by default) lblGlobalInfoLabel->setStyleSheet ( ".QLabel { background: red; }" ); lblGlobalInfoLabel->hide(); @@ -822,6 +825,12 @@ void CClientDlg::OnCLVersionAndOSReceived ( CHostAddress, COSUtil::EOpSystemType void CClientDlg::OnChatTextReceived ( QString strChatText ) { + if ( pSettings->bEnableAudioAlerts ) + { + QSoundEffect* sf = new QSoundEffect(); + sf->setSource ( QUrl::fromLocalFile ( ":sounds/res/sounds/new_message.wav" ) ); + sf->play(); + } ChatDlg.AddChatText ( strChatText ); // Open chat dialog. If a server welcome message is received, we force @@ -876,6 +885,17 @@ void CClientDlg::OnConClientListMesReceived ( CVector vecChanInfo void CClientDlg::OnNumClientsChanged ( int iNewNumClients ) { + if ( pSettings->bEnableAudioAlerts && iNewNumClients > iClients ) + { + QSoundEffect* sf = new QSoundEffect(); + sf->setSource ( QUrl::fromLocalFile ( ":sounds/res/sounds/new_user.wav" ) ); + sf->play(); + } + + // iNewNumClients will be zero on the first trigger of this signal handler when connecting to a new server. + // Subsequent triggers will thus sound the alert (if enabled). + iClients = iNewNumClients; + // update window title SetMyWindowTitle ( iNewNumClients ); } diff --git a/src/clientdlg.h b/src/clientdlg.h index e7e8ac1e8b..3cd26c2696 100644 --- a/src/clientdlg.h +++ b/src/clientdlg.h @@ -38,6 +38,7 @@ #include #include #include +#include #if QT_VERSION >= QT_VERSION_CHECK( 5, 6, 0 ) # include #endif @@ -101,6 +102,7 @@ class CClientDlg : public CBaseDlg, private Ui_CClientDlgBase CClient* pClient; CClientSettings* pSettings; + int iClients; bool bConnected; bool bConnectDlgWasShown; bool bMIDICtrlUsed; diff --git a/src/clientsettingsdlg.cpp b/src/clientsettingsdlg.cpp index 8b2b50b851..1f0426cbb1 100644 --- a/src/clientsettingsdlg.cpp +++ b/src/clientsettingsdlg.cpp @@ -390,6 +390,14 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet chbDetectFeedback->setWhatsThis ( strDetectFeedback ); chbDetectFeedback->setAccessibleName ( tr ( "Feedback Protection check box" ) ); + // audio alerts + QString strAudioAlerts = "" + tr ( "Audio Alerts" ) + ": " + + tr ( "Enable audio alert when receiving a chat message and when a new client joins the session. " + "A second sound device may be required to hear the alerts." ); + lblAudioAlerts->setWhatsThis ( strAudioAlerts ); + chbAudioAlerts->setWhatsThis ( strAudioAlerts ); + chbAudioAlerts->setAccessibleName ( tr ( "Audio Alerts check box" ) ); + // init driver button #if defined( _WIN32 ) && !defined( WITH_JACK ) butDriverSetup->setText ( tr ( "ASIO Device Settings" ) ); @@ -470,6 +478,9 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet // init number of mixer rows spnMixerRows->setValue ( pSettings->iNumMixerPanelRows ); + // init audio alerts + chbAudioAlerts->setCheckState ( pSettings->bEnableAudioAlerts ? Qt::Checked : Qt::Unchecked ); + // update feedback detection chbDetectFeedback->setCheckState ( pSettings->bEnableFeedbackDetection ? Qt::Checked : Qt::Unchecked ); @@ -633,6 +644,8 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet QObject::connect ( chbDetectFeedback, &QCheckBox::stateChanged, this, &CClientSettingsDlg::OnFeedbackDetectionChanged ); + QObject::connect ( chbAudioAlerts, &QCheckBox::stateChanged, this, &CClientSettingsDlg::OnAudioAlertsChanged ); + // line edits QObject::connect ( edtNewClientLevel, &QLineEdit::editingFinished, this, &CClientSettingsDlg::OnNewClientLevelEditingFinished ); @@ -984,6 +997,8 @@ void CClientSettingsDlg::OnMeterStyleActivated ( int iMeterStyleIdx ) UpdateDisplay(); } +void CClientSettingsDlg::OnAudioAlertsChanged ( int value ) { pSettings->bEnableAudioAlerts = value == Qt::Checked; } + void CClientSettingsDlg::OnAutoJitBufStateChanged ( int value ) { pClient->SetDoAutoSockBufSize ( value == Qt::Checked ); diff --git a/src/clientsettingsdlg.h b/src/clientsettingsdlg.h index acdcf256ec..eb17c8ee78 100644 --- a/src/clientsettingsdlg.h +++ b/src/clientsettingsdlg.h @@ -96,6 +96,7 @@ public slots: void OnAudioQualityActivated ( int iQualityIdx ); void OnGUIDesignActivated ( int iDesignIdx ); void OnMeterStyleActivated ( int iMeterStyleIdx ); + void OnAudioAlertsChanged ( int value ); void OnLanguageChanged ( QString strLanguage ) { pSettings->strLanguage = strLanguage; } void OnAliasTextChanged ( const QString& strNewName ); void OnInstrumentActivated ( int iCntryListItem ); @@ -114,6 +115,7 @@ public slots: signals: void GUIDesignChanged(); void MeterStyleChanged(); + void AudioAlertsChanged(); void AudioChannelsChanged(); void CustomDirectoriesChanged(); void NumMixerPanelRowsChanged ( int value ); diff --git a/src/clientsettingsdlgbase.ui b/src/clientsettingsdlgbase.ui index 847d32c550..ef6309c6bc 100644 --- a/src/clientsettingsdlgbase.ui +++ b/src/clientsettingsdlgbase.ui @@ -242,6 +242,13 @@ + + + + Audio Alerts + + + @@ -281,6 +288,13 @@ + + + + Enable + + + @@ -1283,6 +1297,7 @@ cbxMeterStyle cbxLanguage spnMixerRows + chbAudioAlerts cbxSoundcard butDriverSetup cbxLInChan diff --git a/src/res/sounds/new_message.wav b/src/res/sounds/new_message.wav new file mode 100644 index 0000000000..b65fb365f6 Binary files /dev/null and b/src/res/sounds/new_message.wav differ diff --git a/src/res/sounds/new_user.wav b/src/res/sounds/new_user.wav new file mode 100644 index 0000000000..149f1748d5 Binary files /dev/null and b/src/res/sounds/new_user.wav differ diff --git a/src/resources.qrc b/src/resources.qrc index 7a1e45f82a..55d6a2b333 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -367,4 +367,8 @@ res/flags/zm.png res/flags/zw.png + + res/sounds/new_user.wav + res/sounds/new_message.wav + diff --git a/src/settings.cpp b/src/settings.cpp index b858da4f9e..0510ce85e7 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -283,6 +283,12 @@ void CClientSettings::ReadSettingsFromXML ( const QDomDocument& IniXMLDocument, iNumMixerPanelRows = iValue; } + // audio alerts + if ( GetFlagIniSet ( IniXMLDocument, "client", "enableaudioalerts", bValue ) ) + { + bEnableAudioAlerts = bValue; + } + // name pClient->ChannelInfo.strName = FromBase64ToString ( GetIniSetting ( IniXMLDocument, "client", "name_base64", ToBase64 ( QCoreApplication::translate ( "CMusProfDlg", "No Name" ) ) ) ); @@ -634,6 +640,9 @@ void CClientSettings::WriteSettingsToXML ( QDomDocument& IniXMLDocument ) // number of mixer panel rows SetNumericIniSet ( IniXMLDocument, "client", "numrowsmixpan", iNumMixerPanelRows ); + // audio alerts + SetFlagIniSet ( IniXMLDocument, "client", "enableaudioalerts", bEnableAudioAlerts ); + // name PutIniSetting ( IniXMLDocument, "client", "name_base64", ToBase64 ( pClient->ChannelInfo.strName ) ); diff --git a/src/settings.h b/src/settings.h index 744c92380d..6ab7540eaa 100644 --- a/src/settings.h +++ b/src/settings.h @@ -130,6 +130,7 @@ class CClientSettings : public CSettings vstrDirectoryAddress ( MAX_NUM_SERVER_ADDR_ITEMS, "" ), eDirectoryType ( AT_DEFAULT ), bEnableFeedbackDetection ( true ), + bEnableAudioAlerts ( false ), vecWindowPosSettings(), // empty array vecWindowPosChat(), // empty array vecWindowPosConnect(), // empty array @@ -163,6 +164,7 @@ class CClientSettings : public CSettings EDirectoryType eDirectoryType; int iCustomDirectoryIndex; // index of selected custom directory server bool bEnableFeedbackDetection; + bool bEnableAudioAlerts; // window position/state settings QByteArray vecWindowPosSettings; diff --git a/src/util.cpp b/src/util.cpp index 9c2591c23d..d92f8cc428 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -593,26 +593,27 @@ CAboutDlg::CAboutDlg ( QWidget* parent ) : CBaseDlg ( parent ) "

" ); // libraries used by this compilation - txvLibraries->setText ( tr ( "This app uses the following libraries, resources or code snippets:" ) + "

" + - tr ( "Qt cross-platform application framework" ) + - ", https://www.qt.io

" - "

Opus Interactive Audio Codec" - ", https://www.opus-codec.org

" + txvLibraries->setText ( + tr ( "This app uses the following libraries, resources or code snippets:" ) + "

" + tr ( "Qt cross-platform application framework" ) + + ", https://www.qt.io

" + "

Opus Interactive Audio Codec" + ", https://www.opus-codec.org

" # if defined( _WIN32 ) && !defined( WITH_JACK ) - "

ASIO (Audio Stream I/O) SDK" - ", https://www.steinberg.net/developers
" + - "ASIO is a trademark and software of Steinberg Media Technologies GmbH

" + "

ASIO (Audio Stream I/O) SDK" + ", https://www.steinberg.net/developers
" + + "ASIO is a trademark and software of Steinberg Media Technologies GmbH

" # endif - "

" + - tr ( "Audio reverberation code by Perry R. Cook and Gary P. Scavone" ) + - ", 1995 - 2021, " - "The Synthesis ToolKit in C++ (STK)

" - "

" + - tr ( "Some pixmaps are from the" ) + - " Open Clip Art Library (OCAL), " - "https://openclipart.org

" - "

" + - tr ( "Flag icons by Mark James" ) + ", http://www.famfamfam.com

" ); + "

" + + tr ( "Audio reverberation code by Perry R. Cook and Gary P. Scavone" ) + + ", 1995 - 2021, " + "The Synthesis ToolKit in C++ (STK)

" + "

" + + tr ( "Some pixmaps are from the" ) + + " Open Clip Art Library (OCAL), " + "https://openclipart.org

" + "

" + + tr ( "Flag icons by Mark James" ) + ", http://www.famfamfam.com

" + "

" + + tr ( "Some sound samples are from" ) + " Freesound, " + "https://freesound.org

" ); // contributors list txvContributors->setText ( @@ -1613,6 +1614,9 @@ QString GetVersionAndNameStr ( const bool bDisplayInGui ) strVersionText += "\n *** Flag icons by Mark James"; strVersionText += "\n *** "; strVersionText += "\n *** "; + strVersionText += "\n *** Some sound samples are from Freesound"; + strVersionText += "\n *** "; + strVersionText += "\n *** "; strVersionText += "\n *** Copyright (C) 2005-2022 The Jamulus Development Team"; strVersionText += "\n"; }