makinuh commited on
Commit
6234d40
·
verified ·
1 Parent(s): 3fd10c6

Create style.css

Browse files
Files changed (1) hide show
  1. static/style.css +1349 -0
static/style.css ADDED
@@ -0,0 +1,1349 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+
3
+ <html lang="en">
4
+
5
+ <head>
6
+
7
+ <meta charset="UTF-8">
8
+
9
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
10
+
11
+ <title>Low-Bandwidth Connect</title>
12
+
13
+ <script src="https://cdn.tailwindcss.com"></script>
14
+
15
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
16
+
17
+ <style>
18
+
19
+ .video-container {
20
+
21
+ position: relative;
22
+
23
+ width: 100%;
24
+
25
+ padding-bottom: 56.25%; /* 16:9 aspect ratio */
26
+
27
+ background-color: #1e293b;
28
+
29
+ border-radius: 0.5rem;
30
+
31
+ overflow: hidden;
32
+
33
+ }
34
+
35
+ .video-element {
36
+
37
+ position: absolute;
38
+
39
+ top: 0;
40
+
41
+ left: 0;
42
+
43
+ width: 100%;
44
+
45
+ height: 100%;
46
+
47
+ object-fit: cover;
48
+
49
+ }
50
+
51
+ .connection-quality {
52
+
53
+ position: absolute;
54
+
55
+ bottom: 10px;
56
+
57
+ right: 10px;
58
+
59
+ background-color: rgba(0,0,0,0.5);
60
+
61
+ color: white;
62
+
63
+ padding: 2px 6px;
64
+
65
+ border-radius: 4px;
66
+
67
+ font-size: 12px;
68
+
69
+ }
70
+
71
+ .bandwidth-optimizer {
72
+
73
+ transition: all 0.3s ease;
74
+
75
+ }
76
+
77
+ .bandwidth-optimizer:hover {
78
+
79
+ transform: scale(1.05);
80
+
81
+ }
82
+
83
+ .pulse {
84
+
85
+ animation: pulse 2s infinite;
86
+
87
+ }
88
+
89
+ @keyframes pulse {
90
+
91
+ 0% { box-shadow: 0 0 0 0 rgba(74, 222, 128, 0.7); }
92
+
93
+ 70% { box-shadow: 0 0 0 10px rgba(74, 222, 128, 0); }
94
+
95
+ 100% { box-shadow: 0 0 0 0 rgba(74, 222, 128, 0); }
96
+
97
+ }
98
+
99
+ </style>
100
+
101
+ </head>
102
+
103
+ <body class="bg-gray-900 text-gray-100 min-h-screen">
104
+
105
+ <div class="container mx-auto px-4 py-8 max-w-6xl">
106
+
107
+ <header class="flex justify-between items-center mb-8">
108
+
109
+ <div class="flex items-center">
110
+
111
+ <i class="fas fa-signal text-green-400 text-2xl mr-3"></i>
112
+
113
+ <h1 class="text-2xl font-bold bg-gradient-to-r from-green-400 to-blue-500 bg-clip-text text-transparent">
114
+
115
+ LowBand Connect
116
+
117
+ </h1>
118
+
119
+ </div>
120
+
121
+ <div class="flex items-center space-x-4">
122
+
123
+ <div class="hidden md:flex items-center space-x-2 text-sm">
124
+
125
+ <span class="text-gray-400">Optimized for</span>
126
+
127
+ <span class="px-2 py-1 bg-gray-800 rounded-full text-green-400 font-medium">
128
+
129
+ <i class="fas fa-wifi mr-1"></i> Low Bandwidth
130
+
131
+ </span>
132
+
133
+ </div>
134
+
135
+ <button id="settingsBtn" class="p-2 rounded-full hover:bg-gray-800 transition">
136
+
137
+ <i class="fas fa-cog text-gray-400"></i>
138
+
139
+ </button>
140
+
141
+ </div>
142
+
143
+ </header>
144
+
145
+
146
+ <main>
147
+
148
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
149
+
150
+ <!-- Local Video -->
151
+
152
+ <div class="video-container">
153
+
154
+ <video id="localVideo" class="video-element" autoplay muted></video>
155
+
156
+ <div class="connection-quality hidden">
157
+
158
+ <i class="fas fa-signal mr-1"></i>
159
+
160
+ <span>Local</span>
161
+
162
+ </div>
163
+
164
+ <div class="absolute top-2 left-2 bg-gray-900 bg-opacity-70 px-2 py-1 rounded text-sm">
165
+
166
+ You
167
+
168
+ </div>
169
+
170
+ </div>
171
+
172
+
173
+ <!-- Remote Video -->
174
+
175
+ <div class="video-container">
176
+
177
+ <video id="remoteVideo" class="video-element" autoplay></video>
178
+
179
+ <div class="connection-quality hidden">
180
+
181
+ <i class="fas fa-signal mr-1"></i>
182
+
183
+ <span>Remote</span>
184
+
185
+ </div>
186
+
187
+ <div class="absolute top-2 left-2 bg-gray-900 bg-opacity-70 px-2 py-1 rounded text-sm">
188
+
189
+ Partner
190
+
191
+ </div>
192
+
193
+ </div>
194
+
195
+ </div>
196
+
197
+
198
+ <div class="flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0">
199
+
200
+ <div class="flex items-center space-x-2">
201
+
202
+ <div class="text-sm bg-gray-800 px-3 py-1 rounded-full">
203
+
204
+ <span class="text-gray-400">Connection:</span>
205
+
206
+ <span id="connectionStatus" class="font-medium text-yellow-400">Disconnected</span>
207
+
208
+ </div>
209
+
210
+ <div id="bandwidthIndicator" class="text-sm bg-gray-800 px-3 py-1 rounded-full hidden">
211
+
212
+ <span class="text-gray-400">Bandwidth:</span>
213
+
214
+ <span id="bandwidthValue" class="font-medium">-- kbps</span>
215
+
216
+ </div>
217
+
218
+ </div>
219
+
220
+
221
+ <div class="flex space-x-3">
222
+
223
+ <button id="toggleVideoBtn" class="bg-gray-800 hover:bg-gray-700 text-white p-3 rounded-full transition">
224
+
225
+ <i class="fas fa-video"></i>
226
+
227
+ </button>
228
+
229
+ <button id="toggleAudioBtn" class="bg-gray-800 hover:bg-gray-700 text-white p-3 rounded-full transition">
230
+
231
+ <i class="fas fa-microphone"></i>
232
+
233
+ </button>
234
+
235
+ <button id="callBtn" class="bg-green-600 hover:bg-green-700 text-white px-6 py-3 rounded-full font-medium flex items-center pulse">
236
+
237
+ <i class="fas fa-phone mr-2"></i>
238
+
239
+ <span>Start Call</span>
240
+
241
+ </button>
242
+
243
+ <button id="endCallBtn" class="bg-red-600 hover:bg-red-700 text-white px-6 py-3 rounded-full font-medium flex items-center hidden">
244
+
245
+ <i class="fas fa-phone-slash mr-2"></i>
246
+
247
+ <span>End Call</span>
248
+
249
+ </button>
250
+
251
+ </div>
252
+
253
+ </div>
254
+
255
+
256
+ <!-- Bandwidth Optimizer Panel -->
257
+
258
+ <div id="optimizerPanel" class="mt-8 bg-gray-800 rounded-lg p-4 hidden">
259
+
260
+ <h3 class="text-lg font-medium mb-4 flex items-center">
261
+
262
+ <i class="fas fa-tachometer-alt mr-2 text-blue-400"></i>
263
+
264
+ Bandwidth Optimizer
265
+
266
+ </h3>
267
+
268
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
269
+
270
+ <div class="bandwidth-optimizer bg-gray-700 p-4 rounded-lg cursor-pointer" data-preset="low">
271
+
272
+ <div class="flex items-center mb-2">
273
+
274
+ <i class="fas fa-bicycle text-green-400 mr-2"></i>
275
+
276
+ <h4 class="font-medium">Low Bandwidth</h4>
277
+
278
+ </div>
279
+
280
+ <p class="text-sm text-gray-400">Optimized for slow connections (64-128 kbps)</p>
281
+
282
+ <div class="mt-3 text-xs text-gray-500">
283
+
284
+ <span>• 160x120 resolution</span><br>
285
+
286
+ <span>• 10fps</span><br>
287
+
288
+ <span>• Low bitrate</span>
289
+
290
+ </div>
291
+
292
+ </div>
293
+
294
+ <div class="bandwidth-optimizer bg-gray-700 p-4 rounded-lg cursor-pointer" data-preset="medium">
295
+
296
+ <div class="flex items-center mb-2">
297
+
298
+ <i class="fas fa-car text-yellow-400 mr-2"></i>
299
+
300
+ <h4 class="font-medium">Medium Bandwidth</h4>
301
+
302
+ </div>
303
+
304
+ <p class="text-sm text-gray-400">Balanced quality and bandwidth (128-256 kbps)</p>
305
+
306
+ <div class="mt-3 text-xs text-gray-500">
307
+
308
+ <span>• 320x240 resolution</span><br>
309
+
310
+ <span>• 15fps</span><br>
311
+
312
+ <span>• Medium bitrate</span>
313
+
314
+ </div>
315
+
316
+ </div>
317
+
318
+ <div class="bandwidth-optimizer bg-gray-700 p-4 rounded-lg cursor-pointer" data-preset="high">
319
+
320
+ <div class="flex items-center mb-2">
321
+
322
+ <i class="fas fa-rocket text-red-400 mr-2"></i>
323
+
324
+ <h4 class="font-medium">High Bandwidth</h4>
325
+
326
+ </div>
327
+
328
+ <p class="text-sm text-gray-400">For better connections (256+ kbps)</p>
329
+
330
+ <div class="mt-3 text-xs text-gray-500">
331
+
332
+ <span>• 640x480 resolution</span><br>
333
+
334
+ <span>• 24fps</span><br>
335
+
336
+ <span>• High bitrate</span>
337
+
338
+ </div>
339
+
340
+ </div>
341
+
342
+ </div>
343
+
344
+ </div>
345
+
346
+ </main>
347
+
348
+
349
+ <!-- Settings Modal -->
350
+
351
+ <div id="settingsModal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center hidden z-50">
352
+
353
+ <div class="bg-gray-800 rounded-lg p-6 w-full max-w-md">
354
+
355
+ <div class="flex justify-between items-center mb-4">
356
+
357
+ <h3 class="text-xl font-medium">
358
+
359
+ <i class="fas fa-cog mr-2 text-blue-400"></i>
360
+
361
+ Settings
362
+
363
+ </h3>
364
+
365
+ <button id="closeSettingsBtn" class="text-gray-400 hover:text-white">
366
+
367
+ <i class="fas fa-times"></i>
368
+
369
+ </button>
370
+
371
+ </div>
372
+
373
+ <div class="space-y-4">
374
+
375
+ <div>
376
+
377
+ <label class="block text-sm font-medium mb-1">Video Source</label>
378
+
379
+ <select id="videoSource" class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2 text-sm">
380
+
381
+ <option value="">Default Camera</option>
382
+
383
+ </select>
384
+
385
+ </div>
386
+
387
+ <div>
388
+
389
+ <label class="block text-sm font-medium mb-1">Audio Source</label>
390
+
391
+ <select id="audioSource" class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2 text-sm">
392
+
393
+ <option value="">Default Microphone</option>
394
+
395
+ </select>
396
+
397
+ </div>
398
+
399
+ <div>
400
+
401
+ <label class="flex items-center space-x-2">
402
+
403
+ <input type="checkbox" id="enableBandwidthDetection" class="rounded bg-gray-700 border-gray-600" checked>
404
+
405
+ <span class="text-sm">Auto-detect bandwidth</span>
406
+
407
+ </label>
408
+
409
+ </div>
410
+
411
+ <div>
412
+
413
+ <label class="block text-sm font-medium mb-1">Default Bandwidth</label>
414
+
415
+ <select id="defaultBandwidth" class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2 text-sm">
416
+
417
+ <option value="low">Low (64-128 kbps)</option>
418
+
419
+ <option value="medium" selected>Medium (128-256 kbps)</option>
420
+
421
+ <option value="high">High (256+ kbps)</option>
422
+
423
+ </select>
424
+
425
+ </div>
426
+
427
+ </div>
428
+
429
+ <div class="mt-6 flex justify-end space-x-3">
430
+
431
+ <button id="saveSettingsBtn" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded font-medium">
432
+
433
+ Save Settings
434
+
435
+ </button>
436
+
437
+ </div>
438
+
439
+ </div>
440
+
441
+ </div>
442
+
443
+
444
+ <!-- Connection Modal -->
445
+
446
+ <div id="connectionModal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center hidden z-50">
447
+
448
+ <div class="bg-gray-800 rounded-lg p-6 w-full max-w-md text-center">
449
+
450
+ <div class="mb-4">
451
+
452
+ <i class="fas fa-link text-blue-400 text-5xl mb-3"></i>
453
+
454
+ <h3 class="text-xl font-medium mb-2">Establishing Connection</h3>
455
+
456
+ <p class="text-gray-400 text-sm">Optimizing for low bandwidth...</p>
457
+
458
+ </div>
459
+
460
+ <div class="w-full bg-gray-700 rounded-full h-2 mb-4">
461
+
462
+ <div id="connectionProgress" class="bg-blue-500 h-2 rounded-full" style="width: 0%"></div>
463
+
464
+ </div>
465
+
466
+ <button id="cancelConnectionBtn" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded font-medium">
467
+
468
+ Cancel
469
+
470
+ </button>
471
+
472
+ </div>
473
+
474
+ </div>
475
+
476
+ </div>
477
+
478
+
479
+ <script>
480
+
481
+ // DOM Elements
482
+
483
+ const localVideo = document.getElementById('localVideo');
484
+
485
+ const remoteVideo = document.getElementById('remoteVideo');
486
+
487
+ const callBtn = document.getElementById('callBtn');
488
+
489
+ const endCallBtn = document.getElementById('endCallBtn');
490
+
491
+ const toggleVideoBtn = document.getElementById('toggleVideoBtn');
492
+
493
+ const toggleAudioBtn = document.getElementById('toggleAudioBtn');
494
+
495
+ const connectionStatus = document.getElementById('connectionStatus');
496
+
497
+ const bandwidthIndicator = document.getElementById('bandwidthIndicator');
498
+
499
+ const bandwidthValue = document.getElementById('bandwidthValue');
500
+
501
+ const optimizerPanel = document.getElementById('optimizerPanel');
502
+
503
+ const settingsBtn = document.getElementById('settingsBtn');
504
+
505
+ const settingsModal = document.getElementById('settingsModal');
506
+
507
+ const closeSettingsBtn = document.getElementById('closeSettingsBtn');
508
+
509
+ const saveSettingsBtn = document.getElementById('saveSettingsBtn');
510
+
511
+ const connectionModal = document.getElementById('connectionModal');
512
+
513
+ const connectionProgress = document.getElementById('connectionProgress');
514
+
515
+ const cancelConnectionBtn = document.getElementById('cancelConnectionBtn');
516
+
517
+ // State variables
518
+
519
+ let localStream;
520
+
521
+ let peerConnection;
522
+
523
+ let isCallActive = false;
524
+
525
+ let isVideoEnabled = true;
526
+
527
+ let isAudioEnabled = true;
528
+
529
+ let currentBandwidthPreset = 'medium';
530
+
531
+ // Initialize the app
532
+
533
+ async function init() {
534
+
535
+ try {
536
+
537
+ // Get media devices
538
+
539
+ await getMediaDevices();
540
+
541
+ // Set up event listeners
542
+
543
+ setupEventListeners();
544
+
545
+ // Show bandwidth optimizer panel
546
+
547
+ optimizerPanel.classList.remove('hidden');
548
+
549
+ // Set default bandwidth preset
550
+
551
+ applyBandwidthPreset(currentBandwidthPreset);
552
+
553
+ } catch (error) {
554
+
555
+ console.error('Initialization error:', error);
556
+
557
+ }
558
+
559
+ }
560
+
561
+ // Set up event listeners
562
+
563
+ function setupEventListeners() {
564
+
565
+ // Call buttons
566
+
567
+ callBtn.addEventListener('click', startCall);
568
+
569
+ endCallBtn.addEventListener('click', endCall);
570
+
571
+ // Toggle buttons
572
+
573
+ toggleVideoBtn.addEventListener('click', toggleVideo);
574
+
575
+ toggleAudioBtn.addEventListener('click', toggleAudio);
576
+
577
+ // Bandwidth optimizers
578
+
579
+ document.querySelectorAll('.bandwidth-optimizer').forEach(optimizer => {
580
+
581
+ optimizer.addEventListener('click', () => {
582
+
583
+ const preset = optimizer.getAttribute('data-preset');
584
+
585
+ applyBandwidthPreset(preset);
586
+
587
+ });
588
+
589
+ });
590
+
591
+ // Settings
592
+
593
+ settingsBtn.addEventListener('click', () => settingsModal.classList.remove('hidden'));
594
+
595
+ closeSettingsBtn.addEventListener('click', () => settingsModal.classList.add('hidden'));
596
+
597
+ saveSettingsBtn.addEventListener('click', saveSettings);
598
+
599
+ // Connection modal
600
+
601
+ cancelConnectionBtn.addEventListener('click', cancelConnection);
602
+
603
+ }
604
+
605
+ // Get media devices
606
+
607
+ async function getMediaDevices() {
608
+
609
+ try {
610
+
611
+ localStream = await navigator.mediaDevices.getUserMedia({
612
+
613
+ video: true,
614
+
615
+ audio: true
616
+
617
+ });
618
+
619
+ localVideo.srcObject = localStream;
620
+
621
+ // Populate device selectors
622
+
623
+ const devices = await navigator.mediaDevices.enumerateDevices();
624
+
625
+ const videoSource = document.getElementById('videoSource');
626
+
627
+ const audioSource = document.getElementById('audioSource');
628
+
629
+ devices.forEach(device => {
630
+
631
+ if (device.kind === 'videoinput') {
632
+
633
+ const option = document.createElement('option');
634
+
635
+ option.value = device.deviceId;
636
+
637
+ option.text = device.label || `Camera ${videoSource.length + 1}`;
638
+
639
+ videoSource.appendChild(option);
640
+
641
+ } else if (device.kind === 'audioinput') {
642
+
643
+ const option = document.createElement('option');
644
+
645
+ option.value = device.deviceId;
646
+
647
+ option.text = device.label || `Microphone ${audioSource.length + 1}`;
648
+
649
+ audioSource.appendChild(option);
650
+
651
+ }
652
+
653
+ });
654
+
655
+ } catch (error) {
656
+
657
+ console.error('Error accessing media devices:', error);
658
+
659
+ alert('Could not access camera or microphone. Please check permissions.');
660
+
661
+ }
662
+
663
+ }
664
+
665
+ // Start a call
666
+
667
+ async function startCall() {
668
+
669
+ if (!localStream) {
670
+
671
+ alert('Please allow camera and microphone access first.');
672
+
673
+ return;
674
+
675
+ }
676
+
677
+ // Show connection modal
678
+
679
+ connectionModal.classList.remove('hidden');
680
+
681
+ simulateConnectionProgress();
682
+
683
+ try {
684
+
685
+ // Create peer connection
686
+
687
+ const configuration = {
688
+
689
+ iceServers: [
690
+
691
+ { urls: 'stun:stun.l.google.com:19302' },
692
+
693
+ // Add your TURN server here for NAT traversal
694
+
695
+ ]
696
+
697
+ };
698
+
699
+ peerConnection = new RTCPeerConnection(configuration);
700
+
701
+ // Set up event handlers
702
+
703
+ peerConnection.onicecandidate = handleICECandidateEvent;
704
+
705
+ peerConnection.oniceconnectionstatechange = handleICEConnectionStateChangeEvent;
706
+
707
+ peerConnection.onicegatheringstatechange = handleICEGatheringStateChangeEvent;
708
+
709
+ peerConnection.onsignalingstatechange = handleSignalingStateChangeEvent;
710
+
711
+ peerConnection.ontrack = handleTrackEvent;
712
+
713
+ // Add local stream tracks
714
+
715
+ localStream.getTracks().forEach(track => {
716
+
717
+ peerConnection.addTrack(track, localStream);
718
+
719
+ });
720
+
721
+ // Create offer
722
+
723
+ const offer = await peerConnection.createOffer({
724
+
725
+ offerToReceiveAudio: true,
726
+
727
+ offerToReceiveVideo: true
728
+
729
+ });
730
+
731
+ await peerConnection.setLocalDescription(offer);
732
+
733
+ // In a real app, you would send the offer to the other peer via signaling
734
+
735
+ // For this demo, we'll simulate the connection
736
+
737
+ setTimeout(() => {
738
+
739
+ // Simulate receiving an answer
740
+
741
+ simulateAnswer();
742
+
743
+ // Update UI
744
+
745
+ connectionStatus.textContent = 'Connected';
746
+
747
+ connectionStatus.className = 'font-medium text-green-400';
748
+
749
+ // Show bandwidth indicator
750
+
751
+ bandwidthIndicator.classList.remove('hidden');
752
+
753
+ updateBandwidthDisplay();
754
+
755
+ // Toggle call buttons
756
+
757
+ callBtn.classList.add('hidden');
758
+
759
+ endCallBtn.classList.remove('hidden');
760
+
761
+ // Hide connection modal
762
+
763
+ connectionModal.classList.add('hidden');
764
+
765
+ isCallActive = true;
766
+
767
+ }, 2000);
768
+
769
+ } catch (error) {
770
+
771
+ console.error('Error starting call:', error);
772
+
773
+ connectionModal.classList.add('hidden');
774
+
775
+ alert('Failed to start call. Please try again.');
776
+
777
+ }
778
+
779
+ }
780
+
781
+ // Simulate connection progress
782
+
783
+ function simulateConnectionProgress() {
784
+
785
+ let progress = 0;
786
+
787
+ const interval = setInterval(() => {
788
+
789
+ progress += 5;
790
+
791
+ connectionProgress.style.width = `${progress}%`;
792
+
793
+ if (progress >= 100) {
794
+
795
+ clearInterval(interval);
796
+
797
+ }
798
+
799
+ }, 200);
800
+
801
+ }
802
+
803
+ // Simulate receiving an answer (for demo purposes)
804
+
805
+ function simulateAnswer() {
806
+
807
+ if (!peerConnection) return;
808
+
809
+ // In a real app, you would receive this from the other peer
810
+
811
+ const answer = {
812
+
813
+ type: 'answer',
814
+
815
+ sdp: `v=0
816
+
817
+ o=- 123456789 2 IN IP4 127.0.0.1
818
+
819
+ s=-
820
+
821
+ t=0 0
822
+
823
+ a=group:BUNDLE 0 1
824
+
825
+ a=msid-semantic: WMS
826
+
827
+ m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
828
+
829
+ c=IN IP4 0.0.0.0
830
+
831
+ a=rtcp:9 IN IP4 0.0.0.0
832
+
833
+ a=ice-ufrag:xyz
834
+
835
+ a=ice-pwd:abc
836
+
837
+ a=fingerprint:sha-256 AA:BB:CC
838
+
839
+ a=setup:active
840
+
841
+ a=mid:0
842
+
843
+ a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
844
+
845
+ a=sendrecv
846
+
847
+ a=rtpmap:111 opus/48000/2
848
+
849
+ a=fmtp:111 minptime=10;useinbandfec=1
850
+
851
+ a=rtpmap:103 ISAC/16000
852
+
853
+ a=rtpmap:104 ISAC/32000
854
+
855
+ a=rtpmap:9 G722/8000
856
+
857
+ a=rtpmap:0 PCMU/8000
858
+
859
+ a=rtpmap:8 PCMA/8000
860
+
861
+ a=rtpmap:106 CN/32000
862
+
863
+ a=rtpmap:105 CN/16000
864
+
865
+ a=rtpmap:13 CN/8000
866
+
867
+ a=rtpmap:110 telephone-event/48000
868
+
869
+ a=rtpmap:112 telephone-event/32000
870
+
871
+ a=rtpmap:113 telephone-event/16000
872
+
873
+ a=rtpmap:126 telephone-event/8000
874
+
875
+ a=maxptime:60
876
+
877
+ a=ssrc:12345678 cname:audio
878
+
879
+ m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102
880
+
881
+ c=IN IP4 0.0.0.0
882
+
883
+ a=rtcp:9 IN IP4 0.0.0.0
884
+
885
+ a=ice-ufrag:xyz
886
+
887
+ a=ice-pwd:abc
888
+
889
+ a=fingerprint:sha-256 AA:BB:CC
890
+
891
+ a=setup:active
892
+
893
+ a=mid:1
894
+
895
+ a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
896
+
897
+ a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
898
+
899
+ a=extmap:4 urn:3gpp:video-orientation
900
+
901
+ a=sendrecv
902
+
903
+ a=rtpmap:96 VP8/90000
904
+
905
+ a=rtcp-fb:96 goog-remb
906
+
907
+ a=rtcp-fb:96 transport-cc
908
+
909
+ a=rtcp-fb:96 ccm fir
910
+
911
+ a=rtcp-fb:96 nack
912
+
913
+ a=rtcp-fb:96 nack pli
914
+
915
+ a=rtpmap:97 rtx/90000
916
+
917
+ a=fmtp:97 apt=96
918
+
919
+ a=rtpmap:98 VP9/90000
920
+
921
+ a=rtcp-fb:98 goog-remb
922
+
923
+ a=rtcp-fb:98 transport-cc
924
+
925
+ a=rtcp-fb:98 ccm fir
926
+
927
+ a=rtcp-fb:98 nack
928
+
929
+ a=rtcp-fb:98 nack pli
930
+
931
+ a=rtpmap:99 rtx/90000
932
+
933
+ a=fmtp:99 apt=98
934
+
935
+ a=rtpmap:100 H264/90000
936
+
937
+ a=rtcp-fb:100 goog-remb
938
+
939
+ a=rtcp-fb:100 transport-cc
940
+
941
+ a=rtcp-fb:100 ccm fir
942
+
943
+ a=rtcp-fb:100 nack
944
+
945
+ a=rtcp-fb:100 nack pli
946
+
947
+ a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
948
+
949
+ a=rtpmap:101 rtx/90000
950
+
951
+ a=fmtp:101 apt=100
952
+
953
+ a=rtpmap:102 H264/90000
954
+
955
+ a=rtcp-fb:102 goog-remb
956
+
957
+ a=rtcp-fb:102 transport-cc
958
+
959
+ a=rtcp-fb:102 ccm fir
960
+
961
+ a=rtcp-fb:102 nack
962
+
963
+ a=rtcp-fb:102 nack pli
964
+
965
+ a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
966
+
967
+ a=ssrc-group:FID 12345678 12345679
968
+
969
+ a=ssrc:12345678 cname:video
970
+
971
+ a=ssrc:12345679 cname:video`
972
+
973
+ };
974
+
975
+ peerConnection.setRemoteDescription(new RTCSessionDescription(answer));
976
+
977
+ }
978
+
979
+ // End the call
980
+
981
+ function endCall() {
982
+
983
+ if (peerConnection) {
984
+
985
+ peerConnection.close();
986
+
987
+ peerConnection = null;
988
+
989
+ }
990
+
991
+ if (remoteVideo.srcObject) {
992
+
993
+ remoteVideo.srcObject.getTracks().forEach(track => track.stop());
994
+
995
+ remoteVideo.srcObject = null;
996
+
997
+ }
998
+
999
+ // Update UI
1000
+
1001
+ connectionStatus.textContent = 'Disconnected';
1002
+
1003
+ connectionStatus.className = 'font-medium text-yellow-400';
1004
+
1005
+ // Hide bandwidth indicator
1006
+
1007
+ bandwidthIndicator.classList.add('hidden');
1008
+
1009
+ // Toggle call buttons
1010
+
1011
+ callBtn.classList.remove('hidden');
1012
+
1013
+ endCallBtn.classList.add('hidden');
1014
+
1015
+ isCallActive = false;
1016
+
1017
+ }
1018
+
1019
+ // Cancel connection attempt
1020
+
1021
+ function cancelConnection() {
1022
+
1023
+ if (peerConnection) {
1024
+
1025
+ peerConnection.close();
1026
+
1027
+ peerConnection = null;
1028
+
1029
+ }
1030
+
1031
+ connectionModal.classList.add('hidden');
1032
+
1033
+ }
1034
+
1035
+ // Toggle video
1036
+
1037
+ function toggleVideo() {
1038
+
1039
+ if (!localStream) return;
1040
+
1041
+ const videoTrack = localStream.getVideoTracks()[0];
1042
+
1043
+ if (videoTrack) {
1044
+
1045
+ isVideoEnabled = !videoTrack.enabled;
1046
+
1047
+ videoTrack.enabled = isVideoEnabled;
1048
+
1049
+ toggleVideoBtn.innerHTML = isVideoEnabled ? '<i class="fas fa-video"></i>' : '<i class="fas fa-video-slash"></i>';
1050
+
1051
+ toggleVideoBtn.classList.toggle('bg-gray-800');
1052
+
1053
+ toggleVideoBtn.classList.toggle('bg-red-600');
1054
+
1055
+ // Update connection if active
1056
+
1057
+ if (isCallActive) {
1058
+
1059
+ updateBandwidthSettings();
1060
+
1061
+ }
1062
+
1063
+ }
1064
+
1065
+ }
1066
+
1067
+ // Toggle audio
1068
+
1069
+ function toggleAudio() {
1070
+
1071
+ if (!localStream) return;
1072
+
1073
+ const audioTrack = localStream.getAudioTracks()[0];
1074
+
1075
+ if (audioTrack) {
1076
+
1077
+ isAudioEnabled = !audioTrack.enabled;
1078
+
1079
+ audioTrack.enabled = isAudioEnabled;
1080
+
1081
+ toggleAudioBtn.innerHTML = isAudioEnabled ? '<i class="fas fa-microphone"></i>' : '<i class="fas fa-microphone-slash"></i>';
1082
+
1083
+ toggleAudioBtn.classList.toggle('bg-gray-800');
1084
+
1085
+ toggleAudioBtn.classList.toggle('bg-red-600');
1086
+
1087
+ }
1088
+
1089
+ }
1090
+
1091
+ // Apply bandwidth preset
1092
+
1093
+ function applyBandwidthPreset(preset) {
1094
+
1095
+ currentBandwidthPreset = preset;
1096
+
1097
+ // Update UI
1098
+
1099
+ document.querySelectorAll('.bandwidth-optimizer').forEach(opt => {
1100
+
1101
+ opt.classList.remove('border-2', 'border-green-400');
1102
+
1103
+ if (opt.getAttribute('data-preset') === preset) {
1104
+
1105
+ opt.classList.add('border-2', 'border-green-400');
1106
+
1107
+ }
1108
+
1109
+ });
1110
+
1111
+ // Update connection if active
1112
+
1113
+ if (isCallActive) {
1114
+
1115
+ updateBandwidthSettings();
1116
+
1117
+ }
1118
+
1119
+ // Update bandwidth display
1120
+
1121
+ updateBandwidthDisplay();
1122
+
1123
+ }
1124
+
1125
+ // Update bandwidth settings for the connection
1126
+
1127
+ function updateBandwidthSettings() {
1128
+
1129
+ if (!peerConnection || !isCallActive) return;
1130
+
1131
+ const senders = peerConnection.getSenders();
1132
+
1133
+ senders.forEach(sender => {
1134
+
1135
+ if (sender.track.kind === 'video') {
1136
+
1137
+ const parameters = sender.getParameters();
1138
+
1139
+ if (!parameters.encodings) {
1140
+
1141
+ parameters.encodings = [{}];
1142
+
1143
+ }
1144
+
1145
+ // Apply bandwidth constraints based on preset
1146
+
1147
+ switch (currentBandwidthPreset) {
1148
+
1149
+ case 'low':
1150
+
1151
+ parameters.encodings[0].maxBitrate = 128000; // 128 kbps
1152
+
1153
+ break;
1154
+
1155
+ case 'medium':
1156
+
1157
+ parameters.encodings[0].maxBitrate = 256000; // 256 kbps
1158
+
1159
+ break;
1160
+
1161
+ case 'high':
1162
+
1163
+ parameters.encodings[0].maxBitrate = 512000; // 512 kbps
1164
+
1165
+ break;
1166
+
1167
+ }
1168
+
1169
+ // Apply resolution scaling based on preset
1170
+
1171
+ if (sender.track.kind === 'video') {
1172
+
1173
+ const constraints = {};
1174
+
1175
+ switch (currentBandwidthPreset) {
1176
+
1177
+ case 'low':
1178
+
1179
+ constraints.width = { ideal: 160 };
1180
+
1181
+ constraints.height = { ideal: 120 };
1182
+
1183
+ constraints.frameRate = { ideal: 10 };
1184
+
1185
+ break;
1186
+
1187
+ case 'medium':
1188
+
1189
+ constraints.width = { ideal: 320 };
1190
+
1191
+ constraints.height = { ideal: 240 };
1192
+
1193
+ constraints.frameRate = { ideal: 15 };
1194
+
1195
+ break;
1196
+
1197
+ case 'high':
1198
+
1199
+ constraints.width = { ideal: 640 };
1200
+
1201
+ constraints.height = { ideal: 480 };
1202
+
1203
+ constraints.frameRate = { ideal: 24 };
1204
+
1205
+ break;
1206
+
1207
+ }
1208
+
1209
+ sender.track.applyConstraints(constraints);
1210
+
1211
+ }
1212
+
1213
+ sender.setParameters(parameters);
1214
+
1215
+ }
1216
+
1217
+ });
1218
+
1219
+ updateBandwidthDisplay();
1220
+
1221
+ }
1222
+
1223
+ // Update bandwidth display
1224
+
1225
+ function updateBandwidthDisplay() {
1226
+
1227
+ let bandwidthText = '';
1228
+
1229
+ switch (currentBandwidthPreset) {
1230
+
1231
+ case 'low':
1232
+
1233
+ bandwidthText = '64-128 kbps';
1234
+
1235
+ break;
1236
+
1237
+ case 'medium':
1238
+
1239
+ bandwidthText = '128-256 kbps';
1240
+
1241
+ break;
1242
+
1243
+ case 'high':
1244
+
1245
+ bandwidthText = '256-512 kbps';
1246
+
1247
+ break;
1248
+
1249
+ }
1250
+
1251
+ bandwidthValue.textContent = bandwidthText;
1252
+
1253
+ }
1254
+
1255
+ // Save settings
1256
+
1257
+ function saveSettings() {
1258
+
1259
+ const videoSource = document.getElementById('videoSource').value;
1260
+
1261
+ const audioSource = document.getElementById('audioSource').value;
1262
+
1263
+ const enableBandwidthDetection = document.getElementById('enableBandwidthDetection').checked;
1264
+
1265
+ const defaultBandwidth = document.getElementById('defaultBandwidth').value;
1266
+
1267
+ // In a real app, you would save these settings to localStorage or a server
1268
+
1269
+ console.log('Settings saved:', {
1270
+
1271
+ videoSource,
1272
+
1273
+ audioSource,
1274
+
1275
+ enableBandwidthDetection,
1276
+
1277
+ defaultBandwidth
1278
+
1279
+ });
1280
+
1281
+ // Apply default bandwidth if changed
1282
+
1283
+ if (defaultBandwidth !== currentBandwidthPreset) {
1284
+
1285
+ applyBandwidthPreset(defaultBandwidth);
1286
+
1287
+ }
1288
+
1289
+ // Close settings modal
1290
+
1291
+ settingsModal.classList.add('hidden');
1292
+
1293
+ alert('Settings saved successfully!');
1294
+
1295
+ }
1296
+
1297
+ // WebRTC event handlers
1298
+
1299
+ function handleICECandidateEvent(event) {
1300
+
1301
+ if (event.candidate) {
1302
+
1303
+ // In a real app, you would send the candidate to the other peer
1304
+
1305
+ console.log('ICE candidate:', event.candidate);
1306
+
1307
+ }
1308
+
1309
+ }
1310
+
1311
+ function handleICEConnectionStateChangeEvent() {
1312
+
1313
+ if (peerConnection) {
1314
+
1315
+ console.log('ICE connection state:', peerConnection.iceConnectionState);
1316
+
1317
+ }
1318
+
1319
+ }
1320
+
1321
+ function handleICEGatheringStateChangeEvent() {
1322
+
1323
+ if (peerConnection) {
1324
+
1325
+ console.log('ICE gathering state:', peerConnection.iceGatheringState);
1326
+
1327
+ }
1328
+
1329
+ }
1330
+
1331
+ function handleSignalingStateChangeEvent() {
1332
+
1333
+ if (peerConnection) {
1334
+
1335
+ console.log('Signaling state:', peerConnection.signalingState);
1336
+
1337
+ }
1338
+
1339
+ }
1340
+
1341
+ function handleTrackEvent(event) {
1342
+
1343
+ if (event.streams && event.streams[0]) {
1344
+
1345
+ remoteVideo.srcObject = event.streams[0];
1346
+
1347
+ }
1348
+
1349
+ }