A blog for eternal code 💻

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 :)