17 KiB
VDO.Ninja IFRAME API: Generic P2P Data Transmission Guide
This guide focuses specifically on how to send and receive generic data between clients using VDO.Ninja's peer-to-peer (P2P) data channels.
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, distributing the load across the network
- AI Integration Ready: Perfect for distributing AI processing tasks or sharing AI-generated content between users
- 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
UUIDorstreamID: 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
{ sendData: { yourAppName: { type: "command", data: { /* command data */ } } } } -
Include Timestamp: Add timestamps to help with ordering
{ sendData: { yourAppName: { type: "update", data: { /* update data */ }, timestamp: Date.now() } } }
Complete Example: Simple Chat System
Here's a complete example implementing a simple chat system using the P2P data channels:
// Create the interface
const container = document.createElement('div');
container.style.width = '100%';
container.style.maxWidth = '800px';
container.style.margin = '0 auto';
document.body.appendChild(container);
// Create VDO.Ninja iframe
const iframe = document.createElement('iframe');
iframe.allow = "camera;microphone;fullscreen;display-capture;autoplay;";
iframe.src = "https://vdo.ninja/?room=chat-demo&cleanoutput";
iframe.style.width = "100%";
iframe.style.height = "360px";
container.appendChild(iframe);
// Create chat interface
const chatContainer = document.createElement('div');
chatContainer.style.marginTop = '20px';
container.appendChild(chatContainer);
const chatMessages = document.createElement('div');
chatMessages.style.height = '300px';
chatMessages.style.border = '1px solid #ccc';
chatMessages.style.padding = '10px';
chatMessages.style.overflowY = 'scroll';
chatContainer.appendChild(chatMessages);
const inputContainer = document.createElement('div');
inputContainer.style.marginTop = '10px';
inputContainer.style.display = 'flex';
chatContainer.appendChild(inputContainer);
const messageInput = document.createElement('input');
messageInput.type = 'text';
messageInput.placeholder = 'Type your message...';
messageInput.style.flexGrow = '1';
messageInput.style.padding = '8px';
inputContainer.appendChild(messageInput);
const sendButton = document.createElement('button');
sendButton.textContent = 'Send';
sendButton.style.marginLeft = '10px';
sendButton.style.padding = '8px 16px';
inputContainer.appendChild(sendButton);
// Store connected peers
const connectedPeers = {};
// Add event listeners
sendButton.addEventListener('click', sendChatMessage);
messageInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendChatMessage();
}
});
function sendChatMessage() {
const message = messageInput.value.trim();
if (message) {
// Create message object
const chatData = {
type: 'chat',
text: message,
sender: 'Me',
timestamp: Date.now()
};
// Add to local chat
addMessageToChat(chatData.sender, chatData.text);
// Send to all peers
sendDataToAllPeers(chatData);
// Clear input
messageInput.value = '';
}
}
function addMessageToChat(sender, text) {
const messageElement = document.createElement('div');
messageElement.style.marginBottom = '8px';
const senderSpan = document.createElement('strong');
senderSpan.textContent = sender + ': ';
messageElement.appendChild(senderSpan);
const textNode = document.createTextNode(text);
messageElement.appendChild(textNode);
chatMessages.appendChild(messageElement);
// Scroll to bottom
chatMessages.scrollTop = chatMessages.scrollHeight;
}
function sendDataToAllPeers(data) {
// Create the data structure
const payload = {
chatApp: data // Using a custom namespace
};
// Send to all peers
iframe.contentWindow.postMessage({
sendData: payload,
type: "pcs"
}, "*");
}
// Set up event listener for messages from iframe
const eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
const eventer = window[eventMethod];
const messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
eventer(messageEvent, function(e) {
// Make sure the message is from our VDO.Ninja iframe
if (e.source != iframe.contentWindow) return;
// Process connection events
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]);
// Announce new connection in chat
addMessageToChat("System", `${connectedPeers[data.streamID]} joined the chat`);
}
else if (data.action === "push-connection" && data.value === false && data.streamID) {
// Announce disconnection
if (connectedPeers[data.streamID]) {
addMessageToChat("System", `${connectedPeers[data.streamID]} left the chat`);
}
// Remove from tracking
console.log("Guest disconnected:", data.streamID);
delete connectedPeers[data.streamID];
}
}
function handleDataReceived(data, senderUUID) {
// Check for chat messages
if (data.chatApp && data.chatApp.type === 'chat') {
const chatData = data.chatApp;
// Get sender name from our peer tracking if available
const senderName = connectedPeers[senderUUID] || chatData.sender || "Unknown";
// Add to chat
addMessageToChat(senderName, chatData.text);
}
}
Best Practices
- Track Connections: Always maintain a list of connected peers
- Use Namespaces: Organize your data under custom namespaces
- Add Type Information: Include message types for easier processing
- Include Timestamps: Help with ordering and synchronization
- Error Handling: Use try/catch blocks when sending messages
- Data Size: Keep payloads reasonably small to avoid performance issues
- UUID vs StreamID: Prefer UUID for targeting as it's more stable
Troubleshooting
- No Data Received: Verify the UUID or streamID is correct
- Connection Issues: Check if peers are properly connected before sending
- Timing Problems: Ensure the iframe is fully loaded before sending messages
- Data Format: Make sure your data is properly serializable
- Security Settings: Check that your iframe permissions are set correctly
By following this guide, you can implement robust P2P data exchange between VDO.Ninja clients for any custom application.