Skip to main content

Updating Conference Recordings in SignalWire

Overview

When managing conference calls using SignalWire, it's sometimes necessary to pause and resume call recordings dynamically. This guide offers an in-depth look into how to update active conference recordings utilizing the SignalWire REST Client Python SDK as well as SignalWire's REST endpoints.

Utilizing SignalWires REST Client SDKs

We will first cover how to accomplish updating an active conference recording utilizing the SignalWire REST Client SDK. In the below example, we will be using the Python SDK, along with Ngrok and Flask to help our server. Compatibility REST Client SDK.

SignalWire Client SDK Initialization

To interact with SignalWire, we start by initializing the SignalWire client SDK. This requires your SignalWire Project ID, Auth Token, and the Space URL.

from signalwire.rest import Client as SignalWireClient

space_url = 'EXAMPLE.signalwire.com'
project_id = 'PROJECT TOKEN HERE'
auth_token = 'AUTH TOKEN HERE'
client = SignalWireClient(project_id, auth_token, signalwire_space_url=space_url)

Setting Up the Conference with Detailed Callbacks

When a new inbound call is made, it is directed into a conference. Alongside this, we store the details of ongoing conferences, including the recording Session ID (SID) and the recording status (whether it's active or paused).

Examining the conference setup:

@app.route('/inbound', methods=['POST', 'GET'])
def inbound_call():
...
response = VoiceResponse()
dial = Dial()
dial.conference(
name=call_sid,
record='record-from-start',
start_conference_on_enter=True,
status_callback=f'{public_url}/callback',
status_callback_event='leave join',
recording_status_callback=f'{public_url}/store_recording_sid',
recording_status_callback_event='in-progress',
trim='do-not-trim'
)
response.append(dial)
return str(response)

Storing Conference Recording SIDs

All conference information is store in a global dictionary called active_conferences.

active_conferences = {}

This global dictionary will keep track of all conference_sid's added to it. Once a recording has started, we will send the event back to our recording_status_callback url. The conference_sid and the recording_sid will be passed in this request as ConferenceSid and RecordingSid. Once we acquire the recording_sid we can then add it to active_conferences in the corresponding conference_sid. Additionally, we also pass a value of recording_active to keep track if that conferences recording is paused or in-progress.

@app.route('/store_recording_sid', methods=['POST', 'GET'])
def update_recording():
conference_sid = request.form.get('ConferenceSid')
recording_sid = request.form.get('RecordingSid')
active_conferences[conference_sid] = {"recording_sid": recording_sid, "recording_active": True}
return '200'

Using Callbacks to Manage Conference Recordings

To update the status of a conference recording, while utilizing the SignalWire Client SDK, we will use the update method on the conference recording object. This method requires two arguments:

  • pause_behavior: This determines the behavior of the recording when paused. In this guide, we set it to 'skip', which means the recording will skip over the paused segment. The argument 'silence' will allow the recording to continue, but it will remove all noise out of the recording. Both arguments will continue their behavior until the recording is continued or has ended.
  • status: This sets the recording status. It can be either 'paused' or 'in-progress'.

Below are examples on how to use the Client SDK to update a conference recording.

Pausing the conference recording
recording = client.calls(conference_sid) \
.recordings(recording_sid) \
.update(status='paused', pause_behavior='skip')
Resuming the conference recording
recording = client.calls(conference_sid) \
.recordings(recording_sid) \
.update(status='in-progress', pause_behavior='skip')

Dynamically Updating Recordings Base on Participant Events

The actual decision to pause or resume a recording is based on the number of participants in the conference.

  1. If the callback event indicates a participant has left ('participant-leave') and there are fewer than 2 participants remaining, the recording is paused.
  2. Conversely, if the callback event indicates a new participant has joined ('participant-join') and there are more than 1 participant, the recording is resumed.

Here's how this logic is implemented:

@app.route('/callback', methods=['GET', 'POST'])
def callback():
conference_sid = request.form.get('ConferenceSid')
callback_event = request.form.get('StatusCallbackEvent')
recording_data = active_conferences.get(conference_sid, {"recording_sid": None, "recording_active": False})
recording_sid = recording_data["recording_sid"]
is_active = recording_data["recording_active"]

participants = client.conferences(conference_sid).participants.list(limit=50)

if callback_event == 'participant-leave' and len(participants) < 2:
if recording_sid and is_active:
client.conferences(conference_sid).recordings(recording_sid).update(pause_behavior='skip', status='paused')
active_conferences[conference_sid]["recording_active"] = False
elif callback_event == 'participant-join' and len(participants) > 1:
if recording_sid and not is_active:
client.conferences(conference_sid).recordings(recording_sid).update(pause_behavior='skip', status='in-progress')
active_conferences[conference_sid]["recording_active"] = True
return '200'

Example Script

To bring everything together, here's the full script:

from flask import Flask, request, jsonify
from signalwire.rest import Client as SignalWireClient
from signalwire.voice_response import VoiceResponse, Dial
from pyngrok import ngrok

# Initialization
app = Flask(__name__)
space_url = 'EXAMPLE.signalwire.com'
project_id = 'PROJECT ID HERE'
auth_token = 'AUTH TOKEN HERE'
client = SignalWireClient(project_id, auth_token, signalwire_space_url=space_url)
public_url = ''
active_conferences = {}
call_sid = ''


@app.route('/inbound', methods=['POST', 'GET'])
def inbound_call():
global call_sid
if not call_sid:
call_sid = request.form.get('CallSid')
response = VoiceResponse()
dial = Dial()
dial.conference(name=call_sid,
record='record-from-start',
start_conference_on_enter=True,
status_callback=f'{public_url}/callback',
status_callback_event='leave join',
recording_status_callback=f'{public_url}/store_recording_sid',
recording_status_callback_event='in-progress',
trim='do-not-trim')
response.append(dial)
return str(response)


@app.route('/store_recording_sid', methods=['POST', 'GET'])
def store_recording_sid():
conference_sid = request.form.get('ConferenceSid')
recording_sid = request.form.get('RecordingSid')
active_conferences[conference_sid] = {"recording_sid": recording_sid, "recording_active": True}
return '200'


@app.route('/callback', methods=['GET', 'POST'])
def callback():
conference_sid = request.form.get('ConferenceSid')
callback_event = request.form.get('StatusCallbackEvent')
recording_data = active_conferences.get(conference_sid, {"recording_sid": None, "recording_active": False})
recording_sid = recording_data["recording_sid"]
is_active = recording_data["recording_active"]
participants = client.conferences(conference_sid).participants.list(limit=50)
if callback_event == 'participant-leave' and len(participants) < 2:
if recording_sid and is_active:
client.conferences(conference_sid).recordings(recording_sid).update(pause_behavior='skip', status='paused')
active_conferences[conference_sid]["recording_active"] = False
elif callback_event == 'participant-join' and len(participants) > 1:
if recording_sid and not is_active:
client.conferences(conference_sid).recordings(recording_sid).update(pause_behavior='skip',
status='in-progress')
active_conferences[conference_sid]["recording_active"] = True
return '200'


if __name__ == '__main__':
public_url = ngrok.connect(5000).public_url
print(f"Webhook is live at {public_url}")
app.run(port=5000)

Utilizing SignalWires Compatibility REST API

The REST API provides a way for developers to interact with SignalWire services without needing to use the SDKs. This is particularly useful for platforms or situations where an SDK might not be available or ideal. When using the REST API, it's essential to be familiar with the HTTP methods (GET, POST, etc.) and to have a tool or library to make these requests.

For the sake of demonstration, I'll be using curl command-line tool to make the API requests:

Authentication of REST API endpoints

Almost all the REST API endpoints are protected with HTTP Basic Authentication. HTTP Basic Authentication requires you to send an Authorization header of your Project ID and Authentication in Base64 format. Example:

curl -L -X GET "https://<$EXAMPLE>.signalwire.com/api/laml/2010-04-01/Accounts/<$PROJECT_ID>" \
-H "Authorization: Basic <$Base64_Token>"

This should be supported in almost all HTTP clients.

Setting up the conference with callbacks using the REST API

# Making a call and connecting it to a conference
curl -L -X POST "https://<$EXAMPLE>.signalwire.com/api/laml/2010-04-01/Accounts/<$PROJECT_ID>/Calls" \
-H 'Content-Type: application/x-www-form-urlencoded'
-H 'Accept: application/json'
-H 'Authorization: Basic <$Base64Token>'
--data-urlencode 'To=<Destination Number Here>'
--data-urlencode 'From=<SignalWire Number Here!>'
--data-urlencode 'Url=<https://server_example.com/webhook'

In the above command:

  • You're making a POST request to SignalWire to initiate a call.
  • The -u flag is used for basic HTTP authentication, where you'll provide your PROJECT_ID and AUTH_TOKEN.
  • The To and From fields define the destination and source phone numbers respectively.

When your endpoint (specified in the Url parameter) is called by SignalWire, you can return XML like:

<Response>
<Dial>
<Conference
startConferenceOnEnter="true"
record="record-from-start"
statusCallback="<YOUR_SERVER_ENDPOINT_FOR_STATUS_CALLBACKS>"
statusCallbackEvent="leave join end"
recordingStatusCallback="<YOUR_SERVER_ENDPOINT_FOR_RECORDING_STATUS>"
recordingStatusCallbackEvent='in-progress'
trim='true'>
UniqueConferenceName
</Conference>
</Dial>
</Response>

Storing Conference Recording SIDs using REST API

When a conference call is recorded, each recording is associated with a unique Recording SID (Session ID). To manage these recordings (e.g., to pause, resume, or delete them), you first need to know their SIDs. The curl command provided fetches all the recordings associated with a specific conference.

# Fetching the list of recordings for a specific conference
curl -X GET "https://<$EXAMPLE>.signalwire.com/api/laml/2010-04-01/Accounts/$PROJECT_ID/Conferences/<$CONFERENCE_SID>/Recordings.json" \
-H "Authorization: Basic <$Base64_Token>"

Response:

The API will respond with a JSON array containing the details of each recording associated with the specified conference. Each object in the array will include information such as the Recording SID, the start time of the recording, its duration, and its URI.

For Example, a response might look like:

{
"uri": "/api/laml/2010-04-01/Accounts/b08d.../Conferences/bc18.../Recordings?Page=0&PageSize=50",
"first_page_uri": "/api/laml/2010-04-01/Accounts/b08d.../Conferences/bc18.../Recordings?Page=0&PageSize=50",
"next_page_uri": null,
"previous_page_uri": null,
"page": 0,
"page_size": 50,
"recordings": [
{
"sid": "0172....",
"api_version": "2010-04-01",
"channel": "1",
"channels": "1",
"account_sid": "b08d....",
"call_sid": null,
"conference_sid": "bc18...",
"date_created": "Wed, 30 Aug 2023 17:12:03 +0000",
"date_updated": "Wed, 30 Aug 2023 17:12:37 +0000",
"duration": 20,
"start_time": "Wed, 30 Aug 2023 17:12:03 +0000",
"end_time": "Wed, 30 Aug 2023 17:12:37 +0000",
"error_code": null,
"price": 0.0,
"price_unit": "USD",
"source": "Conference",
"status": "completed",
"uri": "/api/laml/2010-04-01/Accounts/b08d.../Conferences/bc18.../Recordings/0172...89.json",
"subresource_uris": {
"transcriptions": "/api/laml/2010-04-01/Accounts/b08d.../Conferences/bc18.../Recordings/0172...89/Transcriptions.json"
},
"encryption_details": null,
"trim": null
}
]
}

Using Callbacks to Manage Conference Recordings with REST API

Listing participants

When SignalWire sends a callback event to your status callback webhook, we can use the following curl command to list all participants in the conference when a user joins or leaves.

curl -L -X GET "https://<$EXAMPLE>.signalwire.com/api/laml/2010-04-01/Accounts/23123/Conferences/<$CONFERENCE_SID>/Participants" \
-H 'Accept: application/json' \
-H "Authorization: Basic <$BASE64_Token>"

This way we can manage the conference recording so that it will pause as long as there as less than 2 participants in the conference.

Updating the Conference Recording

Pausing a recording
curl -X POST "https://<$EXAMPLE>.signalwire.com/api/laml/2010-04-01/Accounts/$PROJECT_SID/Conferences/<$CONFERENCE_SID>/Recordings/<$RECORDING_SID>.json" \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: application/json' \
-H "Authorization: Basic <$Base64_Token>" \
-d "Status=paused"
Resuming a recording
curl -X POST "https://<$EXAMPLE>.signalwire.com/api/laml/2010-04-01/Accounts/$PROJECT_ID/Conferences/<$CONFERENCE_SID>/Recordings/<$RECORDING_SID>.json" \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: application/json' \
-H "Authorization: Basic <$Base64_Token>" \
-d "Status=in-progress"

Remember to replace placeholders like <CONFERENCE_SID>, <RECORDING_SID>, <BASE64_Token> <PROJECT_ID> and <EXAMPLE> with appropriate values.

In conclusion, SignalWire's Compatibility Client SDK and API offer flexible ways to manage conference recordings. Pausing and resuming recordings based on participants' activity ensures efficient resource usage and improves the user experience.