Skip to content

dixydo/meetapp

Repository files navigation

meetapp

Drop-in WebRTC video calls for any web app. One iframe, one npm install, or one React/Vue hook — whichever fits your stack. Self-hosted, open source, runs as a single Docker container.

Release Build License

npm meetapp-sdk npm meetapp-react npm meetapp-vue

Live demo →    Examples →    Integration guide →    Deploy your own →


Add a video call to your site in 30 seconds

Copy any of these and paste into your page. They all talk to our hosted demo server at meet.dokaz.online so you can try without deploying.

1. As an iframe

<iframe
  src="https://meet.dokaz.online/embed?room=hello-world&autoJoin=1"
  allow="camera; microphone; display-capture"
  width="100%" height="600"
  style="border:0; border-radius:8px"
></iframe>

That's it. Open in a browser. Open the same page on another device. Done.

2. Vanilla JS

<video id="me" muted autoplay playsinline style="width:300px"></video>
<div id="remotes" style="display:flex; gap:8px"></div>

<script src="https://meet.dokaz.online/sdk/meetapp-sdk.umd.cjs"></script>
<script>
  const room = new Meetapp.MeetappRoom({
    serverUrl: 'https://meet.dokaz.online',
    roomId: 'hello-world',
  })
  room.on('trackPublished', ({ peerId, stream }) => {
    const v = document.createElement('video')
    v.autoplay = true; v.playsInline = true; v.srcObject = stream
    document.getElementById('remotes').appendChild(v)
  })
  await room.join()
  room.attachLocalVideo(document.getElementById('me'))
</script>

3. React

npm install meetapp-react meetapp-sdk
import { useMeetapp, MeetappVideo } from 'meetapp-react'

export function Call() {
  const { localStream, peers } = useMeetapp({
    serverUrl: 'https://meet.dokaz.online',
    roomId: 'hello-world',
    autoJoin: true,
  })
  return (
    <>
      <MeetappVideo stream={localStream} muted />
      {[...peers].map(([id, p]) => (
        <MeetappVideo key={id} stream={p.stream} />
      ))}
    </>
  )
}

4. Vue 3

npm install meetapp-vue meetapp-sdk vue
<script setup>
import { useMeetapp, MeetappVideo } from 'meetapp-vue'
const { localStream, peers } = useMeetapp({
  serverUrl: 'https://meet.dokaz.online',
  roomId: 'hello-world',
  autoJoin: true,
})
</script>
<template>
  <MeetappVideo :stream="localStream" muted />
  <MeetappVideo v-for="[id, p] in peers" :key="id" :stream="p.stream" />
</template>

Runnable examples for each of these (npm scripts, full project scaffolding) live in examples/ — clone and npm install && npm run dev.

Full integration cookbook with recipes (chat, recording, screen share, device picker, common patterns): docs/INTEGRATION.md.

Once you outgrow the demo server — self-host

The demo at meet.dokaz.online is a real instance. Use it for prototypes and demos, but for production you'll want your own. One Docker compose file, one domain, five minutes:

# On a fresh VPS (Ubuntu 22.04+)
curl -fsSL https://get.docker.com | sudo sh
mkdir -p ~/meetapp && cd ~/meetapp

curl -O https://raw.githubusercontent.com/dixydo/meetapp/main/docker-compose.yml
curl -O https://raw.githubusercontent.com/dixydo/meetapp/main/Caddyfile
curl -o .env https://raw.githubusercontent.com/dixydo/meetapp/main/.env.example

nano .env            # set DOMAIN, MEETAPP_TURN_*, optionally MEETAPP_S3_*

docker compose pull && docker compose up -d

DNS, TLS, firewall, TURN, S3 recording: docs/DEPLOY.md.

What's inside

  • SFU on Pion v4. Each client uploads its tracks once; server fans them out. 4+ participants don't melt your users' laptops.
  • Embedded TURN. Runs as a goroutine inside the same Go binary — most setups don't need a separate coturn.
  • Auto-TLS. Bundled Caddy fetches Let's Encrypt certs on first hit.
  • Server-side recording. Records each track as IVF/Ogg, muxes via ffmpeg into one MP4, uploads to DigitalOcean Spaces / any S3-compatible.
  • No accounts. Anyone with a room URL joins. JWT room tokens are on the roadmap.
  • One binary, one ~140 MB Docker image. Ships with ffmpeg.

Architecture deep dive: docs/ARCHITECTURE.md.

How does it work

                                ┌─────────────────────────────────┐
                                │            Browser              │
                                │  (Nuxt SPA OR meetapp-sdk)      │
                                └───────────┬─────────────────────┘
                                            │
                       HTTPS + WSS          │   WebRTC (SRTP)
                            ┌───────────────┼──────────────────────┐
                            │               │                      │
                            ▼               ▼                      ▼
                      ┌──────────┐   ┌──────────────┐      ┌──────────────┐
                      │ Caddy 80 │   │  Go binary   │      │  Go binary   │
                      │   /443   │──▶│  HTTP+WS+SFU │      │ embedded TURN│
                      └──────────┘   │              │      │ (UDP 3478 +  │
                       (auto-TLS)    │   pion v4    │      │  ephemeral)  │
                                     └──────┬───────┘      └──────────────┘
                                            │
                                     RTP fan-out
                                            │
                                            ▼
                                     ┌──────────────┐
                                     │  Recording   │  IVF + Ogg per track
                                     │ ffmpeg → MP4 │──▶ s3://bucket/...
                                     │  S3 upload   │     (DO Spaces)
                                     └──────────────┘

Tech stack

Layer Pick
Backend Go 1.24, pion/webrtc v4, pion/turn v3, coder/websocket, aws-sdk-go-v2
Frontend (built-in UI) Nuxt 3 (SPA mode), Vue 3, Tailwind
SDK TypeScript, Vite (ESM + UMD + .d.ts)
Infra Docker, Caddy 2, GitHub Actions, ffmpeg
Recording target DigitalOcean Spaces (or any S3-compatible bucket)

Docs

INTEGRATION.md Embed via iframe / SDK / React / Vue with recipes
DEPLOY.md Production VPS deploy + TURN + S3 + auto-deploy
ARCHITECTURE.md SFU design, signaling protocol, recording pipeline
DEVELOPMENT.md Local dev, repo layout, release flow
CONTRIBUTING.md How to file issues and PRs
CHANGELOG.md Release notes

FAQ

How is this different from Jitsi / LiveKit / Daily? Smaller in scope. meetapp is for the case where you want a single Docker image you can read end-to-end in a weekend (~3K Go + ~3K TypeScript), no signup, no per-minute pricing. Jitsi / LiveKit are more featureful and more complex; Daily is hosted and great but commercial.

Can I use it commercially? Yes — MIT licensed. The hosted demo at meet.dokaz.online is for experimentation; for production traffic, run your own.

What about group call limits? Tested cleanly to 6 publishers per room. Bottleneck at scale is server bandwidth (each publisher × every subscriber). Cascaded SFUs are a v2-if-needed feature.

Mobile apps? Browser only — both iOS Safari and Chrome on Android work. Native apps would need Pion bindings (possible, not done).

Auth? Open: anyone with the room URL joins. JWT-based room tokens are on the roadmap. Until then, treat room IDs as the secret.

Why VP8 + Opus only? The recording writers (Pion IVF + Ogg) are codec-specific. Restricting the MediaEngine to VP8/Opus makes recording work predictably across browsers. H264/VP9 + simulcast are on the roadmap.

Can I disable recording? Yes — leave the MEETAPP_S3_* env vars unset. The Record button hides itself in the UI.

License

MIT — see LICENSE.

Acknowledgments

Built on the shoulders of Pion (WebRTC in Go), Caddy (the TLS-by-default reverse proxy), and Nuxt.

About

Self-hosted video calls — single Go binary with embedded Vue/Nuxt SPA

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors