PeerDart is an easy-to-use wrapper for the WebRTC API. Stream data or media in real-time between clients using peer-to-peer communication
In this tutorial, we will create a basic video chat app using PeerDart that allows two clients to connect and stream video and audio with ease.
How PeerDart Simplifies WebRTC?
When it comes to real-time P2P communication in applications, WebRTC is the standard used by many developers. But, it comes with some complexities as follows;
If you use pure WebRTC, first, you define a STUN (Session Traversal Utilities for NAT) server to generate ICE (Interactive Connectivity Establishment) candidates for each peer involved in communication.
Then you need to use your servers to store these ICE candidate details.
Finally, you need to implement WebSockets to handle real-time updates.
Even you haven’t worked with WebRTC before; I’m sure you must be feeling the complexity of its implementation. But, don’t worry, PeerDart is here for the rescue.
With PeerDart, we don’t have to worry about STUNs, ICE candidates, or server creation. We can even avoid implementing WebSockets as well.
PeerDart provides a complete, configurable peer-to-peer connection API and a server called PeerServer to easily establish connections between PeerDart clients.
So, let’s see how we can use PeerDart to create a simple chat/video application.
Getting Setup
Step 1 — Create a new Flutter project
First, create a Flutter project as you would do normally.
flutter create peerdart-example
- Add PeerDart and flutter-webrtc as dependencies.
flutter pub add peerdart flutter-webrtc
Step 2 — Implementation
1- Initiliaze PeerDart
final Peer peer = Peer()
Tip: You can assign your own id with id property.
final Peer peer = Peer(id: “custom id”)
Peer instance has several methods to handle communication between peers. peer.on is used to listen to peer events, and it is useful when receiving calls/data from remote peers.
open event will be emitted after successfully connecting to PeerServer, and we will use this event to update the state of peerId and peer instance.
peer.on("open").listen((event) => print("Connection established"))
Then, we need to use the connection event to listen to remote peer connections.
peer.on<DataConnection>("connection").listen((data) {
setState(() {
connected = true;
});
})
Then, for listening to data we received from another peer:
peer.on("data").listen((data) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(data)));
})
Now, we have implemented all the functionalities to receive messages. As the final step, let’s create a method to send a message.
peer.connect method allows us to connect to the peer by specifying peer id. Then it returns a DataConnection object which can be used to send message data to the peer.
void connect() {
final connection = peer.connect(_controller.text);
conn = connection;
conn.on("open").listen((event) {
setState(() {
connected = true;
});
// Peer closed
connection.on("close").listen((event) {
setState(() {
connected = false;
});
});
conn.on("data").listen((data) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(data)));
});
});
}
And for sending message to other peer simply use send() function to send.
void sendHelloWorld() {
conn.send("Hello world!");
}
Step 3 — Video Chat Implementation
Now, let’s modify our chat room to send video messages. Implementing it is pretty much similar to what we discussed in the previous step. We can use the call event inside peer.on method to listen to calls from the remote peer. It will provide a callback with an object named MediaConnection and receiver’s video and audio streams are provided to the answer method of MediaConnection object.
Step — 1 Adding additional state for rendering video instances.
final _localRenderer = RTCVideoRenderer();
final _remoteRenderer = RTCVideoRenderer();
bool inCall = false;
Step — 2 Initialize video renderers inside initState.
@override
void initState() {
super.initState();
_localRenderer.initialize();
_remoteRenderer.initialize();
}
Step — 3 Adding additional listeners for receiving calls.
@override
void initState() {
super.initState();
//.....
peer.on<MediaConnection>("call").listen((call) async {
final mediaStream = await navigator.mediaDevices
.getUserMedia({"video": true, "audio": false});
call.answer(mediaStream);
call.on("close").listen((event) {
setState(() {
inCall = false;
});
});
call.on<MediaStream>("stream").listen((event) {
_localRenderer.srcObject = mediaStream;
_remoteRenderer.srcObject = event;
setState(() {
inCall = true;
});
});
});
}
Step — 4 Calling other peer with call() function
void connect() async {
final mediaStream = await navigator.mediaDevices
.getUserMedia({"video": true, "audio": false});
final conn = peer.call(_controller.text, mediaStream);
conn.on("close").listen((event) {
setState(() {
inCall = false;
});
});
conn.on<MediaStream>("stream").listen((event) {
_remoteRenderer.srcObject = event;
_localRenderer.srcObject = mediaStream;
setState(() {
inCall = true;
});
});
}
Step — 5 Finalizing Things
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_renderState(),
const Text(
'Connection ID:',
),
SelectableText(peerId ?? ""),
TextField(
controller: _controller,
),
ElevatedButton(onPressed: connect, child: const Text("connect")),
ElevatedButton(
onPressed: send, child: const Text("send message")),
if (inCall)
Expanded(
child: RTCVideoView(
_localRenderer,
),
),
if (inCall)
Expanded(
child: RTCVideoView(
_remoteRenderer,
),
),
],
),
));
}
Widget _renderState() {
Color bgColor = inCall ? Colors.green : Colors.grey;
Color txtColor = Colors.white;
String txt = inCall ? "Connected" : "Standby";
return Container(
decoration: BoxDecoration(color: bgColor),
child: Text(
txt,
style:
Theme.of(context).textTheme.titleLarge?.copyWith(color: txtColor),
),
);
}
That’s it! Now we are all set for a quick video chat. The final implementation will look like this and you can find the full code in examples here.
Top comments (2)
can you send github link ?
This is a very wonderful project. Hope I could find some time and start contributing.