Work around startup UI freeze caused by QtTextToSpeech

This merge request works around an annoying startup hang that I introduced when adding text-to-speech to NeoChat. The previous implementation was a QML singleton that used the `TextToSpeech` QML component. Unfortunately that component blocks the UI thread when first loading it, while it connects to speech-dispatcher.

This MR just rewrites that singleton in C++, and moves initialization of QtTextToSpeech to the first time you read a message aloud. It doesn't fix the performance problem, but it at least stops it from affecting startup.

In the future, I'd like to move speech operations to a background thread to completely mitigate the initialization freeze.
This commit is contained in:
Ritchie Frodomar
2025-05-16 14:27:49 -04:00
parent 3ea844496a
commit d31cc486bb
7 changed files with 41 additions and 32 deletions

View File

@@ -56,7 +56,7 @@ ecm_setup_version(${PROJECT_VERSION}
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
)
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg WebView)
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg TextToSpeech WebView)
set_package_properties(Qt6 PROPERTIES
TYPE REQUIRED
PURPOSE "Basic application components"

View File

@@ -37,10 +37,8 @@ add_library(neochat STATIC
identityserverhelper.h
models/commonroomsmodel.cpp
models/commonroomsmodel.h
)
set_source_files_properties(qml/TextToSpeechWrapper.qml PROPERTIES
QT_QML_SINGLETON_TYPE TRUE
texttospeechhelper.h
texttospeechhelper.cpp
)
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
@@ -87,7 +85,6 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/InvitationView.qml
qml/AvatarTabButton.qml
qml/OsmLocationPlugin.qml
qml/TextToSpeechWrapper.qml
qml/FullScreenMap.qml
qml/LocationsPage.qml
qml/LocationMapItem.qml
@@ -204,6 +201,7 @@ target_link_libraries(neochat PUBLIC
Qt::Multimedia
Qt::Network
Qt::QuickControls2
Qt::TextToSpeech
KF6::I18n
KF6::Kirigami
KF6::Notifications

View File

@@ -196,7 +196,6 @@ Kirigami.ApplicationWindow {
NeoChatSettingsView.window = root;
NeoChatSettingsView.connection = root.connection;
WindowController.setBlur(pageStack, NeoChatConfig.blur && !NeoChatConfig.compactLayout);
TextToSpeechWrapper.warmUp();
if (ShareHandler.text && root.connection) {
root.handleShare()
}

View File

@@ -1,24 +0,0 @@
// SPDX-FileCopyrightText: 2025 Ritchie Frodomar <alkalinethunder@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
pragma Singleton
import QtQuick
import QtTextToSpeech
QtObject {
id: root
readonly property TextToSpeech tts: TextToSpeech {
id: tts
}
function warmUp() {
// TODO: This method is called on startup to avoid a UI freeze the first time you read a message aloud, but there's nothing for it to do.
// This would be a good place to check if TTS can actually be used.
}
function say(text: String) {
tts.say(text)
}
}

View File

@@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: 2025 Ritchie Frodomar <ritchie@kde.org>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "texttospeechhelper.h"
void TextToSpeechHelper::speak(const QString &textToSpeak)
{
if (!m_speech) {
m_speech = new QTextToSpeech();
}
m_speech->say(textToSpeak);
}
#include "moc_texttospeechhelper.cpp"

View File

@@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: 2025 Ritchie Frodomar <ritchie@kde.org>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QObject>
#include <QQmlEngine>
#include <QtTextToSpeech>
class TextToSpeechHelper : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
public:
Q_INVOKABLE void speak(const QString &textToSpeak);
private:
QTextToSpeech *m_speech = nullptr;
};

View File

@@ -94,7 +94,7 @@ DelegateContextMenu {
text: i18nc("@action:inmenu", "Read Text Aloud")
icon.name: "audio-speakers-symbolic"
onTriggered: {
TextToSpeechWrapper.say(i18nc("@info text-to-speech %1 is author %2 is message text", "%1 said %2", root.author.displayName, root.plainText))
TextToSpeechHelper.speak(i18nc("@info text-to-speech %1 is author %2 is message text", "%1 said %2", root.author.displayName, root.plainText))
}
}
Kirigami.Action {