feat: use proper metered stuff

This commit is contained in:
2025-11-01 01:47:12 +01:00
parent 0f3ca69ffc
commit 2aa36b433b
4 changed files with 71 additions and 40 deletions

View File

@@ -1,6 +1,24 @@
# Nuxt Minimal Starter
# Helium
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
Effortless screensharing powered by WebRTC.
## Prerequisites
### TURN Server Setup
To enable connections through restrictive NATs, you need a TURN server. We use [Metered's Open Relay](https://www.metered.ca/tools/openrelay/) which provides 50GB/month free.
1. Sign up for a free account at https://dashboard.metered.ca/signup
2. Create a new app (you'll get an app name like `yourapp.metered.live`)
3. Copy your API key from the dashboard
4. Fill in your credentials in `.env`:
```bash
METERED_APP_NAME=yourapp
METERED_API_KEY=your_api_key_here
REDIS_URL=redis://localhost:6379
```
## Setup

View File

@@ -20,26 +20,20 @@ const { send } = useWebSocket(wsUrl, {
toast.success('stream joined successfully')
}
if (message.event === 'offer') {
let iceServers: RTCIceServer[] = [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
];
try {
const turnCredentials = await $fetch('/api/turn/credentials');
iceServers = turnCredentials as RTCIceServer[];
} catch (error) {
console.warn('Failed to fetch TURN credentials, using STUN only:', error);
}
const peerConnection = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
{
urls: 'turn:openrelay.metered.ca:80',
username: 'openrelayproject',
credential: 'openrelayproject',
},
{
urls: 'turn:openrelay.metered.ca:443',
username: 'openrelayproject',
credential: 'openrelayproject',
},
{
urls: 'turn:openrelay.metered.ca:443?transport=tcp',
username: 'openrelayproject',
credential: 'openrelayproject',
},
],
iceServers,
iceCandidatePoolSize: 10
});
viewerStore.setPeerConnection(peerConnection);

View File

@@ -23,26 +23,20 @@ const { send } = useWebSocket(wsUrl, {
}
if (message.event === 'viewer-joined') {
let iceServers: RTCIceServer[] = [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
];
try {
const turnCredentials = await $fetch('/api/turn/credentials');
iceServers = turnCredentials as RTCIceServer[];
} catch (error) {
console.warn('Failed to fetch TURN credentials, using STUN only:', error);
}
const peerConnection = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
{
urls: 'turn:openrelay.metered.ca:80',
username: 'openrelayproject',
credential: 'openrelayproject',
},
{
urls: 'turn:openrelay.metered.ca:443',
username: 'openrelayproject',
credential: 'openrelayproject',
},
{
urls: 'turn:openrelay.metered.ca:443?transport=tcp',
username: 'openrelayproject',
credential: 'openrelayproject',
},
],
iceServers,
iceCandidatePoolSize: 10
});
streamerStore.addPeerConnection(message.viewerId, peerConnection)

View File

@@ -0,0 +1,25 @@
export default defineEventHandler(async (event) => {
const apiKey = process.env.METERED_API_KEY || '';
const appName = process.env.METERED_APP_NAME || '';
if (!apiKey || !appName) {
throw createError({
statusCode: 500,
message: 'TURN server not configured.'
});
}
try {
const response = await $fetch(
`https://${appName}.metered.live/api/v1/turn/credentials?apiKey=${apiKey}`
);
return response;
} catch (error) {
console.error('Failed to fetch TURN credentials:', error);
throw createError({
statusCode: 500,
message: 'Failed to fetch TURN credentials'
});
}
});