Live Streaming Implementation - nself-org/nchat GitHub Wiki
Comprehensive live streaming system for nself-chat v0.4.0 featuring:
- WebRTC broadcast ingest
- HLS adaptive streaming distribution
- Low latency (<5 seconds)
- Interactive features (chat, reactions, Q&A)
- Stream recording and replay
- Scheduled streams
- Analytics and quality metrics
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Live Streaming Flow β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Broadcaster Media Server Viewers
β β β
ββ WebRTC Stream ββββββββββ>β β
β (Camera + Mic) β β
β β β
β βββββββΌβββββββ β
β β Transcoder β β
β β (FFmpeg) β β
β βββββββ¬βββββββ β
β β β
β βββββββΌβββββββ β
β β HLS Packagerβ β
β β (Multiple β β
β β Qualities) β β
β βββββββ¬βββββββ β
β β β
β βββββββΌβββββββ β
β β HLS Manifestβ β
β β + Segments β β
β βββββββ¬βββββββ β
β β β
β ββ HLS Stream ββββββββ>β
β β (Adaptive) β
β β β
ββ Chat/Reactions βββββββββββΌββββββββββββββββββββ>β
β (Socket.io) β (Socket.io) β
β β β
ββ Quality Metrics βββββββββ>β β
β (Every 5s) β β
β β β
β<β Viewer Count ββββββββββββΌβββββββββββββββββββββββ€
β (Real-time) β β
-
nchat_streams - Main stream table
- Stream details (title, description, thumbnail)
- Scheduling (scheduled_at, started_at, ended_at)
- Status (scheduled, preparing, live, ended, cancelled)
- Configuration (quality, bitrate, FPS)
- Statistics (peak viewers, total views, messages, reactions)
-
nchat_stream_viewers - Viewer tracking
- Session management
- Watch time tracking
- Quality selection
- Engagement metrics
-
nchat_stream_quality_metrics - Real-time metrics
- Viewer count
- Bitrate, FPS, resolution
- Network stats (latency, packet loss)
- Health score
-
nchat_stream_chat_messages - Live chat
- Message content
- Pinning support
- Moderation (deletion)
-
nchat_stream_reactions - Emoji reactions
- Emoji with position
- Real-time delivery
-- Run migration
cd .backend
nself db migrate up 016_live_streaming.sql- TypeScript interfaces for all streaming entities
- Enums for status, quality levels, chat modes
- Error classes
- HLS.js wrapper for video playback
- Adaptive bitrate streaming
- Quality level selection
- Low-latency mode support
- Statistics monitoring
- WebRTC broadcaster client
- Camera/microphone access
- Device switching
- Quality adjustment
- Metrics reporting
- High-level API for stream CRUD
- Stream lifecycle (create, start, end)
- Query operations (live, scheduled, past)
- Viewer management
- Event tracking (joins, leaves, chat, reactions)
- Buffering monitoring
- Quality metrics collection
- Engagement calculations
- Bandwidth estimation (EWMA, sliding window)
- Level selection algorithms
- Buffer-based ABR
- Quality switching logic
import { useLiveStream } from '@/hooks/use-live-stream'
function BroadcasterComponent() {
const {
stream,
isStarting,
isBroadcasting,
localStream,
viewerCount,
duration,
createStream,
startBroadcast,
stopBroadcast,
endStream,
toggleVideo,
toggleAudio,
} = useLiveStream({
onStreamStarted: (stream) => console.log('Live!', stream),
onStreamEnded: (stream) => console.log('Ended', stream),
})
const handleGoLive = async () => {
// Create stream
const newStream = await createStream({
channelId: 'channel-id',
title: 'My Live Stream',
description: 'Streaming now!',
})
// Start broadcasting at 720p
await startBroadcast('720p')
}
return (
<div>
{localStream && (
<video
ref={(el) => {
if (el) el.srcObject = localStream
}}
autoPlay
muted
/>
)}
<div>Viewers: {viewerCount}</div>
<div>Duration: {formatDuration(duration)}</div>
<button onClick={handleGoLive}>Go Live</button>
<button onClick={endStream}>End Stream</button>
</div>
)
}import { useStreamViewer } from '@/hooks/use-stream-viewer'
function ViewerComponent({ streamId }: { streamId: string }) {
const {
stream,
isLoading,
isPlaying,
currentQuality,
viewerCount,
videoRef,
play,
pause,
setQuality,
} = useStreamViewer({
streamId,
autoStart: true,
lowLatencyMode: true,
})
return (
<div>
<video ref={videoRef} controls />
<div>Viewers: {viewerCount}</div>
<div>Quality: {currentQuality}</div>
<button onClick={() => setQuality('1080p')}>1080p</button>
<button onClick={() => setQuality('720p')}>720p</button>
<button onClick={() => setQuality('auto')}>Auto</button>
</div>
)
}import { useStreamChat } from '@/hooks/use-stream-chat'
function StreamChatComponent({ streamId }: { streamId: string }) {
const {
messages,
isSending,
sendMessage,
pinMessage,
} = useStreamChat({ streamId })
const [input, setInput] = useState('')
const handleSend = async () => {
await sendMessage(input)
setInput('')
}
return (
<div>
<div className="messages">
{messages.map((msg) => (
<div key={msg.id} className={msg.isPinned ? 'pinned' : ''}>
<strong>{msg.user?.displayName}:</strong> {msg.content}
{!msg.isPinned && (
<button onClick={() => pinMessage(msg.id)}>Pin</button>
)}
</div>
))}
</div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Send a message..."
maxLength={500}
/>
<button onClick={handleSend} disabled={isSending}>
Send
</button>
</div>
)
}import { useStreamReactions } from '@/hooks/use-stream-reactions'
function StreamReactionsComponent({ streamId }: { streamId: string }) {
const { recentReactions, sendReaction } = useStreamReactions({
streamId,
onNewReaction: (reaction) => console.log('New reaction!', reaction),
})
const emojis = ['π', 'β€οΈ', 'π', 'π₯', 'π']
return (
<div>
{/* Emoji picker */}
<div className="emoji-picker">
{emojis.map((emoji) => (
<button key={emoji} onClick={() => sendReaction(emoji)}>
{emoji}
</button>
))}
</div>
{/* Animated reactions */}
<div className="reactions-overlay">
{recentReactions.map((reaction) => (
<div
key={reaction.id}
className="reaction-bubble animate-float-up"
style={{
left: `${reaction.positionX ?? 50}%`,
bottom: 0,
}}
>
{reaction.emoji}
</div>
))}
</div>
</div>
)
}POST /api/streams/create
Body: { channelId, title, description, scheduledAt?, ... }
POST /api/streams/:id/start
POST /api/streams/:id/end
GET /api/streams/:id
GET /api/streams/live?channelId=...
GET /api/streams/:id/hls
Response: { manifestUrl: "https://..." }
GET /api/streams/:id/viewers
Response: { count: 42 }
GET /api/streams/:id/chat # Get messages
POST /api/streams/:id/chat # Send message
DELETE /api/streams/:id/chat/:msgId # Delete message
POST /api/streams/:id/chat/:msgId/pin # Pin message
POST /api/streams/:id/reactions
Body: { emoji, positionX?, positionY? }
Full broadcaster UI with:
- Preview/Live video display
- Go Live button
- Camera/microphone selection
- Quality settings
- Viewer count
- Duration counter
- Chat integration
- End stream button
Viewer interface with:
- HLS video player
- Quality selector
- Volume controls
- Live indicator
- Viewer count
- Latency display
- Chat panel
- Reaction buttons
Schedule streams for future:
- Date/time picker
- Channel selection
- Stream details
- Notification settings
Configure stream:
- Title and description
- Thumbnail upload
- Quality presets (1080p, 720p, 480p, 360p)
- Bitrate limits
- Enable/disable chat
- Enable/disable reactions
- Chat mode (open, followers, subscribers)
# Install Ant Media Server
wget https://github.com/ant-media/Ant-Media-Server/releases/download/ams-v2.X.X/ant-media-server-2.X.X.zip
unzip ant-media-server-2.X.X.zip
cd ant-media-server
./start.sh
# Configure
# - WebRTC to RTMP Bridge
# - HLS packaging with adaptive bitrate
# - Enable low-latency mode# nginx.conf
rtmp {
server {
listen 1935;
application live {
live on;
record off;
# HLS
hls on;
hls_path /tmp/hls;
hls_fragment 2s;
hls_playlist_length 10s;
# Transcoding
exec ffmpeg -i rtmp://localhost/live/$name
-c:v libx264 -preset veryfast
-b:v 3000k -maxrate 3000k -bufsize 6000k -s 1920x1080 -r 30 -g 60 -f flv rtmp://localhost/hls/$name_1080p
-c:v libx264 -preset veryfast
-b:v 1500k -maxrate 1500k -bufsize 3000k -s 1280x720 -r 30 -g 60 -f flv rtmp://localhost/hls/$name_720p
-c:v libx264 -preset veryfast
-b:v 800k -maxrate 800k -bufsize 1600k -s 854x480 -r 24 -g 48 -f flv rtmp://localhost/hls/$name_480p
-c:v libx264 -preset veryfast
-b:v 400k -maxrate 400k -bufsize 800k -s 640x360 -r 24 -g 48 -f flv rtmp://localhost/hls/$name_360p;
}
}
}
http {
server {
listen 8080;
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /tmp;
add_header Cache-Control no-cache;
add_header Access-Control-Allow-Origin *;
}
}
}# Install LiveKit
docker run --rm -p 7880:7880 -p 7881:7881 -p 7882:7882/udp \
-v $PWD/livekit.yaml:/livekit.yaml \
livekit/livekit-server --config /livekit.yaml# .env.local
NEXT_PUBLIC_STREAM_INGEST_URL=rtmp://localhost:1935/live
NEXT_PUBLIC_HLS_BASE_URL=https://your-cdn.com/hls
STREAM_RECORDING_ENABLED=true
STREAM_RECORDING_PATH=/var/recordings-
stream:start- Stream went live -
stream:end- Stream ended -
stream:quality-update- Quality metrics
-
stream:viewer-joined- Viewer joined -
stream:viewer-left- Viewer left -
stream:viewer-count- Viewer count update
-
stream:chat-message- New chat message -
stream:chat-deleted- Message deleted -
stream:chat-pinned- Message pinned
-
stream:reaction- Emoji reaction sent
curl -X POST http://localhost:3000/api/streams/create \
-H "Content-Type: application/json" \
-d '{
"channelId": "channel-uuid",
"title": "Test Stream",
"description": "Testing live streaming"
}'- Open OBS Studio
- Settings β Stream
- Service: Custom
- Server:
rtmp://localhost:1935/live - Stream Key: [your-stream-key]
- Start Streaming
<video id="video" controls></video>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script>
const video = document.getElementById('video')
const hls = new Hls()
hls.loadSource('https://your-server/hls/stream.m3u8')
hls.attachMedia(video)
</script>- Fragment Size: 2-3 seconds for low latency
- Playlist Length: 6-10 seconds
- Max Buffer: 30 seconds
- Target Latency: 3-5 seconds
- Use hardware acceleration (NVENC, Quick Sync, VA-API)
- Preset: veryfast for real-time
- GOP size: 2x framerate for faster seeking
- Distribute HLS segments via CDN
- Edge caching for manifest and segments
- Geographic distribution
- Index on
statusandchannel_idfor live stream queries - Partition
stream_quality_metricsby month - Archive old streams to cold storage
- Peak concurrent viewers
- Average watch time
- Buffering ratio
- Chat engagement rate
- Reaction rate
- Quality switching frequency
- Real-time viewer count
- Stream health (bitrate, FPS, dropped frames)
- Geographic distribution
- Device/browser breakdown
- Network quality distribution
- Multi-bitrate Recording - Record at multiple qualities
- VOD Processing - Automatic highlights, chapters
- Stream Rewind - DVR functionality
- Clips Creation - User-generated clips
- Simulcast - Stream to multiple platforms (YouTube, Twitch)
- Interactive Polls - Live audience polls
- Guest Speakers - Multi-broadcaster streams
- Monetization - Paid streams, donations, subscriptions
- Moderation Tools - Auto-mod, slow mode, emote-only
- Analytics Export - CSV/PDF reports
- Check WebRTC permissions
- Verify stream key
- Check media server logs
- Test network connectivity
- Enable low-latency mode in HLS player
- Reduce buffer size
- Check network bandwidth
- Use LL-HLS if supported
- Lower quality level
- Check upload bandwidth (broadcaster)
- Check download bandwidth (viewer)
- Verify CDN performance
- Check Socket.io connection
- Verify authentication
- Check RLS policies in database
- Monitor message rate limits
For issues or questions:
- Check logs:
.backend/logs/ - Review Hasura console for GraphQL errors
- Check Socket.io connection in browser dev tools
- Review database RLS policies
See main project LICENSE file.