Tinode WebRTC is the integration of the open-source Tinode messaging server with WebRTC peer-to-peer media capabilities, enabling real-time voice and video calls alongside text chat. Tinode handles signaling through its pub/sub topic model while media flows directly between clients over WebRTC. For developers who need managed scaling beyond peer-to-peer limits, VideoSDK offers a hosted alternative with built-in SFU architecture and multi-platform SDKs. Start with the VideoSDK quickstart guide to compare approaches.
Real-time video is no longer a nice-to-have feature. Users expect face-to-face communication inside the apps they already use, whether that means a doctor consulting a patient, a tutor walking through a math problem, or a support agent resolving a ticket on camera. Building this from scratch with raw WebRTC is notoriously difficult because you have to manage signaling, NAT traversal, codec negotiation, and reconnection logic all on your own.
Tinode enters this picture as a lightweight, open-source messaging server written in Go that now supports WebRTC for peer-to-peer audio and video calls. It gives you a self-hosted signaling layer, a pub/sub topic model for message routing, and client libraries across JavaScript, Swift, Android, and Python. That makes it an appealing starting point for developers who want full control over their real-time communication stack without immediately committing to a commercial platform.
In this guide, you will learn how Tinode WebRTC works under the hood, from architecture and signaling flow to call establishment, ICE configuration, security, scaling, and troubleshooting. By the end, you will have a clear mental model of the entire call lifecycle and the decisions you need to make before shipping video calls to production.

What Is Tinode WebRTC?

Tinode is an open-source instant messaging server designed for multi-platform real-time communication. It was built in Go and uses a topic-based pub/sub model where every conversation, group chat, or channel maps to a topic with its own sequence IDs and access controls. Clients subscribe to topics, publish messages, and receive updates in real time.
Tinode WebRTC extends this messaging foundation with peer-to-peer media capabilities. Instead of routing audio and video through the server, Tinode uses its existing signaling transport to negotiate a direct WebRTC connection between two clients. Once the connection is established, media flows directly between browsers or mobile devices using SRTP encryption, while the Tinode server steps back and only handles call events like ringing, acceptance, and termination.
The core components of a Tinode WebRTC setup include the Tinode server (the Go backend that manages authentication, topics, and message delivery), client libraries (JavaScript for web, Swift for iOS, Kotlin for Android, and Python for backend scripting), and the WebRTC layer that each client implements natively. The server does not process media, which keeps it lightweight and easy to self-host, but it also means you inherit the scaling limitations of peer-to-peer mesh topologies.
For developers evaluating whether to self-host Tinode or use a managed platform, VideoSDK's video calling API provides a contrasting approach where the server handles media routing through an SFU, removing the mesh scaling ceiling entirely.

Architecture Overview

The Tinode WebRTC architecture separates signaling from media, which is the standard pattern for WebRTC systems but worth understanding in Tinode's specific implementation. The Tinode server acts as a signaling relay. It transports call setup messages between clients using the same persistent WebSocket connections that carry chat messages. Media, once negotiated, flows directly between the two peers over WebRTC data channels and media streams.
This design has a significant implication: the Tinode server never touches audio or video packets. It only forwards JSON-formated signaling messages. That keeps server bandwidth low and simplifies deployment, but it also means every client must be able to reach every other client directly or through a TURN relay. If both clients sit behind symmetric NATs and no TURN server is configured, the call will fail even though signaling succeeds.
ICE server configuration is the bridge that makes this work. Tinode lets you define STUN and TURN servers in a call configuration object that the server distributes to clients during call setup. STUN servers help clients discover their public IP addresses, while TURN servers relay media when direct connectivity fails. Choosing the right ICE servers is one of the most impactful production decisions you will make.
The diagram above shows the dual-path architecture. Signaling travels through the Tinode server over WebSocket connections, while media flows directly between clients or through a TURN relay if NAT traversal fails.

Signaling Flow in Tinode

Tinode carries WebRTC call events through its existing message types. The publish message sends call payloads to a topic, the note message handles real-time notifications like typing indicators and call state changes, and the info message carries metadata such as read receipts. During a WebRTC call, these message types transport SDP offers, SDP answers, ICE candidates, and call state transitions.
Topics play a central role here. Every call is associated with a topic (typically a peer-to-peer topic between two users), and all signaling messages are published to that topic. Sequence IDs ensure ordered delivery, which matters because SDP offers and answers must be processed in the correct sequence. Tinode's multi-device support means that if a user has both a phone and a laptop connected, signaling messages reach both devices, and each device must decide independently whether to ring, accept, or ignore the call.

Detailed Call Establishment Process

A Tinode WebRTC call moves through four distinct phases: initiation, acceptance, SDP and ICE exchange, and termination. Understanding each phase in detail is essential because most call failures happen at the boundaries between them.
In the initiation phase, Client A decides to call Client B. Client A publishes a call initiation message to the shared topic, which includes the call type (audio or video) and a unique call identifier. Tinode delivers this message to all of Client B's connected devices. Each device that receives the message can trigger a ringing UI. Client B's device then publishes a ringing event back to the topic, confirming that the call notification was received and is being presented to the user.
In the acceptance phase, Client B either accepts or rejects the call. If accepted, Client B publishes an accept message. If rejected or ignored, a hang-up message terminates the call attempt. At this point, no media has been exchanged yet. Everything is still signaling.
The SDP and ICE exchange phase is where WebRTC does its heavy lifting. Client A creates an SDP offer describing its media capabilities (codecs, resolutions, which tracks it wants to send and receive) and publishes it to the topic. Client B receives the offer, creates an SDP answer describing its own capabilities, and publishes that back. Simultaneously, both clients gather ICE candidates (potential network paths for the media connection) and publish them to the topic as they are discovered. Once both clients have each other's SDP and a working set of ICE candidates, the direct WebRTC media connection opens and audio or video begins flowing.
In the termination phase, either client can publish a hang-up message to end the call. Tinode delivers this to all devices, and each client closes its WebRTC peer connection, releases camera and microphone resources, and updates its UI.
Developers need to hook into client callbacks at each of these phases. On the JavaScript client, you listen for incoming call events, handle SDP creation and remote description setting, register ICE candidate handlers, and manage peer connection state changes. The Tinode client libraries expose these hooks through event-driven callbacks, and your application logic must respond to them promptly to avoid call setup timeouts.

Managing Multiple Devices

Tinode routes call events to every device connected under a single user account. This is a strength for user experience (calls ring on both your phone and laptop) but a challenge for developers. If three devices receive a ringing event and all three show a call UI, the user sees duplicate prompts. The standard strategy is to designate one device as the active handler based on recency of activity, platform priority, or an explicit device selection step. When one device accepts or rejects the call, it publishes that decision to the topic, and other devices use that event to dismiss their ringing UI.

Configuring ICE Servers

ICE server configuration is where most Tinode WebRTC deployments either work smoothly or fail unpredictably. Tinode allows you to define STUN and TURN servers in a call configuration JSON object that the server sends to clients during call setup. Clients use these servers during ICE candidate gathering to discover network paths and establish connectivity.
STUN servers are inexpensive and widely available. Google's public STUN servers work for development, but for production you should use your own or a reliable third-party STUN service to avoid rate limits and uptime issues. TURN servers are more complex and more expensive because they relay media traffic. You need TURN whenever clients are behind restrictive NATs, corporate firewalls, or symmetric network configurations that prevent direct peer-to-peer connectivity.
Security considerations for ICE configuration include credential handling and IP whitelisting. TURN servers should use short-lived time-limited credentials rather than static usernames and passwords. If you self-host TURN (using coturn or a similar project), whitelist the Tinode server's IP for credential generation and restrict access to known client IP ranges where possible. For a deeper comparison of managed RTC infrastructure approaches, see the VideoSDK REST API documentation which describes how a hosted platform handles TURN and media routing automatically.
When choosing between public and self-hosted TURN, consider your user base. If your users are concentrated in a single region, self-hosting TURN near your Tinode server gives you control and lower latency. If your users are global, a distributed TURN service or a managed RTC platform with global edge infrastructure will perform better.

Security and Privacy

WebRTC media is encrypted by default using SRTP (Secure Real-time Transport Protocol). Every WebRTC media stream requires DTLS negotiation before any audio or video packets flow, which means the media path between two Tinode clients is encrypted end-to-end at the transport layer regardless of whether it goes direct or through a TURN relay.
Tinode's signaling layer is secured through token-based authentication. Clients authenticate with the Tinode server using app-level tokens, and all WebSocket connections should be wrapped in TLS (WSS) to prevent man-in-the-middle attacks on signaling traffic. Since signaling carries SDP offers, answers, and ICE candidates, an attacker who intercepts signaling could potentially redirect or disrupt calls.
Best practices for securing a Tinode WebRTC deployment include using short-lived authentication tokens that expire within minutes rather than hours, enforcing TLS on every connection (both signaling and any web interfaces), validating call configuration objects on the server before distributing them to clients, and regularly rotating TURN credentials. You should also log call events for audit purposes without ever logging media content, since media never touches your server in a peer-to-peer topology.
For applications with strict compliance requirements (HIPAA, GDPR, SOC 2), consider whether self-hosted Tinode gives you the auditability and data residency guarantees you need, or whether a managed platform like VideoSDK with E2E encryption better fits your compliance posture.

Scaling Tinode Video Calls

Peer-to-peer WebRTC mesh topologies have a hard scaling limit. In a mesh, every participant sends media directly to every other participant, which means the bandwidth and CPU cost grows quadratically with the number of participants. For a 2-person call, this is trivial. For 4 participants, each client sends 3 streams and receives 3 streams. By 8 participants, most consumer devices and network connections struggle to keep up. Beyond 8, mesh becomes impractical.
If your application needs group video calls with more than 8 participants, you need to introduce an SFU (Selective Forwarding Unit) or MCU (Multipoint Control Unit) into the architecture. An SFU receives each participant's media stream once and forwards it selectively to other participants, reducing the upload burden on each client. Tinode does not include an SFU, so you would need to integrate one separately, which significantly increases infrastructure complexity.
This is where many teams evaluate the tradeoff between self-hosting Tinode for signaling and adopting a managed platform for media. VideoSDK's interactive live streaming and video calling products include a built-in SFU, support for hundreds of participants, and automatic network-adaptive streaming that adjusts bitrate and resolution based on real-time bandwidth conditions. For teams that have outgrown Tinode's peer-to-peer mesh, migrating signaling to Tinode while routing media through a managed SFU is one option, though it requires custom integration work.
Monitoring call health is another scaling concern. Tinode exposes server metrics that can help you track connection counts, message throughput, and topic activity. For WebRTC-specific metrics like packet loss, jitter, and round-trip time, you need to collect WebRTC statistics from the client side using the browser's built-in stats API and aggregate them in your own monitoring system.

Common Pitfalls and Troubleshooting

The most common Tinode WebRTC issues fall into four categories: ICE timeouts, mismatched SDP, token expiry, and NAT traversal failures.
ICE timeouts happen when clients cannot find a viable network path within the ICE gathering window. Symptoms include signaling completing successfully (the call appears to connect) but no audio or video flowing. The fix is to ensure TURN servers are configured and reachable, and to increase the ICE timeout threshold in your client configuration if your user base includes high-latency mobile networks.
Mismatched SDP errors occur when the offer and answer do not align on codecs, media types, or track directions. This often happens when one client has camera permissions denied but still includes video in the offer. The fix is to validate media track availability before creating the SDP offer and to handle permission errors before initiating the call.
Token expiry causes signaling failures where the WebSocket connection drops mid-call or new call attempts are rejected. Tinode tokens have a configurable lifetime, and you should implement client-side token refresh logic that requests a new token before the current one expires, not after.
NAT traversal failures are the hardest to diagnose because they depend on the user's network environment. If a call works on WiFi but fails on cellular, or works in one office but not another, the issue is almost certainly NAT-related. Diagnostic steps include checking whether TURN servers are configured, verifying that TURN credentials are valid and not expired, and testing with a WebRTC troubleshooting tool to see which ICE candidates are being generated and which are succeeding.
Tinode logs are available on the server side and can be configured for different verbosity levels. For client-side debugging, browser developer tools expose WebRTC internals that show peer connection states, ICE candidate gathering results, and media flow statistics. A quick production readiness checklist: verify TURN is configured, test on cellular networks, implement token refresh, handle camera and microphone permission denials gracefully, and test call reconnection after network interruptions.

Real-World Use Cases

Tinode WebRTC fits several real-world scenarios where lightweight, self-hosted real-time communication is valuable.
A telehealth application can use Tinode for secure doctor-patient video consultations. The peer-to-peer media path means sensitive health data never touches your server, which simplifies HIPAA compliance. Tinode's topic-based messaging also handles appointment notifications and post-visit follow-up messages within the same infrastructure.
A collaborative tutoring platform benefits from Tinode's combination of text chat and video calls. Students and tutors can share messages, files, and whiteboard content through Tinode topics, then escalate to a video call for live problem-solving. The lightweight Go server keeps hosting costs low for platforms serving many concurrent one-on-one sessions.
A customer support video chat system can use Tinode to add face-to-face escalation to an existing text-based support workflow. When a text chat cannot resolve an issue, either party can initiate a WebRTC call through the same Tinode connection, avoiding the need for a separate video infrastructure.
For each of these scenarios, the key constraint is participant count. If your use case is exclusively one-to-one or small group calls (under 8 participants), Tinode WebRTC is a solid choice. If you anticipate larger group calls, live streaming, or interactive broadcasts, evaluate whether a managed platform like VideoSDK's Prebuilt UI Kit would reduce your infrastructure burden.

Definitions Glossary

Tinode: An open-source instant messaging server written in Go that uses a topic-based pub/sub model for real-time message delivery across web, mobile, and desktop clients.
WebRTC: A free, open-source project that provides web browsers and mobile applications with real-time peer-to-peer communication of audio, video, and data using simple application programming interfaces.
Signaling: The process of exchanging metadata, session descriptions, and network information between clients to coordinate the establishment of a WebRTC media connection.
SDP (Session Description Protocol): A format for describing multimedia communication sessions, including codecs, resolutions, and media tracks, exchanged during WebRTC call setup.
ICE (Interactive Connectivity Establishment): A framework that allows WebRTC clients to find and establish the best possible network path for media communication, using STUN and TURN servers.
STUN Server: A server that helps a WebRTC client discover its public IP address and the type of NAT it is behind, enabling direct peer-to-peer connections in many network scenarios.
TURN Server: A server that relays WebRTC media traffic when direct peer-to-peer connectivity fails due to restrictive NATs or firewalls, acting as a fallback media path.
SFU (Selective Forwarding Unit): A media server topology where each participant sends one media stream to the server, which selectively forwards streams to other participants, enabling group calls beyond mesh limits.

Key Takeaways

  • Tinode WebRTC combines an open-source Go messaging server with peer-to-peer WebRTC media, using Tinode's topic-based pub/sub model for signaling while media flows directly between clients.
  • The call establishment process moves through four phases (initiation, acceptance, SDP and ICE exchange, termination) with all signaling transported via Tinode's WebSocket connections.
  • ICE server configuration (STUN and TURN) is the single most impactful production decision, determining whether calls succeed across different network environments.
  • Peer-to-peer mesh topology limits Tinode WebRTC to roughly 8 participants before bandwidth and CPU constraints make it impractical, requiring an SFU for larger group calls.
  • Security is handled through SRTP media encryption and Tinode's token-based authentication, but production deployments must enforce TLS, short-lived tokens, and proper TURN credential rotation.
  • For applications that need to scale beyond peer-to-peer limits without self-hosting an SFU, VideoSDK provides a managed alternative with built-in media routing, multi-platform SDKs, and network-adaptive streaming.

Conclusion

Tinode WebRTC gives developers a self-hosted, lightweight path to adding real-time voice and video calls alongside text messaging. Its architecture cleanly separates signaling (handled by the Tinode Go server) from media (flowing directly between clients over encrypted WebRTC), which keeps server costs low and simplifies deployment for one-to-one communication scenarios.
The tradeoff is that peer-to-peer mesh does not scale beyond small groups, and you are responsible for ICE configuration, TURN infrastructure, token management, and client-side reconnection logic. For teams building production video calling at scale, evaluating a managed platform alongside Tinode is a smart move. VideoSDK handles media routing through an SFU, provides SDKs across 10+ platforms including React, Flutter, and iOS, and includes features like network-adaptive streaming and built-in recording that Tinode does not offer out of the box.
Ready to explore your options? Try the VideoSDK quickstart to see how a managed RTC platform compares, browse VideoSDK's code samples for integration patterns, or sign up free at app.videosdk.live/login to start building. What are you building with real-time video? Drop a comment below, I would love to hear what kind of Tinode WebRTC or VideoSDK use case you are working on.

Step 5: Implement Participant View

The participant view is an essential component of your messaging application, allowing users to see who else is in the chat. This section will guide you through implementing the participant view for your Tinode-based messaging application across JavaScript, Swift, Android, and Python.

Creating the Participant View

Let's break down the implementation of the participant view for each platform.

JavaScript (React)

[a] Create the Participant Component

First, create a new file named Participants.js in your components directory.
Add the following code to create the participant view:
JavaScript
1     import React, { useState, useEffect } from 'react';
2     import Tinode from 'tinode-sdk';
3
4     const Participants = ({ tinode }) => {
5       const [participants, setParticipants] = useState([]);
6
7       useEffect(() => {
8         const topic = tinode.getTopic('general');
9         topic.subscribe().then(() => {
10           topic.onMetaDesc = (desc) => {
11             setParticipants(desc.sub);
12           };
13         });
14       }, [tinode]);
15
16       return (
17         <div className="participants-container">
18           <h3>Participants</h3>
19           <ul>
20             {participants.map((participant, index) => (
21               <li key={index}>{participant.user}</li>
22             ))}
23           </ul>
24         </div>
25       );
26     };
27
28     export default Participants;

[b] Styling the Component

Create a CSS file named Participants.css and add some basic styles:
CSS
1     .participants-container {
2       border-left: 1px solid #ddd;
3       padding: 10px;
4     }
5
6     .participants-container h3 {
7       margin-top: 0;
8     }
9
10     .participants-container ul {
11       list-style: none;
12       padding: 0;
13     }
14
15     .participants-container li {
16       padding: 5px 0;
17     }

[c] Integrate the Component

In your main application file (e.g., App.js), import and use the Participants component alongside the Chat component:
JavaScript
1     import React, { useState } from 'react';
2     import Join from './components/Join';
3     import Chat from './components/Chat';
4     import Participants from './components/Participants';
5
6     const App = () => {
7       const [tinode, setTinode] = useState(null);
8
9       return (
10         <div className="App">
11           {!tinode ? (
12             <Join onLogin={setTinode} />
13           ) : (
14             <div className="chat-container">
15               <Chat tinode={tinode} />
16               <Participants tinode={tinode} />
17             </div>
18           )}
19         </div>
20       );
21     };
22
23     export default App;

Swift (iOS)

[a] Create the Participants View Controller

In your Xcode project, create a new Swift file named ParticipantsViewController.swift.
Add the following code to create the participant view:
Swift
1     import UIKit
2     import TinodeSDK
3
4     class ParticipantsViewController: UIViewController, UITableViewDataSource {
5       @IBOutlet weak var tableView: UITableView!
6
7       var tinode: Tinode!
8       var topic: DefaultComTopic!
9       var participants: [Subscription<TheType>] = []
10
11       override func viewDidLoad() {
12         super.viewDidLoad()
13         tableView.dataSource = self
14
15         topic = tinode.getTopic(for: "general") as? DefaultComTopic
16         topic.subscribe().then {
17           self.topic.onMetaDesc = { desc in
18             self.participants = desc.sub
19             self.tableView.reloadData()
20           }
21         }
22       }
23
24       func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
25         return participants.count
26       }
27
28       func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
29         let cell = tableView.dequeueReusableCell(withIdentifier: "ParticipantCell", for: indexPath)
30         cell.textLabel?.text = participants[indexPath.row].user
31         return cell
32       }
33     }

[b] Design the Participants Screen in Interface Builder

  • Open Main.storyboard.
  • Add a ViewController and set its class to ParticipantsViewController.
  • Add a UITableView for displaying participants.
  • Connect the outlet to the ParticipantsViewController.

Android

[a] Create the Participants Activity

In your Android Studio project, create a new Activity named ParticipantsActivity.java.
Add the following code to create the participant view:
Java
1     package com.example.chatapp;
2
3     import android.os.Bundle;
4     import androidx.appcompat.app.AppCompatActivity;
5     import androidx.recyclerview.widget.LinearLayoutManager;
6     import androidx.recyclerview.widget.RecyclerView;
7     import java.util.ArrayList;
8     import java.util.List;
9     import co.tinode.tinodesdk.Tinode;
10     import co.tinode.tinodesdk.model.Subscription;
11
12     public class ParticipantsActivity extends AppCompatActivity {
13         private Tinode tinode;
14         private RecyclerView participantsRecyclerView;
15         private ParticipantsAdapter adapter;
16         private List<Subscription> participants;
17
18         @Override
19         protected void onCreate(Bundle savedInstanceState) {
20             super.onCreate(savedInstanceState);
21             setContentView(R.layout.activity_participants);
22
23             tinode = (Tinode) getIntent().getSerializableExtra("tinode");
24             participantsRecyclerView = findViewById(R.id.participantsRecyclerView);
25
26             participants = new ArrayList<>();
27             adapter = new ParticipantsAdapter(participants);
28             participantsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
29             participantsRecyclerView.setAdapter(adapter);
30
31             Tinode.Topic topic = tinode.getTopic("general");
32             topic.subscribe().thenApply(ignored -> {
33                 topic.onMetaDesc = desc -> {
34                     participants.addAll(desc.sub);
35                     runOnUiThread(() -> adapter.notifyDataSetChanged());
36                 };
37                 return null;
38             }).exceptionally(e -> {
39                 runOnUiThread(() -> Toast.makeText(ParticipantsActivity.this, "Subscription failed: " + e.getMessage(), Toast.LENGTH_SHORT).show());
40                 return null;
41             });
42         }
43     }

[b] Design the Participants Screen Layout

In res/layout, create a new XML layout file named activity_participants.xml:
XML
1     <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2         android:layout_width="match_parent"
3         android:layout_height="match_parent"
4         android:orientation="vertical">
5
6         <androidx.recyclerview.widget.RecyclerView
7             android:id="@+id/participantsRecyclerView"
8             android:layout_width="match_parent"
9             android:layout_height="match_parent"/>
10     </LinearLayout>

[c] Create the Adapter for Participants

Create a new Java class named ParticipantsAdapter.java:
Java
1     package com.example.chatapp;
2
3     import android.view.LayoutInflater;
4     import android.view.View;
5     import android.view.ViewGroup;
6     import android.widget.TextView;
7     import androidx.annotation.NonNull;
8     import androidx.recyclerview.widget.RecyclerView;
9     import java.util.List;
10     import co.tinode.tinodesdk.model.Subscription;
11
12     public class ParticipantsAdapter extends RecyclerView.Adapter<ParticipantsAdapter.ParticipantViewHolder> {
13         private List<Subscription> participants;
14
15         public ParticipantsAdapter(List<Subscription> participants) {
16             this.participants = participants;
17         }
18
19         @NonNull
20         @Override
21         public ParticipantViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
22             View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_participant, parent, false);
23             return new ParticipantViewHolder(view);
24         }
25
26         @Override
27         public void onBindViewHolder(@NonNull ParticipantViewHolder holder, int position) {
28             Subscription participant = participants.get(position);
29             holder.usernameTextView.setText(participant.user);
30         }
31
32         @Override
33         public int getItemCount() {
34             return participants.size();
35         }
36
37         public static class ParticipantViewHolder extends RecyclerView.ViewHolder {
38             TextView usernameTextView;
39
40             public ParticipantViewHolder(@NonNull View itemView) {
41                 super(itemView);
42                 usernameTextView = itemView.findViewById(R.id.usernameTextView);
43             }
44         }
45     }

[d] Create the Item Layout for Participants

In res/layout, create a new XML layout file named item_participant.xml:
XML
1     <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2         android:layout_width="match_parent"
3         android:layout_height="wrap_content"
4         android:orientation="horizontal"
5         android:padding="10dp">
6
7         <TextView
8             android:id="@+id/usernameTextView"
9             android:layout_width="wrap_content"
10             android:layout_height="wrap_content"
11             android:textSize="16sp"/>
12     </LinearLayout>

Python (Kivy)

[a] Create the Participants Screen

In your Kivy project, create a new file named participants.kv for the Kivy language:
Kivy
1     BoxLayout:
2         orientation: 'vertical'
3         spacing: 10
4         padding: 10
5
6         Label:
7             text: 'Participants'
8             size_hint_y: None
9             height: '40dp'
10
11         ScrollView:
12             BoxLayout:
13                 id: participants_container
14                 orientation: 'vertical'
15                 size_hint_y: None
16                 height: self.minimum_height

[b] Implement the Participants

Logic in Python In your main application file (e.g., main.py), add the following code:
Pyhton
1     from kivy.app import App
2     from kivy.uix.boxlayout import BoxLayout
3     from kivy.uix.label import Label
4     from tinode import Tinode
5
6     class ParticipantsScreen(BoxLayout):
7         pass
8
9     class ChatApp(App):
10         def build(self):
11             self.tinode = Tinode()
12             self.tinode.connect('wss://api.tinode.co')
13             self.topic = self.tinode.get_topic('general')
14             self.topic.subscribe().then(self.on_subscribe)
15             return ParticipantsScreen()
16
17         def on_subscribe(self, *args):
18             self.topic.on_meta_desc = self.receive_participants
19
20         def receive_participants(self, desc):
21             participants = desc['sub']
22             for participant in participants:
23                 self.add_participant_to_ui(participant['user'])
24
25         def add_participant_to_ui(self, username):
26             label = Label(text=username, size_hint_y=None, height=40)
27             self.root.ids.participants_container.add_widget(label)
28
29     if __name__ == '__main__':
30         ChatApp().run()
By following these steps, you can implement the participant view for your Tinode-based messaging application on various platforms. This view will allow users to see who else is in the chat, enhancing the collaborative experience. In the next part, we will focus on running your code to test the full functionality of your application.

Step 6: Run Your Code Now

After setting up the server, implementing the join screen, adding controls, and creating the participant view, it's time to run your Tinode-based messaging application and see it in action. This section provides instructions for running the application on different platforms and some debugging tips to ensure everything works smoothly.

Running the Application

Let's go through the steps to run your application on each platform:

JavaScript (React)

[a] Start the Development Server

Navigate to your project directory and run the following command to start the development server:
sh
1     npm start
This command will compile your React application and open it in your default web browser.

[b] Test the Application

  • Open your browser and navigate to http://localhost:3000 if it doesn't open automatically.
  • You should see the join screen where you can enter a username and password to log in.
  • After logging in, you will be directed to the chat screen where you can send messages and view participants.

Swift (iOS)

Build and Run in Xcode

  • Open your project in Xcode.
  • Select your target device or simulator.
  • Click the "Run" button or press Cmd + R to build and run the application.

Test the Application

  • The app will launch on your selected device or simulator.
  • You should see the join screen where you can enter a username and password to log in.
  • After logging in, you will be directed to the chat screen where you can send messages and view participants.

Android

Build and Run in Android Studio

  • Open your project in Android Studio.
  • Select your target device or emulator.
  • Click the "Run" button or press Shift + F10 to build and run the application.

Test the Application

  • The app will launch on your selected device or emulator.
  • You should see the join screen where you can enter a username and password to log in.
  • After logging in, you will be directed to the chat screen where you can send messages and view participants.

Python (Kivy)

Run the Application

Navigate to your project directory and run the following command:
sh
1     python main.py
This command will start your Kivy application.

Test the Application

  • The app will open in a new window.
  • You should see the join screen where you can enter a username and password to log in.
  • After logging in, you will be directed to the chat screen where you can send messages and view participants.

Debugging Tips

If you encounter any issues while running your application, here are some common debugging tips:

Check Console Logs

  • For JavaScript, open the browser's developer console to check for any error messages.
  • For Swift, check the Xcode console for logs.
  • For Android, use Logcat in Android Studio to view logs.
  • For Python, check the terminal output for any errors.

Verify Server Connectivity

  • Ensure that your Tinode server is running and accessible.
  • Check the server logs for any errors or connection issues.

Verify Configuration Settings

  • Ensure that all configuration settings (e.g., server URL, database connection) are correct and properly set up.

Review Code for Errors

  • Double-check your code for any syntax errors or logical mistakes.
  • Ensure that all necessary dependencies are installed and properly configured.
By following these steps, you can run and test your Tinode-based messaging application on various platforms. This completes the development process, and your application should now be fully functional. Next, we will provide a conclusion and address some frequently asked questions (FAQs) to wrap up the guide.

Conclusion

Congratulations! You have successfully built a Tinode-based messaging application that leverages WebRTC for real-time communication. This guide covered the setup of the server, creation of the join screen, implementation of chat controls, and development of the participant view. By following these steps, you now have a functional messaging app that you can further customize and expand upon.

Free $20 Balance for AI Voice Agents & Video Calls

FAQ