mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-03-21 18:11:27 -07:00
update skills
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
# Patterns & Use Cases
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Client (WebRTC) <---> CF Edge <---> Backend (HTTP)
|
||||
|
|
||||
CF Backbone (310+ DCs)
|
||||
|
|
||||
Other Edges <---> Other Clients
|
||||
```
|
||||
|
||||
Anycast: Last-mile <50ms (95%), no region select, NACK shield, distributed consensus
|
||||
|
||||
Cascading trees auto-scale to millions:
|
||||
```
|
||||
Publisher -> Edge A -> Edge B -> Sub1
|
||||
\-> Edge C -> Sub2,3
|
||||
```
|
||||
|
||||
## Use Cases
|
||||
|
||||
**1:1:** A creates session+publishes, B creates+subscribes to A+publishes, A subscribes to B
|
||||
**N:N:** All create session+publish, backend broadcasts track IDs, all subscribe to others
|
||||
**1:N:** Publisher creates+publishes, viewers each create+subscribe (no fan-out limit)
|
||||
**Breakout:** Same PeerConnection! Backend closes/adds tracks, no recreation
|
||||
|
||||
## PartyTracks (Recommended)
|
||||
|
||||
Observable-based client with automatic device/network handling:
|
||||
|
||||
```typescript
|
||||
import {PartyTracks} from 'partytracks';
|
||||
|
||||
// Create client
|
||||
const pt = new PartyTracks({
|
||||
apiUrl: '/api/calls',
|
||||
sessionId: 'my-session',
|
||||
onTrack: (track, peer) => {
|
||||
const video = document.getElementById(`video-${peer.id}`) as HTMLVideoElement;
|
||||
video.srcObject = new MediaStream([track]);
|
||||
}
|
||||
});
|
||||
|
||||
// Publish camera (push API)
|
||||
const camera = await pt.getCamera(); // Auto-requests permissions, handles device changes
|
||||
await pt.publishTrack(camera, {trackName: 'my-camera'});
|
||||
|
||||
// Subscribe to remote track (pull API)
|
||||
await pt.subscribeToTrack({trackName: 'remote-camera', sessionId: 'other-session'});
|
||||
|
||||
// React hook example
|
||||
import {useObservableAsValue} from 'observable-hooks';
|
||||
|
||||
function VideoCall() {
|
||||
const localTracks = useObservableAsValue(pt.localTracks$);
|
||||
const remoteTracks = useObservableAsValue(pt.remoteTracks$);
|
||||
|
||||
return <div>{/* Render tracks */}</div>;
|
||||
}
|
||||
|
||||
// Screenshare
|
||||
const screen = await pt.getScreenshare();
|
||||
await pt.publishTrack(screen, {trackName: 'my-screen'});
|
||||
|
||||
// Handle device changes (automatic)
|
||||
// PartyTracks detects device changes (e.g., Bluetooth headset) and renegotiates
|
||||
```
|
||||
|
||||
## Backend
|
||||
|
||||
Express:
|
||||
```js
|
||||
app.post('/api/new-session', async (req, res) => {
|
||||
const r = await fetch(`${CALLS_API}/apps/${process.env.CALLS_APP_ID}/sessions/new`,
|
||||
{method: 'POST', headers: {'Authorization': `Bearer ${process.env.CALLS_APP_SECRET}`}});
|
||||
res.json(await r.json());
|
||||
});
|
||||
```
|
||||
|
||||
Workers: Same pattern, use `env.CALLS_APP_ID` and `env.CALLS_APP_SECRET`
|
||||
|
||||
DO Presence: See configuration.md for boilerplate
|
||||
|
||||
## Audio Level Detection
|
||||
|
||||
```typescript
|
||||
// Attach analyzer to audio track
|
||||
function attachAudioLevelDetector(track: MediaStreamTrack) {
|
||||
const ctx = new AudioContext();
|
||||
const analyzer = ctx.createAnalyser();
|
||||
const src = ctx.createMediaStreamSource(new MediaStream([track]));
|
||||
src.connect(analyzer);
|
||||
|
||||
const data = new Uint8Array(analyzer.frequencyBinCount);
|
||||
const checkLevel = () => {
|
||||
analyzer.getByteFrequencyData(data);
|
||||
const level = data.reduce((a, b) => a + b) / data.length;
|
||||
if (level > 30) console.log('Speaking:', level); // Trigger UI update
|
||||
requestAnimationFrame(checkLevel);
|
||||
};
|
||||
checkLevel();
|
||||
}
|
||||
```
|
||||
|
||||
## Connection Quality Monitoring
|
||||
|
||||
```typescript
|
||||
pc.getStats().then(stats => {
|
||||
stats.forEach(report => {
|
||||
if (report.type === 'inbound-rtp' && report.kind === 'video') {
|
||||
const {packetsLost, packetsReceived, jitter} = report;
|
||||
const lossRate = packetsLost / (packetsLost + packetsReceived);
|
||||
if (lossRate > 0.05) console.warn('High packet loss:', lossRate);
|
||||
if (jitter > 100) console.warn('High jitter:', jitter);
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Stage Management (Limit Visible Participants)
|
||||
|
||||
```typescript
|
||||
// Subscribe to top 6 active speakers only
|
||||
let activeSubscriptions = new Set<string>();
|
||||
|
||||
function updateStage(topSpeakers: string[]) {
|
||||
const toAdd = topSpeakers.filter(id => !activeSubscriptions.has(id)).slice(0, 6);
|
||||
const toRemove = [...activeSubscriptions].filter(id => !topSpeakers.includes(id));
|
||||
|
||||
toRemove.forEach(id => {
|
||||
pc.getSenders().find(s => s.track?.id === id)?.track?.stop();
|
||||
activeSubscriptions.delete(id);
|
||||
});
|
||||
|
||||
toAdd.forEach(async id => {
|
||||
await fetch(`/api/subscribe`, {method: 'POST', body: JSON.stringify({trackId: id})});
|
||||
activeSubscriptions.add(id);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced
|
||||
|
||||
Bandwidth mgmt:
|
||||
```ts
|
||||
const s = pc.getSenders().find(s => s.track?.kind === 'video');
|
||||
const p = s.getParameters();
|
||||
if (!p.encodings) p.encodings = [{}];
|
||||
p.encodings[0].maxBitrate = 1200000; p.encodings[0].maxFramerate = 24;
|
||||
await s.setParameters(p);
|
||||
```
|
||||
|
||||
Simulcast (CF auto-forwards best layer):
|
||||
```ts
|
||||
pc.addTransceiver('video', {direction: 'sendonly', sendEncodings: [
|
||||
{rid: 'high', maxBitrate: 1200000},
|
||||
{rid: 'med', maxBitrate: 600000, scaleResolutionDownBy: 2},
|
||||
{rid: 'low', maxBitrate: 200000, scaleResolutionDownBy: 4}
|
||||
]});
|
||||
```
|
||||
|
||||
DataChannel:
|
||||
```ts
|
||||
const dc = pc.createDataChannel('chat', {ordered: true, maxRetransmits: 3});
|
||||
dc.onopen = () => dc.send(JSON.stringify({type: 'chat', text: 'Hi'}));
|
||||
dc.onmessage = (e) => console.log('RX:', JSON.parse(e.data));
|
||||
```
|
||||
|
||||
**WHIP/WHEP:** For streaming interop (OBS → SFU, SFU → video players), use WHIP (ingest) and WHEP (egress) protocols. See Cloudflare Stream integration docs.
|
||||
|
||||
Integrations: R2 for recording `env.R2_BUCKET.put(...)`, Queues for analytics
|
||||
|
||||
Perf: 100-250ms connect, ~50ms latency (95%), 200-400ms glass-to-glass, no participant limit (client: 10-50 tracks)
|
||||
Reference in New Issue
Block a user