Skip to content
GurukulLiveDevelopers
Build

Embedding & JS SDK

Put the GurukulLive classroom inside your own page or app. The room runs in an iframe; the @vmeet/embed SDK creates it with the right permissions and gives you typed events and commands.

Quick start

Get an embedUrl from a join link on your server, then on your page:

html
<div id="classroom" style="width: 100%; height: 70vh"></div>
<script src="https://gurukullive.example.com/sdk/vmeet-embed.js"></script>
<script>
  const call = VMeet.createFrame(document.getElementById('classroom'), {
    url: EMBED_URL,          // from POST /public/meetings/:id/join-links (your backend)
    theme: 'light',
    prejoin: true,
    brandColor: '#0F766E',
  });

  call.on('joined', (e) => console.log('joined as', e.participant.name, 'with', e.participants.length, 'others'));
  call.on('participant-joined', (e) => console.log(e.participant.name, 'joined'));
  call.on('hand-raised', (e) => e.raised && notifyTeacher(e.participant.name));
  call.on('meeting-ended', () => location.assign('/classes'));
  call.on('error', (e) => console.warn(e.code, e.message));

  document.getElementById('mute').onclick = async () => {
    const { state } = await call.toggleMic();      // { micEnabled, cameraEnabled, handRaised, displayName }
    console.log('mic is now', state.micEnabled ? 'on' : 'off');
  };
</script>

With a bundler: import { createFrame } from '@vmeet/embed' — same API, fully typed, about 3 KB gzipped, no dependencies.

Plain iframe

Without the SDK, add the permissions the room needs and your page's origin:

html
<iframe
  src="https://gurukullive.example.com/embed/meeting/algebra-10a-k3f9q2?jt=vjt_9Qm…&parentOrigin=https%3A%2F%2Flms.example.com&theme=light"
  allow="camera; microphone; display-capture; autoplay; fullscreen; picture-in-picture; clipboard-write"
  allowfullscreen
  title="Algebra class"
  style="border: 0; width: 100%; height: 70vh"
></iframe>

Do not add a sandbox attribute — the room needs scripts, same-origin storage and media. Give the frame at least 360 × 480 px; it adapts down to phone widths.

Query parameters

ParameterDefaultEffect
jt—Join token. Already part of embedUrl.
parentOrigin—Origin of your page, e.g. https://lms.example.com. Required for events/commands. The SDK adds it for you.
themeviewer’s themelight or dark for the pre-join and status screens. The call itself is always dark.
prejointruefalse skips the camera/microphone check and joins immediately.
chattruefalse hides in-room chat.
brandColorGurukulLive indigo#RRGGBB accent for buttons (URL-encode # as %23).
hideLeaveButtonfalsetrue hides the Leave button — call leave() from your own UI.
leaveRedirect—https URL to open after the user leaves (the top page is navigated when the user clicked Leave). Prefer handling the left event.

The embedded room's interface is in English.

Allowed origins

GurukulLive only lets approved sites frame the room, and only talks to approved origins:

  • Framing is controlled by the Content-Security-Policy: frame-ancestors header on /embed/*. By default only GurukulLive itself may frame it. Self-hosted deployments list integrator sites in EMBED_DEFAULT_FRAME_ANCESTORS (space- or comma-separated origins, https://*.example.com wildcards allowed); per-organization lists come from the organization's embed settings when the deployment enables that lookup. Ask your GurukulLive administrator to add your origin.
  • Events and commands use postMessage with exact origins on both sides. The room posts only to parentOrigin, and only after checking it against the browser-reported parent page (and the organization's allowed origins when configured). It accepts commands only from that same window and origin. The SDK, in turn, accepts messages only from its own iframe and the room's origin.

SDK reference

VMeet.createFrame(container, options)

container is an element or a CSS selector. options: url (required), theme, prejoin, chat, brandColor, hideLeaveButton, leaveRedirect, width/height (CSS sizes, default 100%), title (accessible name of the iframe), commandTimeoutMs (default 10 000). Returns a VMeetFrame.

VMeetFrame

  • on(type, handler), once(type, handler), off(type, handler?) — on('*', (payload, type) => …) receives every event.
  • whenReady() — promise of the ready payload; isReady.
  • Commands return a promise of { ok, state?, error? }; they are queued until ready.
  • destroy() — removes the iframe (the user leaves) and all listeners.
  • iframe, origin — the element and the room's exact origin.

Events (room → your page)

EventPayload / when
ready{ version, meeting, role, prejoin } — the room loaded and accepted your origin. Commands sent earlier are delivered now.
joined{ meeting, role, participant, participants[], state } — connected to the class.
left{ reason: 'user' | 'removed' | 'duplicate' | 'error' } — the user left, was removed, joined elsewhere, or the connection failed.
participant-joined{ participant } — someone else joined.
participant-left{ participant } — someone else left.
recording-started{ recordingId } — cloud recording started (also sent if it was already running at join).
recording-stopped{ recordingId }
hand-raised{ participant, raised } — a hand went up or down (including your user’s own).
meeting-ended{ meetingId } — the host ended the meeting for everyone.
error{ code, message, fatal? } — e.g. join_token_expired, media_permission_denied, connection_failed, meeting_full (the room is full; offer a retry later).

participant objects are { identity, userId, name, isHost, isLocal }; userId is theGurukulLive user id you get back from POST /public/users.

Commands (your page → room)

Message typeSDK methodEffect
toggle-mictoggleMic()Mute / unmute (works on the pre-join screen too).
toggle-cameratoggleCamera()Camera on / off.
raise-handraiseHand(raised = true) · lowerHand()Raise or lower the hand.
set-display-namesetDisplayName(name)Rename the user in the room (when the session allows renaming).
leaveleave()Leave the meeting (emits left).

Raw protocol (without the SDK)

js
const ROOM_ORIGIN = 'https://gurukullive.example.com';
const frame = document.querySelector('iframe');

window.addEventListener('message', (event) => {
  // Always check both the origin and the source window
  if (event.origin !== ROOM_ORIGIN || event.source !== frame.contentWindow) return;
  const msg = event.data;                          // { source: 'vmeet', type, payload, version }
  if (!msg || msg.source !== 'vmeet') return;
  if (msg.type === 'joined') console.log('joined', msg.payload.participant.name);
});

// Commands: optional requestId → the room answers { source: 'vmeet', type: 'ack', requestId, payload: { ok, state } }
frame.contentWindow.postMessage({ source: 'vmeet', type: 'toggle-mic', requestId: 'r1' }, ROOM_ORIGIN);

Mobile apps and WebViews

The quickest path is opening the join link's url in an Android Custom Tab or iOS SFSafariViewController — no extra setup. To show the room inside your own screen, use a WebView with media enabled:

// AndroidManifest: CAMERA, RECORD_AUDIO, MODIFY_AUDIO_SETTINGS, INTERNET — request CAMERA/RECORD_AUDIO at runtime first
webView.settings.apply {
    javaScriptEnabled = true
    domStorageEnabled = true                    // required: the room keeps per-tab state
    mediaPlaybackRequiresUserGesture = false    // let remote audio/video autoplay
}
webView.webChromeClient = object : WebChromeClient() {
    override fun onPermissionRequest(request: PermissionRequest) {
        // Grant only to your GurukulLive host
        if (request.origin.host == "gurukullive.example.com") {
            request.grant(arrayOf(
                PermissionRequest.RESOURCE_VIDEO_CAPTURE,
                PermissionRequest.RESOURCE_AUDIO_CAPTURE,
            ))
        } else request.deny()
    }
}
// Optional: receive room events (same JSON as postMessage events)
webView.addJavascriptInterface(object {
    @JavascriptInterface fun postMessage(json: String) { handleRoomEvent(JSONObject(json)) }
}, "VMeetAndroid")
webView.loadUrl(joinLink.url)   // or embedUrl

// Send a command
webView.evaluateJavascript("window.vmeetCommand && window.vmeetCommand('toggle-mic')", null)
  • Room events reach native code through VMeetAndroid.postMessage, the vmeet WKScriptMessageHandler or ReactNativeWebView.postMessage — whichever your app registers. Send commands with window.vmeetCommand(type, payload).
  • Browsers may block audio until the user taps the page; the room then shows a “Tap to enable meeting audio” button. Disabling the gesture requirement (above) avoids it.
  • The room keeps the screen awake during the class (where the platform allows it) and respects notches and home indicators.

Embedding recordings

Ask for a signed player URL on your server — scope recordings:read — and iframe it. Links expire (default 1 hour, up to 24 hours); mint one per page view.

curl -X POST https://gurukullive.example.com/api/v1/public/recordings/REC_ID/playback \
  -H "Authorization: Bearer $VMEET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"expiresInSeconds":3600,"viewerExternalId":"student-1001"}'
html
<iframe src="https://gurukullive.example.com/embed/recording/REC_ID?token=…&theme=dark&start=120"
        allow="autoplay; fullscreen; picture-in-picture" allowfullscreen
        title="Class recording" style="border:0; width:100%; aspect-ratio:16/9"></iframe>

Player parameters: token (from embedUrl), theme, autoplay=true (starts muted, as browsers require), start (seconds), parentOrigin. With parentOrigin (added by createFrame) the player emits ready, play, pause, ended and error. Multi-part class recordings play back to back.