Skip to content

ConfBridge Conferencing Guide

This guide covers managing Asterisk ConfBridge conferences via the Asterisk Manager Interface (AMI) in VueSIP.

Overview

VueSIP provides a reactive useAmiConfBridge composable for managing ConfBridge audio conferences. This enables:

FeatureDescription
Room ManagementList, lock/unlock, mute conferences
RecordingStart/stop conference recording
Participant ControlMute, unmute, kick users
Video SourceSet single video source for video conferences
Real-time EventsJoin, leave, talking status updates

Architecture

┌─────────────────┐      ┌──────────────────┐      ┌─────────────────┐
│  VueSIP App     │◄────►│  amiws Proxy     │◄────►│  Asterisk PBX   │
│  (Browser)      │  WS  │  (WebSocket)     │  AMI │  (ConfBridge)   │
└─────────────────┘      └──────────────────┘      └─────────────────┘


                                                   ┌─────────────────┐
                                                   │  Conference     │
                                                   │  Rooms          │
                                                   └─────────────────┘

Components

  1. Asterisk PBX - Hosts ConfBridge conferences
  2. amiws Proxy - Converts AMI protocol to WebSocket (JSON)
  3. VueSIP - useAmiConfBridge composable for reactive conference management

Prerequisites

Asterisk Configuration

1. Configure ConfBridge

Create or edit /etc/asterisk/confbridge.conf:

ini
; /etc/asterisk/confbridge.conf

[general]

; Default user profile
[default_user]
type=user
admin=no
marked=no
startmuted=no
music_on_hold_when_empty=yes
music_on_hold_class=default
quiet=no
announce_user_count=yes
announce_join_leave=yes
dtmf_passthrough=no
announce_user_count_all=yes

; Admin user profile
[admin_user]
type=user
admin=yes
marked=yes
startmuted=no
dtmf_passthrough=no

; Default bridge profile
[default_bridge]
type=bridge
max_members=50
record_conference=no
internal_sample_rate=auto
mixing_interval=20
video_mode=follow_talker

2. Create Conference Dialplan

Edit /etc/asterisk/extensions.conf or use FreePBX Custom Extensions:

; /etc/asterisk/extensions.conf

[confbridge-rooms]
; Room 1000 - Regular conference
exten => 1000,1,Answer()
 same => n,ConfBridge(1000,default_bridge,default_user)
 same => n,Hangup()

; Room 1001 - Admin conference
exten => 1001,1,Answer()
 same => n,ConfBridge(1001,default_bridge,admin_user)
 same => n,Hangup()

; Dynamic room - dial any 4-digit number starting with 1
exten => _1XXX,1,Answer()
 same => n,ConfBridge(${EXTEN},default_bridge,default_user)
 same => n,Hangup()

3. Configure AMI User

Edit /etc/asterisk/manager.conf:

ini
; /etc/asterisk/manager.conf
[general]
enabled = yes
port = 5038
bindaddr = 127.0.0.1

[vuesip]
secret = your-secure-password
deny = 0.0.0.0/0.0.0.0
permit = 127.0.0.1/255.255.255.255
read = call,user,reporting
write = call,originate
eventfilter = !Event: RTCP*
eventfilter = !Event: VarSet

Required Permissions:

  • read = call - Receive ConfBridge events
  • write = call - Send ConfBridge commands

4. Reload Configuration

bash
asterisk -rx "module reload app_confbridge.so"
asterisk -rx "manager reload"

FreePBX Configuration

In FreePBX, conferences are managed via the GUI:

  1. Navigate to Applications > Conferences
  2. Click + Add Conference
  3. Configure:
    • Conference Number: e.g., 1000
    • Conference Name: e.g., Main Conference
    • User PIN: Optional access PIN
    • Admin PIN: Optional admin PIN
    • Options: Join/Leave announcements, recording, etc.
  4. Submit and Apply Config

For AMI access, configure via Settings > Asterisk Manager Users as described in the AMI CDR Guide.

VueSIP Integration

Basic Setup

typescript
import { computed } from 'vue'
import { useAmi, useAmiConfBridge } from 'vuesip'

// Create AMI connection
const ami = useAmi()

// Connect to amiws proxy
await ami.connect('ws://pbx.example.com:8080')

// Initialize ConfBridge management
const {
  rooms, // Map<string, ConfBridgeRoom>
  users, // Map<string, ConfBridgeUser>
  roomList, // Computed array of rooms
  userList, // Computed array of users
  totalParticipants, // Total participant count
  isLoading, // Loading state
  error, // Error state
  listRooms, // Fetch all conferences
  listUsers, // Fetch users in a conference
  lockRoom, // Lock a conference
  unlockRoom, // Unlock a conference
  startRecording, // Start recording
  stopRecording, // Stop recording
  muteUser, // Mute a participant
  unmuteUser, // Unmute a participant
  kickUser, // Remove a participant
  setVideoSource, // Set video source
  refresh, // Refresh all data
} = useAmiConfBridge(computed(() => ami.getClient()))

Conference Dashboard

vue
<template>
  <div class="confbridge-dashboard">
    <!-- Conference List -->
    <div class="conferences">
      <h2>Active Conferences</h2>
      <div v-for="room in roomList" :key="room.conference" class="conference-card">
        <div class="conference-header">
          <h3>Room {{ room.conference }}</h3>
          <span class="participant-count">{{ room.parties }} participants</span>
        </div>

        <div class="conference-status">
          <span v-if="room.locked" class="badge locked">Locked</span>
          <span v-if="room.recording" class="badge recording">Recording</span>
          <span v-if="room.muted" class="badge muted">Muted</span>
        </div>

        <div class="conference-actions">
          <button @click="toggleLock(room)">
            {{ room.locked ? 'Unlock' : 'Lock' }}
          </button>
          <button @click="toggleRecording(room)">
            {{ room.recording ? 'Stop Recording' : 'Start Recording' }}
          </button>
          <button @click="viewParticipants(room)">View Participants</button>
        </div>
      </div>
    </div>

    <!-- Participant List -->
    <div v-if="selectedRoom" class="participants">
      <h2>Participants in {{ selectedRoom.conference }}</h2>
      <div v-for="user in roomParticipants" :key="user.channel" class="participant-card">
        <div class="participant-info">
          <span class="name">{{ user.callerIdName || user.callerIdNum }}</span>
          <span v-if="user.admin" class="badge admin">Admin</span>
          <span v-if="user.talking" class="badge talking">Speaking</span>
        </div>

        <div class="participant-actions">
          <button @click="toggleMute(user)">
            {{ user.muted ? 'Unmute' : 'Mute' }}
          </button>
          <button @click="kick(user)" class="danger">Kick</button>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'
import { useAmi, useAmiConfBridge } from 'vuesip'
import type { ConfBridgeRoom, ConfBridgeUser } from 'vuesip'

const ami = useAmi()
const {
  roomList,
  userList,
  lockRoom,
  unlockRoom,
  startRecording,
  stopRecording,
  muteUser,
  unmuteUser,
  kickUser,
  listUsers,
} = useAmiConfBridge(computed(() => ami.getClient()))

const selectedRoom = ref<ConfBridgeRoom | null>(null)

const roomParticipants = computed(() =>
  userList.value.filter((u) => u.conference === selectedRoom.value?.conference)
)

async function viewParticipants(room: ConfBridgeRoom) {
  selectedRoom.value = room
  await listUsers(room.conference)
}

async function toggleLock(room: ConfBridgeRoom) {
  if (room.locked) {
    await unlockRoom(room.conference)
  } else {
    await lockRoom(room.conference)
  }
}

async function toggleRecording(room: ConfBridgeRoom) {
  if (room.recording) {
    await stopRecording(room.conference)
  } else {
    await startRecording(room.conference)
  }
}

async function toggleMute(user: ConfBridgeUser) {
  if (user.muted) {
    await unmuteUser(user.conference, user.channel)
  } else {
    await muteUser(user.conference, user.channel)
  }
}

async function kick(user: ConfBridgeUser) {
  await kickUser(user.conference, user.channel)
}
</script>

Real-time Events

Subscribe to conference events for live updates:

typescript
const { rooms, users } = useAmiConfBridge(
  computed(() => ami.getClient()),
  {
    // Auto-refresh room list on connect
    autoRefresh: true,

    // Handle user join events
    onUserJoin: (user) => {
      console.log(`${user.callerIdName} joined ${user.conference}`)
      showNotification(`${user.callerIdName} joined the conference`)
    },

    // Handle user leave events
    onUserLeave: (user) => {
      console.log(`${user.callerIdName} left ${user.conference}`)
    },

    // Handle talking status changes
    onTalkingChange: (user, isTalking) => {
      if (isTalking) {
        console.log(`${user.callerIdName} is speaking`)
        // Update active speaker indicator
      }
    },

    // Optional: Filter conferences
    conferenceFilter: (room) => {
      // Only show conferences starting with '100'
      return room.conference.startsWith('100')
    },

    // Optional: Transform user data
    transformUser: (user) => ({
      ...user,
      displayName: user.callerIdName || `Ext ${user.callerIdNum}`,
    }),
  }
)

Recording Management

typescript
// Start recording with custom filename
const result = await startRecording(
  '1000',
  '/var/spool/asterisk/monitor/conf-1000-${TIMESTAMP}.wav'
)

if (result.success) {
  console.log('Recording started')
} else {
  console.error('Recording failed:', result.error)
}

// Stop recording
await stopRecording('1000')

Video Conferencing

For video conferences, set the single video source:

typescript
// Set a specific participant as the video source
await setVideoSource('1000', 'PJSIP/1001-00000001')

// The video source participant's video will be broadcast to all

Note: Video mode must be configured in confbridge.conf:

ini
[video_bridge]
type=bridge
video_mode=single_source  ; or follow_talker

API Reference

ConfBridgeRoom Interface

FieldTypeDescription
conferencestringConference name/number
partiesnumberNumber of participants
lockedbooleanWhether room is locked
mutedbooleanWhether room is globally muted
recordingbooleanRecording active
markedUsersnumberCount of admin/marked users

ConfBridgeUser Interface

FieldTypeDescription
conferencestringConference name
callerIdNumstringCaller's number
callerIdNamestringCaller's display name
channelstringChannel identifier
adminbooleanIs admin/moderator
markedbooleanIs marked user
mutedbooleanIs muted
talkingbooleanCurrently talking
joinedAtDateJoin timestamp

Actions

MethodParametersDescription
listRooms()-Fetch all active conferences
listUsers(conference)conference: stringFetch users in a conference
lockRoom(conference)conference: stringLock conference (no new joins)
unlockRoom(conference)conference: stringUnlock conference
startRecording(conference, file?)conference: string, file?: stringStart recording
stopRecording(conference)conference: stringStop recording
muteUser(conference, channel)conference: string, channel: stringMute participant
unmuteUser(conference, channel)conference: string, channel: stringUnmute participant
kickUser(conference, channel)conference: string, channel: stringRemove participant
setVideoSource(conference, channel)conference: string, channel: stringSet video source
refresh()-Refresh all room and user data

AMI Events Reference

VueSIP automatically handles these AMI events:

EventDescription
ConfbridgeJoinUser joined conference
ConfbridgeLeaveUser left conference
ConfbridgeTalkingUser talking status changed
ConfbridgeMuteUser was muted
ConfbridgeUnmuteUser was unmuted
ConfbridgeLockConference was locked
ConfbridgeUnlockConference was unlocked
ConfbridgeRecordRecording started

Troubleshooting

No Conferences Visible

  1. Verify ConfBridge is loaded:

    bash
    asterisk -rx "module show like app_confbridge"
    # Should show: app_confbridge.so
  2. Check active conferences:

    bash
    asterisk -rx "confbridge list"
  3. Verify AMI permissions:

    bash
    asterisk -rx "manager show user vuesip"
    # Check read permissions include 'call'

Lock/Recording Actions Fail

  1. Check write permissions:

    • AMI user needs write = call permission
  2. Verify conference exists:

    bash
    asterisk -rx "confbridge show 1000"
  3. Check for error responses:

    typescript
    const result = await lockRoom('1000')
    if (!result.success) {
      console.error('Lock failed:', result.error)
    }

Talking Events Not Working

  1. Enable talk detection in confbridge.conf:

    ini
    [default_bridge]
    type=bridge
    talk_detection_events=yes
  2. Reload configuration:

    bash
    asterisk -rx "module reload app_confbridge.so"

Recording Path Issues

  1. Ensure directory exists and is writable:

    bash
    mkdir -p /var/spool/asterisk/monitor
    chown asterisk:asterisk /var/spool/asterisk/monitor
  2. Use absolute paths:

    typescript
    await startRecording('1000', '/var/spool/asterisk/monitor/conf-1000.wav')

Security Best Practices

  1. Restrict AMI permissions - Only grant required read/write permissions
  2. Use conference PINs - Configure user/admin PINs for access control
  3. Enable TLS - Use WSS for WebSocket connections
  4. Limit conference sizes - Set max_members in bridge profiles
  5. Monitor recordings - Implement retention policies for recorded conferences

External Resources

Released under the MIT License.