diff --git a/README.md b/README.md index 25b5821..be200ee 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/app/pages/index.vue b/app/pages/index.vue index 991d837..3da47dd 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -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); diff --git a/app/pages/stream.vue b/app/pages/stream.vue index da308f7..06e015b 100644 --- a/app/pages/stream.vue +++ b/app/pages/stream.vue @@ -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) diff --git a/server/routes/api/turn/credentials.ts b/server/routes/api/turn/credentials.ts new file mode 100644 index 0000000..68675ed --- /dev/null +++ b/server/routes/api/turn/credentials.ts @@ -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' + }); + } +});