Client (node) event example
Simple VDO.Ninja API WebSocket Client Example
Simple VDO.Ninja API WebSocket Client Example
const WebSocket = require('ws');
class VDONinjaMonitor {
constructor(apiKey) {
this.apiKey = apiKey;
this.apiServer = 'wss://api.vdo.ninja:443';
this.ws = null;
this.connected = false;
this.streams = {};
this.reconnectTimeout = null;
this.pollInterval = null;
}
// Connect to the WebSocket server
connect() {
console.log(`Connecting to ${this.apiServer}...`);
this.ws = new WebSocket(this.apiServer);
this.ws.on('open', () => {
console.log('WebSocket connection established');
// Join with the API key
this.ws.send(JSON.stringify({ join: this.apiKey }));
this.connected = true;
// Start polling for details periodically
this.startPolling();
});
this.ws.on('message', (data) => {
try {
const message = JSON.parse(data);
this.handleMessage(message);
} catch (error) {
console.error('Error parsing message:', error);
}
});
this.ws.on('close', () => {
console.log('WebSocket connection closed');
this.connected = false;
this.stopPolling();
this.scheduleReconnect();
});
this.ws.on('error', (error) => {
console.error('WebSocket error:', error);
this.stopPolling();
this.scheduleReconnect();
});
}
// Handle messages from the WebSocket
handleMessage(message) {
// Log all messages for debugging
console.log('Received message:', JSON.stringify(message, null, 2));
// Handle connection events
if (message.action === 'guest-connected') {
const streamID = message.streamID;
console.log(`Stream connected: ${streamID}`);
// Store stream info
this.streams[streamID] = {
connected: true,
label: message.value?.label || 'Unknown',
connectTime: new Date()
};
// You could trigger additional actions here
}
// Handle alternative connection event
else if (message.action === 'push-connection' && (message.value === true || message.value === "true")) {
const streamID = message.streamID;
console.log(`Stream connected (via push-connection): ${streamID}`);
// Store stream info or update existing
if (!this.streams[streamID]) {
this.streams[streamID] = {
connected: true,
connectTime: new Date()
};
} else {
this.streams[streamID].connected = true;
this.streams[streamID].reconnectTime = new Date();
}
}
// Handle disconnection events
else if (message.action === 'push-connection' && (message.value === false || message.value === "false")) {
const streamID = message.streamID;
console.log(`Stream disconnected: ${streamID}`);
if (this.streams[streamID]) {
this.streams[streamID].connected = false;
this.streams[streamID].disconnectTime = new Date();
// Optional: Remove from active streams after a period
setTimeout(() => {
if (this.streams[streamID] && !this.streams[streamID].connected) {
delete this.streams[streamID];
}
}, 60000);
}
}
// Handle responses to our getDetails requests
else if (message.callback && message.callback.action === 'getDetails') {
console.log('Received stream details:');
// The result contains detailed information about all connected streams
const details = message.callback.result;
if (details && details.guests) {
// Update our local cache with the latest details
Object.keys(details.guests).forEach(streamID => {
if (!this.streams[streamID]) {
this.streams[streamID] = {
connected: true,
connectTime: new Date()
};
console.log(`Discovered stream in poll: ${streamID}`);
}
// Update stream details
this.streams[streamID] = {
...this.streams[streamID],
...details.guests[streamID],
lastSeen: new Date()
};
});
// Check for streams that are in our cache but not in the response
Object.keys(this.streams).forEach(streamID => {
if (this.streams[streamID].connected && !details.guests[streamID]) {
console.log(`Stream not found in poll, marking as disconnected: ${streamID}`);
this.streams[streamID].connected = false;
this.streams[streamID].disconnectTime = new Date();
}
});
}
}
// Handle action-specific events
else if (message.action) {
switch (message.action) {
case 'remote-mute-state':
// Handle remote mute state changes
if (message.streamID && this.streams[message.streamID]) {
this.streams[message.streamID].remoteMuted = message.value;
console.log(`Stream ${message.streamID} remote mute state: ${message.value}`);
}
break;
case 'remote-video-mute-state':
// Handle remote video mute state changes
if (message.streamID && this.streams[message.streamID]) {
this.streams[message.streamID].videoMuted = message.value;
console.log(`Stream ${message.streamID} video mute state: ${message.value}`);
}
break;
case 'director':
case 'codirector':
// Handle director status changes
if (message.streamID && this.streams[message.streamID]) {
this.streams[message.streamID].isDirector = message.value;
console.log(`Stream ${message.streamID} director state: ${message.value}`);
}
break;
}
}
}
// Send a command to the WebSocket
sendCommand(action, value = null, target = null) {
if (!this.connected) {
console.warn('Cannot send command: WebSocket not connected');
return false;
}
const command = { action };
if (value !== null) command.value = value;
if (target !== null) command.target = target;
try {
this.ws.send(JSON.stringify(command));
return true;
} catch (error) {
console.error('Error sending command:', error);
return false;
}
}
// Request updated details about all streams
requestDetails() {
return this.sendCommand('getDetails');
}
// Start polling for stream details
startPolling(intervalMs = 10000) {
this.stopPolling();
this.pollInterval = setInterval(() => {
this.requestDetails();
}, intervalMs);
// Initial request
this.requestDetails();
}
// Stop polling
stopPolling() {
if (this.pollInterval) {
clearInterval(this.pollInterval);
this.pollInterval = null;
}
}
// Schedule reconnection attempt
scheduleReconnect(delayMs = 5000) {
if (this.reconnectTimeout) {
clearTimeout(this.reconnectTimeout);
}
this.reconnectTimeout = setTimeout(() => {
console.log('Attempting to reconnect...');
this.connect();
}, delayMs);
}
// Get a summary of connected streams
getStreamSummary() {
const connectedStreams = Object.keys(this.streams).filter(id => this.streams[id].connected);
console.log(`\n=== Stream Summary ===`);
console.log(`Total tracked streams: ${Object.keys(this.streams).length}`);
console.log(`Currently connected: ${connectedStreams.length}`);
console.log(`\nConnected streams:`);
connectedStreams.forEach(streamID => {
const stream = this.streams[streamID];
console.log(`- ${streamID} (${stream.label || 'Unlabeled'})`);
});
return {
total: Object.keys(this.streams).length,
connected: connectedStreams.length,
streams: this.streams
};
}
// Close the connection
disconnect() {
this.stopPolling();
if (this.reconnectTimeout) {
clearTimeout(this.reconnectTimeout);
this.reconnectTimeout = null;
}
if (this.ws) {
this.ws.close();
this.ws = null;
}
this.connected = false;
}
}
// Usage example
const API_KEY = 'YOUR_API_KEY_HERE'; // Replace with your actual API key
const monitor = new VDONinjaMonitor(API_KEY);
// Connect to the API
monitor.connect();
// Periodically print a summary of streams (every 30 seconds)
setInterval(() => {
monitor.getStreamSummary();
}, 30000);
// Handle application shutdown
process.on('SIGINT', () => {
console.log('Shutting down...');
monitor.disconnect();
process.exit(0);
});How to Use This Example
Key Features
Usage on links
Last updated
Was this helpful?