<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<base href="https://wiki.asterisk.org/wiki" />
<title>Message Title</title>
<style type="text/css">@media only screen and (max-device-width: 480px) {.mobile-only {
width: auto !important;
height: auto !important;
overflow: visible !important;
line-height: normal !important;
font-size: inherit !important;
mso-hide: all;
}
.desktop-only {
display: none !important;
}
/* iPhone 3GS fix for unwanted 20px right margin */
body { min-width: 100% !important; padding: 0; margin: 0; }
#center-content-table { max-width: none; !important; }
#header-pattern-container { padding: 10px 10px 10px 10px !important; line-height: 20px !important; }
#header-avatar-image-container { padding-right: 8px !important; }
#email-content-container { padding: 0 !important; }
.mobile-expand { border-radius: 0 !important; border-left: 0 !important; border-right: 0 !important; padding-left: 26px !important;}
.mobile-resize-text { font-size: 16px !important; line-height: 22px !important; }
#page-title-pattern-header { font-size: 20px !important; line-height: 28px !important; }
#page-title-pattern-icon-image-container-cell { padding-top: 7px !important; }
#inline-user-pattern { display: block !important; }
#inline-user-pattern-avatar { padding-top: 3px !important; }
.contextual-area-pattern { border-bottom: 1px solid #ccc !important; padding: 15px 10px 0 10px !important;}
.users-involved-pattern-column-table { width: 100% !important; }
.users-involved-pattern-avatar-table-cell { padding: 3px 5px 5px 0 !important; }
.users-involved-pattern-column-container { padding-right: 0 !important; }
.contextual-excerpt-pattern, #users-involved-pattern { border: 0 !important; }
/** Aui Typography upsized for mobile **/
#content-excerpt-pattern-container, #contextual-excerpt-pattern-text-container { font-size: 16px !important; line-height: 22px !important; }
#content-excerpt-pattern-container h1, #contextual-excerpt-pattern-text-container h1 { font-size: 24px !important; line-height: 28px !important; }
#content-excerpt-pattern-container h2, #contextual-excerpt-pattern-text-container h2 { font-size: 20px !important; line-height: 28px !important; }
#content-excerpt-pattern-container h3, #contextual-excerpt-pattern-text-container h3 { font-size: 18px !important; line-height: 24px !important; }
#content-excerpt-pattern-container h4, #contextual-excerpt-pattern-text-container h4 { font-size: 16px !important; line-height: 22px !important; }
#content-excerpt-pattern-container h5, #contextual-excerpt-pattern-text-container h5 { font-size: 14px !important; line-height: 20px !important; }
#content-excerpt-pattern-container h6, #contextual-excerpt-pattern-text-container h6 { font-size: 14px !important; line-height: 20px !important; }
.user-mention { line-height: 18px !important; }
/** Aui Typography end **/
/* Show appropriate footer logo on mobile, display links vertically */
#footer-pattern { padding: 15px 10px !important; }
#footer-pattern-logo-desktop-container { padding: 0 !important; }
#footer-pattern-logo-desktop { width: 0 !important; height: 0 !important; }
#footer-pattern-logo-mobile {
padding-top: 10px !important;
width: 30px !important;
height: 27px !important;
display: inline !important;
}
#footer-pattern-text {
display: block !important;
}
#footer-pattern-links-container { line-height: 0 !important;}
.footer-pattern-links.mobile-resize-text,
.footer-pattern-links.mobile-resize-text,
#footer-pattern-text.mobile-resize-text,
#footer-pattern-links-container.no-footer-links {
font-size: 14px !important;
line-height: 20px !important;
}
.footer-link { display: block !important; }
#footer-pattern-links-container table { display: inline-block !important; float: none !important; }
#footer-pattern-links-container, #footer-pattern-text { text-align: center !important; }
#footer-pattern-links { padding-bottom: 5px !important; }
/** Team Calendar overrides, these should be removed when notifications are updated in Team Calendars. For now CSS
overrides are being used because the structure of the content can't change without rereleasing the plugin */
.mail-calendar-container .day-header + table tr td:first-child {
vertical-align: top !important;
padding-top: 5px !important;
}}
@media (min-width: 900px) {#center-content-table { width: 900px; }}
@media all {#outlook a {padding:0;} /* Force Outlook to provide a "view in browser" menu link. */
/* Prevent Webkit and Windows Mobile platforms from changing default font sizes.*/
body{-webkit-text-size-adjust:100%; -ms-text-size-adjust:100%;}
.ExternalClass {width:100%;} /* Force Hotmail to display emails at full width */
#background-table {margin:0; padding:0; width:100% !important; }
/* Needed to override highlighting on date and time links in iOS */
.grey a {color: #707070; text-decoration: none; }/* These styles are appended to the head element of a notification in order to prevent Apple Mail and similar
clients from underlining the due dates with a blue hyperlink */
/* a lozenge outside an inline task should always be #333, lozenges inside an inline task should be
colored according to their upcoming due dates, a completed task date lozenge or deleted task date
lozenge should always be #707070 */
.date-time-lozenge a {color: #333333; text-decoration: none; }
.inline-task-text-container .date-time-lozenge.date-upcoming a {color: #DF6F00; text-decoration: none; }
.inline-task-text-container .date-time-lozenge.date-past a {color: #D04437; text-decoration: none; }
.inline-task-text-container.content-deleted-color .date-time-lozenge a,
.inline-task-text-container.checked .date-time-lozenge a {
color: #707070; text-decoration: none;
}}
</style>
</head>
<body>
<table id="background-table" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333; background-color: #f5f5f5">
<tbody>
<tr>
<td id="header-pattern-container" style="padding: 0px; border-collapse: collapse; padding: 10px 20px">
<table id="header-pattern" cellspacing="0" cellpadding="0" border="0" style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td id="header-avatar-image-container" valign="top" style="padding: 0px; border-collapse: collapse; vertical-align: top; width: 32px; padding-right: 9px"><a href="https://wiki.asterisk.org/wiki/display/~mmichelson?src=email" style="color: #3b73af; text-decoration: none"><img id="header-avatar-image" class="image_fix" src="cid:avatar_99ed0aa29d6f204db4785296f8170422" height="32" width="32" border="0" style="border-radius: 3px; vertical-align: top" /></a></td>
<td id="header-text-container" valign="middle" style="padding: 0px; border-collapse: collapse; vertical-align: middle; font-family: Arial, sans-serif; font-size: 14px; line-height: 20px; mso-line-height-rule: exactly; mso-text-raise: 1px">Mark Michelson <strong>created</strong> a page</td>
</tr>
</tbody>
</table> </td>
</tr>
<!-- End Header pattern -->
<tr>
<td id="email-content-container" style="padding: 0px; border-collapse: collapse; padding: 0 20px">
<table id="email-content-table" cellspacing="0" cellpadding="0" border="0" width="100%" style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333; border-spacing: 0; border-collapse: separate">
<tbody>
<tr>
<td class="email-content-rounded-top mobile-expand" style="padding: 0px; border-collapse: collapse; color: #fff; padding: 0 15px 0 16px; height: 15px; background-color: #fff; border-left: 1px solid #ccc; border-top: 1px solid #ccc; border-right: 1px solid #ccc; border-bottom: 0; border-top-right-radius: 5px; border-top-left-radius: 5px"> </td>
</tr>
<tr>
<td class="email-content-main mobile-expand" style="padding: 0px; border-collapse: collapse; border-left: 1px solid #ccc; border-right: 1px solid #ccc; border-top: 0; border-bottom: 0; padding: 0 15px 15px 16px; background-color: #fff">
<table id="page-title-pattern" cellspacing="0" cellpadding="0" border="0" width="100%" style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td id="page-title-pattern-icon-image-container" valign="top" style="padding: 0px; border-collapse: collapse; width: 16px; vertical-align: top">
<table cellspacing="0" cellpadding="0" border="0" style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td id="page-title-pattern-icon-image-container-cell" style="padding: 0px; border-collapse: collapse; width: 16px; padding: 9px 8px 0px 0px; mso-text-raise: 5px; mso-line-height-rule: exactly"><a href="https://wiki.asterisk.org/wiki/display/AST/Recording?src=email" title="page icon" style="vertical-align: top;; color: #3b73af; text-decoration: none"><img style="vertical-align: top; display: block;" src="cid:page-icon" alt="page icon" title="page icon" height="16" width="16" border="0" /></a></td>
</tr>
</tbody>
</table> </td>
<td style="vertical-align: top;; padding: 0px; border-collapse: collapse; padding-right: 5px; font-size: 20px; line-height: 30px; mso-line-height-rule: exactly" id="page-title-pattern-header-container"><span id="page-title-pattern-header" style="font-family: Arial, sans-serif; padding: 0; font-size: 20px; line-height: 30px; mso-text-raise: 2px; mso-line-height-rule: exactly; vertical-align: middle"><a href="https://wiki.asterisk.org/wiki/display/AST/Recording?src=email" title="Recording" style="color: #3b73af; text-decoration: none">Recording</a></span></td>
</tr>
</tbody>
</table> </td>
</tr>
<tr>
<td class="email-content-main mobile-expand" style="padding: 0px; border-collapse: collapse; border-left: 1px solid #ccc; border-right: 1px solid #ccc; border-top: 0; border-bottom: 0; padding: 0 15px 15px 16px; background-color: #fff">
<table class="content-excerpt-pattern" cellspacing="0" cellpadding="0" border="0" width="100%" style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333; font-family: Arial, sans-serif; font-size: 14px; line-height: 20px; mso-line-height-rule: exactly; mso-text-raise: 1px">
<tbody>
<tr>
<td class="content-excerpt-pattern-container mobile-resize-text " style="padding: 0px; border-collapse: collapse; padding: 0 0 0 24px">
<div class="sectionColumnWrapper">
<div class="sectionMacro">
<div class="sectionMacroRow">
<div class="columnMacro">
<h1 id="Recording-TheAPI" style="margin: 10px 0 0 0; margin-top: 0; font-size: 24px; font-weight: normal; line-height: 30px; margin: 40px 0 0 0; margin-top: 0">The API</h1>
<p style="margin: 10px 0 0 0">Recordings in ARI are divided into two main categories: live and stored. Live recordings are recordings that are currently being used on a channel or bridge, and stored recordings are recordings that have been finished and stored on the file system. The API for the <code style="font-family: monospace">/recordings</code> resource can be found here.</p>
<p style="margin: 10px 0 0 0">Live recordings can be manipulated as they are being made, with options to manipulate the flow of audio such as muting, pausing, stopping, or canceling the recording. Stored recordings are simply files on the file system on which Asterisk is installed. The location of stored recordings is in the <code style="font-family: monospace">/recording</code> subdirectory of the configured <code style="font-family: monospace">astspooldir</code> in <code style="font-family: monospace">asterisk.conf</code>. By default, this places recordings in <code style="font-family: monospace">/var/spool/asterisk/recording</code>.</p>
<p style="margin: 10px 0 0 0">Channels can have their audio recorded using the <code style="font-family: monospace">/channels/{channelId}/record</code> resource, and Bridges can have their audio recorded using the <code style="font-family: monospace">/bridges/{bridgeId}/record</code> resource.</p>
<h1 id="Recording-RecordingVoicemails" style="margin: 10px 0 0 0; font-size: 24px; font-weight: normal; line-height: 30px; margin: 40px 0 0 0">Recording Voicemails</h1>
<p style="margin: 10px 0 0 0">Now that we have discussed the state machine in great depth, let's apply it to a very simple case. When a caller enters the application, the caller should be able to record a message. When the caller has completed recording the message, the caller may press the '#' key or may hang up to end recording and accept the recording. Here is a state machine diagram for the application:</p>
<p style="text-align: center;; margin: 10px 0 0 0"> <span id="gliffy-container-30474296-3396" class="gliffy-container " data-fullwidth="520" data-ceoid="30277826" data-filename="record"> <span class="gliffy-chrome-container"> <span class="gliffy-chrome "> <span class="gliffy-item gliffy-first"> <img class="gliffy-logo" src="/wiki/download/resources/com.gliffy.integration.confluence/icons/logo_16x16.png" title="Gliffy" alt="Gliffy" /> Gliffy </span> </span> </span> <map id="gliffy-map-30474296-7920" name="gliffy-map-30474296-7920"></map> <img id="gliffy-image-30474296-3396" class="gliffy-image " width="520" height="535" data-full-width="520" data-full-height="535" src="/wiki/download/attachments/30277826/record.png?version=1&modificationDate=1412016117502&api=v2" alt="record" usemap="#gliffy-map-30474296-7920" /> <map id="gliffy-dynamic-map-30474296-3396" class="gliffy-dynamic" name="gliffy-dynamic-map-30474296-3396"></map> </span> </p>
<p style="margin: 10px 0 0 0">For this, we will be defining three states: recording, hungup, and ending. The following is the code for the three states:</p>
</div>
<div class="columnMacro">
<div class="panel" style="border-width: 1px;">
<div class="panelHeader" style="border-bottom-width: 1px;">
<b>On this Page</b>
</div>
<div class="panelContent">
<p style="margin: 10px 0 0 0; margin-top: 0"> <style type="text/css">/**/
div.rbtoc1412016121343 {padding: 0px;}
div.rbtoc1412016121343 ul {list-style: disc;margin-left: 0px;}
div.rbtoc1412016121343 li {margin-left: 0px;padding-left: 0px;}
/**/</style> </p>
<div class="toc-macro rbtoc1412016121343" style="padding: 0px; padding: 0px">
<ul class="toc-indentation" style="margin: 10px 0 0 0; margin-top: 0; list-style: disc; margin-left: 0px; list-style: disc; margin-left: 0px">
<li style="margin-left: 0px; padding-left: 0px; margin-left: 0px; padding-left: 0px"> <a href="#Recording-TheAPI" style="color: #3b73af; text-decoration: none">The API</a> </li>
<li style="margin-left: 0px; padding-left: 0px; margin-left: 0px; padding-left: 0px"> <a href="#Recording-RecordingVoicemails" style="color: #3b73af; text-decoration: none">Recording Voicemails</a> </li>
<li style="margin-left: 0px; padding-left: 0px; margin-left: 0px; padding-left: 0px"> <a href="#Recording-CancellingaRecording" style="color: #3b73af; text-decoration: none">Cancelling a Recording</a> </li>
<li style="margin-left: 0px; padding-left: 0px; margin-left: 0px; padding-left: 0px"> <a href="#Recording-OperatingonStoredRecordings" style="color: #3b73af; text-decoration: none">Operating on Stored Recordings</a> </li>
<li style="margin-left: 0px; padding-left: 0px; margin-left: 0px; padding-left: 0px"> <a href="#Recording-RecordingBridges" style="color: #3b73af; text-decoration: none">Recording Bridges</a> </li>
</ul>
</div>
<p style="margin: 10px 0 0 0"></p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="code panel pdl" style="border-width: 1px;">
<div class="codeHeader panelHeader pdl hide-border-bottom" style="border-bottom-width: 1px;">
<b class=" code-title">recording_state.py</b>
<span class="collapse-source expand-control"><span class="expand-control-icon icon"> </span><span class="expand-control-text">Expand source</span></span>
</div>
<div class="codeContent panelContent pdl hide-toolbar">
<pre class="theme: Confluence; brush: py; collapse: true; gutter: false" style="font-size:12px;; margin: 10px 0 0 0; margin-top: 0">from event import Event
class RecordingState(object):
state_name = "recording"
def __init__(self, call):
self.call = call
self.hangup_event = None
self.dtmf_event = None
self.recording = None
def enter(self):
print "Entering recording state"
self.hangup_event = self.call.channel.on_event('ChannelHangupRequest',
self.on_hangup)
self.dtmf_event = self.call.channel.on_event('ChannelDtmfReceived',
self.on_dtmf)
self.recording = self.call.channel.record(name=self.call.vm_path,
format='wav',
beep=True,
ifExists='overwrite')
print "Recording voicemail at {0}".format(self.call.vm_path)
def cleanup(self):
print "Cleaning up event handlers"
self.dtmf_event.close()
self.hangup_event.close()
def on_hangup(self, channel, event):
print "Accepted recording {0} on hangup".format(self.call.vm_path)
self.cleanup()
self.call.state_machine.change_state(Event.HANGUP)
def on_dtmf(self, channel, event):
digit = event.get('digit')
if digit == '#':
rec_name = self.recording.json.get('name')
print "Accepted recording {0} on DTMF #".format(rec_name)
self.cleanup()
self.recording.stop()
self.call.state_machine.change_state(Event.DTMF_OCTOTHORPE)</pre>
</div>
</div>
<div class="code panel pdl" style="border-width: 1px;">
<div class="codeHeader panelHeader pdl hide-border-bottom" style="border-bottom-width: 1px;">
<b class=" code-title">recording_state.js</b>
<span class="collapse-source expand-control"><span class="expand-control-icon icon"> </span><span class="expand-control-text">Expand source</span></span>
</div>
<div class="codeContent panelContent pdl hide-toolbar">
<pre class="theme: Confluence; brush: js; collapse: true; gutter: false" style="font-size:12px;; margin: 10px 0 0 0; margin-top: 0">var Event = require('./event')
var RecordingState = function(call) {
this.state_name = "recording";
this.call = call;
}
RecordingState.prototype.enter = function() {
var self = this;
var recording = self.call.client.LiveRecording(self.call.client, {name: self.call.vm_path});
console.log("Entering recording state");
self.call.channel.on("ChannelHangupRequest", on_hangup);
self.call.channel.on("ChannelDtmfReceived", on_dtmf);
self.call.channel.record({name: recording.name, format: 'wav', beep: true, ifExists: 'overwrite'}, recording);
function cleanup() {
self.call.channel.removeListener('ChannelHangupRequest', on_hangup);
self.call.channel.removeListener('ChannelDtmfReceived', on_dtmf);
}
function on_hangup(event, channel) {
console.log("Accepted recording %s on hangup", recording.name);
cleanup();
self.call.state_machine.change_state(Event.HANGUP);
}
function on_dtmf(event, channel) {
switch (event.digit) {
case '#':
console.log("Accepted recording", self.call.vm_path);
cleanup();
recording.stop(function(err) {
self.call.state_machine.change_state(Event.DTMF_OCTOTHORPE);
});
break;
}
}
}
module.exports = RecordingState;</pre>
</div>
</div> <p style="margin: 10px 0 0 0">When entered, the state sets up listeners for hangup and DTMF events on the channel, since those are the events that will cause the state to change. In all cases, before a state change occurs, the <code style="font-family: monospace">cleanup()</code> function is invoked to remove event listeners. This way, the event listeners set by the recording state will not accidentally still be set up when the next state is entered.</p> <p style="margin: 10px 0 0 0">The <code style="font-family: monospace">stop</code> method causes a live recording to finish and be saved to the file system. Notice that the <code style="font-family: monospace">on_hangup()</code> method does not attempt to stop the live recording. This is because when a channel hangs up, any live recordings on that channel are automatically stopped and stored.</p> <p style="margin: 10px 0 0 0">The other two states in the state machine are much simpler, since they are terminal states and do not need to watch for any events.</p>
<div class="code panel pdl" style="border-width: 1px;">
<div class="codeHeader panelHeader pdl hide-border-bottom" style="border-bottom-width: 1px;">
<b class=" code-title">ending_state.py</b>
<span class="collapse-source expand-control"><span class="expand-control-icon icon"> </span><span class="expand-control-text">Expand source</span></span>
</div>
<div class="codeContent panelContent pdl hide-toolbar">
<pre class="theme: Confluence; brush: py; collapse: true; gutter: false" style="font-size:12px;; margin: 10px 0 0 0; margin-top: 0">class EndingState(object):
state_name = "ending"
def __init__(self, call):
self.call = call
def enter(self):
channel_name = self.call.channel.json.get('name')
print "Ending voice mail call from {0}".format(channel_name)
self.call.channel.hangup()</pre>
</div>
</div>
<div class="code panel pdl" style="border-width: 1px;">
<div class="codeHeader panelHeader pdl hide-border-bottom" style="border-bottom-width: 1px;">
<b class=" code-title">ending_state.js</b>
<span class="collapse-source expand-control"><span class="expand-control-icon icon"> </span><span class="expand-control-text">Expand source</span></span>
</div>
<div class="codeContent panelContent pdl hide-toolbar">
<pre class="theme: Confluence; brush: js; collapse: true; gutter: false" style="font-size:12px;; margin: 10px 0 0 0; margin-top: 0">var EndingState = function(call) {
this.state_name = "ending";
this.call = call;
}
EndingState.prototype.enter = function() {
channel_name = this.call.channel.name;
console.log("Ending voice mail call from", channel_name);
this.call.channel.hangup();
}
module.exports = EndingState;</pre>
</div>
</div>
<div class="code panel pdl" style="border-width: 1px;">
<div class="codeHeader panelHeader pdl hide-border-bottom" style="border-bottom-width: 1px;">
<b class=" code-title">hangup_state.py</b>
<span class="collapse-source expand-control"><span class="expand-control-icon icon"> </span><span class="expand-control-text">Expand source</span></span>
</div>
<div class="codeContent panelContent pdl hide-toolbar">
<pre class="theme: Confluence; brush: py; collapse: true; gutter: false" style="font-size:12px;; margin: 10px 0 0 0; margin-top: 0">class HungUpState(object):
state_name = "hungup"
def __init__(self, call):
self.call = call
def enter(self):
channel_name = self.call.channel.json.get('name')
print "Channel {0} hung up".format(channel_name)</pre>
</div>
</div>
<div class="code panel pdl" style="border-width: 1px;">
<div class="codeHeader panelHeader pdl hide-border-bottom" style="border-bottom-width: 1px;">
<b class=" code-title">hungup_state.js</b>
<span class="collapse-source expand-control"><span class="expand-control-icon icon"> </span><span class="expand-control-text">Expand source</span></span>
</div>
<div class="codeContent panelContent pdl hide-toolbar">
<pre class="theme: Confluence; brush: js; collapse: true; gutter: false" style="font-size:12px;; margin: 10px 0 0 0; margin-top: 0">var HungUpState = function(call) {
this.state_name = "hungup";
this.call = call;
}
HungUpState.prototype.enter = function() {
channel_name = this.call.channel.name;
console.log("Channel %s hung up", channel_name);
}
module.exports = HungUpState;</pre>
</div>
</div> <p style="margin: 10px 0 0 0">These two states are two sides to the same coin. The <code style="font-family: monospace">EndingState</code> is used to end the call by hanging up the channel, and the <code style="font-family: monospace">HungUpState</code> is used to terminate the state machine when the caller has hung up.</p> <p style="margin: 10px 0 0 0">Using the application skeleton we set up earlier, we can make the following modifications to accommodate our state machine:</p>
<div class="code panel pdl" style="border-width: 1px;">
<div class="codeHeader panelHeader pdl hide-border-bottom" style="border-bottom-width: 1px;">
<b class=" code-title">vm-call.py</b>
<span class="collapse-source expand-control"><span class="expand-control-icon icon"> </span><span class="expand-control-text">Expand source</span></span>
</div>
<div class="codeContent panelContent pdl hide-toolbar">
<pre class="theme: Confluence; brush: py; collapse: true; gutter: false" style="font-size:12px;; margin: 10px 0 0 0; margin-top: 0"># At the top of the file
from recording_state import RecordingState
from ending_state import EndingState
from hungup_state import HungUpState
# Inside our VoiceMailCall class
def setup_state_machine(self):
hungup_state = HungUpState(self)
recording_state = RecordingState(self)
ending_state = EndingState(self)
self.state_machine = StateMachine()
self.state_machine.add_transition(recording_state, Event.DTMF_OCTOTHORPE,
ending_state)
self.state_machine.add_transition(recording_state, Event.HANGUP,
hungup_state)
self.state_machine.start(recording_state)</pre>
</div>
</div>
<div class="code panel pdl" style="border-width: 1px;">
<div class="codeHeader panelHeader pdl hide-border-bottom" style="border-bottom-width: 1px;">
<b class=" code-title">vm-call.js</b>
<span class="collapse-source expand-control"><span class="expand-control-icon icon"> </span><span class="expand-control-text">Expand source</span></span>
</div>
<div class="codeContent panelContent pdl hide-toolbar">
<pre class="theme: Confluence; brush: js; collapse: true; gutter: false" style="font-size:12px;; margin: 10px 0 0 0; margin-top: 0">// At the top of the file
var RecordingState = require('./recording_state');
var EndingState = require('./ending_state');
var HungUpState = require('./hungup_state');
// Inside our VoiceMailCall prototype
VoiceMailCall.prototype.setup_state_machine = function() {
var hungup_state = new HungUpState(self)
var recording_state = new RecordingState(self)
var ending_state = new EndingState(self)
this.state_machine = new StateMachine()
this.state_machine.add_transition(recording_state, Event.DTMF_OCTOTHORPE, ending_state)
this.state_machine.add_transition(recording_state, Event.HANGUP, hungup_state)
this.state_machine.start(recording_state)
}</pre>
</div>
</div> <p style="margin: 10px 0 0 0">We will create the following dialplan entry in <code style="font-family: monospace">extensions.conf</code> to be able to call into our voice mail recording application.</p>
<div class="preformatted panel" style="border-width: 1px;">
<div class="preformattedContent panelContent">
<pre style="margin: 10px 0 0 0; margin-top: 0">[default]
exten => _3XX,1,NoOp()
same => n,Stasis(vm-record, ${EXTEN})
same => n,Hangup()</pre>
</div>
</div> <p style="margin: 10px 0 0 0">This way, when calling any three-digit extension that begins with the number '3', the user will leave a voice mail message for the mailbox they dialled (e.g. dialling "305" will allow the user to leave a message for mailbox "305").</p> <p style="margin: 10px 0 0 0">The following is a sample output of a user calling the application and pressing the '#' key when finished recording</p>
<div class="preformatted panel" style="border-width: 1px;">
<div class="preformattedContent panelContent">
<pre style="margin: 10px 0 0 0; margin-top: 0">Channel PJSIP/200-00000003 recording voicemail for 305
Entering recording state
Recording voicemail at voicemail/305/1411497846.53
Accepted recording voicemail/305/1411497846.53
Cleaning up event handlers
Ending voice mail call from PJSIP/200-00000003</pre>
</div>
</div>
<div class="panel" style="background-color: silver;border-style: solid;border-width: 1px;">
<div class="panelHeader" style="border-bottom-width: 1px;border-bottom-style: solid;background-color: seagreen;color: black;">
<b>Reader Exercise 1</b>
</div>
<div class="panelContent" style="background-color: silver;">
Currently, the voicemails being recorded are all kept in a single "folder" for a specific mailbox. See if you can change the code to record messages in an "INBOX" folder on the mailbox instead.
</div>
</div>
<div class="panel" style="background-color: silver;border-style: solid;border-width: 1px;">
<div class="panelHeader" style="border-bottom-width: 1px;border-bottom-style: solid;background-color: seagreen;color: black;">
<b>Reader Exercise 2</b>
</div>
<div class="panelContent" style="background-color: silver;">
<p style="margin: 10px 0 0 0; margin-top: 0"> <code style="font-family: monospace">EndingState</code> and <code style="font-family: monospace">HungUpState</code> don't do much of anything at the moment. States like these can be great in voice mail applications for updating the message-waiting state of mailboxes on a system. If you're feeling industrious, read the API for the <code style="font-family: monospace">/mailboxes</code> resource in ARI. Try to change <code style="font-family: monospace">HungUpState</code> and <code style="font-family: monospace">EndingState</code> to update the message-waiting status of a mailbox when a new message is left. To keep the exercise simple, for now you can assume the following:</p>
<ul style="margin: 10px 0 0 0">
<li>The number of "old" messages in a mailbox is always 0</li>
<li>Since you are more concerned with alerting the mailbox owner that there exist new messages, do not try to count the messages in the mailbox. Instead, just state that there is 1 new message.</li>
</ul>
</div>
</div> <h1 id="Recording-CancellingaRecording" style="margin: 10px 0 0 0; font-size: 24px; font-weight: normal; line-height: 30px; margin: 40px 0 0 0">Cancelling a Recording</h1> <p style="margin: 10px 0 0 0">Now we have a simple application set up to record a message, but it's pretty bare at the moment. Let's start expanding some. One feature we can add is the ability to press a DTMF key while recording a voice mail to cancel the current recording and re-start the recording process. We'll use the DTMF '*' key to accomplish this. The updated state machine diagram looks like the following:</p> <p style="text-align: center;; margin: 10px 0 0 0"> <span id="gliffy-container-30474298-3598" class="gliffy-container " data-fullwidth="580" data-ceoid="30277826" data-filename="record-with-retry"> <span class="gliffy-chrome-container"> <span class="gliffy-chrome "> <span class="gliffy-item gliffy-first"> <img class="gliffy-logo" src="/wiki/download/resources/com.gliffy.integration.confluence/icons/logo_16x16.png" title="Gliffy" alt="Gliffy" /> Gliffy </span> </span> </span> <map id="gliffy-map-30474298-5626" name="gliffy-map-30474298-5626"></map> <img id="gliffy-image-30474298-3598" class="gliffy-image " width="580" height="505" data-full-width="580" data-full-height="505" src="/wiki/download/attachments/30277826/record-with-retry.png?version=1&modificationDate=1412016117456&api=v2" alt="record-with-retry" usemap="#gliffy-map-30474298-5626" /> <map id="gliffy-dynamic-map-30474298-3598" class="gliffy-dynamic" name="gliffy-dynamic-map-30474298-3598"></map> </span> </p> <p style="margin: 10px 0 0 0">All that has changed is that there is a new transition, which means a minimal change to our current code to facilitate the change. In our <code style="font-family: monospace">recording_state</code> file, we will rewrite the <code style="font-family: monospace">on_dtmf</code> method as follows:</p>
<div class="code panel pdl" style="border-width: 1px;">
<div class="codeHeader panelHeader pdl hide-border-bottom" style="border-bottom-width: 1px;">
<b class=" code-title">recording_state.py</b>
<span class="collapse-source expand-control"><span class="expand-control-icon icon"> </span><span class="expand-control-text">Expand source</span></span>
</div>
<div class="codeContent panelContent pdl hide-toolbar">
<pre class="theme: Confluence; brush: py; collapse: true; gutter: false" style="font-size:12px;; margin: 10px 0 0 0; margin-top: 0"> def on_dtmf(self, channel, event):
digit = event.get('digit')
if digit == '#':
rec_name = self.recording.json.get('name')
print "Accepted recording {0} on DTMF #".format(rec_name)
self.cleanup()
self.recording.stop()
self.call.state_machine.change_state(Event.DTMF_OCTOTHORPE)
# NEW CONTENT
elif digit == '*':
rec_name = self.recording.json.get('name')
print "Canceling recording {0} on DTMF *".format(rec_name)
self.cleanup()
self.recording.cancel()
self.call.state_machine.change_state(Event.DTMF_STAR)</pre>
</div>
</div>
<div class="code panel pdl" style="border-width: 1px;">
<div class="codeHeader panelHeader pdl hide-border-bottom" style="border-bottom-width: 1px;">
<b class=" code-title">recording_state.js</b>
<span class="collapse-source expand-control"><span class="expand-control-icon icon"> </span><span class="expand-control-text">Expand source</span></span>
</div>
<div class="codeContent panelContent pdl hide-toolbar">
<pre class="theme: Confluence; brush: js; collapse: true; gutter: false" style="font-size:12px;; margin: 10px 0 0 0; margin-top: 0"> function on_dtmf(event, channel) {
switch (event.digit) {
case '#':
console.log("Accepted recording", self.call.vm_path);
cleanup();
recording.stop(function(err) {
self.call.state_machine.change_state(Event.DTMF_OCTOTHORPE);
});
break;
// NEW CONTENT
case '*':
console.log("Canceling recording", self.call.vm_path);
cleanup();
recording.cancel(function(err) {
self.call.state_machine.change_state(Event.DTMF_STAR);
});
break;
}
}</pre>
</div>
</div> <p style="margin: 10px 0 0 0">The first part of the method is the same as it was before, but we have added extra handling for when the user presses the '*' key. The <code style="font-family: monospace">cancel()</code> method for live recordings causes the live recording to be stopped and for it not to be stored on the file system.</p> <p style="margin: 10px 0 0 0">We also need to add our new transition while setting up our state machine. Our <code style="font-family: monospace">VoiceMailCall::setup_state_machine()</code> method now looks like:</p>
<div class="code panel pdl" style="border-width: 1px;">
<div class="codeHeader panelHeader pdl hide-border-bottom" style="border-bottom-width: 1px;">
<b class=" code-title">vm-call.py</b>
<span class="collapse-source expand-control"><span class="expand-control-icon icon"> </span><span class="expand-control-text">Expand source</span></span>
</div>
<div class="codeContent panelContent pdl hide-toolbar">
<pre class="theme: Confluence; brush: py; collapse: true; gutter: false" style="font-size:12px;; margin: 10px 0 0 0; margin-top: 0"> def setup_state_machine(self):
hungup_state = HungUpState(self)
recording_state = RecordingState(self)
ending_state = EndingState(self)
self.state_machine = StateMachine()
self.state_machine.add_transition(recording_state, Event.DTMF_OCTOTHORPE,
ending_state)
self.state_machine.add_transition(recording_state, Event.HANGUP,
hungup_state)
self.state_machine.add_transition(recording_state, Event.DTMF_STAR,
recording_state)
self.state_machine.start(recording_state)</pre>
</div>
</div>
<div class="code panel pdl" style="border-width: 1px;">
<div class="codeHeader panelHeader pdl hide-border-bottom" style="border-bottom-width: 1px;">
<b class=" code-title">vm-call.js</b>
<span class="collapse-source expand-control"><span class="expand-control-icon icon"> </span><span class="expand-control-text">Expand source</span></span>
</div>
<div class="codeContent panelContent pdl hide-toolbar">
<pre class="theme: Confluence; brush: js; collapse: true; gutter: false" style="font-size:12px;; margin: 10px 0 0 0; margin-top: 0">VoiceMailCall.prototype.setup_state_machine = function() {
var hungup_state = new HungUpState(this);
var recording_state = new RecordingState(this);
var ending_state = new EndingState(this);
this.state_machine = new StateMachine();
this.state_machine.add_transition(recording_state, Event.DTMF_OCTOTHORPE, ending_state);
this.state_machine.add_transition(recording_state, Event.HANGUP, hungup_state);
this.state_machine.add_transition(recording_state, Event.DTMF_STAR, recording_state);
this.state_machine.start(recording_state);
}</pre>
</div>
</div> <p style="margin: 10px 0 0 0">This is exactly the same as it was, except for the penultimate line adding the <code style="font-family: monospace"><code style="font-family: monospace">Event.DTMF_STAR</code></code> transition. Here is sample output for when a user calls in, presses '*' twice, and then presses '#' to complete the call</p>
<div class="preformatted panel" style="border-width: 1px;">
<div class="preformattedContent panelContent">
<pre style="margin: 10px 0 0 0; margin-top: 0">Channel PJSIP/200-00000007 recording voicemail for 305
Entering recording state
Recording voicemail at voicemail/305/1411498790.65
Canceling recording voicemail/305/1411498790.65 on DTMF *
Cleaning up event handlers
Entering recording state
Recording voicemail at voicemail/305/1411498790.65
Canceling recording voicemail/305/1411498790.65 on DTMF *
Cleaning up event handlers
Entering recording state
Recording voicemail at voicemail/305/1411498790.65
Accepted recording voicemail/305/1411498790.65 on DTMF #
Cleaning up event handlers
Ending voice mail call from PJSIP/200-00000007</pre>
</div>
</div>
<div class="panel" style="background-color: silver;border-style: solid;border-width: 1px;">
<div class="panelHeader" style="border-bottom-width: 1px;border-bottom-style: solid;background-color: seagreen;color: black;">
<b>Reader Exercise 3</b>
</div>
<div class="panelContent" style="background-color: silver;">
<p style="margin: 10px 0 0 0; margin-top: 0">We have covered the <code style="font-family: monospace">stop()</code> and <code style="font-family: monospace">cancel()</code> methods, but live recordings provide other methods as well. In particular, there are <code style="font-family: monospace">pause()</code>, which causes the live recording to temporarily stop recording audio, and <code style="font-family: monospace">unpause()</code>, which causes the live recording to resume recording audio.</p>
<p style="margin: 10px 0 0 0">Modify <code style="font-family: monospace">RecordingState::on_dtmf()</code> to allow the DTMF '5' key to toggle pausing and unpausing the live recording.</p>
</div>
</div> <h1 id="Recording-OperatingonStoredRecordings" style="margin: 10px 0 0 0; font-size: 24px; font-weight: normal; line-height: 30px; margin: 40px 0 0 0">Operating on Stored Recordings</h1> <p style="margin: 10px 0 0 0">So far, we've recorded a channel, stopped a live recording, and cancelled a live recording. Now let's turn our attention to operations that can be performed on stored recordings. An obvious operation to start with is to play back the stored recording. We're going to make another modification to our voice mail recording application that adds a "reviewing" state after a voicemail is recorded. In this state, a user that has recorded a voice mail will hear the recorded message played back to him/her. The user may press the '#' key or hang up in order to accept the recorded message, or the user may press '*' to erase the stored recording and record a new message in its place. Below is the updated state diagram with the new "reviewing" state added.</p> <p style="text-align: center;; margin: 10px 0 0 0"> <span id="gliffy-container-30474300-25" class="gliffy-container " data-fullwidth="830" data-ceoid="30277826" data-filename="record-with-review"> <span class="gliffy-chrome-container"> <span class="gliffy-chrome "> <span class="gliffy-item gliffy-first"> <img class="gliffy-logo" src="/wiki/download/resources/com.gliffy.integration.confluence/icons/logo_16x16.png" title="Gliffy" alt="Gliffy" /> Gliffy </span> </span> </span> <map id="gliffy-map-30474300-1932" name="gliffy-map-30474300-1932"></map> <img id="gliffy-image-30474300-25" class="gliffy-image " width="830" height="477" data-full-width="830" data-full-height="477" src="/wiki/download/attachments/30277826/record-with-review.png?version=1&modificationDate=1412016117485&api=v2" alt="record-with-review" usemap="#gliffy-map-30474300-1932" /> <map id="gliffy-dynamic-map-30474300-25" class="gliffy-dynamic" name="gliffy-dynamic-map-30474300-25"></map> </span> </p> <p style="margin: 10px 0 0 0">To realize this, here is the code for our new "reviewing" state:</p>
<div class="code panel pdl" style="border-width: 1px;">
<div class="codeHeader panelHeader pdl hide-border-bottom" style="border-bottom-width: 1px;">
<b class=" code-title">reviewing_state.py</b>
<span class="collapse-source expand-control"><span class="expand-control-icon icon"> </span><span class="expand-control-text">Expand source</span></span>
</div>
<div class="codeContent panelContent pdl hide-toolbar">
<pre class="theme: Confluence; brush: py; collapse: true; gutter: false" style="font-size:12px;; margin: 10px 0 0 0; margin-top: 0">import uuid
class ReviewingState(object):
state_name = "reviewing"
def __init__(self, call):
self.call = call
self.playback_id = None
self.hangup_event = None
self.playback_finished = None
self.dtmf_event = None
self.playback = None
def enter(self):
self.playback_id = str(uuid.uuid4())
print "Entering reviewing state"
self.hangup_event = self.call.channel.on_event("ChannelHangupRequest",
self.on_hangup)
self.playback_finished = self.call.client.on_event(
'PlaybackFinished', self.on_playback_finished)
self.dtmf_event = self.call.channel.on_event('ChannelDtmfReceived',
self.on_dtmf)
self.playback = self.call.channel.playWithId(
playbackId=self.playback_id, media="recording:{0}".format(
self.call.vm_path))
def cleanup(self):
self.playback_finished.close()
if self.playback:
self.playback.stop()
self.dtmf_event.close()
self.hangup_event.close()
def on_hangup(self, channel, event):
print "Accepted recording {0} on hangup".format(self.call.vm_path)
self.cleanup()
self.call.state_machine.change_state(Event.HANGUP)
def on_playback_finished(self, event):
if self.playback_id == event.get('playback').get('id'):
self.playback = None
def on_dtmf(self, channel, event):
digit = event.get('digit')
if digit == '#':
print "Accepted recording {0} on DTMF #".format(self.call.vm_path)
self.cleanup()
self.call.state_machine.change_state(Event.DTMF_OCTOTHORPE)
elif digit == '*':
print "Discarding stored recording {0} on DTMF *".format(self.call.vm_path)
self.cleanup()
self.call.client.recordings.deleteStored(
recordingName=self.call.vm_path)
self.call.state_machine.change_state(Event.DTMF_STAR)</pre>
</div>
</div>
<div class="code panel pdl" style="border-width: 1px;">
<div class="codeHeader panelHeader pdl hide-border-bottom" style="border-bottom-width: 1px;">
<b class=" code-title">reviewing_state.js</b>
<span class="collapse-source expand-control"><span class="expand-control-icon icon"> </span><span class="expand-control-text">Expand source</span></span>
</div>
<div class="codeContent panelContent pdl hide-toolbar">
<pre class="theme: Confluence; brush: js; collapse: true; gutter: false" style="font-size:12px;; margin: 10px 0 0 0; margin-top: 0">var Event = require('./event');
function ReviewingState(call) {
this.state_name = "reviewing";
this.call = call;
}
ReviewingState.prototype.enter = function() {
var self = this;
var playback = self.call.client.Playback();
var url = "recording:" + self.call.vm_path;
console.log("Entering reviewing state");
self.call.channel.on("ChannelHangupRequest", on_hangup);
self.call.channel.on("ChannelDtmfReceived", on_dtmf);
self.call.client.on("PlaybackFinished", on_playback_finished);
self.call.channel.play({media: url}, playback);
function cleanup() {
self.call.channel.removeListener('ChannelHangupRequest', on_hangup);
self.call.channel.removeListener('ChannelDtmfReceived', on_dtmf);
self.call.client.removeListener('PlaybackFinished', on_playback_finished);
if (playback) {
playback.stop();
}
}
function on_hangup(event, channel) {
console.log("Accepted recording %s on hangup", self.call.vm_path);
playback = null;
cleanup();
self.call.state_machine.change_state(Event.HANGUP);
}
function on_playback_finished(event) {
if (playback && (playback.id === event.playback.id)) {
playback = null;
}
}
function on_dtmf(event, channel) {
switch (event.digit) {
case '#':
console.log("Accepted recording", self.call.vm_path);
cleanup();
self.call.state_machine.change_state(Event.DTMF_OCTOTHORPE);
break;
case '*':
console.log("Canceling recording", self.call.vm_path);
cleanup();
self.call.client.recordings.deleteStored({recordingName: self.call.vm_path});
self.call.state_machine.change_state(Event.DTMF_STAR);
break;
}
}
}
module.exports = ReviewingState;</pre>
</div>
</div> <p style="margin: 10px 0 0 0"> </p> <p style="margin: 10px 0 0 0">The code for this state is similar to the code from <code style="font-family: monospace">RecordingState</code>. The big difference is that instead of recording a message, it is playing back a stored recording. Stored recordings can be played using the channel's <code style="font-family: monospace">play()</code> method (or as we have used in the python code, <code style="font-family: monospace">playWithId()</code>). If the URI of the media to be played is prefixed with the "recording:" scheme, then Asterisk knows to search for the specified file where recordings are stored. More information on playing back files on channels, as well as a detailed list of media URI schemes can be found here. Note the method that is called when a DTMF '*' is received. The <code style="font-family: monospace">deleteStored()</code> method can be used on the <code style="font-family: monospace">/recordings</code> resource of the ARI client to delete a stored recording from the file system on which Asterisk is running.</p> <p style="margin: 10px 0 0 0">One more thing to point out is the code that runs in <code style="font-family: monospace">on_playback_finished()</code>. When reviewing a voicemail recording, the message may finish playing back before the user decides what to do with it. If this happens, we detect that the playback has finished so that we do not attempt to stop it once the user does decide what to do.</p> <p style="margin: 10px 0 0 0">We need to get this new state added into our state machine, so we make the following modifications to our code to allow for the new state to be added:</p>
<div class="code panel pdl" style="border-width: 1px;">
<div class="codeHeader panelHeader pdl hide-border-bottom" style="border-bottom-width: 1px;">
<b class=" code-title">vm-call.py</b>
<span class="collapse-source expand-control"><span class="expand-control-icon icon"> </span><span class="expand-control-text">Expand source</span></span>
</div>
<div class="codeContent panelContent pdl hide-toolbar">
<pre class="theme: Confluence; brush: py; collapse: true; gutter: false" style="font-size:12px;; margin: 10px 0 0 0; margin-top: 0">#At the top of the file
from reviewing_state import ReviewingState
#In VoiceMailCall::setup_state_machine
def setup_state_machine(self):
hungup_state = HungUpState(self)
recording_state = RecordingState(self)
ending_state = EndingState(self)
reviewing_state = ReviewingState(self)
self.state_machine = StateMachine()
self.state_machine.add_transition(recording_state, Event.DTMF_OCTOTHORPE,
reviewing_state)
self.state_machine.add_transition(recording_state, Event.HANGUP,
hungup_state)
self.state_machine.add_transition(recording_state, Event.DTMF_STAR,
recording_state)
self.state_machine.add_transition(reviewing_state, Event.HANGUP,
hungup_state)
self.state_machine.add_transition(reviewing_state, Event.DTMF_OCTOTHORPE,
ending_state)
self.state_machine.add_transition(reviewing_state, Event.DTMF_STAR,
recording_state)
self.state_machine.start(recording_state)</pre>
</div>
</div>
<div class="code panel pdl" style="border-width: 1px;">
<div class="codeHeader panelHeader pdl hide-border-bottom" style="border-bottom-width: 1px;">
<b class=" code-title">vm-call.js</b>
<span class="collapse-source expand-control"><span class="expand-control-icon icon"> </span><span class="expand-control-text">Expand source</span></span>
</div>
<div class="codeContent panelContent pdl hide-toolbar">
<pre class="theme: Confluence; brush: js; collapse: true; gutter: false" style="font-size:12px;; margin: 10px 0 0 0; margin-top: 0">// At the top of the file
var ReviewingState = require('./reviewing_state');
// In VoicemailCall::setup_state_machine
VoiceMailCall.prototype.setup_state_machine = function() {
var hungup_state = new HungUpState(this);
var recording_state = new RecordingState(this);
var ending_state = new EndingState(this);
var reviewing_state = new ReviewingState(this);
this.state_machine = new StateMachine();
this.state_machine.add_transition(recording_state, Event.DTMF_OCTOTHORPE, reviewing_state);
this.state_machine.add_transition(recording_state, Event.HANGUP, hungup_state);
this.state_machine.add_transition(recording_state, Event.DTMF_STAR, recording_state);
this.state_machine.add_transition(reviewing_state, Event.DTMF_OCTOTHORPE, ending_state);
this.state_machine.add_transition(reviewing_state, Event.HANGUP, hungup_state);
this.state_machine.add_transition(reviewing_state, Event.DTMF_STAR, recording_state);
this.state_machine.start(recording_state);
}</pre>
</div>
</div> <p style="margin: 10px 0 0 0">The following is the output from a sample call. The user records audio, then presses '#'. Upon hearing the recording, the user decides to record again, so the user presses '*'. After re-recording, the user presses '#'. The user hears the new version of the recording played back and is satisfied with it, so the user presses '#' to accept the recording.</p>
<div class="preformatted panel" style="border-width: 1px;">
<div class="preformattedContent panelContent">
<pre style="margin: 10px 0 0 0; margin-top: 0">Channel PJSIP/200-00000009 recording voicemail for 305
Entering recording state
Recording voicemail at voicemail/305/1411501058.42
Accepted recording voicemail/305/1411501058.42 on DTMF #
Cleaning up event handlers
Entering reviewing state
Discarding stored recording voicemail/305/1411501058.42 on DTMF *
Entering recording state
Recording voicemail at voicemail/305/1411501058.42
Accepted recording voicemail/305/1411501058.42 on DTMF #
Cleaning up event handlers
Entering reviewing state
Accepted recording voicemail/305/1411501058.42 on DTMF #
Ending voice mail call from PJSIP/200-00000009</pre>
</div>
</div>
<div class="panel" style="background-color: silver;border-style: solid;border-width: 1px;">
<div class="panelHeader" style="border-bottom-width: 1px;border-bottom-style: solid;background-color: seagreen;color: black;">
<b>Reader Exercise 4</b>
</div>
<div class="panelContent" style="background-color: silver;">
<p style="margin: 10px 0 0 0; margin-top: 0">In the previous section we introduced the ability to delete a stored recording. Stored recordings have a second operation available to them: copying. The <code style="font-family: monospace">copy()</code> method of a stored recording can be used to copy the stored recording from one location to another.</p>
<p style="margin: 10px 0 0 0">For this exercise modify <code style="font-family: monospace">ReviewingState::on_dtmf()</code> to let a DTMF key of your choice copy the message to a different mailbox on the system. When a user presses this DTMF key, the state machine should transition into a new state called "copying." The "copying" state should gather DTMF from the user to determine which mailbox the message should be copied to. If '#' is entered, then the message is sent to the mailbox the user has typed in. If '*' is entered, then the copying operation is canceled. Both a '#' and a '*' should cause the state machine to transition back into <code style="font-family: monospace">ReviewingState</code>.</p>
<p style="margin: 10px 0 0 0">As an example, let's say that you have set DTMF '0' to be the key that the user presses in <code style="font-family: monospace">ReviewingState</code> to copy the message. The user presses '0'. The user then presses '3' '2' '0' '#'. The message should be copied to mailbox "320", and the user should start hearing the message played back again. Now let's say the user presses '0' to copy the message again. The user then presses '3' '2' '1' '0' '*'. The message should not be copied to any mailbox, and the user should start hearing the message played back again.</p>
</div>
</div> <h1 id="Recording-RecordingBridges" style="margin: 10px 0 0 0; font-size: 24px; font-weight: normal; line-height: 30px; margin: 40px 0 0 0">Recording Bridges</h1> <p style="margin: 10px 0 0 0">This discussion of recordings has focused on recording channel audio. It's important to note that bridges also have an option to be recorded. What's the difference? Recording a channel's audio records only the audio coming <strong>from</strong> a channel. Recording a bridge records the mixed audio coming from all channels into the bridge. This means that if you are attempting to do something like record a conversation between participants in a phone call, you would want to record the audio in the bridge rather than on either of the channels involved.</p> <p style="margin: 10px 0 0 0">Once recording is started on a bridge, the operations available for the live recording and the resulting stored recording are exactly the same as for live recordings and stored recordings on a channel. Since the API for recording a bridge and recording a channel are so similar, this page will not provide any examples of recording bridge audio.</p> </td>
</tr>
</tbody>
</table> </td>
</tr>
<tr>
<td class="email-content-main mobile-expand action-padding last-row-padding" style="padding: 0px; border-collapse: collapse; border-left: 1px solid #ccc; border-right: 1px solid #ccc; border-top: 0; border-bottom: 0; padding: 0 15px 15px 16px; background-color: #fff; padding-bottom: 10px; padding-bottom: 10px">
<table id="actions-pattern" cellspacing="0" cellpadding="0" border="0" width="100%" style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333; font-family: Arial, sans-serif; font-size: 14px; line-height: 20px; mso-line-height-rule: exactly; mso-text-raise: 1px">
<tbody>
<tr>
<td id="actions-pattern-container" valign="middle" style="padding: 0px; border-collapse: collapse; padding: 15px 0 0 24px; vertical-align: middle">
<table align="left" style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td class="actions-pattern-action-icon-container" style="padding: 0px; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 14px; line-height: 20px; mso-line-height-rule: exactly; mso-text-raise: 0px; vertical-align: middle"><a href="https://wiki.asterisk.org/wiki/display/AST/Recording?src=email" title="View page Icon" style="color: #3b73af; text-decoration: none"><img class="actions-pattern-action-icon-image" height="16" width="16" border="0" title="View page Icon" src="cid:com.atlassian.confluence.plugins.confluence-email-resources%3Aview-page-email-adg-footer-item%3Aicon" alt="View page Icon" style="vertical-align: middle" /></a></td>
<td class="actions-pattern-action-text-container" style="padding: 0px; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 14px; line-height: 20px; mso-line-height-rule: exactly; mso-text-raise: 4px; padding-left: 5px; white-space: nowrap"><a href="https://wiki.asterisk.org/wiki/display/AST/Recording?src=email" title="View page" style="color: #3b73af; text-decoration: none">View page</a></td>
<td class="actions-pattern-action-bull" style="padding: 0px; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 14px; line-height: 20px; mso-line-height-rule: exactly; mso-text-raise: 4px; color: #999; padding: 0 5px">•</td>
</tr>
</tbody>
</table>
<table align="left" style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td class="actions-pattern-action-icon-container" style="padding: 0px; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 14px; line-height: 20px; mso-line-height-rule: exactly; mso-text-raise: 0px; vertical-align: middle"><a href="https://wiki.asterisk.org/wiki/display/AST/Recording?showComments=true&showCommentArea=true&src=email#addcomment" title="Add comment Icon" style="color: #3b73af; text-decoration: none"><img class="actions-pattern-action-icon-image" height="16" width="16" border="0" title="Add comment Icon" src="cid:com.atlassian.confluence.plugins.confluence-email-resources%3Aadd-comment-to-content-email-adg-footer-item%3Aicon" alt="Add comment Icon" style="vertical-align: middle" /></a></td>
<td class="actions-pattern-action-text-container" style="padding: 0px; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 14px; line-height: 20px; mso-line-height-rule: exactly; mso-text-raise: 4px; padding-left: 5px; white-space: nowrap"><a href="https://wiki.asterisk.org/wiki/display/AST/Recording?showComments=true&showCommentArea=true&src=email#addcomment" title="Add comment" style="color: #3b73af; text-decoration: none">Add comment</a></td>
<td class="actions-pattern-action-bull" style="padding: 0px; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 14px; line-height: 20px; mso-line-height-rule: exactly; mso-text-raise: 4px; color: #999; padding: 0 5px">•</td>
</tr>
</tbody>
</table>
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td class="actions-pattern-action-icon-container" style="padding: 0px; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 14px; line-height: 20px; mso-line-height-rule: exactly; mso-text-raise: 0px; vertical-align: middle"><a href="https://wiki.asterisk.org/wiki/plugins/likes/like.action?contentId=30277826&src=email" title="Like Icon" style="color: #3b73af; text-decoration: none"><img class="actions-pattern-action-icon-image" height="16" width="16" border="0" title="Like Icon" src="cid:com.atlassian.confluence.plugins.confluence-like%3Aview-email-adg-content-item%3Aicon" alt="Like Icon" style="vertical-align: middle" /></a></td>
<td class="actions-pattern-action-text-container" style="padding: 0px; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 14px; line-height: 20px; mso-line-height-rule: exactly; mso-text-raise: 4px; padding-left: 5px; white-space: nowrap"><a href="https://wiki.asterisk.org/wiki/plugins/likes/like.action?contentId=30277826&src=email" title="Like" style="color: #3b73af; text-decoration: none">Like</a></td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
</table> </td>
</tr>
<tr>
<td class="email-content-rounded-bottom mobile-expand" style="padding: 0px; border-collapse: collapse; color: #fff; height: 5px; line-height: 5px; padding: 0 15px 0 16px; background-color: #fff; border-bottom-right-radius: 5px; border-bottom-left-radius: 5px; border-top: 0; border-left: 1px solid #ccc; border-bottom: 1px solid #ccc; border-right: 1px solid #ccc; mso-line-height-rule: exactly"> </td>
</tr>
</tbody>
</table> </td>
</tr>
<tr>
<td id="footer-pattern" style="padding: 0px; border-collapse: collapse; padding: 12px 20px">
<table id="footer-pattern-container" cellspacing="0" cellpadding="0" border="0" width="100%" style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td id="footer-pattern-links-container" width="100%" style="padding: 0px; border-collapse: collapse; color: #999; font-size: 12px; line-height: 18px; font-family: Arial, sans-serif; mso-line-height-rule: exactly; mso-text-raise: 2px">
<table align="left" style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333; font-size: 12px; line-height: 18px; font-family: Arial, sans-serif; mso-line-height-rule: exactly; mso-text-raise: 2px">
<tbody>
<tr>
<td class="footer-pattern-links mobile-resize-text" style="padding: 0px; border-collapse: collapse"><a href="https://wiki.asterisk.org/wiki/users/removespacenotification.action?spaceKey=AST&src=email" title="" style="color: #3b73af; text-decoration: none">Stop watching space</a></td>
<td class="footer-pattern-links-bull" style="padding: 0px; border-collapse: collapse; padding: 0 5px; color: #999">•</td>
</tr>
</tbody>
</table>
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333; font-size: 12px; line-height: 18px; font-family: Arial, sans-serif; mso-line-height-rule: exactly; mso-text-raise: 2px">
<tbody>
<tr>
<td class="footer-pattern-links mobile-resize-text" style="padding: 0px; border-collapse: collapse"><a href="https://wiki.asterisk.org/wiki/users/editmyemailsettings.action?src=email" title="" style="color: #3b73af; text-decoration: none">Manage notifications</a></td>
</tr>
</tbody>
</table> </td>
</tr>
<tr>
<td id="footer-pattern-text" class="mobile-resize-text" width="100%" style="padding: 0px; border-collapse: collapse; color: #999; font-size: 12px; line-height: 18px; font-family: Arial, sans-serif; mso-line-height-rule: exactly; mso-text-raise: 2px; display: none">This message was sent by Atlassian Confluence 5.6.1</td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
</table>
<table id="sealed-section" border="0" cellpadding="0" cellspacing="0" width="0" style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333; display: none">
<tbody>
<tr>
<td style="padding: 0px; border-collapse: collapse; border: 0; font-size: 0px; line-height: 0; mso-line-height-rule: exactly"></td>
</tr>
</tbody>
</table>
</body>
</html>