-
Notifications
You must be signed in to change notification settings - Fork 4
GWP Multiplexing Sample
This sample demonstrates how to display multiple cameras simultaneously over a single WebSocket connection using GWP's multiplexing feature. This is ideal for multi-camera grids, video walls, and surveillance dashboards.
Multiplexing allows multiple GWP players to share a single WebSocket connection to the Media Gateway instead of creating one connection per player. This provides:
- Reduced Connection Overhead - Only one WebSocket instead of N connections
- Better Performance - Lower network and server resource usage
- Scalability - Handle more simultaneous cameras
- Simplified Management - One shared service manages all streams
This sample application includes:
- Flexible Grid Layouts - 2x2 (4 cameras), 3x3 (9 cameras), or 4x4 (16 cameras)
-
Shared WebSocket - All cameras use one connection via
buildMediaGatewayService() - Individual Control - Each player can be controlled independently
- Dynamic Camera Configuration - Add/remove cameras by GUID
- Visual Status Indicators - See connection status for each camera
- Real-time Statistics - Monitor connections, active players, and streaming count
- Error Handling - Per-camera error detection and display
The sample provides:
┌─────────────────────────────────────────┐
│ Connection Settings & Camera GUIDs │
└─────────────────────────────────────────┘
┌─────────────┬─────────────┬─────────────┐
│ Camera 1 │ Camera 2 │ Camera 3 │
│ [Status] │ [Status] │ [Status] │
├─────────────┼─────────────┼─────────────┤
│ Camera 4 │ Camera 5 │ Camera 6 │
│ [Status] │ [Status] │ [Status] │
└─────────────┴─────────────┴─────────────┘
┌─────────────────────────────────────────┐
│ Statistics & Information │
└─────────────────────────────────────────┘
Each camera tile shows:
- Camera number/label
- Connection status (Waiting, Connecting, Streaming, Error)
- Color-coded border (orange=connecting, green=streaming, red=error)
Save the following as an HTML file (e.g., gwp-multiplexing.html) and open it in a web browser:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GWP Multiplexing Demo - Multiple Cameras Grid</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #1a1a1a;
color: #fff;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px 30px;
border-radius: 8px;
margin-bottom: 20px;
}
header h1 {
font-size: 24px;
margin-bottom: 5px;
}
header p {
font-size: 14px;
opacity: 0.9;
}
.controls {
background: #2d2d2d;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
font-size: 13px;
font-weight: 500;
margin-bottom: 5px;
color: #ccc;
}
input[type="text"],
input[type="password"],
select {
width: 100%;
padding: 8px 10px;
border: 1px solid #444;
border-radius: 4px;
font-size: 13px;
background: #1a1a1a;
color: #fff;
font-family: inherit;
}
input[type="text"]:focus,
input[type="password"]:focus,
select:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
}
.camera-inputs {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
margin: 15px 0;
}
button {
padding: 10px 20px;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
margin-right: 10px;
}
.btn-primary {
background-color: #667eea;
color: white;
}
.btn-primary:hover {
background-color: #5568d3;
}
.btn-danger {
background-color: #f44336;
color: white;
}
.btn-danger:hover {
background-color: #da190b;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #5a6268;
}
.grid-controls {
display: flex;
gap: 10px;
align-items: center;
margin-top: 15px;
}
.camera-grid {
display: grid;
gap: 10px;
background: #2d2d2d;
padding: 10px;
border-radius: 8px;
margin-bottom: 20px;
}
.camera-grid.grid-2x2 {
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, 300px);
}
.camera-grid.grid-3x3 {
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 250px);
}
.camera-grid.grid-4x4 {
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(4, 200px);
}
.camera-tile {
background: #000;
border: 2px solid #444;
border-radius: 4px;
position: relative;
overflow: hidden;
}
.camera-tile:hover {
border-color: #667eea;
}
.camera-tile.error {
border-color: #f44336;
}
.camera-tile.loading {
border-color: #ff9800;
}
.camera-label {
position: absolute;
top: 5px;
left: 5px;
background: rgba(0, 0, 0, 0.7);
padding: 3px 8px;
border-radius: 3px;
font-size: 11px;
z-index: 10;
}
.camera-status {
position: absolute;
bottom: 5px;
right: 5px;
background: rgba(0, 0, 0, 0.7);
padding: 3px 8px;
border-radius: 3px;
font-size: 10px;
z-index: 10;
}
.status-connected {
color: #4CAF50;
}
.status-connecting {
color: #ff9800;
}
.status-error {
color: #f44336;
}
.player-container {
width: 100%;
height: 100%;
}
.info-panel {
background: #2d2d2d;
padding: 15px;
border-radius: 8px;
font-size: 13px;
}
.info-panel h3 {
margin-bottom: 10px;
color: #667eea;
}
.info-panel ul {
margin-left: 20px;
}
.info-panel li {
margin-bottom: 5px;
line-height: 1.6;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-top: 15px;
}
.stat-card {
background: #1a1a1a;
padding: 15px;
border-radius: 6px;
border-left: 3px solid #667eea;
}
.stat-label {
font-size: 11px;
color: #999;
text-transform: uppercase;
margin-bottom: 5px;
}
.stat-value {
font-size: 20px;
font-weight: 600;
}
.note {
background: #3d3d00;
border-left: 3px solid #ffeb3b;
padding: 10px 15px;
margin-top: 15px;
border-radius: 4px;
font-size: 12px;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>🎥 GWP Multiplexing Demo</h1>
<p>Demonstrate multiple cameras over a single WebSocket connection</p>
</header>
<div class="controls">
<h3 style="margin-bottom: 15px;">Connection Settings</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
<div class="form-group">
<label for="mediaGateway">Media Gateway Address</label>
<input type="text" id="mediaGateway" placeholder="localhost or IP" value="127.0.0.1">
</div>
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" value="admin">
</div>
<div class="form-group">
<label for="appId">SDK Certificate</label>
<input type="text" id="appId" value="KxsD11z743Hf5Gq9mv3+5ekxzemlCiUXkTFY5ba1NOGcLCmGstt2n0zYE9NsNimv">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password">
</div>
</div>
<h3 style="margin: 20px 0 15px 0;">Camera GUIDs</h3>
<div class="camera-inputs" id="cameraInputs">
<div class="form-group">
<label for="camera1">Camera 1 GUID</label>
<input type="text" id="camera1" placeholder="00000000-0000-0000-0000-000000000000">
</div>
<div class="form-group">
<label for="camera2">Camera 2 GUID</label>
<input type="text" id="camera2" placeholder="00000000-0000-0000-0000-000000000000">
</div>
<div class="form-group">
<label for="camera3">Camera 3 GUID</label>
<input type="text" id="camera3" placeholder="00000000-0000-0000-0000-000000000000">
</div>
<div class="form-group">
<label for="camera4">Camera 4 GUID</label>
<input type="text" id="camera4" placeholder="00000000-0000-0000-0000-000000000000">
</div>
</div>
<div class="grid-controls">
<label for="gridSize">Grid Size:</label>
<select id="gridSize">
<option value="2x2" selected>2x2 (4 cameras)</option>
<option value="3x3">3x3 (9 cameras)</option>
<option value="4x4">4x4 (16 cameras)</option>
</select>
<button id="startBtn" class="btn-primary">Start All Cameras</button>
<button id="stopBtn" class="btn-danger" disabled>Stop All Cameras</button>
</div>
<div class="note">
<strong>Note:</strong> Leave camera GUIDs empty to skip those tiles. The demo will only start players for cameras with valid GUIDs.
</div>
</div>
<div class="camera-grid grid-2x2" id="cameraGrid">
<!-- Camera tiles will be generated dynamically -->
</div>
<div class="info-panel">
<h3>📊 Statistics</h3>
<div class="stats">
<div class="stat-card">
<div class="stat-label">WebSocket Connections</div>
<div class="stat-value" id="statConnections">0</div>
</div>
<div class="stat-card">
<div class="stat-label">Active Players</div>
<div class="stat-value" id="statPlayers">0</div>
</div>
<div class="stat-card">
<div class="stat-label">Streaming Cameras</div>
<div class="stat-value" id="statStreaming">0</div>
</div>
<div class="stat-card">
<div class="stat-label">GWP Version</div>
<div class="stat-value" id="statVersion">-</div>
</div>
</div>
<h3 style="margin-top: 20px;">ℹ️ How Multiplexing Works</h3>
<ul>
<li><strong>Single WebSocket:</strong> All players share one WebSocket connection to the Media Gateway</li>
<li><strong>Shared Service:</strong> Created using <code>gwp.buildMediaGatewayService()</code></li>
<li><strong>Individual Control:</strong> Each player can be controlled independently (play, pause, seek)</li>
<li><strong>Resource Efficiency:</strong> Reduces network overhead and connection count</li>
<li><strong>Scaling:</strong> Better performance when displaying many cameras simultaneously</li>
<li><strong>Shared Fate:</strong> If the WebSocket fails, all players are affected</li>
</ul>
<h3 style="margin-top: 20px;">🔧 Code Pattern</h3>
<pre style="background: #1a1a1a; padding: 15px; border-radius: 4px; overflow-x: auto; font-size: 12px; margin-top: 10px;">
// 1. Create shared Media Gateway service
const service = await gwp.buildMediaGatewayService(
mediaGatewayUrl,
getTokenFunction
);
// 2. Build individual players
const player1 = gwp.buildPlayer(container1);
const player2 = gwp.buildPlayer(container2);
// 3. Start players with the shared service
await player1.startWithService(cameraGuid1, service);
await player2.startWithService(cameraGuid2, service);
// 4. Control each player independently
player1.playLive();
player2.playLive();
</pre>
</div>
</div>
<script>
// ============================================================================
// GLOBAL STATE
// ============================================================================
let mediaGatewayService = null;
let players = [];
let gwpScript = null;
let gridSize = '2x2';
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
function updateStats() {
const activeCount = players.filter(p => p.player?.isStarted).length;
const streamingCount = players.filter(p => p.player?.isStarted && !p.player?.isPaused).length;
document.getElementById('statConnections').textContent = mediaGatewayService ? '1' : '0';
document.getElementById('statPlayers').textContent = activeCount;
document.getElementById('statStreaming').textContent = streamingCount;
}
function updateTileStatus(index, status, message) {
const tile = document.getElementById(`tile-${index}`);
if (!tile) return;
const statusEl = tile.querySelector('.camera-status');
if (statusEl) {
statusEl.textContent = message;
statusEl.className = 'camera-status status-' + status;
}
tile.className = 'camera-tile ' + status;
}
async function getToken(cameraId) {
const username = document.getElementById('username').value.trim();
const appId = document.getElementById('appId').value.trim();
const password = document.getElementById('password').value.trim();
const mediaGateway = document.getElementById('mediaGateway').value.trim();
const credentials = `${username};${appId}:${password}`;
try {
const response = await fetch(`https://${mediaGateway}/media/v2/token/${cameraId}`, {
credentials: 'include',
headers: { 'Authorization': `Basic ${btoa(credentials)}` }
});
if (!response.ok) {
throw new Error(`${response.status} ${response.statusText}`);
}
return await response.text();
} catch (error) {
console.error(`Token retrieval failed for ${cameraId}:`, error.message);
throw error;
}
}
// ============================================================================
// GRID MANAGEMENT
// ============================================================================
function createGrid(size) {
gridSize = size;
const grid = document.getElementById('cameraGrid');
grid.className = 'camera-grid grid-' + size;
grid.innerHTML = '';
const counts = {
'2x2': 4,
'3x3': 9,
'4x4': 16
};
const tileCount = counts[size];
// Generate camera input fields if needed
const inputsContainer = document.getElementById('cameraInputs');
inputsContainer.innerHTML = '';
for (let i = 1; i <= tileCount; i++) {
// Create input field
const formGroup = document.createElement('div');
formGroup.className = 'form-group';
formGroup.innerHTML = `
<label for="camera${i}">Camera ${i} GUID</label>
<input type="text" id="camera${i}" placeholder="00000000-0000-0000-0000-000000000000">
`;
inputsContainer.appendChild(formGroup);
// Create grid tile
const tile = document.createElement('div');
tile.className = 'camera-tile';
tile.id = `tile-${i}`;
tile.innerHTML = `
<div class="camera-label">Camera ${i}</div>
<div class="camera-status status-connecting">Waiting...</div>
<div class="player-container" id="player-${i}"></div>
`;
grid.appendChild(tile);
}
}
// ============================================================================
// PLAYER MANAGEMENT
// ============================================================================
async function startAllCameras() {
const mediaGateway = document.getElementById('mediaGateway').value.trim();
if (!mediaGateway) {
alert('Please enter Media Gateway address');
return;
}
try {
document.getElementById('startBtn').disabled = true;
updateStats();
// Load gwp.js
console.log('Loading GWP library...');
gwpScript = document.createElement('script');
gwpScript.src = `https://${mediaGateway}/media/v2/files/gwp.js`;
await new Promise((resolve, reject) => {
gwpScript.onload = resolve;
gwpScript.onerror = () => reject(new Error('Failed to load gwp.js'));
document.body.appendChild(gwpScript);
});
console.log('GWP library loaded');
// Get GWP version
if (typeof gwp !== 'undefined') {
const version = gwp.version();
document.getElementById('statVersion').textContent = version;
console.log('GWP Version:', version);
}
// Create shared Media Gateway service
console.log('Creating Media Gateway service...');
mediaGatewayService = await gwp.buildMediaGatewayService(
`https://${mediaGateway}/media`,
getToken
);
console.log('Media Gateway service created (1 WebSocket connection)');
updateStats();
// Get camera GUIDs
const counts = { '2x2': 4, '3x3': 9, '4x4': 16 };
const tileCount = counts[gridSize];
const cameraGuids = [];
for (let i = 1; i <= tileCount; i++) {
const guid = document.getElementById(`camera${i}`).value.trim();
cameraGuids.push(guid || null);
}
// Start players for each camera
for (let i = 0; i < tileCount; i++) {
const cameraGuid = cameraGuids[i];
const playerIndex = i + 1;
if (!cameraGuid) {
updateTileStatus(playerIndex, 'error', 'No GUID');
players.push({ player: null, cameraGuid: null });
continue;
}
try {
updateTileStatus(playerIndex, 'connecting', 'Connecting...');
const container = document.getElementById(`player-${playerIndex}`);
const player = gwp.buildPlayer(container);
// Register error handler
player.onErrorStateRaised.register((error) => {
console.error(`Camera ${playerIndex} error:`, error.value);
updateTileStatus(playerIndex, 'error', `Error: ${error.errorCode}`);
});
// Register stream status handler
player.onStreamStatusChanged.register((event) => {
if (event.state === 5) { // Streaming
updateTileStatus(playerIndex, 'connected', 'Streaming');
}
});
// Start player with shared service
await player.startWithService(cameraGuid, mediaGatewayService);
player.playLive();
players.push({ player, cameraGuid });
updateTileStatus(playerIndex, 'connected', 'Connected');
console.log(`Camera ${playerIndex} started successfully`);
} catch (error) {
console.error(`Failed to start camera ${playerIndex}:`, error.message);
updateTileStatus(playerIndex, 'error', error.message);
players.push({ player: null, cameraGuid });
}
}
updateStats();
document.getElementById('stopBtn').disabled = false;
console.log(`Started ${players.filter(p => p.player).length} players over 1 WebSocket connection`);
} catch (error) {
console.error('Failed to start cameras:', error);
alert(`Error: ${error.message}`);
document.getElementById('startBtn').disabled = false;
}
}
function stopAllCameras() {
console.log('Stopping all cameras...');
// Stop and dispose all players
players.forEach((p, index) => {
if (p.player) {
try {
p.player.stop();
p.player.dispose();
updateTileStatus(index + 1, 'error', 'Stopped');
} catch (error) {
console.error(`Error stopping camera ${index + 1}:`, error);
}
}
});
players = [];
mediaGatewayService = null;
updateStats();
document.getElementById('startBtn').disabled = false;
document.getElementById('stopBtn').disabled = true;
console.log('All cameras stopped');
}
// ============================================================================
// EVENT LISTENERS
// ============================================================================
document.addEventListener('DOMContentLoaded', () => {
// Initialize grid
createGrid('2x2');
// Grid size change
document.getElementById('gridSize').addEventListener('change', (e) => {
if (players.length > 0) {
if (!confirm('Changing grid size will stop all cameras. Continue?')) {
e.target.value = gridSize;
return;
}
stopAllCameras();
}
createGrid(e.target.value);
});
// Start/Stop buttons
document.getElementById('startBtn').addEventListener('click', startAllCameras);
document.getElementById('stopBtn').addEventListener('click', stopAllCameras);
console.log('Multiplexing demo initialized');
});
</script>
</body>
</html>-
Enter Connection Details
- Media Gateway Address - Your Media Gateway hostname or IP
- Username - Security Center username
- SDK Certificate - Your application's SDK certificate
- Password - Security Center password
-
Configure Cameras
- Enter camera GUIDs for the cameras you want to display
- Leave fields empty to skip those positions
- You can enter fewer cameras than the grid size
-
Select Grid Size
- Choose 2x2 (4 cameras), 3x3 (9 cameras), or 4x4 (16 cameras)
- Changing grid size while running will stop all cameras
-
Start Cameras
- Click "Start All Cameras"
- Watch the status indicators for each camera
- Monitor the statistics panel to see connection count (should be 1)
Camera Status Indicators:
- Waiting (Gray) - No GUID provided or waiting to start
- Connecting (Orange border) - Attempting to connect
- Streaming (Green border) - Successfully streaming video
- Error (Red border) - Connection or stream error
Statistics Display:
- WebSocket Connections - Should always show "1" when running
- Active Players - Number of successfully started players
- Streaming Cameras - Number of cameras actively streaming
- GWP Version - Version of the loaded GWP library
// Create one shared service for all players
const mediaGatewayService = await gwp.buildMediaGatewayService(
`https://${mediaGateway}/media`,
getToken
);// Build individual players
const player = gwp.buildPlayer(containerElement);
// Start with the shared service (NOT player.start())
await player.startWithService(cameraGuid, mediaGatewayService);
// Play live
player.playLive();// Each player has independent error handling
player.onErrorStateRaised.register((error) => {
console.error(`Camera ${index} error:`, error.value);
updateTileStatus(index, 'error', `Error: ${error.errorCode}`);
});| Feature | Multiplexing | Individual Connections |
|---|---|---|
| WebSocket Connections | 1 (shared) | N (one per player) |
| Network Overhead | Low | High (N connections) |
| Media Gateway Load | Lower | Higher |
| Scalability | Better (more cameras) | Limited by connection count |
| Failure Impact | All players affected | Only failed player affected |
| Setup Complexity | Slightly more complex | Simpler |
Use multiplexing when:
- Displaying 4+ cameras simultaneously
- Building video walls or dashboards
- Connection count is limited
- Network efficiency is important
- All cameras connect to the same Media Gateway
Use individual connections when:
- Displaying 1-3 cameras
- Cameras connect to different Media Gateways
- Isolation is critical (one failure shouldn't affect others)
- Simplicity is preferred over efficiency
- All players must connect to the same Media Gateway
- If the shared WebSocket fails, all players are affected
- Cannot mix
start()andstartWithService()on the same service - Slightly more complex error recovery
- Genetec Web Player Developer Guide - Complete GWP documentation
- Web Player Sample Application - Comprehensive single-player demo
- GWP API Reference - Full API documentation
-
Security Center SDK Developer Guide Overview of the SDK framework and how to build integrations with Security Center.
-
Platform SDK
- Platform SDK Overview Introduction to the Platform SDK and core concepts.
- SDK Certificates Details certificates, licensing, and connection validation.
- Entity Guide Explains the core entity model, inheritance, and how to work with entities.
- Entity Cache Guide Describes the engine's local entity cache and synchronization.
- SDK Transactions Covers batching operations for performance and consistency.
- ReportManager Querying entities and activity data from Security Center.
- Events and Actions Subscribing to events and handling actions.
- Logging with the Genetec SDK How to configure logging, diagnostics, and debug methods.
- Referencing SDK Assemblies Best practices for referencing assemblies and resolving them at runtime.
- SDK Compatibility Guide Understanding backward compatibility and versioning in the SDK.
-
Plugin SDK
- Plugin SDK Overview Introduction to plugin architecture and capabilities.
- Plugin SDK Certificates SDK certificate requirements for plugin roles.
- Plugin SDK Lifecycle Initialization and disposal patterns.
- Plugin SDK Threading Threading model, QueueUpdate, and async patterns.
- Plugin SDK Configuration Configuration storage and monitoring.
- Plugin SDK Restricted Configuration Secure credential storage and admin-only configuration.
- Plugin SDK Database Database integration and schema management.
- Plugin SDK Events Event subscription and handling.
- Plugin SDK Queries Query processing and response handling.
- Plugin SDK Request Manager Request/response communication with clients.
- Plugin SDK Entity Ownership Understanding plugin-owned entities, running state management, and ownership release.
- Plugin SDK Entity Mappings Using EntityMappings for plugin-specific configuration and external system integration.
- Plugin SDK State Management Reporting plugin health and diagnostics.
- Plugin SDK Server Management High availability and server failover.
- Custom Privileges Defining and enforcing custom privileges.
- Resolving Non-SDK Assemblies Handling third-party dependencies in plugins and workspace modules.
- Deploying Plugins Registering and deploying plugins and workspace modules.
-
- Macro SDK Developer Guide Complete guide to creating server-side automation scripts in Security Center using C#.
- Getting Started Setup, authentication, and basic configuration for the Web SDK.
- Referencing Entities Entity discovery, search capabilities, and parameter formats.
- Entity Operations CRUD operations, multi-value fields, and method execution.
- Partitions Managing partitions, entity membership, and user access control.
- Custom Fields Creating, reading, writing, and filtering custom entity fields.
- Custom Card Formats Managing custom credential card format definitions.
- Actions Control operations for doors, cameras, macros, and notifications.
- Events and Alarms Real-time event monitoring, alarm monitoring, and custom events.
- Incidents Incident management, creation, and attachment handling.
- Reports Activity reports, entity queries, and historical data retrieval.
- Performance Guide Optimization tips and best practices for efficient API usage.
- Reference Entity GUIDs, EntityType enumeration, and EventType enumeration.
- Under the Hood Technical architecture, query reflection, and SDK internals.
- Troubleshooting Common error resolution and debugging techniques.
- Media Gateway Guide Setup and configuration of the Media Gateway role for video streaming.
- Web Player Guide Complete guide to integrating GWP for live and playback video streaming.
- Web Player API Reference Full API documentation with interfaces, methods, properties, and events.
- Web Player Sample Application Comprehensive demo showcasing all GWP features with timeline and PTZ controls.
- Genetec Web Player Multiplexing Sample Multi-camera grid demo using a shared WebSocket connection.