Skip to main content

Intercom / SIP API for Web Developers

Hardware Compatibility

AVS-10 and AVS-15 touch panels only. This API requires dedicated AVstudio touch panel hardware with onboard intercom capabilities.

App Container features a built-in SIP / intercom engine supporting both peer-to-peer (P2P) direct calling between panels across the local network and SIP PBX registration (connecting with IP PBX servers, door stations, or extensions).

Starting with App Container v1.x, web developers can control the intercom engine directly from hosted HTML5 projects via window.appcontainer.sip — placing and answering calls, triggering broadcast paging ("Page All Panels"), and reacting to real-time telephony events without leaving the web project's UI.


🏗️ Architecture & Trust Model

  • Native Audio & Video Rendering: Audio and video streams for active calls are handled and rendered natively by App Container hardware acceleration, not inside the browser WebView DOM.
  • Control & Telemetry Plane: The JavaScript API is specifically for control, dialing, and status telemetry (the same trust level as window.appcontainer.gpioBridge).
  • Always Available: The bridge is accessible immediately when your project loads. It does not require the technician-only Intercom Setup screen to be open.
  • Promise & Event Pattern: All dialing and control methods return a Promise resolving to the latest status snapshot. Telephony failures (busy, no answer, network dropped) are delivered asynchronously via events (callFailed, callEnded) rather than rejected Promises.

🚀 Placing and Controlling Calls

All control methods return a Promise that resolves to a status snapshot.

// Peer-to-peer: Call another panel directly by local IP address
await window.appcontainer.sip.placeCallToIp('192.168.1.50');

// Via PBX: Call a registered extension (requires SIP Server mode configured in Intercom Setup)
await window.appcontainer.sip.placeCallToExtension('101');

// Answer incoming ringing call
await window.appcontainer.sip.answer();

// Reject incoming call (immediately sends SIP 486 "Busy Here" to caller)
await window.appcontainer.sip.reject();

// Silently dismiss ring locally:
// No response is sent to caller, who keeps ringing until their ~20s timeout.
// No callEnded event fires for this action.
await window.appcontainer.sip.ignore();

// Hang up / end current active call
await window.appcontainer.sip.hangup();

// Broadcast paging: call every panel currently discovered on the local network (P2P mode only)
await window.appcontainer.sip.pageAll();
await window.appcontainer.sip.hangupPageAll();

// Toggle automatic answer for incoming calls (answers immediately without ringing)
await window.appcontainer.sip.setAutoAnswer(true);

📊 Status & Panel Discovery

Getting Intercom Status

Call getStatus() at any time to obtain a comprehensive snapshot of the SIP engine:

const status = await window.appcontainer.sip.getStatus();
console.log('Intercom status:', status);

Status Snapshot Structure

{
"callStatus": "connected (incoming)",
"calleeBusy": false,
"callHasVideo": true,
"activeCallId": "39279a15-2889-1240-a124-001248ac9120",
"incomingCallerName": "Front Door Station",
"incomingIsBroadcast": false,
"registered": true,
"pagingActiveCount": 0,
"deviceName": "Panel-575afc"
}

Fields Description

FieldTypeDescription
callStatusstringCurrent call state (see possible values below).
calleeBusybooleantrue if the dialed remote target responded busy.
callHasVideobooleantrue if the call includes an active video stream.
activeCallIdstring | nullUnique UUID for the current active/ringing call session.
incomingCallerNamestring | nullName or caller ID of the incoming caller.
incomingIsBroadcastbooleantrue if the incoming call is a broadcast page from another panel.
registeredbooleanPBX registration status (true when successfully registered with SIP server).
pagingActiveCountnumberNumber of currently connected panels during a pageAll() broadcast.
deviceNamestringLocal device network identifier (e.g., Panel-575afc).

Possible callStatus Values

  • idle — No active or pending calls
  • ringing — Incoming call is ringing locally
  • calling... — Dialing outgoing call
  • incoming... — Remote incoming call session initializing
  • connected (incoming) — Call answered (incoming direction)
  • connected (outgoing) — Call answered (outgoing P2P direction)
  • connected (outgoing via PBX) — Call answered (outgoing PBX direction)
  • connected (outgoing, legacy audio) — Connected with legacy audio codec fallback
  • connected (outgoing via PBX, legacy audio) — Connected via PBX with legacy audio fallback
  • paging... — Broadcast paging starting
  • paging (N active) — Broadcast paging in progress to N panels
  • failed — Call attempt failed

Discovering Network Panels (P2P Mode)

Retrieve panels automatically discovered via mDNS/broadcast on the local network subnet:

const panels = await window.appcontainer.sip.listPanels();
console.log('Discovered panels:', panels);

Response Example

[
{ "name": "Panel-Kitchen", "host": "192.168.1.50", "port": 5060 },
{ "name": "Panel-MasterBed", "host": "192.168.1.52", "port": 5060 }
]

🔔 Telephony Events

Register event handlers using window.appcontainer.sip.on(event, handler) and remove them with window.appcontainer.sip.off(event, handler).

// Incoming call alert
window.appcontainer.sip.on('incomingCall', ({ callId, callerName, isBroadcast, hasVideo }) => {
console.log(`Incoming call from ${callerName} (Video: ${hasVideo})`);
// Display custom UI modal with Answer / Reject / Ignore buttons
});

// Call connected
window.appcontainer.sip.on('callConnected', ({ callId, direction, hasVideo, viaPbx, legacy }) => {
console.log(`Call connected in direction: ${direction} (PBX: ${viaPbx})`);
});

// Call ended
window.appcontainer.sip.on('callEnded', ({ callId, reason }) => {
console.log(`Call ${callId} ended. Reason: ${reason}`);
// Reason can be: 'local' | 'remote' | 'remote_bye' | 'rejected' | 'timeout'
});

// Call failed
window.appcontainer.sip.on('callFailed', ({ reason, busy }) => {
console.warn(`Call failed: ${reason}, Callee busy: ${busy}`);
});

// PBX Registration status changes
window.appcontainer.sip.on('registrationChanged', ({ registered, lastError }) => {
console.log(`SIP Registration: ${registered ? 'ONLINE' : 'OFFLINE'}, Error: ${lastError}`);
});

// Panel discovery events (P2P)
window.appcontainer.sip.on('panelFound', ({ name, host, port }) => {
console.log(`Discovered intercom panel: ${name} (${host}:${port})`);
});

window.appcontainer.sip.on('panelLost', ({ name }) => {
console.log(`Intercom panel went offline: ${name}`);
});

// Broadcast paging count update
window.appcontainer.sip.on('pagingStatusChanged', ({ activeCount }) => {
console.log(`Active paging listeners: ${activeCount}`);
});

Unsubscribing from Events

const handleIncoming = (data) => {
console.log('Incoming call:', data);
};

// Subscribe
window.appcontainer.sip.on('incomingCall', handleIncoming);

// Unsubscribe when component unmounts
window.appcontainer.sip.off('incomingCall', handleIncoming);

💡 Practical Web UI Example

Here is a full example illustrating how to integrate incoming call notifications and a lobby intercom speed dial into your HTML5 UI:

<!DOCTYPE html>
<html>
<head>
<style>
#call-banner {
display: none;
position: fixed;
top: 20px;
right: 20px;
padding: 16px 24px;
background: #1e293b;
color: #fff;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(0,0,0,0.4);
z-index: 10000;
font-family: sans-serif;
}
.btn {
padding: 8px 16px;
margin-left: 8px;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
}
.btn-green { background: #22c55e; color: white; }
.btn-red { background: #ef4444; color: white; }
.btn-gray { background: #64748b; color: white; }
</style>
</head>
<body>

<!-- Incoming Call Banner -->
<div id="call-banner">
<span id="caller-label">Incoming Call...</span>
<button class="btn btn-green" onclick="answerCall()">Answer</button>
<button class="btn btn-red" onclick="rejectCall()">Reject</button>
<button class="btn btn-gray" onclick="ignoreCall()">Mute</button>
</div>

<!-- Speed Dial Buttons -->
<button class="btn btn-green" onclick="callGate()">Call Gate (P2P)</button>
<button class="btn btn-gray" onclick="pageAllPanels()">Broadcast All</button>

<script>
const banner = document.getElementById('call-banner');
const label = document.getElementById('caller-label');

// Register Intercom event listeners if running inside App Container
if (window.appcontainer?.sip) {
window.appcontainer.sip.on('incomingCall', ({ callerName, hasVideo }) => {
label.textContent = `Call from ${callerName || 'Door'} ${hasVideo ? '📹' : '📞'}`;
banner.style.display = 'block';
});

window.appcontainer.sip.on('callEnded', () => {
banner.style.display = 'none';
});

window.appcontainer.sip.on('callFailed', () => {
banner.style.display = 'none';
});
}

async function answerCall() {
await window.appcontainer?.sip?.answer();
banner.style.display = 'none';
}

async function rejectCall() {
await window.appcontainer?.sip?.reject();
banner.style.display = 'none';
}

async function ignoreCall() {
await window.appcontainer?.sip?.ignore();
banner.style.display = 'none';
}

async function callGate() {
await window.appcontainer?.sip?.placeCallToIp('192.168.1.50');
}

async function pageAllPanels() {
await window.appcontainer?.sip?.pageAll();
}
</script>
</body>
</html>