One-to-one calls only

MyHub Call SDK

Integration documentation and an independent browser test panel for direct one-to-one audio/video calling. Meetings, group calls, and livestreaming are intentionally not included.

1. Requirements

  • A modern browser with WebRTC support.
  • Microphone permission for audio calls.
  • Camera and microphone permission for video calls.
  • Run through HTTPS or localhost so browser media permissions work correctly.
  • Each connected user must have a unique userId.
  • The SDK creates the signaling callId. The application user does not enter it.
  • The caller reads the generated callId from sdk.controller.currentCallId immediately after sdk.startCall(...).
  • If onCallRinging provides a callId, the same value is stored again there.
  • The receiver receives the same callId in onIncomingCall.
  • Use that callId as the LiveKit roomId for both users.
  • For a video call, pass isVideo: true. For an audio-only call, pass isVideo: false.
  • The receiver must use the same isVideo value received in onIncomingCall.
No separate room ID is required: do not ask the user for a room ID and do not add a new room ID field to WebSocket signaling. Pass the received callId as the roomId value when calling startMedia or acceptCall.
One-to-one media controls: use muteCallAudio, muteCallVideo, and switchCallCamera. The shorter muteAudio, muteVideo, and switchCamera names are assigned to meeting controls in the combined SDK.

2. Import and initialize the SDK

import { CallSDK } from "https://rtc.myhubs.in/myhub-livekit-sdk/myhub-livekit-sdk.js";

const sdk = new CallSDK();

3. One-to-one call flow

  1. Create the SDK and register the listener.
  2. Connect the current user using sdk.connect({ userId }).
  3. The caller selects the call type: isVideo: true for video or isVideo: false for audio.
  4. The caller starts a call using sdk.startCall(...) with that isVideo value.
  5. Immediately after sdk.startCall(...), the caller stores sdk.controller.currentCallId.
  6. If onCallRinging(callId) supplies an ID, store that same value there too.
  7. The receiver stores the same callId received in onIncomingCall(callId, fromUserId, isVideo).
  8. The receiver accepts using roomId: currentCallId.
  9. The caller starts media after onCallAccepted using roomId: currentCallId.
  10. Attach localStream and remoteStream to the video elements.
  11. Either user can mute audio/video, switch the camera, or end the call.

4. SDK methods

Method Parameters Purpose
connect { userId } Connects and registers the current user for signaling.
startCall { toUserId, isVideo } Starts an outgoing one-to-one call. Use true for video or false for audio.
acceptCall { roomId: currentCallId, userId, fromUserId, isVideo } Accepts an incoming call using the received callId as the media room and the received isVideo call type.
rejectCall None Rejects the current incoming call.
startMedia { roomId: currentCallId, userId, isVideo } Starts caller-side media using the ringing call's callId as the media room.
endCall None Ends the current call.
muteCallAudio boolean Mutes or unmutes the current user's microphone.
muteCallVideo boolean Disables or enables the current user's camera track.
switchCallCamera None Switches between available front and rear cameras.

5. Listener callbacks

Callback Arguments When it is called
onIncomingCall callId, fromUserId, isVideo A direct call is received.
onCallRinging callId when supplied The outgoing call is ringing. Use the callback value when present; otherwise use sdk.controller.currentCallId, which was generated by startCall.
onCallAccepted None The receiver accepted the call.
onCallRejected None The receiver rejected the call.
onCallEnded None The active call ended.
onUserOffline None The target user is not connected.
onRemoteAudioMuted muted The remote user mutes or unmutes audio.
onRemoteVideoMuted muted The remote user disables or enables video.
onLocalAudioMuted muted The local microphone state changes.
onLocalVideoMuted muted The local camera state changes.

6. Complete one-to-one integration example

This is the same call flow used by the independent tester below.

import { CallSDK } from "https://rtc.myhubs.in/myhub-livekit-sdk/myhub-livekit-sdk.js";

const sdk = new CallSDK();

let streams = null;
let currentCallId = null;
let incomingCallerId = null;
let currentCallIsVideo = true;
let audioMuted = false;
let videoMuted = false;

sdk.setListener({
  onIncomingCall: (callId, fromUserId, isVideo) => {
    currentCallId = callId;
    incomingCallerId = fromUserId;
    currentCallIsVideo = isVideo;
    console.log("Incoming call", { callId, fromUserId, isVideo });
  },
 onCallerBusy: (callId) => {
    alert("You cannot start a new call because you are already in an active call on another device.");
  },
  onCalleeBusy: (callId) => {
    alert("The person you are trying to call is currently on another call.");
  },
  onCallAnsweredElsewhere: (callId) => {
    // Hide the ringing screen because you just answered it on your phone/other tab!
    console.log(`Call ${callId} was answered on another device.`);
  },
  onError: (code, message) => {
    console.error(`Received error ${code}: ${message}`);
  },
  onCallRinging: (callId) => {
    currentCallId = callId || sdk.controller?.currentCallId || currentCallId;
    console.log("Ringing", { callId: currentCallId });
  },

  onCallAccepted: async () => {
    streams = await sdk.startMedia({
      roomId: currentCallId,
      userId: "user_123",
      isVideo: currentCallIsVideo
    });

    localVideo.srcObject = streams.localStream;
    remoteVideo.srcObject = streams.remoteStream;
  },

  onCallRejected: () => console.log("Call rejected"),
  onCallEnded: () => console.log("Call ended"),
  onUserOffline: () => console.log("User offline"),
  onRemoteAudioMuted: (muted) => console.log("Remote audio muted:", muted),
  onRemoteVideoMuted: (muted) => console.log("Remote video muted:", muted),
  onLocalAudioMuted: (muted) => console.log("Local audio muted:", muted),
  onLocalVideoMuted: (muted) => console.log("Local video muted:", muted)
});

// Connect current user
sdk.connect({ userId: "user_123" });

// Caller: true for video, false for audio-only
currentCallIsVideo = true;

sdk.startCall({
  toUserId: "user_439",
  isVideo: currentCallIsVideo
});

// The SDK generates the signaling callId synchronously.
// Use exactly that value as the LiveKit roomId.
currentCallId = sdk.controller?.currentCallId;

// Receiver
streams = await sdk.acceptCall({
  roomId: currentCallId,
  userId: "user_439",
  fromUserId: incomingCallerId,
  isVideo: currentCallIsVideo
});

localVideo.srcObject = streams.localStream;
remoteVideo.srcObject = streams.remoteStream;

// Other one-to-one call actions
sdk.rejectCall();
sdk.muteCallAudio(true);
sdk.muteCallVideo(true);
sdk.switchCallCamera();
sdk.endCall();

7. Independent browser test

Open this page for both users and use different user IDs. Connect both users before starting the call. The caller reads the generated callId from the existing SDK controller, while the receiver gets the same value from onIncomingCall. Both use it automatically as the LiveKit room ID.

Not connected
Ready.