diff --git a/examples/gamecontroller.html b/examples/gamecontroller.html new file mode 100644 index 0000000..40b8442 --- /dev/null +++ b/examples/gamecontroller.html @@ -0,0 +1,696 @@ + + + + Controller Visualizer + + + +
+
+
+

Game Controllers

+
+
+
No gamepads detected
+ Connect an Xbox, PlayStation, or other gamepad and press any button. +
+
+
+
+

Other USB Devices

+
+
+
No other devices connected
+ Click "Connect Device" to add USB game controllers. +
+
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + A + B + X + Y + + + + + + + + + + + + + + + + + X + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/googleai.html b/examples/googleai.html new file mode 100644 index 0000000..a229d80 --- /dev/null +++ b/examples/googleai.html @@ -0,0 +1,964 @@ + + + + + + Gemini Vision Chat - Live AI Video Conversations + + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ Get your free Gemini API key at Google AI Studio. +
+
+
+ +
+
+
+
+
+ + +
+
+
+ + + + + + + + + diff --git a/examples/httpwssapi.md b/examples/httpwssapi.md new file mode 100644 index 0000000..4319c03 --- /dev/null +++ b/examples/httpwssapi.md @@ -0,0 +1,290 @@ +# VDO.Ninja Remote Control API Documentation + +## Overview + +VDO.Ninja's Remote Control API allows programmatic control of VDO.Ninja sessions via HTTP or WebSocket connections. This powerful API enables integration with stream decks, custom applications, and automation tools for controlling cameras, microphones, layouts, and other features. + +## Basic Setup + +To enable the API on any VDO.Ninja instance, add the `&api` parameter with a unique API key: + +``` +https://vdo.ninja/?api=YOUR_UNIQUE_API_KEY&webcam +``` + +This key must be kept private and will be used to authenticate API requests. The same key must be used when making API calls to control this specific VDO.Ninja instance. + +## Connection Methods + +The API supports three connection methods: + +1. **WebSocket API** (recommended for real-time control) +2. **HTTP GET API** (good for simple controllers and hotkeys) +3. **Server-Sent Events** (SSE) for one-way event monitoring + +### WebSocket API + +Connect to `wss://api.vdo.ninja:443` and authenticate with your API key: + +```javascript +const socket = new WebSocket("wss://api.vdo.ninja:443"); + +socket.onopen = function() { + // Join with your API key + socket.send(JSON.stringify({"join": "YOUR_UNIQUE_API_KEY"})); + + // After joining, you can send commands + socket.send(JSON.stringify({ + "action": "mic", + "value": false // mute microphone + })); +}; + +// Listen for responses and events +socket.onmessage = function(event) { + const data = JSON.parse(event.data); + console.log("Received:", data); +}; +``` + +### HTTP GET API + +Structure: `https://api.vdo.ninja/{apiKey}/{action}/{target}/{value}` + +Examples: +``` +https://api.vdo.ninja/YOUR_UNIQUE_API_KEY/mic/false // Mute microphone +https://api.vdo.ninja/YOUR_UNIQUE_API_KEY/camera/toggle // Toggle camera +``` + +### Server-Sent Events (SSE) + +For monitoring events without sending commands: + +```javascript +const eventSource = new EventSource(`https://api.vdo.ninja/sse/YOUR_UNIQUE_API_KEY`); +eventSource.onmessage = function(event) { + console.log(JSON.parse(event.data)); +}; +``` + +## API Commands Reference + +### Self-Targeted Commands + +These commands affect the local VDO.Ninja instance that has the API key enabled. + +| Action | Value Options | Description | +|--------|--------------|-------------| +| `mic` | `true`, `false`, `toggle` | Control microphone state | +| `camera` | `true`, `false`, `toggle` | Control camera state | +| `speaker` | `true`, `false`, `toggle` | Control speaker state | +| `volume` | `0` to `200` | Set playback volume (percentage) | +| `bitrate` | Integer (kbps), `-1` for auto | Set video bitrate | +| `record` | `true`, `false` | Control local recording | +| `hangup` | N/A | Disconnect current session | +| `reload` | N/A | Reload the page | +| `sendChat` | Text string | Send a chat message | +| `togglehand` | N/A | Toggle raised hand status | +| `togglescreenshare` | N/A | Toggle screen sharing | +| `forceKeyframe` | N/A | Force video keyframes ("rainbow puke fix") | +| `getDetails` | N/A | Get detailed state information | +| `getGuestList` | N/A | Get list of connected guests with IDs | + +### Layout Control Commands + +| Action | Value | Description | +|--------|-------|-------------| +| `layout` | `0` or `false` | Switch to auto-mixer layout | +| `layout` | Integer (`1`, `2`, etc.) | Switch to specific predefined layout | +| `layout` | Layout object/array | Apply custom layout configuration | + +### Camera Control (PTZ) Commands + +| Action | Value | Description | +|--------|-------|-------------| +| `zoom` | `-1.0` to `1.0` | Adjust zoom level (relative) | +| `zoom` | `0.0` to `1.0` with `value2="abs"` | Set absolute zoom level | +| `focus` | `-1.0` to `1.0` | Adjust focus (relative) | +| `pan` | `-1.0` to `1.0` | Adjust camera pan (negative=left) | +| `tilt` | `-1.0` to `1.0` | Adjust camera tilt (negative=down) | +| `exposure` | `0.0` to `1.0` | Adjust camera exposure | + +### Group Communication Commands + +| Action | Value | Description | +|--------|-------|-------------| +| `group` | `1` to `8` | Toggle participation in specified group | +| `joinGroup` | `1` to `8` | Join a specific group | +| `leaveGroup` | `1` to `8` | Leave a specific group | +| `viewGroup` | `1` to `8` | Toggle view of specified group | +| `joinViewGroup` | `1` to `8` | View a specific group | +| `leaveViewGroup` | `1` to `8` | Stop viewing a specific group | + +### Timer Commands + +| Action | Value | Description | +|--------|-------|-------------| +| `startRoomTimer` | Integer (seconds) | Start countdown timer for room | +| `pauseRoomTimer` | N/A | Pause the room timer | +| `stopRoomTimer` | N/A | Stop and reset the room timer | + +### Presentation Control + +| Action | Value | Description | +|--------|-------|-------------| +| `nextSlide` | N/A | Advance to next slide (for PowerPoint integration) | +| `prevSlide` | N/A | Go to previous slide | +| `soloVideo` | `true`, `false`, `toggle` | Highlight video for all guests | + +### Director-Only Guest Commands + +These commands target specific guests when you are the director. + +| Action | Target | Value | Description | +|--------|--------|-------|-------------| +| `forward` | Guest ID/slot | Room name | Transfer guest to another room | +| `addScene` | Guest ID/slot | Scene ID (1-8) | Toggle guest in/out of scene | +| `muteScene` | Guest ID/slot | Scene ID | Toggle guest's audio in scene | +| `mic` | Guest ID/slot | `true`, `false`, `toggle` | Control guest's microphone | +| `hangup` | Guest ID/slot | N/A | Disconnect a specific guest | +| `soloChat` | Guest ID/slot | N/A | Private chat with guest | +| `soloChatBidirectional` | Guest ID/slot | N/A | Two-way private chat | +| `speaker` | Guest ID/slot | N/A | Toggle guest's speaker | +| `display` | Guest ID/slot | N/A | Toggle guest's display | +| `forceKeyframe` | Guest ID/slot | N/A | Fix video artifacts for guest | +| `soloVideo` | Guest ID/slot | N/A | Highlight specific guest's video | +| `volume` | Guest ID/slot | `0` to `100` | Set guest's microphone volume | +| `mixorder` | Guest ID/slot | `-1` or `1` | Change guest's position in mixer | + +## Target Parameter Explanation + +When using director commands, you can specify targets in two ways: + +1. **Slot number**: Simple integers like `1`, `2`, `3` (corresponds to position in room) +2. **Stream ID**: The unique ID for a specific guest (more reliable as slots can change) + +Examples: +```javascript +// Target guest in slot 1 +{"action": "mic", "target": 1, "value": false} + +// Target guest with specific stream ID +{"action": "mic", "target": "abc123xyz", "value": false} +``` + +## Callbacks and Responses + +API commands receive callbacks with the current state after execution: + +```javascript +// WebSocket example response when toggling mic +{ + "callback": { + "action": "mic", + "value": "toggle", + "result": false // Indicates mic is now muted + } +} +``` + +## Custom Layout Format + +The layout API supports complex scene configurations. Layouts can be arrays of objects with properties: + +```javascript +// Simple layout with two videos +{ + "action": "layout", + "value": [ + {"x": 0, "y": 0, "w": 50, "h": 100, "slot": 0}, + {"x": 50, "y": 0, "w": 50, "h": 100, "slot": 1} + ] +} +``` + +Layout object properties: +- `x`, `y`: Position (percentage of canvas) +- `w`, `h`: Width and height (percentage) +- `slot`: Which video slot to display (0-indexed) +- `z`: Z-index for layering (optional) +- `c`: Cover mode (true/false, optional) + +## Implementation Examples + +### Python Example + +```python +import websockets +import asyncio +import json + +async def control_camera(): + async with websockets.connect("wss://api.vdo.ninja:443") as websocket: + # Join with API key + await websocket.send(json.dumps({"join": "YOUR_API_KEY"})) + + # Zoom in camera + await websocket.send(json.dumps({ + "action": "zoom", + "value": 0.5, + "value2": "abs" + })) + + # Wait for response + response = await websocket.recv() + print(f"Response: {response}") + +asyncio.run(control_camera()) +``` + +### JavaScript HTTP Example + +```javascript +// Toggle microphone via HTTP +fetch("https://api.vdo.ninja/YOUR_API_KEY/mic/toggle") + .then(response => response.text()) + .then(result => console.log("Mic toggled, new state:", result)); +``` + +## Integration with Automation Tools + +The API integrates well with: + +1. **BitFocus Companion**: Official module available at [github.com/bitfocus/companion-module-vdo-ninja](https://github.com/bitfocus/companion-module-vdo-ninja) +2. **Stream Deck**: Can use HTTP requests for button actions +3. **Node-RED**: Great for complex automation workflows +4. **Home Assistant**: For smart home integration + +## Security Considerations + +- Keep your API key private +- Consider using unique keys for different productions +- The API has full control over the VDO.Ninja instance it's connected to +- All connections are encrypted over SSL/TLS + +## Troubleshooting + +- Ensure the API key matches exactly between VDO.Ninja and your requests +- For WebSocket connections, implement reconnection logic (connections timeout after ~1 minute of inactivity) +- When using HTTP API, a `timeout` response means the request couldn't reach the target + +## Additional Resources + +- Complete API documentation: [github.com/steveseguin/Companion-Ninja](https://github.com/steveseguin/Companion-Ninja) +- Interactive demo: [companion.vdo.ninja](https://companion.vdo.ninja) +- For Python implementations: See the Python sample in the repository + +## Advanced Usage: Self-Hosting the API + +For production environments, you can self-host the API server: + +1. Clone the repository from GitHub +2. Install dependencies with `npm install` +3. Modify the server URL in your VDO.Ninja instances: + ```javascript + session.apiserver = "wss://your-custom-domain:443"; + ``` +4. Run the server with proper SSL certificates + +Note: Self-hosting support is limited and should only be attempted by experienced developers. \ No newline at end of file diff --git a/examples/iframeapi.md b/examples/iframeapi.md new file mode 100644 index 0000000..476353d --- /dev/null +++ b/examples/iframeapi.md @@ -0,0 +1,258 @@ +# Understanding the VDO.Ninja IFRAME API: Detecting User Joins and Disconnects + +The VDO.Ninja IFRAME API allows websites to embed and interact with VDO.Ninja streams. One of the most useful features is the ability to detect when users join or disconnect from your stream through event messaging. This guide will explain how to implement this functionality in your own projects. + +## How the IFRAME API Works + +VDO.Ninja's IFRAME API uses the browser's `postMessage` API to communicate between your parent website and the embedded VDO.Ninja iframe. This allows you to: + +1. Send commands to control the VDO.Ninja instance +2. Receive events and data from the VDO.Ninja instance + +## Setting Up the Basic Structure + +First, you need to create an iframe that loads VDO.Ninja: + +```javascript +// 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 the Event Listener + +To detect joins and disconnects, you need to set up an event listener for messages from the iframe: + +```javascript +// Set up event listener (cross-browser compatible) +var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; +var eventer = window[eventMethod]; +var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; + +// Add the event listener +eventer(messageEvent, function (e) { + // Make sure the message is from our VDO.Ninja iframe + if (e.source != iframe.contentWindow) return; + + // Log the data for debugging + console.log(e.data); + + // Process specific events + if ("action" in e.data) { + // Handle different actions + handleAction(e.data); + } +}, false); +``` + +## Detecting User Joins and Disconnects + +The key events to watch for are: + +### Guest Connections + +```javascript +function handleAction(data) { + if (data.action === "guest-connected") { + // A new guest has connected + console.log("Guest connected:", data.streamID); + + // You can access additional info if available + if (data.value && data.value.label) { + console.log("Guest label:", data.value.label); + } + } + else if (data.action === "view-connection") { + // Someone viewing the stream has connected + console.log("Viewer connected:", data.streamID); + + // The value property will be true for connections + if (data.value) { + console.log("New viewer connected"); + } else { + console.log("Viewer disconnected"); + } + } + else if (data.action === "director-connected") { + // The director has connected + console.log("Director connected"); + } + else if (data.action === "scene-connected") { + // A scene has connected + console.log("Scene connected:", data.value); // Scene ID + } + else if (data.action === "slot-updated") { + // A stream has been assigned to a slot + console.log("Stream", data.streamID, "assigned to slot", data.value); + } +} +``` + +### Disconnections + +```javascript +function handleAction(data) { + // Handling disconnections + if (data.action === "view-connection" && data.value === false) { + // A viewer has disconnected + console.log("Viewer disconnected:", data.streamID); + } + else if (data.action === "director-share" && data.value === false) { + // A director has stopped sharing + console.log("Director stopped sharing:", data.streamID); + } + else if (data.action === "push-connection" && data.value === false) { + // A guest has disconnected + console.log("Guest disconnected:", data.streamID); + } +} +``` + +## Complete Working Example + +Here's a complete example that demonstrates detecting joins and disconnects: + +```javascript +// Create the container for the iframe +var container = document.createElement("div"); +container.id = "vdo-container"; +document.body.appendChild(container); + +// Create the iframe element +var iframe = document.createElement("iframe"); +iframe.allow = "camera;microphone;fullscreen;display-capture;autoplay;"; +iframe.src = "https://vdo.ninja/?room=your-room-name&cleanoutput"; +iframe.style.width = "100%"; +iframe.style.height = "100%"; +container.appendChild(iframe); + +// Create a status display element +var statusDiv = document.createElement("div"); +statusDiv.id = "connection-status"; +document.body.appendChild(statusDiv); + +// Set up event listener +var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; +var eventer = window[eventMethod]; +var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; + +// Keep track of connected users +var connectedUsers = {}; + +// Add the event listener +eventer(messageEvent, function (e) { + // Make sure the message is from our VDO.Ninja iframe + if (e.source != iframe.contentWindow) return; + + // Log all messages for debugging + console.log(e.data); + + // Process specific actions + if ("action" in e.data) { + handleAction(e.data); + } +}, false); + +function handleAction(data) { + // Handle connections + if (data.action === "guest-connected" && data.streamID) { + connectedUsers[data.streamID] = data.value?.label || "Guest"; + updateStatusDisplay("Guest connected: " + (data.value?.label || data.streamID)); + } + else if (data.action === "view-connection") { + if (data.value && data.streamID) { + connectedUsers[data.streamID] = "Viewer"; + updateStatusDisplay("Viewer connected: " + data.streamID); + } else if (data.streamID) { + delete connectedUsers[data.streamID]; + updateStatusDisplay("Viewer disconnected: " + data.streamID); + } + } + else if (data.action === "director-connected") { + updateStatusDisplay("Director connected"); + } + else if (data.action === "push-connection" && data.value === false && data.streamID) { + delete connectedUsers[data.streamID]; + updateStatusDisplay("User disconnected: " + data.streamID); + } +} + +function updateStatusDisplay(message) { + var timestamp = new Date().toLocaleTimeString(); + statusDiv.innerHTML += `

${timestamp}: ${message}

`; + + // Update connected users count + var count = Object.keys(connectedUsers).length; + document.getElementById("user-count").textContent = count; +} + +// Add a user count display +var countDiv = document.createElement("div"); +countDiv.innerHTML = "Connected users: 0"; +document.body.insertBefore(countDiv, statusDiv); +``` + +## Waiting Room Example + +You can implement a waiting room like the one in the `waitingroom.html` file from your code samples: + +```javascript +// Setup event listener +var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; +var eventer = window[eventMethod]; +var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; +var waiting = null; + +eventer(messageEvent, function (e) { + if (e.source != iframe.contentWindow) return; + + if ("action" in e.data) { + if (e.data.action == "joining-room") { + // Show initial joining message + outputWindow.innerHTML = "JOINING ROOM"; + + // After 1 second, show waiting message if director hasn't joined + waiting = setTimeout(function() { + outputWindow.innerHTML = "Waiting for the director to join"; + outputWindow.classList.remove("hidden"); + }, 1000); + } + else if (e.data.action == "director-connected") { + // Director has joined, clear waiting message + clearTimeout(waiting); + outputWindow.innerHTML = ""; + outputWindow.classList.add("hidden"); + } + } +}); +``` + +## Getting Additional Information About Connections + +For more detailed information about connections, you can use the `getStreamIDs` or `getDetailedState` commands: + +```javascript +// Request info about all connected streams +iframe.contentWindow.postMessage({ "getStreamIDs": true }, "*"); + +// Request detailed state information +iframe.contentWindow.postMessage({ "getDetailedState": true }, "*"); +``` + +## Best Practices + +1. **Always check the source**: Make sure messages are coming from your VDO.Ninja iframe. +2. **Handle disconnections gracefully**: Sometimes connections drop unexpectedly. +3. **Consider implementing reconnection logic**: If important users disconnect, you might want to notify them or attempt to reconnect. +4. **Debug with console.log**: Log all events during development to understand the full message flow. +5. **Test with multiple users**: The behavior can be different depending on who connects first. + +By implementing these techniques, you can build sophisticated applications that respond to users joining and leaving your VDO.Ninja sessions, creating more interactive and responsive experiences. \ No newline at end of file diff --git a/examples/labelonly.html b/examples/labelonly.html new file mode 100644 index 0000000..642bd7f --- /dev/null +++ b/examples/labelonly.html @@ -0,0 +1,165 @@ + + + + + + + VDO.Ninja - Connected Label + + + + + + + + \ No newline at end of file diff --git a/examples/testsdp.html b/examples/testsdp.html new file mode 100644 index 0000000..316ccbb --- /dev/null +++ b/examples/testsdp.html @@ -0,0 +1,1072 @@ + + + + + + SDP Compression Tool + + + + +

SDP Compression Tool

+ +
+
Compression
+
Testing
+
ICE Candidates
+
+ +
+

SDP Compression/Decompression

+ +
+
+

Input SDP

+ + + + +
+ +
+

Compressed Output

+ + + + +
+
+ +
+
+ Original Size: 0 bytes +
+
+ Compressed Size: 0 bytes +
+
+ Compression Ratio: 0% +
+
+ +
+

QR Code

+
+
+ +

Decompressed Result

+ +
+ +
+

SDP Compression Testing

+ + +

Test Output

+
+
+ +
+

ICE Candidates Compression

+
+

ICE Candidates (JSON Array)

+ + + +
+ +
+

Compressed ICE

+ + + +
+ +
+

Decompressed ICE

+ +
+
+ + + \ No newline at end of file diff --git a/examples/wireless.html b/examples/wireless.html new file mode 100644 index 0000000..c07a61f --- /dev/null +++ b/examples/wireless.html @@ -0,0 +1,1739 @@ + + + + + + Manual Message Relay with Compression + + + +
+
+
+
Left Session
+ +
+
+
Right Session
+ +
+
+ +
+ + + +
+ +
+
+ Connection Status: Waiting for connections... +
+ +
+ + + + +
+ +
+ + +
+
+
+
+ + + + \ No newline at end of file