Adding voice chat for Mankala
Hi everyone! This week we added voice chat for Mankala.
For Mankala, our goal was to elevate the player experience by adding real-time voice chat. We have already added text chat using XMPP, and to make this experience better, voice chat is a much better option to add.
Mankala relies on the XMPP protocol for matchmaking and text chat using the opponent and player XMPP IDs, respectively. I took KDE’s own chat application in reference: Kaidan, to implement Jingle (XEP-0167)—the XMPP extension for media sessions and connect it using an audio processing pipeline. After that, I designed the UI for this voice call to connect, accept, and decline calls with sync in UI and XMPP.
How voice is being implemented
I used the QXmpp library to handle the Jingle signaling and designed a C++ class, VoiceCallManager, which controls all the call sessions. This manager listens for incoming Jingle requests and establishes the peer-to-peer connection.
// VoiceCallManager.cpp
void VoiceCallManager::initializeManager() {
auto* callManager = m_client->findExtension<QXmppCallManager>();
// Listen for incoming XMPP Jingle calls
connect(callManager, &QXmppCallManager::callReceived,
this, [this](QXmppCall* call) {
m_activeCall = call;
m_remoteJid = call->jid();
m_isCallActive = true;
// Notify the QML frontend that a call has started
emit callStateChanged();
emit remoteInfoChanged();
// Accept the call and set up QtMultimedia audio routing
call->accept();
setupAudioPipeline();
});
}
Challenges faced
It was hard to have Game Window and Jingle work together. To handle this, I exposed the call state—such as isCallActive and remoteJid so that the UI can connect these signals easily, making the call interactive in the layout.
Here is how the QML dynamically reacts to the C++ properties:
// GameWindowLandscape.qml
ColumnLayout {
anchors. fill: parent
// 1. The Standard Text Chat UI
Item {
id: textChatView
Layout.fillWidth: true
Layout.fillHeight: true
// Hide text chat when a voice call is active!
visible: !voiceManager.isCallActive
/* ... text chat UI components ... */
}
// 2. The Active Voice Call UI
ColumnLayout {
id: activeCallView
Layout.fillWidth: true
Layout.fillHeight: true
visible: voiceManager.isCallActive
Kirigami.Icon {
source: "im-jabber"
width: 96; height: 96
}
Text {
text: voiceManager.remoteName
font.pixelSize: 24
font. bold: true
}
Button {
text: "End Call"
icon.name: "call-stop"
onClicked: voiceManager.endCall()
}
}
}
By binding QML components to the VoiceCallManager signals, the UI updates instantly based on the state of the call. After this we have successfully implemented voice and text chat for Mankala.
Thanks for reading :)