Live Demo: https://glimpse-vc.netlify.app/
Developer Portfolio: https://aryansrao.github.io
Terms and conditions https://glimpse-vc.netlify.app/terms.html
- Overview
- Features
- Technology Stack
- Project Structure
- How It Works
- User Interface
- Configuration
- Technical Reference
- Browser Compatibility
- Usage Guide
- Contributing
- Acknowledgments
Glimpse is a real-time video chat application that connects strangers through peer-to-peer WebRTC connections. Built with modern web technologies and PeerJS signaling, the application provides instant video/audio streaming with text chat capabilities.
The platform operates entirely through browser-based peer-to-peer connections. No video, audio, or text data passes through any server—all communications happen directly between users. The lobby-based matching system ensures quick connections while maintaining simplicity.
Core Principles:
- Peer-to-peer architecture
- No user accounts required
- Instant connections
- Browser-based functionality
- Modern, accessible design
- Instant Random Matching: Automatic pairing with strangers worldwide through intelligent lobby system
- WebRTC Video Streaming: High-quality peer-to-peer video with adaptive bitrate
- Real-Time Audio: Audio streaming with echo cancellation
- Real-Time Text Chat: Peer-to-peer messaging via WebRTC data channels
- Next Button: Skip to next stranger instantly with one click
- Video Controls: Toggle camera on/off during conversation
- Audio Controls: Mute/unmute microphone as needed
- Start/Stop: Begin or end chat sessions
- Next Person: Disconnect and find new stranger
- Video Toggle: Enable/disable camera feed
- Audio Toggle: Mute/unmute microphone
- Chat Panel: Slide-in text chat interface
- Connection Status: Real-time status indicators
- Slide-In Panel: Non-intrusive chat interface from right side
- Real-Time Messaging: Instant message delivery through WebRTC
- Message Bubbles: Clean sent/received message distinction
- Auto-Scroll: Automatic scroll to newest messages
- Enter to Send: Quick message sending with Enter key
- Glassmorphic UI: Modern frosted glass effects with backdrop blur
- Pure Black Theme: OLED-friendly true black background
- Geist Font: Clean, professional typography from Google Fonts
- Smooth Animations: Fluid transitions and interactive feedback
- Responsive Layout: Optimized for desktop, tablet, and mobile
- Floating Controls: Pill-shaped control bar with blur effects
- Status Indicators: Real-time connection status with animated dots
- Peer-to-Peer: Direct browser-to-browser connections via WebRTC
- STUN Servers: Google STUN servers for NAT traversal
- Lobby Slots: Fixed 20-slot system for efficient matching
- Connection Retry: Automatic reconnection on failures
- Browser-Based: No apps or downloads required
- Single Page Application: Complete functionality in one HTML file
- HTML5: Semantic markup with modern structure
- CSS3: Custom properties, Flexbox, Grid, animations
- Vanilla JavaScript: Pure ES6+ with async/await patterns
-
PeerJS v1.5.2: WebRTC wrapper for simplified peer connections
- Automatic signaling via PeerJS cloud server
- Connection management and error handling
- Data channel abstraction
-
WebRTC: Core peer-to-peer communication
- getUserMedia for camera/microphone access
- RTCPeerConnection for media streaming
- RTCDataChannel for text messaging
-
STUN Servers: NAT traversal with Google STUN
- stun.l.google.com:19302
- stun1.l.google.com:19302
- stun2.l.google.com:19302
- stun3.l.google.com:19302
- Geist Font Family: Modern sans-serif from Google Fonts
- Weights: 400 (Regular), 500 (Medium), 600 (SemiBold), 700 (Bold)
- Optimized with preconnect for performance
- Letter spacing: -0.03em for modern look
Glimpse/
│
├── index.html # Complete application
│ ├── SEO meta tags (comprehensive)
│ ├── Open Graph & Twitter cards
│ ├── JSON-LD structured data
│ ├── Start screen with creator credit
│ ├── Video grid (local + remote)
│ ├── Slide-in chat panel
│ ├── Floating control buttons
│ └── Status indicators
│
├── Embedded Styles # Internal CSS
│ ├── CSS custom properties
│ ├── Glassmorphic effects
│ ├── Grid layouts
│ ├── Component styles
│ ├── Animations
│ ├── Responsive breakpoints
│ └── Mobile optimizations
│
├── Embedded Scripts # Internal JavaScript
│ ├── PeerJS initialization
│ ├── WebRTC connection logic
│ ├── Lobby slot management
│ ├── Video/audio controls
│ ├── Chat functionality
│ ├── Event handlers
│ └── State management
│
└── External Dependencies
├── PeerJS v1.5.2 (CDN)
└── Geist font (Google Fonts)
Glimpse uses a lobby-based peer-to-peer architecture with PeerJS as the signaling layer:
- Page Load: Browser loads single HTML file with embedded styles/scripts
- Camera Access: Request getUserMedia permissions for video/audio
- PeerJS Setup: Initialize peer with unique lobby slot ID
- Lobby Entry: Occupy one of 20 fixed lobby slots
- Matching Loop: Cycle through other slots attempting connections
- Connection: First successful handshake establishes call
- Data Channel: Open RTCDataChannel for text chat
- Ready State: Video/audio streams and chat functional
┌─────────────┐
│ Idle │ ──Start──> ┌──────────────┐
└─────────────┘ │ Initializing│
└──────┬───────┘
│
┌──────▼───────┐
│ Searching │ <──┐
└──────┬───────┘ │
│ │
┌──────▼───────┐ │
│ Connected │ │ Next
└──────┬───────┘ │
│ │
┌──────▼───────┐ │
│ Disconnected │ ───┘
└──────────────┘
Fixed Slot Architecture:
- 20 Lobby Slots:
glimpse-0throughglimpse-19 - Random Assignment: Each user occupies one random slot
- Sequential Search: Cycle through all other slots (1 per second)
- Collision Handling: If slot taken, try next slot (modulo wraparound)
Connection Process:
// User A occupies slot 5
myPeerId = "glimpse-5"
// User A searches slots: 0,1,2,3,4,6,7,8,9...19,0,1... (skip 5)
// User B occupies slot 12
// When User A tries slot 12, connection initiates
// User A calls User B
peer.call("glimpse-12", localStream)
// User B answers
call.answer(localStream)
// Connection established with bidirectional video/audioAdvantages:
- Simple and predictable
- No external database required
- Quick matching (maximum 20 seconds)
- No race conditions with proper state management
Detailed Connection Steps:
-
Peer A (Caller):
const call = peer.call("glimpse-12", localStream) // Sends offer SDP through PeerJS signaling
-
PeerJS Signaling:
- Peer A → PeerJS Server → Peer B (offer)
- Peer B → PeerJS Server → Peer A (answer)
-
ICE Candidates Exchange:
- Both peers gather ICE candidates
- Exchange via PeerJS signaling
- Establish connection through NAT/firewall
-
STUN Server Role:
- Discover public IP addresses
- Determine NAT type
- Enable direct peer connection
-
Media Stream:
call.on('stream', (remoteStream) => { remoteVideo.srcObject = remoteStream // Video/audio now flowing peer-to-peer })
-
Data Channel (for chat):
const conn = peer.connect("glimpse-12") conn.on('open', () => { conn.send("Hello!") // Text message })
Connection Types:
- Video/Audio: RTCPeerConnection media streams
- Text Chat: RTCDataChannel (reliable, ordered)
Chat Implementation:
// Sender
dataConnection.send("Hello stranger!")
// Receiver
dataConnection.on('data', (message) => {
addMessage(message, 'received')
})Message Flow:
- User types message and hits Send/Enter
- Message sent via RTCDataChannel (peer-to-peer)
- Receiver gets 'data' event instantly
- Message rendered in chat panel
Reliability:
- Ordered delivery (SCTP protocol)
- Automatic retransmission
- Binary or text data support
- Large gradient title "Glimpse"
- Tagline: "Connect with strangers worldwide"
- Primary CTA button
- Creator credit with link to portfolio
- Fade-in animation
- Desktop: 2-column side-by-side (You | Stranger)
- Mobile: 2-row vertical stack (You / Stranger)
object-fit: containfor proper aspect ratio- Black background for letterboxing
- Video labels in bottom-left corner
- Floating at top with semi-transparent background
- Logo: "Glimpse" (left)
- Status indicator: dot + text (right)
- Gray dot + "Searching" (disconnected)
- Green dot + "Connected" (active call)
- Slides in from right side (desktop and mobile)
- Header with title and close button
- Scrollable message area
- Sent messages (right, blue-tinted bubbles)
- Received messages (left, white-tinted bubbles)
- Input field + Send button at bottom
- Pill-shaped floating container at bottom center
- Glassmorphic background with blur
- 5 buttons in row:
- Video toggle (camera icon)
- Audio toggle (mic icon)
- Chat toggle (message icon)
- Next person (arrow icon, blue accent)
- End call (X icon, red accent)
/* Primary Colors */
--primary-bg: #000000 /* Pure black background */
--glass-bg: rgba(0,0,0,0.6) /* Glassmorphic overlay */
--glass-border: rgba(255,255,255,0.1) /* Subtle borders */
/* Text Colors */
--text-primary: #ffffff /* Main text */
--text-secondary: #6b7280 /* Muted text */
/* Accent Colors */
--accent-blue: #3b82f6 /* Primary actions */
--accent-green: #10b981 /* Success/connected */
--accent-red: #ef4444 /* Danger/end call */
/* Status Colors */
--status-searching: #6b7280 /* Gray for searching */
--status-connected: #10b981 /* Green for connected */| Breakpoint | Width | Layout Changes |
|---|---|---|
| Desktop | 1024px+ | 2-column video, chat 380px width |
| Tablet | 769-1023px | 2-column video, chat 90% width |
| Mobile | ≤768px | Vertical video stack, chat 90% width |
| Small Mobile | ≤480px | Reduced text sizes, compact controls |
Mobile Optimizations:
- Header padding reduced (16px vs 24px)
- Control buttons: 48px on mobile (vs 52px)
- Video grid switches to vertical
- Chat panel full-width overlay
- Font sizes scale down
- Touch-friendly targets (44px minimum)
Glassmorphism:
backdrop-filter: blur(20px);
background: rgba(0,0,0,0.6);
border: 1px solid rgba(255,255,255,0.1);Status Dot Pulse:
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}Chat Panel Slide:
transform: translateX(100%); /* Hidden */
transform: translateX(0); /* Visible */
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);Button Hover:
.control-btn:hover {
background: rgba(255,255,255,0.1);
transform: translateY(-2px);
}Modify the number of lobby slots in the JavaScript section:
const LOBBY_SLOTS = 20; // Default: 20 slots
// More slots = longer search time
// Fewer slots = higher collision chance
// Recommended: 10-30 slotsChange the lobby ID prefix:
const LOBBY_PREFIX = 'glimpse-'; // Default prefix
// Example alternatives:
// 'mychat-'
// 'random-'
// 'vc-'Change how often the app tries new slots:
searchInterval = setInterval(() => {
// Connection attempts
}, 1000); // Default: 1000ms (1 second)
// Lower = faster matching but more load
// Higher = slower matching but more efficientEnhance NAT traversal with additional STUN servers:
peer = new Peer(peerId, {
config: {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
{ urls: 'stun:stun2.l.google.com:19302' },
{ urls: 'stun:stun3.l.google.com:19302' },
// Add more STUN servers
{ urls: 'stun:stun.stunprotocol.org:3478' },
{ urls: 'stun:stun.voip.eutelia.it:3478' }
]
}
});Adjust video constraints:
localStream = await navigator.mediaDevices.getUserMedia({
video: {
width: 1280, // Default: 1280
height: 720 // Default: 720
// Options:
// { width: 1920, height: 1080 } // Full HD
// { width: 640, height: 480 } // Lower quality
// { width: { ideal: 1280 }, height: { ideal: 720 } } // Flexible
},
audio: true
});Edit CSS custom properties:
:root {
--primary-bg: #000000;
--accent-blue: #3b82f6;
--accent-green: #10b981;
--accent-red: #ef4444;
--glass-bg: rgba(0,0,0,0.6);
--glass-border: rgba(255,255,255,0.1);
}Change chat panel dimensions:
.chat-panel {
width: 380px; /* Desktop default */
}
@media (max-width: 768px) {
.chat-panel {
width: 90%; /* Mobile default */
max-width: 360px;
}
}Initializes camera/microphone and starts the matching process.
async startChat() {
// Request media permissions
localStream = await navigator.mediaDevices.getUserMedia({
video: { width: 1280, height: 720 },
audio: true
});
// Display local video
document.getElementById('localVideo').srcObject = localStream;
// Initialize peer and start searching
initializePeer();
}Creates PeerJS instance with lobby slot ID and sets up event handlers.
function initializePeer() {
const peerId = LOBBY_PREFIX + myLobbySlot;
peer = new Peer(peerId, { config: { iceServers: [...] } });
peer.on('open', (id) => { startSearching(); });
peer.on('call', (call) => { handleIncomingCall(call); });
peer.on('connection', (conn) => { setupDataConnection(conn); });
peer.on('error', (error) => { handlePeerError(error); });
}Cycles through lobby slots attempting connections.
function startSearching() {
searchInterval = setInterval(() => {
const slotToTry = currentSlotIndex % LOBBY_SLOTS;
if (slotToTry !== myLobbySlot) {
const call = peer.call(LOBBY_PREFIX + slotToTry, localStream);
// Handle stream reception
}
currentSlotIndex++;
}, 1000);
}Processes incoming call and establishes connection.
function handleIncomingCall(call) {
call.answer(localStream); // Answer with our stream
call.on('stream', (remoteStream) => {
displayRemoteStream(remoteStream);
});
}Establishes text chat data channel.
function setupDataConnection(conn) {
dataConnection = conn;
conn.on('open', () => { console.log('Chat ready'); });
conn.on('data', (message) => { addMessage(message, 'received'); });
}Controls media track enable/disable state.
function toggleVideo() {
isVideoEnabled = !isVideoEnabled;
localStream.getVideoTracks()[0].enabled = isVideoEnabled;
// Update button icon
}Disconnects current call and resumes searching.
function nextPerson() {
handleDisconnect(); // Clean up current connection
// startSearching() called automatically after cleanup
}Sends text message through data channel.
function sendMessage() {
const text = chatInput.value.trim();
if (text && dataConnection && dataConnection.open) {
dataConnection.send(text);
addMessage(text, 'sent');
}
}| Event | Trigger | Handler |
|---|---|---|
peer.on('open') |
Peer initialized | Start searching |
peer.on('call') |
Incoming call | Answer and establish stream |
peer.on('connection') |
Incoming data conn | Setup chat channel |
peer.on('error') |
Peer error | Retry or slot change |
call.on('stream') |
Remote stream | Display video |
call.on('close') |
Call ended | Disconnect and search |
conn.on('data') |
Chat message | Display message |
let peer; // PeerJS instance
let localStream; // Local media stream
let currentCall; // Active call object
let dataConnection; // Chat data channel
let myLobbySlot; // Current lobby slot (0-19)
let isConnected; // Connection status
let isSearching; // Searching status
let isVideoEnabled; // Camera state
let isAudioEnabled; // Mic state| Browser | Version | getUserMedia | WebRTC | PeerJS | Notes |
|---|---|---|---|---|---|
| Chrome | 87+ | Yes | Yes | Yes | Full support |
| Firefox | 78+ | Yes | Yes | Yes | Full support |
| Safari | 14.1+ | Yes | Yes | Yes | iOS 14.5+ |
| Edge | 87+ | Yes | Yes | Yes | Chromium-based |
| Opera | 73+ | Yes | Yes | Yes | Full support |
| Feature | Requirement | Fallback |
|---|---|---|
| Camera/Mic | HTTPS or localhost | None (required) |
| WebRTC | Modern browser | None (required) |
| Data Channels | WebRTC support | Chat disabled |
| Geist Font | Internet connection | System sans-serif |
getUserMedia API requires HTTPS except:
http://localhosthttp://127.0.0.1http://[::1]
Production deployments must use HTTPS for camera/microphone access.
// Check WebRTC support
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
alert('Your browser does not support camera access');
}
// Check RTCPeerConnection
if (!window.RTCPeerConnection) {
alert('Your browser does not support WebRTC');
}-
Open Application:
- Visit https://glimpse-vc.netlify.app/
- Click "Start Chatting" button
-
Grant Permissions:
- Allow camera access when prompted
- Allow microphone access when prompted
- Denying permissions will prevent video chat functionality
-
Wait for Match:
- Status shows "Searching"
- Gray pulsing dot indicates searching
- Usually connects within 5-20 seconds
-
Start Chatting:
- Status changes to "Connected" (green dot)
- Your video appears on left (or top on mobile)
- Stranger's video appears on right (or bottom)
- Chat button available in controls
Video Toggle (Camera Icon):
- Click to disable your camera
- Icon changes to "camera off" when disabled
- Stranger can no longer see you
- Click again to re-enable
Audio Toggle (Microphone Icon):
- Click to mute your microphone
- Icon changes to "mic off" when muted
- Stranger can no longer hear you
- Click again to unmute
Chat Toggle (Message Icon):
- Opens slide-in chat panel from right
- Type message and press Enter or click Send
- Messages appear in real-time
- Click X or button again to close
Next Person (Arrow Icon):
- Disconnect from current stranger
- Immediately search for new person
- Chat history cleared
- Enabled only when connected
End Chat (X Icon):
- Completely stop video chat
- Return to start screen
- Camera and microphone released
- All connections closed
Sending Messages:
- Click chat button to open panel
- Type message in input field
- Press Enter or click Send
- Message appears on right (blue bubble)
Receiving Messages:
- Messages appear on left (white bubble)
- Auto-scrolls to newest message
- Real-time delivery (instant)
"Camera/Microphone not accessible":
- Check browser permissions
- Ensure HTTPS connection
- Verify camera not in use by another app
- Try different browser
"No one found" (long search time):
- Few users currently online
- Try again later
- Check internet connection
- Refresh page and retry
"Connection lost":
- Network interruption
- Other user disconnected
- Click Next to find new person
- Check internet stability
Video not showing:
- Check camera permissions
- Ensure camera not blocked
- Try toggling video on/off
- Refresh page
No audio:
- Check microphone permissions
- Verify microphone not muted in OS
- Check browser audio settings
- Try toggling audio on/off
Contributions are welcome for technical improvements and bug fixes.
Report Bugs:
- Check existing issues
- Create new issue with:
- Browser and OS details
- Steps to reproduce
- Expected vs actual behavior
- Console errors or screenshots
Suggest Features:
- Open issue with clear description
- Explain use case and benefits
- Consider technical feasibility
Submit Pull Requests:
- Fork repository
- Create feature branch:
git checkout -b feature/FeatureName - Make changes following existing code style
- Test across multiple browsers
- Commit:
git commit -m 'Add FeatureName' - Push:
git push origin feature/FeatureName - Open Pull Request with detailed description
Code Style:
- Use ES6+ JavaScript features
- Meaningful variable and function names
- Comment complex logic
- Follow existing formatting
Testing Checklist:
- Test on Chrome, Firefox, Safari
- Test on desktop and mobile devices
- Test video/audio functionality
- Test chat messaging
- Test all control buttons
- Verify responsive design
- Check console for errors
Features:
- Screen sharing capability
- Connection quality indicators
- Better mobile optimization
- Improved error handling
Performance:
- Connection optimization
- Reduced search time
- Better NAT traversal
UI/UX:
- Additional animations
- Accessibility improvements
- Theme customization options
- PeerJS - Simplified WebRTC wrapper for peer-to-peer connections
- WebRTC - Real-time communication protocol for browser-to-browser media streaming
- Google STUN Servers - Network traversal for establishing peer connections
- Google Fonts - Geist font family for typography
- Netlify - Platform hosting and HTTPS deployment
Aryan S Rao
- Portfolio: https://aryansrao.github.io
- GitHub: @aryansrao
- Portfolio: aryansrao.github.io
Glimpse - Connect face-to-face with the world.
