iOS video call SDK

Add a video call to an iOS app

MeetoWidget is a Swift Package that shows a Meeto video call inside your iOS app. It is a thin wrapper: a WKWebView that loads Meeto's hosted call widget, plus a native bridge for events and commands. You get the same lobby, video grid and chat as on the web, a SwiftUI view and a UIKit view. It builds for iOS 14, needs iOS 14.3 or newer for camera and microphone, and uses a public mk_live_… key.

How does the iOS SDK work?

The SDK wraps the hosted Meeto widget in a web view; it is not a native video engine. MeetoWidgetView loads https://meeto.me/embed with your key in a WKWebView, sends commands into the page and turns the page's messages into Swift events. Every call runs through Meeto's media servers and is encrypted in transit, not end to end.

The upside is size and upkeep: no WebRTC framework in your binary, and calls improve when the hosted widget does. The trade-off is that the call behaves like Meeto in a mobile browser. The screen-share button appears only where the web view offers screen capture to pages, which iPhone web views do not, so people in your app see screens others share rather than share their own. If your users only need to join meetings, the browser link or the Meeto app may be enough.

Install the Swift Package

Add the package by its repository URL in Xcode, or declare it in Package.swift. The current tag is 0.1.2, and the library product is MeetoWidget.

// Xcode → File → Add Package Dependencies…
// https://github.com/meeto-app/meeto-widget-ios.git

// or in Package.swift:
dependencies: [
    .package(url: "https://github.com/meeto-app/meeto-widget-ios.git", from: "0.1.2")
]

Set up the key and permissions

A native app has no web origin, so the SDK presents itself as app:// plus your bundle identifier. In Settings → Website widget, create a key and add that origin, for example app://com.example.myapp. Without it the widget reports an error instead of loading the call.

Add both usage descriptions to Info.plist. iOS terminates an app that touches the camera or microphone without them.

<key>NSCameraUsageDescription</key>
<string>The camera is used for video calls.</string>
<key>NSMicrophoneUsageDescription</key>
<string>The microphone is used for video calls.</string>

An app origin is declared by the SDK rather than checked by a browser, so treat the mk_live_… key as public, as it is on the web. The server key mk_secret_… never belongs in an app.

Show a call in SwiftUI

MeetoWidget is a SwiftUI view that takes a configuration and an event closure. Give it a frame; the call fills it.

import SwiftUI
import MeetoWidget

struct SupportCallView: View {
    @Binding var isPresented: Bool

    var body: some View {
        MeetoWidget(config: .init(key: "mk_live_…", roomName: "support", userName: "Anna")) { event in
            switch event.type {
            case "joined":
                print("joined room", event.payload["room"] as? String ?? "")
            case "left":
                isPresented = false
            case "error":
                print("Meeto error", event.payload["code"] as? String ?? "")
            default:
                break
            }
        }
        .frame(maxWidth: .infinity, minHeight: 520)
    }
}

The configuration is fixed when the view is created. To switch rooms or keys, give the view a new identity with .id(roomCode) so SwiftUI builds a fresh one. The SwiftUI view does not expose commands; for join, leave or mute buttons, use MeetoWidgetView below or wrap it in your own UIViewRepresentable.

MeetoWidgetConfig fields
FieldPurpose
keyRequired public key mk_live_…
roomNamePersistent named room shared by everyone using it, such as support
roomCode of an existing open room, such as the slug POST /api/embed/rooms returns to your server; rooms of /api/v1 meetings ask for a password the SDK cannot pass
userName / lang / accentPrefilled display name, interface language and hex accent color
autoJoinSkip the lobby in open-door rooms; default false
baseURLThe Meeto address; defaults to https://meeto.me

Use UIKit and send commands

MeetoWidgetView is a UIView that starts loading as soon as you create it. Pin it with constraints and keep a reference for commands.

import UIKit
import MeetoWidget

final class CallViewController: UIViewController {
    private var widget: MeetoWidgetView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // An open room your server created with POST /api/embed/rooms
        widget = MeetoWidgetView(config: .init(key: "mk_live_…", room: "misty-fox-a18f493cb027fe823cb6", userName: "Anna"))
        widget.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(widget)
        NSLayoutConstraint.activate([
            widget.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            widget.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            widget.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            widget.trailingAnchor.constraint(equalTo: view.trailingAnchor),
        ])
        widget.onEvent = { [weak self] event in
            if event.type == "left" { self?.dismiss(animated: true) }
        }
    }

    func muteMe() { widget.setMicrophone(false) }
    func leaveCall() { widget.leave() }
}
Events and commands
NameDetails
ready / phaseThe widget loaded; phase reports prejoin, room, finished or error
joined / leftpayload has room and identity; left has reason and durationSec
participants / errorpayload has count; error has code and message
join(name:) / leave()Join from the lobby of an open-door room, or leave the call
setMicrophone(_:) / setCamera(_:)Turn the microphone or camera on or off during the call

Events arrive on the main thread as MeetoEvent values: a type string and the full payload dictionary of the widget protocol, the same events the JavaScript SDK emits. Commands can be called from any thread.

Limits and production checklist

Register the bundle ID

Each app target with its own bundle identifier, including a debug variant, needs its own app:// origin on the key.

Ask for permission first

Show your own explanation before the system camera prompt. If the person declines, the web view gets no camera or microphone.

Test on a device

The Simulator has no real camera. Test a second participant from a browser on another device.

Plan limits for calls in the SDK
PlanLimit
FreeUp to 10 participants and 60-minute calls once two people are in
ProUp to 25 participants; no time limit for up to 2 simultaneous meetings per account
Every planThe SDK is free; the Powered by Meeto badge stays visible

Limits follow the plan of the account that owns the key. Recording and AI summaries are started by a Pro or Team host; see pricing.

Frequently asked questions

Is MeetoWidget a native WebRTC SDK?

No. It is a WKWebView wrapper around Meeto's hosted call page with a Swift bridge for events and commands. Media runs through WebKit's WebRTC and Meeto's media servers, so you do not ship or update a video engine yourself.

Which iOS versions are supported?

The package builds for iOS 14 and later, but camera and microphone inside a WKWebView need iOS 14.3 or later. From iOS 15 the SDK approves the web view's camera and microphone request itself, so people see only the system permission prompt and not a second one from the page.

Does it add CallKit, push notifications or a background call?

No. The SDK is an in-app call screen. Ringing, CallKit and push notifications are features of the Meeto app itself, not of this package; your app decides when to open the call screen.

How do I put two app users into the same call?

Give both the same roomName, or create an open room on your server with POST /api/embed/rooms and the secret key, and pass its code to both apps as room. Rooms of meetings scheduled through /api/v1 ask for a password, which the SDK cannot pass. A roomName room is shared by everyone who uses that name with your key, so pick names that are not guessable for private calls.

Put it to work

Open your Meeto account to set it up, or explore the working product first.