This guide focuses specifically on how to send and receive generic data between clients using VDO.Ninja's peer-to-peer (P2P) data channels over the IFRAME API.
Understanding the P2P Data Channels
VDO.Ninja provides a powerful API that allows websites to send arbitrary data between connected clients through its peer-to-peer infrastructure. This enables you to:
Create custom communication channels between clients
Implement application-specific data exchange
Build interactive multi-user experiences
Exchange any type of serializable data
Why VDO.Ninja's P2P Data Channels Are Powerful
VDO.Ninja's data channels offer several compelling advantages that make them ideal for modern web applications:
Production-Proven Reliability: Used in production applications like Social Stream Ninja, which processes hundreds of messages per minute per peer connection
Automatic LAN Optimization: Detects when connections are on the same local network and routes data directly, reducing latency
Firewall Traversal: Enables communication between devices behind different firewalls without port forwarding
Cost-Effective: No server costs or bandwidth charges for data transmission, as everything happens peer-to-peer
Low Latency: Direct connections between peers minimize delay, ideal for real-time applications
Scalability: Each peer connects directly to others, allowing for programmed distribution of loads
AI Integration Ready: Perfect for distributing AI processing tasks or sharing AI-generated content between users, or accessing private AI services that are behind firewalls.
Remote Control Applications: Enables secure remote control of devices through firewalls without complex networking setups
Works Across Platforms: Functions on mobile, desktop, and various browsers without additional plugins
The creators of VDO.Ninja use these data channels in numerous applications beyond video, demonstrating their versatility and reliability in real-world scenarios.
Basic Setup
First, set up your VDO.Ninja iframe:
// Create the iframe element
var iframe = document.createElement("iframe");
// Set necessary permissions
iframe.allow = "camera;microphone;fullscreen;display-capture;autoplay;";
// Set the source URL (your VDO.Ninja room)
iframe.src = "https://vdo.ninja/?room=your-room-name&cleanoutput";
// Add the iframe to your page
document.getElementById("container").appendChild(iframe);
Setting Up Event Listeners
To receive data from other clients, set up an event listener:
// Set up event listener (cross-browser compatible)
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
// Connected peers storage
var connectedPeers = {};
// Add the event listener
eventer(messageEvent, function(e) {
// Make sure the message is from our VDO.Ninja iframe
if (e.source != iframe.contentWindow) return;
// Process connection events to track connected peers
if ("action" in e.data) {
handleConnectionEvents(e.data);
}
// Handle received data
if ("dataReceived" in e.data) {
handleDataReceived(e.data.dataReceived, e.data.UUID);
}
}, false);
function handleConnectionEvents(data) {
if (data.action === "guest-connected" && data.streamID) {
// Store connected peer information
connectedPeers[data.streamID] = data.value?.label || "Guest";
console.log("Guest connected:", data.streamID, "Label:", connectedPeers[data.streamID]);
}
else if (data.action === "push-connection" && data.value === false && data.streamID) {
// Remove disconnected peers
console.log("Guest disconnected:", data.streamID);
delete connectedPeers[data.streamID];
}
}
function handleDataReceived(data, senderUUID) {
console.log("Data received from:", senderUUID, "Data:", data);
// Example: Check for your custom data namespace
if (data.overlayNinja) {
processCustomData(data.overlayNinja, senderUUID);
}
}
function processCustomData(data, senderUUID) {
// Process based on your application's needs
console.log("Processing custom data:", data);
// Example: Handle different data types
if (data.message) {
displayMessage(data.message);
} else if (data.command) {
executeCommand(data.command);
}
}
Sending Data
Send Data Structure
When sending data via the VDO.Ninja IFRAME API, you use this general format:
iframe.contentWindow.postMessage({
sendData: yourDataPayload,
type: "pcs", // Connection type (see below)
UUID: targetUUID // Optional: specific target
}, "*");
The components are:
sendData: Your data payload (object)
type: Connection type (string)
"pcs": Use peer connections (most reliable)
"rpcs": Use request-based connections
UUID or streamID: Optional target identifier
Sending to All Connected Peers
function sendDataToAllPeers(data) {
// Create the data structure with your custom namespace
var payload = {
overlayNinja: data // Your custom data under a namespace
};
// Send to all peers
iframe.contentWindow.postMessage({
sendData: payload,
type: "pcs" // Use peer connection for reliability
}, "*");
}
// Example usage
sendDataToAllPeers({
message: "Hello everyone!",
timestamp: Date.now()
});
Sending to a Specific Peer by UUID
function sendDataToPeer(data, targetUUID) {
// Create the data structure
var payload = {
overlayNinja: data // Your custom data
};
// Send to specific UUID
iframe.contentWindow.postMessage({
sendData: payload,
type: "pcs",
UUID: targetUUID
}, "*");
}
// Example usage
sendDataToPeer({
message: "Hello specific peer!",
timestamp: Date.now()
}, "peer-uuid-123");
Sending to Peers with Specific Labels
function sendDataByLabel(data, targetLabel) {
// Create the data structure
var payload = {
overlayNinja: data // Your custom data
};
// Iterate through connected peers to find those with matching label
var keys = Object.keys(connectedPeers);
for (var i = 0; i < keys.length; i++) {
try {
var UUID = keys[i];
var label = connectedPeers[UUID];
if (label === targetLabel) {
// Send to this specific peer
iframe.contentWindow.postMessage({
sendData: payload,
type: "pcs",
UUID: UUID
}, "*");
}
} catch (e) {
console.error("Error sending to peer:", e);
}
}
}
// Example usage
sendDataByLabel({
message: "Hello all viewers!",
timestamp: Date.now()
}, "viewer");
Sending to a Peer by StreamID
function sendDataByStreamID(data, streamID) {
// Create the data structure
var payload = {
overlayNinja: data // Your custom data
};
// Send to specific streamID
iframe.contentWindow.postMessage({
sendData: payload,
type: "pcs",
streamID: streamID
}, "*");
}
// Example usage
sendDataByStreamID({
message: "Hello by stream ID!",
timestamp: Date.now()
}, "stream-123");
Tracking Connected Peers
To reliably communicate with peers, keep track of connections and disconnections:
// Store connected peers
var connectedPeers = {};
function handleConnectionEvents(data) {
// Guest connections
if (data.action === "guest-connected" && data.streamID) {
connectedPeers[data.streamID] = data.value?.label || "Guest";
console.log("Guest connected:", data.streamID, "Label:", connectedPeers[data.streamID]);
}
// View connections
else if (data.action === "view-connection") {
if (data.value && data.streamID) {
connectedPeers[data.streamID] = "Viewer";
console.log("Viewer connected:", data.streamID);
} else if (data.streamID) {
console.log("Viewer disconnected:", data.streamID);
delete connectedPeers[data.streamID];
}
}
// Director connections
else if (data.action === "director-connected") {
console.log("Director connected");
}
// Handle disconnections
else if (data.action === "push-connection" && data.value === false && data.streamID) {
console.log("User disconnected:", data.streamID);
delete connectedPeers[data.streamID];
}
}
Getting All Connected StreamIDs
You can request a list of all connected streams:
function getConnectedPeers() {
iframe.contentWindow.postMessage({ getStreamIDs: true }, "*");
}
// In your event listener, handle the response:
if ("streamIDs" in e.data) {
console.log("Connected streams:");
for (var key in e.data.streamIDs) {
console.log("StreamID:", key, "Label:", e.data.streamIDs[key]);
}
}
Detailed State Information
For more comprehensive information about the current state:
function getDetailedState() {
iframe.contentWindow.postMessage({ getDetailedState: true }, "*");
}
// Handle the response in your event listener
Data Structure Best Practices
Use a Namespace: Put your data under a custom namespace to avoid conflicts
{
sendData: {
yourAppName: {
// Your data here
}
}
}
Include Type Information: Include type identifiers to differentiate messages