forked from EmulatorJS/EmulatorJS
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathARCADE_LOBBY_USAGE.js
More file actions
251 lines (187 loc) · 8.45 KB
/
ARCADE_LOBBY_USAGE.js
File metadata and controls
251 lines (187 loc) · 8.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
// Arcade Lobby Stream Compositor - Usage Examples
// ==============================================================================
// EXAMPLE 1: Automatic Stream Registration (Most Common)
// ==============================================================================
// When a user enters arcade lobby mode and SFU consumers connect,
// streams are automatically registered:
// In NetplayEngine initialization:
this.initializeStreamCompositor(1280, 720); // Initialize compositor
// When SFU creates a consumer (automatic):
const consumer = await this.createConsumerWithCompositor(
producerId,
'video',
'Player Name' // Optional display name
);
// Stream is automatically registered and rendering starts!
// ==============================================================================
// EXAMPLE 2: Manual Stream Registration (Advanced)
// ==============================================================================
// For special cases, you can manually register streams:
// From Engine:
engine.registerProducerStream(
'producer-123',
'John (Host)',
mediaStream,
false // pinned state (false = unpinned)
);
// From Menu:
netplayMenu.arcadeLobbyRegisterStream(
'producer-456',
'Jane (Player 2)',
mediaStream,
true // pin to left grid
);
// ==============================================================================
// EXAMPLE 3: Pin/Unpin Streams
// ==============================================================================
// Toggle a stream between pinned (80%) and unpinned (20%) sections:
// From Engine:
engine.toggleProducerPin('producer-123');
// From Menu:
netplayMenu.arcadeLobbyTogglePin('producer-123');
// After toggle, grid re-renders automatically
// Remote viewers see the updated layout
// ==============================================================================
// EXAMPLE 4: Remove Streams
// ==============================================================================
// When a player leaves or consumer closes:
// From Engine:
engine.unregisterProducerStream('producer-123');
// From Menu:
netplayMenu.arcadeLobbyUnregisterStream('producer-123');
// Stream is cleaned up and rendering updates
// ==============================================================================
// EXAMPLE 5: Check Stream State
// ==============================================================================
// Query the compositor state:
const compositor = engine.streamCompositor;
// Get all registered streams
const streams = compositor.getStreams();
// Returns: { [producerId]: { id, name, mediaStream, videoElement, pinned }, ... }
// Get stream count
const count = compositor.getStreamCount();
// Get the canvas for capture
const canvas = compositor.getCanvas(); // Returns HTMLCanvasElement
// ==============================================================================
// EXAMPLE 6: Cleanup on Room Exit
// ==============================================================================
// Proper cleanup when leaving arcade lobby:
// Dispose all streams and stop rendering
engine.disposeStreamCompositor();
// Resources freed:
// - All video elements removed from DOM
// - MediaStreams cleaned up
// - Rendering loop stopped
// - Canvas removed from DOM
// ==============================================================================
// EXAMPLE 7: Arcade Lobby Room Flow
// ==============================================================================
// Complete flow of entering arcade lobby with compositing:
async function enterArcadeLobby() {
// 1. User clicks "Arcade Lobby"
netplayMenu.netplaySwitchToArcadeLobbyRoom('Arcade Lobby', null);
// 2. Menu initializes compositor
netplayMenu.initializeArcadeLobbyCompositor();
// → engine.initializeStreamCompositor(1280, 720) called
// 3. SFU connects and fetches existing producers
// → For each producer, createConsumerWithCompositor() called
// → Each video stream automatically registers with compositor
// 4. Rendering starts automatically
// → RequestAnimationFrame loop draws all streams to hidden canvas
// → Grid layout updates dynamically
// 5. Video capture happens automatically
// → netplayCaptureCanvasVideo() checks for compositor first
// → Captures composited canvas instead of emulator canvas
// → Remote viewers see grid layout
// 6. Players can toggle pins
// → Click pin button → arcadeLobbyTogglePin('producer-X')
// → Grid re-renders → Remote viewers see new layout
// 7. Player leaves arcade lobby
// → engine.disposeStreamCompositor()
// → All streams cleaned up
// → Rendering stopped
}
// ==============================================================================
// EXAMPLE 8: React to Stream Changes
// ==============================================================================
// Monitor compositor state (polling or event-based):
setInterval(() => {
const compositor = engine.streamCompositor;
if (compositor) {
const count = compositor.getStreamCount();
console.log(`Current streams: ${count}`);
// Update UI or trigger actions based on stream count
if (count === 0) {
console.log('No streams connected');
} else if (count === 1) {
console.log('One stream connected');
} else {
console.log(`${count} streams connected`);
}
}
}, 1000);
// ==============================================================================
// EXAMPLE 9: Get Canvas for Display (Advanced)
// ==============================================================================
// The compositor canvas is hidden, but you can use it:
const compositor = engine.streamCompositor;
const canvas = compositor.getCanvas();
// Display canvas (don't do this normally - defeats purpose of hidden canvas)
// canvas.style.display = 'block';
// document.body.appendChild(canvas);
// Capture for recording/streaming (already done by SFU)
// const stream = canvas.captureStream(30);
// const videoTrack = stream.getVideoTracks()[0];
// → This is what netplayCaptureCanvasVideo() does automatically
// ==============================================================================
// EXAMPLE 10: Configuration
// ==============================================================================
// Customize compositor on initialization:
// Standard 1280x720 (default)
engine.initializeStreamCompositor(1280, 720);
// Higher resolution for better quality
engine.initializeStreamCompositor(1920, 1080);
// Lower resolution for bandwidth/performance
engine.initializeStreamCompositor(854, 480);
// The compositor will render all streams at the specified resolution
// and SFU will stream at that resolution to remote viewers
// ==============================================================================
// STREAM REGISTRATION FLOW DETAILS
// ==============================================================================
// When a consumer is created in arcade lobby mode:
// 1. SFU consumer created with video track
const consumer = await this.sfuTransport.createConsumer(producerId, 'video');
// consumer = { id, kind: 'video', track: MediaStreamVideoTrack, ... }
// 2. createConsumerWithCompositor() called:
// - Creates MediaStream from track
// - Gets display name (from parameter or falls back to producerId)
// - Calls registerProducerStream()
// 3. registerProducerStream() in engine:
// - Delegates to streamCompositor.registerStream()
// 4. StreamCompositor.registerStream():
// - Creates hidden video element
// - Sets srcObject to MediaStream
// - Stores stream info { id, name, mediaStream, videoElement, pinned }
// - Starts rendering loop if needed
// 5. Rendering loop:
// - Every frame: reads video element's current frame
// - Draws to hidden canvas in grid layout
// - Remote viewers see this canvas via SFU capture
// ==============================================================================
// ERROR HANDLING
// ==============================================================================
// Compositor handles errors gracefully:
// Video playback failed?
// → Placeholder text displayed
// → Other streams continue rendering
// Compositor not initialized?
// → registerProducerStream() returns early with warning
// → Falls back to emulator canvas capture
// Stream registration fails?
// → Warning logged
// → Consumer still created and usable
// → Just not visible on remote streams
// Always check compositor exists before using:
if (engine.streamCompositor && engine.streamCompositor.getStreamCount() > 0) {
console.log('Compositor has streams');
}