Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
97f43b2
fix: comment out unused components
obrucheoghene Jan 9, 2026
cc68859
chore: remove chat container
obrucheoghene Jan 9, 2026
c52bc92
feat: implement emoji picker
obrucheoghene Jan 9, 2026
9dcd2c2
feat: add device change listener to update media devices
obrucheoghene Jan 14, 2026
da63601
feat: add set consumer preferred layers
obrucheoghene Jan 19, 2026
14b35f3
docs: add server implementation
obrucheoghene Jan 19, 2026
d917eeb
feat: implement viewport quality management and speaking state handling
obrucheoghene Jan 19, 2026
7ac573b
feat: add connection quality manager to adjust video quality based on…
obrucheoghene Jan 20, 2026
6d07f41
feat: implement metrics exposure for load testing and enhance perform…
obrucheoghene Jan 21, 2026
add79cc
refactor: clean up unused PerformanceMonitor import and adjust badge …
obrucheoghene Jan 22, 2026
aacab27
fix: correct HEARTBEAT_INTERVAL to 30000 for optimal performance
obrucheoghene Feb 1, 2026
2171763
refactor: improve message handling in RoomProvider and ensure callbac…
obrucheoghene Mar 9, 2026
ca40b18
fix: ensure callback execution on error in handleMessage function
obrucheoghene Mar 9, 2026
beacbe7
feat: implement waitlist management and recording features in the room
obrucheoghene Mar 12, 2026
232733a
feat: add waiting room functionality and waiter admission system
obrucheoghene Mar 12, 2026
607d38e
feat: enhance direct messaging functionality with DM thread and peer …
obrucheoghene Mar 12, 2026
9efe7cb
feat: implement recording controls in CautionModal and Record components
obrucheoghene Mar 12, 2026
a0c0978
feat: add mute all and lower all hands functionality in ParticipantCo…
obrucheoghene Mar 12, 2026
5839a76
feat: add Participant Spotlight/Pin
obrucheoghene Mar 12, 2026
6f4877a
feat: add noise suppression toggle in settings and audio level indica…
obrucheoghene Mar 12, 2026
6eaf114
feat: implement virtual background functionality with blur and image …
obrucheoghene Mar 13, 2026
75cb2bb
fix: implement fix for virtual background freezing
obrucheoghene Mar 17, 2026
8884a14
feat: make some fixes
obrucheoghene Mar 19, 2026
7bdfb20
feat: polish room UI with labeled controls and header improvements
obrucheoghene Mar 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
505 changes: 505 additions & 0 deletions SCALING_IMPLEMENTATION.md

Large diffs are not rendered by default.

192 changes: 192 additions & 0 deletions SERVER_IMPLEMENTATION_REQUIRED.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# Server-Side Implementation Required for Phase 1

## Overview

The client-side bandwidth optimization (Phase 1) is now complete, but requires corresponding server-side changes in the `mitsi-signaling` and `mitsi-media` repositories to function properly.

## What Was Implemented on Client

The client now sends a `set_consumer_preferred_layers` action to the server with the following payload:

```typescript
{
action: 'set_consumer_preferred_layers',
args: {
consumerId: string, // The consumer ID
producerPeerId: string, // The peer ID producing the stream
producerSource: ProducerSource, // 'camera' | 'screen' | 'mic' | 'screenAudio'
spatialLayer: number, // 0 = LOW, 1 = MEDIUM, 2 = HIGH
temporalLayer: number // Temporal layer (usually same as spatial)
}
}
```

## Required Server-Side Changes

### 1. Add Action Handler (mitsi-signaling)

In your signaling server's message handler, add:

```typescript
// Example location: src/handlers/consumer-handlers.ts or similar

import { Actions } from './types/actions';

// Add to your WebSocket message handler
case Actions.SetConsumerPreferredLayers: {
const { consumerId, spatialLayer, temporalLayer } = data.args;

// Get the consumer from your consumer map
const consumer = getConsumerById(consumerId);

if (!consumer) {
socket.emit('error', { message: 'Consumer not found' });
return;
}

// Call mediasoup's setPreferredLayers on the server-side consumer
try {
await consumer.setPreferredLayers({ spatialLayer, temporalLayer });

// Optional: Send acknowledgment back to client
socket.emit('consumer_layers_updated', {
consumerId,
spatialLayer,
temporalLayer
});
} catch (error) {
console.error('Failed to set preferred layers:', error);
socket.emit('error', { message: 'Failed to set preferred layers' });
}
break;
}
```

### 2. Update Actions Enum

Add the action to your server-side Actions enum:

```typescript
export enum Actions {
// ... existing actions
SetConsumerPreferredLayers = 'set_consumer_preferred_layers',
// ... rest of actions
}
```

### 3. Add Type Definitions

Add type definition for the action arguments:

```typescript
interface SetConsumerPreferredLayersArgs {
consumerId: string;
producerPeerId: string;
producerSource: 'camera' | 'screen' | 'mic' | 'screenAudio';
spatialLayer: number;
temporalLayer: number;
}
```

### 4. Consumer Map Structure

Ensure your server maintains a consumer map to look up consumers by ID:

```typescript
// Example consumer storage
const consumers = new Map<string, mediasoup.Consumer>();

// When creating consumer
consumers.set(consumer.id, consumer);

// Lookup function
function getConsumerById(consumerId: string) {
return consumers.get(consumerId);
}
```

## Testing

After implementing the server-side changes:

1. Start the signaling server with the new handler
2. Join a room from the client
3. Open browser DevTools console
4. Verify messages are being sent: Look for `set_consumer_preferred_layers` in Network → WS tab
5. Check server logs for layer changes
6. Use `chrome://webrtc-internals` to verify layer switching is working

## Expected Behavior

With the server implementation complete:

- **Off-screen peers**: Should receive only spatial layer 0 (LOW) or no video (AUDIO_ONLY)
- **On-screen peers**: Should receive spatial layer 1 (MEDIUM)
- **Active speakers**: Should receive spatial layer 2 (HIGH)
- **Bandwidth**: Should reduce by 80-90% compared to all peers receiving HIGH quality

## Debugging

### Client-side debugging:

```javascript
// In browser console
const { qualityManager } = useMedia();
qualityManager.startStatsMonitoring(); // Shows layer changes every 5 seconds
```

### Server-side debugging:

```typescript
consumer.on('layerschange', (layers) => {
console.log('Consumer layers changed:', {
consumerId: consumer.id,
spatialLayer: layers?.spatialLayer,
temporalLayer: layers?.temporalLayer,
});
});
```

## Mediasoup Consumer API Reference

The server-side Consumer object (mediasoup v3) has these methods:

```typescript
// Set preferred layers
await consumer.setPreferredLayers({
spatialLayer: 0 | 1 | 2,
temporalLayer: 0 | 1 | 2
});

// Get current layers
const currentLayers = consumer.currentLayers;
// Returns: { spatialLayer: number, temporalLayer: number } | undefined

// Get preferred layers
const preferredLayers = consumer.preferredLayers;
// Returns: { spatialLayer: number, temporalLayer: number } | undefined

// Listen for layer changes
consumer.on('layerschange', (layers) => {
// layers: { spatialLayer: number, temporalLayer: number } | null
});
```

## Additional Notes

1. **Simulcast must be enabled**: Ensure producers are created with simulcast encoding on the server
2. **VP8/H264 codec**: Layer selection only works with VP8 or H264 (not VP9 SVC by default)
3. **Error handling**: Handle cases where consumer doesn't support simulcast
4. **Rate limiting**: Consider rate limiting layer change requests if needed

## Repository Links

- **Client repo**: mitsi-web (this repo) ✅ Complete
- **Server repos**: mitsi-signaling and mitsi-media ⚠️ **Requires implementation**

## Questions?

If you encounter issues implementing the server-side changes, refer to:
- Mediasoup v3 Consumer API: https://mediasoup.org/documentation/v3/mediasoup/api/#Consumer
- Simulcast guide: https://mediasoup.org/documentation/v3/mediasoup/design/#simulcast

52 changes: 50 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
},
"dependencies": {
"@hookform/resolvers": "^5.2.1",
"@mediapipe/tasks-vision": "^0.10.32",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-checkbox": "^1.3.3",
Expand All @@ -28,9 +29,11 @@
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tooltip": "^1.2.8",
"@tailwindcss/vite": "^4.1.12",
"@types/react-window": "^1.8.8",
"axios": "^1.11.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"emoji-picker-react": "^4.16.1",
"hark": "^1.2.3",
"immer": "^10.0.2",
"lucide-react": "^0.541.0",
Expand All @@ -42,6 +45,7 @@
"react-helmet": "^6.1.0",
"react-hook-form": "^7.62.0",
"react-router-dom": "^7.8.2",
"react-window": "^2.2.5",
"socket.io-client": "^4.8.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1",
Expand Down
10 changes: 8 additions & 2 deletions src/components/modals/caution-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,14 @@
};

const okAction: Record<CautionType, () => void> = {
START_RECORDING: () => {},
STOP_RECORDING: () => {},
START_RECORDING: () => {
signalingService?.sendMessage({ action: Actions.Record, args: { recording: true } });

Check failure on line 40 in src/components/modals/caution-modal.tsx

View workflow job for this annotation

GitHub Actions / Lint, Build, and Test (20.x)

Replace `·action:·Actions.Record,·args:·{·recording:·true·}` with `⏎········action:·Actions.Record,⏎········args:·{·recording:·true·},⏎·····`
cautionActions.set(CautionType.Hide);
},
STOP_RECORDING: () => {
signalingService?.sendMessage({ action: Actions.Record, args: { recording: false } });

Check failure on line 44 in src/components/modals/caution-modal.tsx

View workflow job for this annotation

GitHub Actions / Lint, Build, and Test (20.x)

Replace `·action:·Actions.Record,·args:·{·recording:·false·}` with `⏎········action:·Actions.Record,⏎········args:·{·recording:·false·},⏎·····`
cautionActions.set(CautionType.Hide);
},
END_SESSION: () => {
signalingService?.sendMessage({
action: Actions.EndRoom,
Expand Down
Loading
Loading