From 8c037f83c5c3b770e85f10e0b07cec280883cc58 Mon Sep 17 00:00:00 2001
From: trilene <trilene@runbox.com>
Date: Thu, 12 Nov 2020 19:55:35 -0500
Subject: [PATCH] Make incoming call ringtone a user setting

---
 src/CallManager.cpp      | 32 ++++++++++++++++++++----
 src/CallManager.h        |  3 ++-
 src/UserSettingsPage.cpp | 54 ++++++++++++++++++++++++++++++++++++++++
 src/UserSettingsPage.h   |  6 +++++
 4 files changed, 89 insertions(+), 6 deletions(-)

diff --git a/src/CallManager.cpp b/src/CallManager.cpp
index ac0636e00..89cfeaf99 100644
--- a/src/CallManager.cpp
+++ b/src/CallManager.cpp
@@ -98,7 +98,7 @@ CallManager::CallManager(QObject *parent)
         connect(&session_, &WebRTCSession::stateChanged, this, [this](webrtc::State state) {
                 switch (state) {
                 case webrtc::State::DISCONNECTED:
-                        playRingtone("qrc:/media/media/callend.ogg", false);
+                        playRingtone(QUrl("qrc:/media/media/callend.ogg"), false);
                         clear();
                         break;
                 case webrtc::State::ICEFAILED: {
@@ -121,6 +121,24 @@ CallManager::CallManager(QObject *parent)
                         if (status == QMediaPlayer::LoadedMedia)
                                 player_.play();
                 });
+
+        connect(&player_,
+                QOverload<QMediaPlayer::Error>::of(&QMediaPlayer::error),
+                [this](QMediaPlayer::Error error) {
+                        stopRingtone();
+                        switch (error) {
+                        case QMediaPlayer::FormatError:
+                        case QMediaPlayer::ResourceError:
+                                nhlog::ui()->error("WebRTC: valid ringtone file not found");
+                                break;
+                        case QMediaPlayer::AccessDeniedError:
+                                nhlog::ui()->error("WebRTC: access to ringtone file denied");
+                                break;
+                        default:
+                                nhlog::ui()->error("WebRTC: unable to play ringtone");
+                                break;
+                        }
+                });
 }
 
 void
@@ -153,7 +171,7 @@ CallManager::sendInvite(const QString &roomid, bool isVideo)
         callPartyName_      = callee.display_name.isEmpty() ? callee.user_id : callee.display_name;
         callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
         emit newCallParty();
-        playRingtone("qrc:/media/media/ringback.ogg", true);
+        playRingtone(QUrl("qrc:/media/media/ringback.ogg"), true);
         if (!session_.createOffer(isVideo)) {
                 emit ChatPage::instance()->showNotification("Problem setting up call.");
                 endCall();
@@ -247,7 +265,11 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
                 return;
         }
 
-        playRingtone("qrc:/media/media/ring.ogg", true);
+        const QString &ringtone = ChatPage::instance()->userSettings()->ringtone();
+        if (ringtone != "Mute")
+                playRingtone(ringtone == "Default" ? QUrl("qrc:/media/media/ring.ogg")
+                                                   : QUrl::fromLocalFile(ringtone),
+                             true);
         roomid_ = QString::fromStdString(callInviteEvent.room_id);
         callid_ = callInviteEvent.content.call_id;
         remoteICECandidates_.clear();
@@ -409,13 +431,13 @@ CallManager::retrieveTurnServer()
 }
 
 void
-CallManager::playRingtone(const QString &ringtone, bool repeat)
+CallManager::playRingtone(const QUrl &ringtone, bool repeat)
 {
         static QMediaPlaylist playlist;
         playlist.clear();
         playlist.setPlaybackMode(repeat ? QMediaPlaylist::CurrentItemInLoop
                                         : QMediaPlaylist::CurrentItemOnce);
-        playlist.addMedia(QUrl(ringtone));
+        playlist.addMedia(ringtone);
         player_.setVolume(100);
         player_.setPlaylist(&playlist);
 }
diff --git a/src/CallManager.h b/src/CallManager.h
index da7569e2d..8004e8386 100644
--- a/src/CallManager.h
+++ b/src/CallManager.h
@@ -15,6 +15,7 @@ namespace mtx::responses {
 struct TurnServer;
 }
 
+class QUrl;
 class WebRTCSession;
 
 class CallManager : public QObject
@@ -69,6 +70,6 @@ private:
         void generateCallID();
         void clear();
         void endCall();
-        void playRingtone(const QString &ringtone, bool repeat);
+        void playRingtone(const QUrl &ringtone, bool repeat);
         void stopRingtone();
 };
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index a5a40111b..d82ddbdd4 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -83,6 +83,7 @@ UserSettings::load()
         presence_ =
           settings.value("user/presence", QVariant::fromValue(Presence::AutomaticPresence))
             .value<Presence>();
+        ringtone_         = settings.value("user/ringtone", "Default").toString();
         microphone_       = settings.value("user/microphone", QString()).toString();
         camera_           = settings.value("user/camera", QString()).toString();
         cameraResolution_ = settings.value("user/camera_resolution", QString()).toString();
@@ -321,6 +322,16 @@ UserSettings::setShareKeysWithTrustedUsers(bool shareKeys)
         save();
 }
 
+void
+UserSettings::setRingtone(QString ringtone)
+{
+        if (ringtone == ringtone_)
+                return;
+        ringtone_ = ringtone;
+        emit ringtoneChanged(ringtone);
+        save();
+}
+
 void
 UserSettings::setMicrophone(QString microphone)
 {
@@ -450,6 +461,7 @@ UserSettings::save()
         settings.setValue("font_family", font_);
         settings.setValue("emoji_font_family", emojiFont_);
         settings.setValue("presence", QVariant::fromValue(presence_));
+        settings.setValue("ringtone", ringtone_);
         settings.setValue("microphone", microphone_);
         settings.setValue("camera", camera_);
         settings.setValue("camera_resolution", cameraResolution_);
@@ -530,6 +542,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
         fontSizeCombo_             = new QComboBox{this};
         fontSelectionCombo_        = new QFontComboBox{this};
         emojiFontSelectionCombo_   = new QComboBox{this};
+        ringtoneCombo_             = new QComboBox{this};
         microphoneCombo_           = new QComboBox{this};
         cameraCombo_               = new QComboBox{this};
         cameraResolutionCombo_     = new QComboBox{this};
@@ -720,14 +733,26 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
 
         formLayout_->addRow(callsLabel);
         formLayout_->addRow(new HorizontalLine{this});
+        boxWrap(tr("Ringtone"),
+                ringtoneCombo_,
+                tr("Set the notification sound to play when a call invite arrives"));
         boxWrap(tr("Microphone"), microphoneCombo_);
         boxWrap(tr("Camera"), cameraCombo_);
         boxWrap(tr("Camera resolution"), cameraResolutionCombo_);
         boxWrap(tr("Camera frame rate"), cameraFrameRateCombo_);
+
+        ringtoneCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
+        ringtoneCombo_->addItem("Mute");
+        ringtoneCombo_->addItem("Default");
+        ringtoneCombo_->addItem("Other...");
+        const QString &ringtone = settings_->ringtone();
+        if (!ringtone.isEmpty() && ringtone != "Mute" && ringtone != "Default")
+                ringtoneCombo_->addItem(ringtone);
         microphoneCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
         cameraCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
         cameraResolutionCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
         cameraFrameRateCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
+
         boxWrap(tr("Allow fallback call assist server"),
                 useStunServer_,
                 tr("Will use turn.matrix.org as assist when your home server does not offer one."));
@@ -786,6 +811,34 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
                 static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
                 [this](const QString &family) { settings_->setEmojiFontFamily(family.trimmed()); });
 
+        connect(ringtoneCombo_,
+                static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
+                [this](const QString &ringtone) {
+                        if (ringtone == "Other...") {
+                                QString homeFolder =
+                                  QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
+                                auto filepath = QFileDialog::getOpenFileName(
+                                  this, tr("Select a file"), homeFolder, tr("All Files (*)"));
+                                if (!filepath.isEmpty()) {
+                                        const auto &oldSetting = settings_->ringtone();
+                                        if (oldSetting != "Mute" && oldSetting != "Default")
+                                                ringtoneCombo_->removeItem(
+                                                  ringtoneCombo_->findText(oldSetting));
+                                        settings_->setRingtone(filepath);
+                                        ringtoneCombo_->addItem(filepath);
+                                        ringtoneCombo_->setCurrentText(filepath);
+                                } else {
+                                        ringtoneCombo_->setCurrentText(settings_->ringtone());
+                                }
+                        } else if (ringtone == "Mute" || ringtone == "Default") {
+                                const auto &oldSetting = settings_->ringtone();
+                                if (oldSetting != "Mute" && oldSetting != "Default")
+                                        ringtoneCombo_->removeItem(
+                                          ringtoneCombo_->findText(oldSetting));
+                                settings_->setRingtone(ringtone);
+                        }
+                });
+
         connect(microphoneCombo_,
                 static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
                 [this](const QString &microphone) { settings_->setMicrophone(microphone); });
@@ -916,6 +969,7 @@ UserSettingsPage::showEvent(QShowEvent *)
         utils::restoreCombobox(fontSizeCombo_, QString::number(settings_->fontSize()) + " ");
         utils::restoreCombobox(scaleFactorCombo_, QString::number(utils::scaleFactor()));
         utils::restoreCombobox(themeCombo_, settings_->theme());
+        utils::restoreCombobox(ringtoneCombo_, settings_->ringtone());
 
         // FIXME: Toggle treats true as "off"
         trayToggle_->setState(!settings_->tray());
diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
index 0bd0ac841..d1ae93f0a 100644
--- a/src/UserSettingsPage.h
+++ b/src/UserSettingsPage.h
@@ -73,6 +73,7 @@ class UserSettings : public QObject
         Q_PROPERTY(
           QString emojiFont READ emojiFont WRITE setEmojiFontFamily NOTIFY emojiFontChanged)
         Q_PROPERTY(Presence presence READ presence WRITE setPresence NOTIFY presenceChanged)
+        Q_PROPERTY(QString ringtone READ ringtone WRITE setRingtone NOTIFY ringtoneChanged)
         Q_PROPERTY(QString microphone READ microphone WRITE setMicrophone NOTIFY microphoneChanged)
         Q_PROPERTY(QString camera READ camera WRITE setCamera NOTIFY cameraChanged)
         Q_PROPERTY(QString cameraResolution READ cameraResolution WRITE setCameraResolution NOTIFY
@@ -120,6 +121,7 @@ public:
         void setAvatarCircles(bool state);
         void setDecryptSidebar(bool state);
         void setPresence(Presence state);
+        void setRingtone(QString ringtone);
         void setMicrophone(QString microphone);
         void setCamera(QString camera);
         void setCameraResolution(QString resolution);
@@ -152,6 +154,7 @@ public:
         QString font() const { return font_; }
         QString emojiFont() const { return emojiFont_; }
         Presence presence() const { return presence_; }
+        QString ringtone() const { return ringtone_; }
         QString microphone() const { return microphone_; }
         QString camera() const { return camera_; }
         QString cameraResolution() const { return cameraResolution_; }
@@ -181,6 +184,7 @@ signals:
         void fontChanged(QString state);
         void emojiFontChanged(QString state);
         void presenceChanged(Presence state);
+        void ringtoneChanged(QString ringtone);
         void microphoneChanged(QString microphone);
         void cameraChanged(QString camera);
         void cameraResolutionChanged(QString resolution);
@@ -216,6 +220,7 @@ private:
         QString font_;
         QString emojiFont_;
         Presence presence_;
+        QString ringtone_;
         QString microphone_;
         QString camera_;
         QString cameraResolution_;
@@ -286,6 +291,7 @@ private:
         QComboBox *fontSizeCombo_;
         QFontComboBox *fontSelectionCombo_;
         QComboBox *emojiFontSelectionCombo_;
+        QComboBox *ringtoneCombo_;
         QComboBox *microphoneCombo_;
         QComboBox *cameraCombo_;
         QComboBox *cameraResolutionCombo_;
-- 
GitLab