<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>edited</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 class="notification-comment-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: 2px">
<tbody>
<tr>
<td class="notification-comment-pattern-container mobile-resize-text" style="padding: 0px; border-collapse: collapse; padding: 0px"><strong>Change comment:</strong> Very rough draft with all python code samples in place. Needs diagrams, js samples, and lots of editing.</td>
</tr>
</tbody>
</table> </td>
</tr>
<tr>
<td class="email-content-main mobile-expand padding-top border-top" 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; border-top: 1px solid #ccc; padding-top: 15px">
<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/pages/viewpage.action?pageId=29396202&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/pages/viewpage.action?pageId=29396202&src=email" title="MEDIA? MORE LIKE MEDI-DUH!" style="color: #3b73af; text-decoration: none">MEDIA? MORE LIKE MEDI-DUH!</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"> <p class="diff-context-placeholder" style="margin: 10px 0 0 0; margin-top: 0">...</p>
<table class="diff-macro diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/images/icons/macrobrowser/dropdown/section.png" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Section</th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse">
<table class="diff-macro" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/images/icons/macrobrowser/dropdown/column.png" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Column</th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse">
<table class="diff-macro" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/images/icons/macrobrowser/dropdown/warning.png" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Warning</th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <p style="margin: 10px 0 0 0; margin-top: 0">This page is a work in progress. Please refrain from commenting until this warning is removed.</p> </td>
</tr>
</tbody>
</table> <p style="margin: 10px 0 0 0"> </p> <h1 id="MEDIA?MORELIKEMEDI-DUH!-MediaControl" style="margin: 10px 0 0 0; font-size: 24px; font-weight: normal; line-height: 30px; margin: 40px 0 0 0">Media Control</h1> <p style="margin: 10px 0 0 0">ARI contains tools for manipulating media, such as playing sound files, playing tones, playing numbers and digits, recording media, deleting stored recordings, manipulating playbacks (e.g. rewind and fast-forward), and intercepting DTMF tones. Some channel-specific information and examples for playing media and intercepting DTMF have been covered on previous pages. While some information will be repeated here, the intention of this section is to delve deeper into media manipulation.</p> <p style="margin: 10px 0 0 0">To frame the discussion, we will be creating a set of applications that mimic a voice mail system<span class="diff-html-removed" id="removed-diff-0" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">.</span> </p> </td>
</tr>
</tbody>
</table>
<table class="diff-macro diff-html-added" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ddfade;border-color: #93c49f;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/images/icons/macrobrowser/dropdown/column.png" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Column</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse">
<table class="diff-macro diff-html-added" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ddfade;border-color: #93c49f;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/images/icons/macrobrowser/dropdown/panel.png" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Panel</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">title</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">On this Page</span></td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <p style="margin: 10px 0 0 0; margin-top: 0"></p>
<table class="diff-macro bodyless diff-html-added" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;margin: 5px 0; padding: 0; width: auto;background-color: #ddfade;border-color: #93c49f;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/images/icons/macrobrowser/dropdown/toc.png" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Table of Contents</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">maxLevel</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">2</span></td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
</table> <p style="margin: 10px 0 0 0"></p> </td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
</table> <h1 id="MEDIA?MORELIKEMEDI-DUH!-StateMachine" class="diff-block-target diff-block-context" style="margin: 10px 0 0 0; font-size: 24px; font-weight: normal; line-height: 30px; margin: 40px 0 0 0"> <span class="diff-html-changed" id="changed-diff-0" style="background-color: #d6f0ff;">State Machine</span> </h1> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;">Voice mail, at its heart, is an IVR. IVRs are most easily represented using a finite state machine. The way a state machine works is that a program switches between pre-defined states based on events that occur. Certain events will cause program code within the state to take certain actions, and other events will result in a change to a different program state. Each state can almost be seen as a self-contained program.</span> </p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;">To start our state machine, we will define what events might cause state transitions. If you think about a typical IVR, the events that can occur are DTMF key presses, and changes in the state of a call, such as hanging up. As such, we'll define a base set of events.</span> </p>
<table class="diff-macro diff-html-changed diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-changed" id="changed-diff-1" style="background-color: #d6f0ff;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" id="changed-diff-2" style="background-color: #d6f0ff;">title</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">event.py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">language</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">collapse</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">true</span></td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-changed" id="changed-diff-3" style="background-color: #d6f0ff;">class Event(object):
DTMF_1 = "1"
DTMF_2 = "2"
DTMF_3 = "3"
DTMF_4 = "4"
DTMF_5 = "5"
DTMF_6 = "6"
DTMF_7 = "7"
DTMF_8 = "8"
DTMF_9 = "9"
DTMF_0 = "0"
DTMF_OCTOTHORPE = "#"
DTMF_STAR = "*"
HANGUP = "hangup"</span>
</pre> </td>
</tr>
</tbody>
</table> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;">There is no hard requirement that we define events as named constants, but doing so makes it easier for tools like pylint and jslint to find potential mistakes.</span> </p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;">After we have defined our events, we need to create a state machine itself. The state machine keeps track of what states exist, what the current state is, and which states events cause the program to transition to. Here is a simple implementation of a state machine</span> </p>
<table class="diff-macro diff-html-changed diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-changed" id="changed-diff-4" style="background-color: #d6f0ff;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" id="changed-diff-5" style="background-color: #d6f0ff;">title</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">state_machine.py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">language</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">collapse</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">true</span></td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-changed" id="changed-diff-6" style="background-color: #d6f0ff;">class StateMachine(object):
def __init__(self):
self.transitions = {}
self.current_state = None
def add_transition(self, src_state, event, dst_state):
if not self.transitions.get(src_state.state_name):
self.transitions[src_state.state_name] = {}
self.transitions[src_state.state_name][event] = dst_state
def change_state(self, event):
self.current_state = self.transitions[self.current_state.state_name][event]
self.current_state.enter()
def start(self, initial_state):
self.current_state = initial_state
self.current_state.enter()</span>
</pre> </td>
</tr>
</tbody>
</table> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;">The state machine code is pretty straightforward. The state machine is defined and has transtions added to it with the </span><code style="font-family: monospace"><span class="diff-html-changed" style="background-color: #d6f0ff;">add_transition()</span></code><span class="diff-html-changed" style="background-color: #d6f0ff;"> method. The state machine can be started with the </span><code style="font-family: monospace"><span class="diff-html-changed" style="background-color: #d6f0ff;">start()</span></code><span class="diff-html-changed" style="background-color: #d6f0ff;"> method. Our use of the state machine will always be to define all transtions up front, then start the state machine.</span> </p> <h3 id="MEDIA?MORELIKEMEDI-DUH!-States" class="diff-block-target diff-block-context" style="margin: 10px 0 0 0; font-size: 16px; line-height: 25px; margin: 30px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;">States</span> </h3> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;">States within a state machine have certain duties that they must fulfill if they want to work well in the state machine we have devised</span> </p>
<ul class="diff-block-target diff-block-context" style="margin: 10px 0 0 0">
<li> <span class="diff-html-changed" style="background-color: #d6f0ff;">A state must know what state machine-defined events should cause it to change states, though it does not need to know what state it will be transitioning to.</span> </li>
<li> <span class="diff-html-changed" style="background-color: #d6f0ff;">A state must set up ARI event listeners each time the state is entered, and it must remove these before changing states. In the states we will be defining, we will write a </span><code style="font-family: monospace"><span class="diff-html-changed" style="background-color: #d6f0ff;">cleanup()</span></code><span class="diff-html-changed" style="background-color: #d6f0ff;"> method.</span> </li>
<li> <span class="diff-html-changed" style="background-color: #d6f0ff;">The state must define the following attributes:</span>
<ul style="margin: 10px 0 0 0">
<li> <code style="font-family: monospace"><span class="diff-html-changed" style="background-color: #d6f0ff;">state_name</span></code><span class="diff-html-changed" style="background-color: #d6f0ff;">, a string that represents the name of the state.</span> </li>
<li> <code style="font-family: monospace"><span class="diff-html-changed" style="background-color: #d6f0ff;">enter()</span></code><span class="diff-html-changed" style="background-color: #d6f0ff;">, a method that is called whenever the state is entered.</span> </li>
</ul> </li>
</ul> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;">It should be noted that this state machine implementation is not necessarily ideal, since it requires the states to know what events cause it to change states. However, it will become clear later that for a simple voice mail system, this is not that big a deal.</span> </p> <h3 id="MEDIA?MORELIKEMEDI-DUH!-Applicationskeleton" class="diff-block-target diff-block-context" style="margin: 10px 0 0 0; font-size: 16px; line-height: 25px; margin: 30px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;"> Application skeleton</span> </h3> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;">Since our application is defined by the states being used and the transitions between the states, we can create an application skeleton that we can re-use as needed.</span> </p>
<table class="diff-macro diff-html-changed diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-changed" id="changed-diff-7" style="background-color: #d6f0ff;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" id="changed-diff-8" style="background-color: #d6f0ff;">title</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">vm-call.py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">language</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">collapse</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">true</span></td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-changed" id="changed-diff-9" style="background-color: #d6f0ff;">#!/usr/bin/env python
import ari
import logging
import time
import os
import sys
from event import Event
# import state machine states here
logging.basicConfig(level=logging.ERROR)
LOGGER = logging.getLogger(__name__)
client = ari.connect('http://10.24.20.249:8088', 'asterisk', 'asterisk')
class VoiceMailCall(object):
def __init__(self, ari_client, channel, mailbox):
self.client = ari_client
self.channel = channel
self.vm_path = os.path.join('voicemail', mailbox, str(time.time())
self.setup_state_machine()
def setup_state_machine(self):
# Initialize states and set up state machine transitions here,
# then start the state machine
def stasis_start_cb(channel_obj, event):
channel = channel_obj['channel']
channel_name = channel.json.get('name')
mailbox = event.get('args')[0]
channel.answer()
VoiceMailCall(client, channel, mailbox)
client.on_channel_event('StasisStart', stasis_start_cb)
client.run(apps=sys.argv[1])
</span>
</pre> </td>
</tr>
</tbody>
</table> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;">This script should illustrate a few points:</span> </p>
<ul class="diff-block-target diff-block-context" style="margin: 10px 0 0 0">
<li> <span class="diff-html-changed" style="background-color: #d6f0ff;">States for the state machine are all defined in separate files outside the main application file. This makes it easy to add new states to an existing state machine.</span> </li>
<li> <span class="diff-html-changed" style="background-color: #d6f0ff;">We </span><span class="diff-html-added" id="added-diff-1" style="font-size: 100%; background-color: #ddfade;">create a </span><code style="font-family: monospace"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">vm_path</span></code><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"> member that contains the intended name of the voice mail recording. This path is made up of the top-level "voicemail" directory, followed by a directory for the mailbox name, followed by the current time. Using a timestamp for the name of the file is a simple cheat to allow for voicemails to automatically be sorted chronologically.</span> </li>
<li> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">We </span><span class="diff-html-changed" id="changed-diff-10" style="background-color: #d6f0ff;">are assuming that for voice mail, all of our Stasis applications take a single argument: the relevant voicemail box.</span> </li>
<li> <span class="diff-html-changed" style="background-color: #d6f0ff;">The actual name of the Stasis application is a command-line argument.</span> </li>
</ul> <h1 id="MEDIA?MORELIKEMEDI-DUH!-RecordingaVoiceMail" class="diff-block-target diff-block-context" style="margin: 10px 0 0 0; font-size: 24px; font-weight: normal; line-height: 30px; margin: 40px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;">Recording a Voice Mail</span> </h1> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;">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. Here is a state machine diagram for the application:</span> </p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;">For this, we will be defining three states: recording, hungup, and ending. The following is the code for the three states:</span> </p>
<table class="diff-macro diff-html-changed diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-changed" id="changed-diff-11" style="background-color: #d6f0ff;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" id="changed-diff-12" style="background-color: #d6f0ff;">title</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">recording_state.py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">language</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">collapse</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">true</span></td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-changed" id="changed-diff-13" style="background-color: #d6f0ff;">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}</span><span class="diff-html-added" id="added-diff-2" style="font-size: 100%; background-color: #ddfade;"> on hangup</span><span class="diff-html-changed" id="changed-diff-14" style="background-color: #d6f0ff;">".format(self.call.vm_path)
self.cleanup()
self.call.state_machine.change_state(Event.HANGUP)
def on_dtmf(self, channel, event):
</span>
</pre> <span class="diff-html-removed" id="removed-diff-1" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">print ("Got DTMF. Stored channel: {0}, channel param: {1}, " "event channel: {2}".format(self.call.channel.json.get('name'), channel.json.get('name'), event.get('channel').get('name'))) </span> <pre style="margin: 10px 0 0 0">
<span class="diff-html-changed" id="changed-diff-15" style="background-color: #d6f0ff;">digit = event.get('digit')
if digit == '#':
rec_name = self.recording.json.get('name')
print "Accepted recording {0}</span><span class="diff-html-added" id="added-diff-3" style="font-size: 100%; background-color: #ddfade;"> on DTMF #</span><span class="diff-html-changed" id="changed-diff-16" style="background-color: #d6f0ff;">".format(rec_name)
self.cleanup()
self.recording.stop()
self.call.state_machine.change_state(Event.DTMF_OCTOTHORPE)</span>
</pre> </td>
</tr>
</tbody>
</table> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;">When entered, the state sets up listeners for a hangup or DTMF ARI event on the channel, since those are the events that will cause the state to change. In all cases, before a state change occurs, the </span><code style="font-family: monospace"><span class="diff-html-changed" style="background-color: #d6f0ff;">cleanup()</span></code><span class="diff-html-changed" style="background-color: #d6f0ff;"> function is invoked to clean up 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.</span> </p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;">The other two states are much simpler, since they are terminal states and do not need to watch for any events.</span> </p>
<table class="diff-macro diff-html-changed diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-changed" id="changed-diff-17" style="background-color: #d6f0ff;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" id="changed-diff-18" style="background-color: #d6f0ff;">title</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">ending_state.py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">language</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">collapse</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">true</span></td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-changed" id="changed-diff-19" style="background-color: #d6f0ff;">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()</span>
</pre> </td>
</tr>
</tbody>
</table>
<table class="diff-macro diff-html-changed diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-changed" id="changed-diff-20" style="background-color: #d6f0ff;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" id="changed-diff-21" style="background-color: #d6f0ff;">title</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">hangup_state.py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">language</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">collapse</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">true</span></td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-changed" id="changed-diff-22" style="background-color: #d6f0ff;">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)</span>
</pre> </td>
</tr>
</tbody>
</table> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;">These two states are two sides to the same coin. The </span><code style="font-family: monospace"><span class="diff-html-changed" style="background-color: #d6f0ff;">EndingState</span></code><span class="diff-html-changed" style="background-color: #d6f0ff;"> is used to end the call by hanging up the channel, and the </span><code style="font-family: monospace"><span class="diff-html-changed" style="background-color: #d6f0ff;">HungUpState</span></code><span class="diff-html-changed" style="background-color: #d6f0ff;"> is used to terminate the state machine when the caller has hung up.</span> </p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;">Using the application skeleton we set up earlier, we can make the following modifications to accommodate our state machine:</span> </p>
<table class="diff-macro diff-html-changed diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-changed" id="changed-diff-23" style="background-color: #d6f0ff;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" id="added-diff-4" style="font-size: 100%; background-color: #ddfade;">title</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">vm-call.py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">language</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">collapse</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">true</span></td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-changed" id="changed-diff-24" style="background-color: #d6f0ff;"># 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)</span>
</pre> </td>
</tr>
</tbody>
</table> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;">We will create the following dialplan entry in </span><code style="font-family: monospace"><span class="diff-html-changed" style="background-color: #d6f0ff;">extensions.conf</span></code><span class="diff-html-changed" style="background-color: #d6f0ff;"> to be able to call into our voice mail recording application.</span> </p>
<table class="diff-macro diff-html-changed diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-changed" id="changed-diff-25" style="background-color: #d6f0ff;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/images/icons/macrobrowser/dropdown/noformat.png" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>No Format</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-changed" id="changed-diff-26" style="background-color: #d6f0ff;">[default]
exten => _3XX,1,NoOp()
same => n,Stasis(vm-record, ${EXTEN})
same => n,Hangup()</span>
</pre> </td>
</tr>
</tbody>
</table> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;">This way, when calling any three-digit extension that begins with the number '3', the user will leave a voice mail message </span> </p> <p class="diff-context-placeholder" style="margin: 10px 0 0 0">...</p> <p class="_mce_tagged_br diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> </span> </p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> </span> </p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> </span> </p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> </span> </p> <h1 id="MEDIA?MORELIKEMEDI-DUH!-Thesoundsresource" class="diff-block-target diff-block-context" style="margin: 10px 0 0 0; font-size: 24px; font-weight: normal; line-height: 30px; margin: 40px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">The </span><code style="font-family: monospace"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">sounds</span></code><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> resource</span> </h1> <p class="diff-context-placeholder" style="margin: 10px 0 0 0">...</p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-changed" style="background-color: #d6f0ff;"> </span><span class="diff-html-changed" id="changed-diff-27" style="background-color: #d6f0ff;">for </span> </p> <p class="diff-context-placeholder" style="margin: 10px 0 0 0">...</p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">Sounds on an Asterisk system are stored in the </span><code style="font-family: monospace"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">/sounds</span></code><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> directory within the </span><code style="font-family: monospace"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">astdatadir</span></code><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> configured in </span><code style="font-family: monospace"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">asterisk.conf</span></code><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">. By default, sounds are therefore stored at </span><code style="font-family: monospace"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">/var/lib/asterisk/sounds</span></code><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">. If you're writing a voicemail application, this resource isn't necessarily going to be used in the application, but it could be useful to help debug the application.</span> </p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">Below are some sample programs that will print the name and available formats of every English sound on an Asterisk system.</span> </p> <h3 id="MEDIA?MORELIKEMEDI-DUH!-Python" class="diff-block-target diff-block-context" style="margin: 10px 0 0 0; font-size: 16px; line-height: 25px; margin: 30px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">Python</span> </h3> <p class="diff-context-placeholder" style="margin: 10px 0 0 0">...</p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" id="added-diff-5" style="font-size: 100%; background-color: #ddfade;">the mailbox they dialled (e.g. dialling "305" will allow the user to leave a message for mailbox "305").</span> </p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">The following is a sample output of a user calling the application and pressing the '#' key when finished recording</span> </p>
<table class="diff-macro diff-html-added diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ddfade;border-color: #93c49f;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/images/icons/macrobrowser/dropdown/noformat.png" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>No Format</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">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</span>
</pre> </td>
</tr>
</tbody>
</table> <h3 id="MEDIA?MORELIKEMEDI-DUH!-CancellingaRecording" class="diff-block-target diff-block-context" style="margin: 10px 0 0 0; font-size: 16px; line-height: 25px; margin: 30px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">Cancelling a Recording</span> </h3> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">Now we have a simple application set up to record voice mails, 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:</span> </p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">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 </span><code style="font-family: monospace"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">recording_state.py</span></code><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"> file, we will rewrite the </span><code style="font-family: monospace"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">on_dtmf</span></code><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"> method as follows:</span> </p>
<table class="diff-macro diff-html-added diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ddfade;border-color: #93c49f;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">title</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">recording_state.py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" id="changed-diff-28" style="background-color: #d6f0ff;">language</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">collapse</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-changed" style="background-color: #d6f0ff;">true</span></td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">language</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">js</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">collapse</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">true</span></td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"><span class="diff-html-removed" id="removed-diff-4" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">#!/usr/bin/env</span> <pre style="margin: 10px 0 0 0">
<span class="diff-html-changed" id="changed-diff-29" style="background-color: #d6f0ff;"> </span>
</pre> <span class="diff-html-removed" id="removed-diff-5" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">python</span> <pre style="margin: 10px 0 0 0">
<span class="diff-html-changed" id="changed-diff-30" style="background-color: #d6f0ff;"> </span>
</pre> <span class="diff-html-removed" id="removed-diff-6" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">import</span> <pre style="margin: 10px 0 0 0">
<span class="diff-html-changed" id="changed-diff-31" style="background-color: #d6f0ff;"> </span>
</pre> <span class="diff-html-removed" id="removed-diff-7" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">sys import ari client = ari.connect('http://localhost:8088', 'asterisk', 'asterisk') resp = client.sounds.list(lang='en') for sound in resp: print("ID: {0}".format(sound['id'])) print("Text: {0}".format(sound.get('text', 'None'))) formats = [fmt['format'] for fmt in sound['formats']] print("Formats: {0}".format(', '.join(formats))) print('--------')</span> <p style="margin: 10px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> </span> </p> <p style="margin: 10px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> </span> </p>
<table class="diff-macro diff-html-removed" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ffe7e7;border-color: #df9898;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/images/icons/macrobrowser/dropdown/column.png" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Column</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse">
<table class="diff-macro diff-html-removed" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ffe7e7;border-color: #df9898;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/images/icons/macrobrowser/dropdown/panel.png" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Panel</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">title</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">On this Page</span></td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <p style="margin: 10px 0 0 0; margin-top: 0"></p>
<table class="diff-macro bodyless diff-html-removed" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;margin: 5px 0; padding: 0; width: auto;background-color: #ffe7e7;border-color: #df9898;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/images/icons/macrobrowser/dropdown/toc.png" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Table of Contents</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">maxLevel</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">2</span></td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
</table> <p style="margin: 10px 0 0 0"></p> </td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
</table> <h3 id="MEDIA?MORELIKEMEDI-DUH!-Javascript" class="diff-block-target diff-block-context" style="margin: 10px 0 0 0; font-size: 16px; line-height: 25px; margin: 30px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">Javascript</span> </h3>
<table class="diff-macro diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">/*jshint node:true*/
'use strict';
var ari = require('ari-client');
ari.connect('http://localhost:8088', 'asterisk', 'asterisk', clientLoaded);
function clientLoaded(err, client) {
if (err) {
throw err;
}
client.sounds.list({lang: 'en'}, function (err, sounds) {</span><span class="diff-html-added" id="added-diff-6" style="font-size: 100%; background-color: #ddfade;">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)</span>
</pre> </td>
</tr>
</tbody>
</table> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">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. We also need to add our new transition while setting up our state machine. Our </span><code style="font-family: monospace"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">VoiceMailCall::setup_state_machine()</span></code><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"> method now looks like:</span> </p>
<table class="diff-macro diff-html-added diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ddfade;border-color: #93c49f;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">title</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">vm-call.py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">language</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">collapse</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">true</span></td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"> 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,
</span> <span class="diff-html-removed" id="removed-diff-8" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">for</span> <span class="diff-html-removed" id="removed-diff-9" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">(var</span> <span class="diff-html-removed" id="removed-diff-10" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">sound</span> <span class="diff-html-removed" id="removed-diff-11" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">in</span> <span class="diff-html-removed" id="removed-diff-12" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">sounds)</span> <span class="diff-html-removed" id="removed-diff-13" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">{</span> <span class="diff-html-removed" id="removed-diff-14" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">console.log("ID:", sounds[sound].id);</span><span class="diff-html-added" id="added-diff-7" style="font-size: 100%; background-color: #ddfade;"> ending_state)
self.state_machine.add_transition(recording_state, Event.HANGUP,</span>
<span class="diff-html-removed" id="removed-diff-15" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">if</span> <span class="diff-html-removed" id="removed-diff-16" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">(sounds[sound].hasOwnProperty("text"))</span> <span class="diff-html-removed" id="removed-diff-17" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">{</span> <span class="diff-html-removed" id="removed-diff-18" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">console.log("Text:", sounds[sound].text);</span><span class="diff-html-added" id="added-diff-8" style="font-size: 100%; background-color: #ddfade;"> hungup_state)
self.state_machine.add_transition(recording_state, Event.DTMF_STAR,</span>
<span class="diff-html-removed" id="removed-diff-19" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">}</span> <span class="diff-html-removed" id="removed-diff-20" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">var</span> <span class="diff-html-removed" id="removed-diff-21" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">fmts</span> <span class="diff-html-removed" id="removed-diff-22" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">=</span> <span class="diff-html-removed" id="removed-diff-23" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">sounds[sound].formats.map(function(format_obj)</span> <span class="diff-html-removed" id="removed-diff-24" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">{</span> <span class="diff-html-added" id="added-diff-9" style="font-size: 100%; background-color: #ddfade;">recording_state)</span>
<span class="diff-html-removed" id="removed-diff-25" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">return format_obj.format;
});
console.log("Formats:", fmts.join(', '));
console.log("--------");
}
});
}
</span>
</pre> </td>
</tr>
</tbody>
</table> <h1 id="MEDIA?MORELIKEMEDI-DUH!-Recordings" class="diff-block-target diff-block-context" style="margin: 10px 0 0 0; font-size: 24px; font-weight: normal; line-height: 30px; margin: 40px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">Recordings</span> </h1> <h2 id="MEDIA?MORELIKEMEDI-DUH!-StoredRecordings" class="diff-block-target diff-block-context" style="margin: 10px 0 0 0; font-size: 20px; font-weight: normal; line-height: 30px; margin: 40px 0 0 0; margin-top: 10px"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">Stored Recordings</span> </h2> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">ARI provides the ability to record media from a channel and store the result in a file on the system. Recording is available on both </span><a href="https://wiki.asterisk.org/wiki/display/AST/Asterisk+13+Channels+REST+API#Asterisk13ChannelsRESTAPI-record" rel="nofollow" style="color: #3b73af; text-decoration: none"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">channels</span></a><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> and </span><a href="https://wiki.asterisk.org/wiki/display/AST/Asterisk+13+Bridges+REST+API#Asterisk13BridgesRESTAPI-record" rel="nofollow" style="color: #3b73af; text-decoration: none"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">bridges</span></a><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">.</span> </p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">The resulting file gets stored in the </span><code style="font-family: monospace"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">/recording</span></code><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> directory within the configured </span><code style="font-family: monospace"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">astspooldir</span></code><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> in </span><code style="font-family: monospace"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">asterisk.conf</span></code><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">. By default, this means that recordings are stored in </span><code style="font-family: monospace"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">/var/spool/asterisk/recording/</span></code><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">. Obviously, when writing a voice mail application, the ability to record a channel's media is invaluable. In the following code samples, we will illustrate a simple IVR where a user is prompted with a beep and can press '#' when finished recording.</span> </p> <h3 id="MEDIA?MORELIKEMEDI-DUH!-Python.1" class="diff-block-target diff-block-context" style="margin: 10px 0 0 0; font-size: 16px; line-height: 25px; margin: 30px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">Python</span> </h3> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">vm-record</span>
<table class="diff-macro diff-html-removed diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ffe7e7;border-color: #df9898;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr>
<th style="background-color: transparent; text-align: left; font-weight: normal;"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">title</span></th>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-added" id="added-diff-10" style="font-size: 100%; background-color: #ddfade;"> self.state_machine.start(recording_state)</span>
</pre> </td>
</tr>
</tbody>
</table> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">This is exactly the same as it was, except for the penultimate line adding the </span><code style="font-family: monospace"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">Event.DTMF_STAR</span></code><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"> transition. Here is sample output for when a user calls in, presses '*' twice, and then presses '#' to complete the call</span> </p>
<table class="diff-macro diff-html-added diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ddfade;border-color: #93c49f;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/images/icons/macrobrowser/dropdown/noformat.png" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>No Format</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">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</span>
</pre> </td>
</tr>
</tbody>
</table> <h3 id="MEDIA?MORELIKEMEDI-DUH!-OperatingonStoredRecordings" class="diff-block-target diff-block-context" style="margin: 10px 0 0 0; font-size: 16px; line-height: 25px; margin: 30px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">Operating on Stored Recordings</span> </h3> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">So far, we've recorded a channel, stopped a live recording, and cancelled a live recording. What we have not done yet is to manipulate stored recordings. 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, we will play back the recorded voicemail, and provide the same DTMF keys that we had before to be able to accept the recording or to cancel the recording and start over. Here is our updated state machine diagram:</span> </p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">To realize this, here is the code for our new "reviewing" state:</span> </p>
<table class="diff-macro diff-html-added diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ddfade;border-color: #93c49f;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">title</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">reviewing_state</span>.py</td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse">language</td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse">py</td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse">collapse</td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse">true</td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-removed" id="removed-diff-26" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">#!/usr/bin/env python</span><span class="diff-html-added" id="added-diff-11" style="font-size: 100%; background-color: #ddfade;">import uuid</span>
<span class="diff-html-added" id="added-diff-12" style="font-size: 100%; background-color: #ddfade;">class</span> <span class="diff-html-removed" id="removed-diff-27" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">import</span><span class="diff-html-added" id="added-diff-13" style="font-size: 100%; background-color: #ddfade;">ReviewingState(object):</span>
<span class="diff-html-removed" id="removed-diff-28" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">ari</span> <span class="diff-html-removed" id="removed-diff-29" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">import</span> <span class="diff-html-removed" id="removed-diff-30" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">logging</span> <span class="diff-html-removed" id="removed-diff-31" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">import</span> <span class="diff-html-removed" id="removed-diff-32" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">time</span><span class="diff-html-added" id="added-diff-14" style="font-size: 100%; background-color: #ddfade;">state_name</span> <span class="diff-html-removed" id="removed-diff-33" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">import</span><span class="diff-html-added" id="added-diff-15" style="font-size: 100%; background-color: #ddfade;">=</span> <span class="diff-html-removed" id="removed-diff-34" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">os</span><span class="diff-html-added" id="added-diff-16" style="font-size: 100%; background-color: #ddfade;">"reviewing"</span>
<span class="diff-html-removed" id="removed-diff-35" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">logging.basicConfig(level=logging.ERROR)
LOGGER = logging.getLogger(__name__)
client = ari.connect('http://10.24.20.249:8088', 'asterisk', 'asterisk')
def recording_finished(recording, event, channel</span><span class="diff-html-added" id="added-diff-17" style="font-size: 100%; background-color: #ddfade;"> 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</span>):
<span class="diff-html-added" id="added-diff-18" style="font-size: 100%; background-color: #ddfade;"> </span>print<span class="diff-html-removed" id="removed-diff-36" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">(</span><span class="diff-html-added" id="added-diff-19" style="font-size: 100%; background-color: #ddfade;"> </span>"<span class="diff-html-removed" id="removed-diff-37" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">Recording</span><span class="diff-html-added" id="added-diff-20" style="font-size: 100%; background-color: #ddfade;">Accepted</span> <span class="diff-html-removed" id="removed-diff-38" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">of</span><span class="diff-html-added" id="added-diff-21" style="font-size: 100%; background-color: #ddfade;">recording</span> <span class="diff-html-removed" id="removed-diff-39" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">channel </span>{0} <span class="diff-html-removed" id="removed-diff-40" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">completed</span><span class="diff-html-added" id="added-diff-22" style="font-size: 100%; background-color: #ddfade;">on hangup</span>".format(<span class="diff-html-added" id="added-diff-23" style="font-size: 100%; background-color: #ddfade;">self.call.vm_path)</span>
<span class="diff-html-removed" id="removed-diff-41" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">channel</span><span class="diff-html-added" id="added-diff-24" style="font-size: 100%; background-color: #ddfade;">self</span>.<span class="diff-html-removed" id="removed-diff-42" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">json.get('name')))</span><span class="diff-html-added" id="added-diff-25" style="font-size: 100%; background-color: #ddfade;">cleanup()</span>
<span class="diff-html-removed" id="removed-diff-43" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">channel.hangup(</span><span class="diff-html-added" id="added-diff-26" style="font-size: 100%; background-color: #ddfade;"> self.call.state_machine.change_state(Event.HANGUP</span>)
<span class="diff-html-removed" id="removed-diff-44" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> </span> <span class="diff-html-added" id="added-diff-27" style="font-size: 100%; background-color: #ddfade;"> </span>def <span class="diff-html-removed" id="removed-diff-45" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">stasis</span><span class="diff-html-added" id="added-diff-28" style="font-size: 100%; background-color: #ddfade;">on</span>_<span class="diff-html-removed" id="removed-diff-46" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">start</span><span class="diff-html-added" id="added-diff-29" style="font-size: 100%; background-color: #ddfade;">playback</span>_<span class="diff-html-removed" id="removed-diff-47" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">cb</span><span class="diff-html-added" id="added-diff-30" style="font-size: 100%; background-color: #ddfade;">finished</span>(<span class="diff-html-removed" id="removed-diff-48" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">channel_obj</span><span class="diff-html-added" id="added-diff-31" style="font-size: 100%; background-color: #ddfade;">self</span>, event):
<span class="diff-html-removed" id="removed-diff-49" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">channel</span> <span class="diff-html-removed" id="removed-diff-50" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">=</span> <span class="diff-html-removed" id="removed-diff-51" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">channel_obj['channel']</span> <span class="diff-html-removed" id="removed-diff-52" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> channel_name</span><span class="diff-html-added" id="added-diff-32" style="font-size: 100%; background-color: #ddfade;">if self.playback_id</span> =<span class="diff-html-added" id="added-diff-33" style="font-size: 100%; background-color: #ddfade;">=</span> <span class="diff-html-removed" id="removed-diff-53" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">channel.json</span><span class="diff-html-added" id="added-diff-34" style="font-size: 100%; background-color: #ddfade;">event.get('playback')</span>.get('<span class="diff-html-removed" id="removed-diff-54" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">name</span><span class="diff-html-added" id="added-diff-35" style="font-size: 100%; background-color: #ddfade;">id</span>')<span class="diff-html-added" id="added-diff-36" style="font-size: 100%; background-color: #ddfade;">:</span>
<span class="diff-html-removed" id="removed-diff-55" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">mailbox = event</span><span class="diff-html-added" id="added-diff-37" style="font-size: 100%; background-color: #ddfade;"> self.playback = None
def on_dtmf(self, channel, event):
digit = event</span>.get('<span class="diff-html-removed" id="removed-diff-56" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">args</span><span class="diff-html-added" id="added-diff-38" style="font-size: 100%; background-color: #ddfade;">digit</span>')<span class="diff-html-removed" id="removed-diff-57" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">[0]</span>
<span class="diff-html-removed" id="removed-diff-58" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">path</span> <span class="diff-html-added" id="added-diff-39" style="font-size: 100%; background-color: #ddfade;"> if digit </span>=<span class="diff-html-removed" id="removed-diff-59" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> os.path.join('voicemail', mailbox, str(time.time()))
print("Channel {0} leaving voicemail for {1}".format(channel_name, mailbox))</span><span class="diff-html-added" id="added-diff-40" style="font-size: 100%; background-color: #ddfade;">= '#':
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)</span>
<span class="diff-html-removed" id="removed-diff-60" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">channel.answer</span><span class="diff-html-added" id="added-diff-41" style="font-size: 100%; background-color: #ddfade;"> self.cleanup</span>()
<span class="diff-html-removed" id="removed-diff-61" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">recording</span> <span class="diff-html-removed" id="removed-diff-62" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">= channel.record(name=path, format='wav', beep=True,
</span><span class="diff-html-added" id="added-diff-42" style="font-size: 100%; background-color: #ddfade;"> self.call.client.recordings.deleteStored(
recordingName=self.call.vm_path)
self.call.state_machine.change_state(Event.DTMF_STAR)</span>
</pre> </td>
</tr>
</tbody>
</table> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">The </span><code style="font-family: monospace"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">on_hangup</span></code><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"> and </span><code style="font-family: monospace"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">on_dtmf</span></code><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"> methods are the same as they are in the recording state since this state reacts to the same events. What's new here is the code to play back the recorded voice mail.</span> </p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">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:</span> </p>
<table class="diff-macro diff-html-added diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ddfade;border-color: #93c49f;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">title</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">vm-call.py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">language</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">collapse</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">true</span></td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">#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)</span>
<span class="diff-html-added" id="added-diff-43" style="font-size: 100%; background-color: #ddfade;">recording_state</span> <span class="diff-html-added" id="added-diff-44" style="font-size: 100%; background-color: #ddfade;">=</span> <span class="diff-html-added" id="added-diff-45" style="font-size: 100%; background-color: #ddfade;">RecordingState(self)</span>
<span class="diff-html-removed" id="removed-diff-63" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">terminateOn='#', ifExists='overwrite'</span><span class="diff-html-added" id="added-diff-46" style="font-size: 100%; background-color: #ddfade;"> ending_state = EndingState(self)
reviewing_state = ReviewingState(self</span>)
<span class="diff-html-added" id="added-diff-47" style="font-size: 100%; background-color: #ddfade;"> self.state_machine = StateMachine()
self.state_machine.add_transition(</span>recording<span class="diff-html-added" id="added-diff-48" style="font-size: 100%; background-color: #ddfade;">_state, Event</span>.<span class="diff-html-removed" id="removed-diff-64" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">on_event('RecordingFinished', recording_finished, channel)
client.on_channel_event('StasisStart', stasis_start_cb)
client.run(apps='record-voicemail')</span>
</pre> </td>
</tr>
</tbody>
</table> <h3 id="MEDIA?MORELIKEMEDI-DUH!-Javascript.1" class="diff-block-target diff-block-context" style="margin: 10px 0 0 0; font-size: 16px; line-height: 25px; margin: 30px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">Javascript</span> </h3>
<table class="diff-macro diff-html-removed diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ffe7e7;border-color: #df9898;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">title</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">vm-record.js</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">language</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">js</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">collapse</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">true</span></td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> /*jshint node:true*/
'use strict';
var ari = require('ari-client');
var util = require('util');
var path = require('path');
ari.connect('http://10.24.20.249:8088', 'asterisk', 'asterisk', clientLoaded);
function clientLoaded(err, client) {
if (err) {</span><span class="diff-html-added" id="added-diff-49" style="font-size: 100%; background-color: #ddfade;">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)</span>
</pre> </td>
</tr>
</tbody>
</table> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">With this set up, here is an example of a user call. The user records audio, then presses '#'. Upon hearing the recording, the user decides to re-record 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.</span> </p>
<table class="diff-macro diff-html-added diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ddfade;border-color: #93c49f;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/images/icons/macrobrowser/dropdown/noformat.png" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>No Format</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">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</span>
</pre> </td>
</tr>
</tbody>
</table> <h1 id="MEDIA?MORELIKEMEDI-DUH!-Playbacks" class="diff-block-target diff-block-context" style="margin: 10px 0 0 0; font-size: 24px; font-weight: normal; line-height: 30px; margin: 40px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">Playbacks</span> </h1> <h3 id="MEDIA?MORELIKEMEDI-DUH!-Queryingforsounds" class="diff-block-target diff-block-context" style="margin: 10px 0 0 0; font-size: 16px; line-height: 25px; margin: 30px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">Querying for sounds</span> </h3> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">Playing media on a channel has been covered elsewhere already, but there are lots more that can be done with playbacks. The first thing we are going to do is to create a new state for our state machine that runs at the very beginning and plays an introduction message to the caller before leaving a voicemail. This is the updated state machine diagram:</span> </p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">Playing a sound file is nothing new, but we're going to add something to our new state that has not yet been covered. When the state is initialized, we will ensure that the sound we want to play is installed on the Asterisk system. Here is the code for our new state:</span> </p>
<table class="diff-macro diff-html-added diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ddfade;border-color: #93c49f;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">title</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">greeting_state.py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">language</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">collapse</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">true</span></td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">from event import Event
def sounds_installed(client):
try:
client.sounds.get(soundId='vm-intro')
except:
print "Required sound 'vm-intro' not installed. Aborting"
raise
class GreetingState(object):
state_name = "greeting"
def __init__(self, call):
self.call = call
self.hangup_event = None
self.playback_finished = None
self.dtmf_event = None
self.playback = None
sounds_installed(call.client)
def enter(self):
</span> <span class="diff-html-added" id="added-diff-50" style="font-size: 100%; background-color: #ddfade;">print</span> <span class="diff-html-removed" id="removed-diff-65" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">throw</span><span class="diff-html-added" id="added-diff-51" style="font-size: 100%; background-color: #ddfade;">"Entering</span> <span class="diff-html-removed" id="removed-diff-66" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">err;</span><span class="diff-html-added" id="added-diff-52" style="font-size: 100%; background-color: #ddfade;">greeting</span> <span class="diff-html-added" id="added-diff-53" style="font-size: 100%; background-color: #ddfade;">state"</span>
<span class="diff-html-removed" id="removed-diff-67" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">}</span> <span class="diff-html-removed" id="removed-diff-68" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">client.on('StasisStart', stasisStart);</span><span class="diff-html-added" id="added-diff-54" style="font-size: 100%; background-color: #ddfade;">self.hangup_event = self.call.channel.on_event('ChannelHangupRequest',
self.on_hangup)</span>
<span class="diff-html-removed" id="removed-diff-69" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">function stasisStart(event, channel) {</span><span class="diff-html-added" id="added-diff-55" style="font-size: 100%; background-color: #ddfade;"> self.playback_finished = self.call.client.on_event(
</span> <span class="diff-html-removed" id="removed-diff-70" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">var</span> <span class="diff-html-removed" id="removed-diff-71" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">mailbox</span> <span class="diff-html-removed" id="removed-diff-72" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">= event.args[0]</span><span class="diff-html-added" id="added-diff-56" style="font-size: 100%; background-color: #ddfade;"> 'PlaybackFinished', self.on_playback_finished)</span>
<span class="diff-html-added" id="added-diff-57" style="font-size: 100%; background-color: #ddfade;">self.dtmf_event = self.call.</span>channel.<span class="diff-html-removed" id="removed-diff-73" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">answer(function(err) {</span><span class="diff-html-added" id="added-diff-58" style="font-size: 100%; background-color: #ddfade;">on_event('ChannelDtmfReceived',
</span> <span class="diff-html-removed" id="removed-diff-74" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">if</span> <span class="diff-html-removed" id="removed-diff-75" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">(err)</span> <span class="diff-html-removed" id="removed-diff-76" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">{</span> <span class="diff-html-removed" id="removed-diff-77" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">throw</span> <span class="diff-html-removed" id="removed-diff-78" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">err;</span> <span class="diff-html-removed" id="removed-diff-79" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">}</span> <span class="diff-html-added" id="added-diff-59" style="font-size: 100%; background-color: #ddfade;">self.on_dtmf)</span>
<span class="diff-html-added" id="added-diff-60" style="font-size: 100%; background-color: #ddfade;">self.playback</span> <span class="diff-html-removed" id="removed-diff-80" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> startRecording(channel, mailbox);</span><span class="diff-html-added" id="added-diff-61" style="font-size: 100%; background-color: #ddfade;">= self.call.channel.play(media="sound:vm-intro")
def cleanup(self):</span>
<span class="diff-html-removed" id="removed-diff-81" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">});</span><span class="diff-html-added" id="added-diff-62" style="font-size: 100%; background-color: #ddfade;">self.playback_finished.close()</span>
<span class="diff-html-removed" id="removed-diff-82" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">}</span> <span class="diff-html-removed" id="removed-diff-83" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> function startRecording(</span><span class="diff-html-added" id="added-diff-63" style="font-size: 100%; background-color: #ddfade;">self.dtmf_event.close()
self.hangup_event.close()
def on_hangup(self, </span>channel, <span class="diff-html-removed" id="removed-diff-84" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">mailbox</span><span class="diff-html-added" id="added-diff-64" style="font-size: 100%; background-color: #ddfade;">event</span>)<span class="diff-html-added" id="added-diff-65" style="font-size: 100%; background-color: #ddfade;">:</span>
<span class="diff-html-removed" id="removed-diff-85" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">{</span> <span class="diff-html-added" id="added-diff-66" style="font-size: 100%; background-color: #ddfade;">print</span> <span class="diff-html-removed" id="removed-diff-86" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">var vm_path = path.join('voicemail/', mailbox, Date.now().toString());</span><span class="diff-html-added" id="added-diff-67" style="font-size: 100%; background-color: #ddfade;">"Abandoning voicemail recording on hangup"
self.cleanup()
self.call.state_machine.change_state(Event.HANGUP)
def on_playback_finished(self, playback):
self.cleanup()</span>
<span class="diff-html-removed" id="removed-diff-87" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">console.log("Channel %s leaving voicemail for %s", channel.name, mailbox);</span><span class="diff-html-added" id="added-diff-68" style="font-size: 100%; background-color: #ddfade;">self.call.state_machine.change_state(Event.PLAYBACK_COMPLETE)
def on_dtmf(self, channel, event):</span>
<span class="diff-html-removed" id="removed-diff-88" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">client.once</span><span class="diff-html-added" id="added-diff-69" style="font-size: 100%; background-color: #ddfade;">digit = event.get</span>('<span class="diff-html-removed" id="removed-diff-89" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">RecordingFinished</span><span class="diff-html-added" id="added-diff-70" style="font-size: 100%; background-color: #ddfade;">digit</span>'<span class="diff-html-removed" id="removed-diff-90" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">, recordingFinished</span>)<span class="diff-html-removed" id="removed-diff-91" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">;</span>
<span class="diff-html-removed" id="removed-diff-92" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">channel.record({name: vm_path, format: 'wav', beep: true, terminateOn: '#'}, function(err, recording) {</span><span class="diff-html-added" id="added-diff-71" style="font-size: 100%; background-color: #ddfade;">if digit == '#':
print "Cutting off greeting on DTMF #"
# Let on_playback_finished take care of state change
self.playback.stop()</span>
</pre> </td>
</tr>
</tbody>
</table> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">And here is our updated state machine:</span> </p>
<table class="diff-macro diff-html-added diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ddfade;border-color: #93c49f;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">title</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">vm-call.py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">language</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">collapse</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">true</span></td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">#At the top of the file
from greeting_state import GreetingState
#In VoiceMailCall::setup_state_machine()
def setup_state_machine(self):
hungup_state = HungUpState(self)
recording_state = RecordingState(self)
</span> <span class="diff-html-added" id="added-diff-72" style="font-size: 100%; background-color: #ddfade;">ending_state</span> <span class="diff-html-added" id="added-diff-73" style="font-size: 100%; background-color: #ddfade;">=</span> <span class="diff-html-added" id="added-diff-74" style="font-size: 100%; background-color: #ddfade;">EndingState(self)</span>
<span class="diff-html-removed" id="removed-diff-93" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">if (err</span><span class="diff-html-added" id="added-diff-75" style="font-size: 100%; background-color: #ddfade;"> reviewing_state = ReviewingState(self</span>)
<span class="diff-html-removed" id="removed-diff-94" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">{</span> <span class="diff-html-added" id="added-diff-76" style="font-size: 100%; background-color: #ddfade;">greeting_state</span> <span class="diff-html-added" id="added-diff-77" style="font-size: 100%; background-color: #ddfade;">=</span> <span class="diff-html-added" id="added-diff-78" style="font-size: 100%; background-color: #ddfade;">GreetingState(self)</span>
<span class="diff-html-removed" id="removed-diff-95" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">throw</span> <span class="diff-html-removed" id="removed-diff-96" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">err;</span> <span class="diff-html-added" id="added-diff-79" style="font-size: 100%; background-color: #ddfade;">self.state_machine</span> <span class="diff-html-added" id="added-diff-80" style="font-size: 100%; background-color: #ddfade;">=</span> <span class="diff-html-added" id="added-diff-81" style="font-size: 100%; background-color: #ddfade;">StateMachine()</span>
<span class="diff-html-removed" id="removed-diff-97" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> }</span><span class="diff-html-added" id="added-diff-82" style="font-size: 100%; background-color: #ddfade;">self.state_machine.add_transition(recording_state, Event.DTMF_OCTOTHORPE,</span>
<span class="diff-html-removed" id="removed-diff-98" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">});</span><span class="diff-html-added" id="added-diff-83" style="font-size: 100%; background-color: #ddfade;"> reviewing_state)</span>
<span class="diff-html-removed" id="removed-diff-99" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">function recordingFinished(event, recording) {</span><span class="diff-html-added" id="added-diff-84" style="font-size: 100%; background-color: #ddfade;">self.state_machine.add_transition(recording_state, Event.HANGUP,
hungup_state)
self.state_machine.add_transition(recording_state, Event.DTMF_STAR,
recording_state)
</span> <span class="diff-html-added" id="added-diff-85" style="font-size: 100%; background-color: #ddfade;">self.state_machine.add_transition(reviewing_state,</span> <span class="diff-html-added" id="added-diff-86" style="font-size: 100%; background-color: #ddfade;">Event.HANGUP,</span>
<span class="diff-html-removed" id="removed-diff-100" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">console.log("Recording of channel %s completed", channel.name);</span><span class="diff-html-added" id="added-diff-87" style="font-size: 100%; background-color: #ddfade;"> hungup_state)
self.state_machine.add_transition(reviewing_state, Event.DTMF_OCTOTHORPE,
ending_state)
self.state_machine.add_transition(reviewing_state, Event.DTMF_STAR,</span>
<span class="diff-html-removed" id="removed-diff-101" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">channel.hangup();
</span><span class="diff-html-added" id="added-diff-88" style="font-size: 100%; background-color: #ddfade;"> recording_state)
self.state_machine.add_transition(greeting_state, Event.HANGUP,</span>
<span class="diff-html-removed" id="removed-diff-102" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">}</span> <span class="diff-html-removed" id="removed-diff-103" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">}</span> <span class="diff-html-removed" id="removed-diff-104" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">client.start('record-voicemail');
}
</span>
</pre> </td>
</tr>
</tbody>
</table> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">Each of these scripts starts a Stasis application called </span><code style="font-family: monospace"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">record-voicemail</span></code><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> that takes a single argument, a mailbox into which to record a voicemail. The scripts use this mailbox number to help determine the path on the file system where the recording will reside. Since this is a bare-bones voicemail application, there are no such things as "folders" for voicemail boxes. The path the scripts construct is relative to the folder where recordings are stored and is constructed as "voicemail/{mailbox}/{timestamp}". The use of a timestamp serves as a cheap way to order voicemails by recording time and saves our scripts from having to keep track of how many voicemails exist in each mailbox.</span> </p>
<table class="diff-macro diff-html-removed diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ffe7e7;border-color: #df9898;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/images/icons/macrobrowser/dropdown/note.png" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Note</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <p style="margin: 10px 0 0 0; margin-top: 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">Though the Python and Node.js scripts perform the exact same tasks, it is not recommended to switch between the two at will. This is because Javascript and Python represent Unix timestamps differently: Javascript returns an integer count of milliseconds since the epoch, and Python returns a floating point number representing the seconds and fractions of a second since the epoch. As such, mixing files between these two could cause lexicographical sorting anomalies if you are trying to let the file system sort the recordings by date.</span> </p> </td>
</tr>
</tbody>
</table> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">Within Asterisk, the </span><code style="font-family: monospace"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">record-voicemail</span></code><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> application can be invoked however is desired. I set up the following simple extension in my dialplan:</span> </p>
<table class="diff-macro diff-html-removed diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ffe7e7;border-color: #df9898;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/images/icons/macrobrowser/dropdown/noformat.png" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>No Format</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">[default]
exten => _3XX,1,Stasis(record-voicemail, ${EXTEN});</span>
</pre> </td>
</tr>
</tbody>
</table> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">This allows for mailboxes 300-399 to exist on the system. Whatever extension is dialed by the user is the mailbox in which the mailbox is stored. Since I use the default </span><code style="font-family: monospace"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">astspooldir</span></code><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> on my system, this means that if I were to dial 305, the voicemail I leave will be stored at, for extample </span><code style="font-family: monospace"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">/var/spool/asterisk/recording/voicemail/305/1410806204299.wav</span></code><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">.</span> </p> <h2 id="MEDIA?MORELIKEMEDI-DUH!-LiveRecordings" class="diff-block-target diff-block-context" style="margin: 10px 0 0 0; font-size: 20px; font-weight: normal; line-height: 30px; margin: 40px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">Live Recordings</span> </h2> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">In addition to the simple act of recording, ARI also provides the capability to manipulate stored and live recordings via the </span><code style="font-family: monospace"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">/recordings</span></code><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> resource, documented </span><a class="confluence-link unresolved" href="#" style="color: #3b73af; text-decoration: none"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">here</span></a><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">. The following code samples build on the previous samples to allow a user to cancel their current live recording and re-record their message. This makes use of DTMF interception techniques that were introduced on this </span><a class="confluence-link unresolved" href="#" style="color: #3b73af; text-decoration: none"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">page</span></a><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">. When the user presses the '*' key, the current recording is discarded and the user is re-prompted to record their message.</span> </p> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">vm-record-retry</span>
<table class="diff-macro diff-html-removed diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ffe7e7;border-color: #df9898;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr>
<th style="background-color: transparent; text-align: left; font-weight: normal;"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">title</span></th>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-added" id="added-diff-89" style="font-size: 100%; background-color: #ddfade;"> hungup_state)
self.state_machine.add_transition(greeting_state,
Event.PLAYBACK_COMPLETE,
recording_state)
self.state_machine.start(greeting_state)</span>
</pre> </td>
</tr>
</tbody>
</table> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">Here is a sample run where the user cuts off the greeting by pressing the '#' key, records a greeting and presses the '#' key, and after listening to the recording presses the '#' key once more.</span> </p>
<table class="diff-macro diff-html-added diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ddfade;border-color: #93c49f;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/images/icons/macrobrowser/dropdown/noformat.png" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>No Format</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">Channel PJSIP/200-0000000b recording voicemail for 305
Entering greeting state
Cutting off greeting on DTMF #
Entering recording state
Recording voicemail at voicemail/305/1411503204.75
Accepted recording voicemail/305/1411503204.75 on DTMF #
Cleaning up event handlers
Entering reviewing state
Accepted recording voicemail/305/1411503204.75 on DTMF #
Ending voice mail call from PJSIP/200-0000000b</span>
</pre> </td>
</tr>
</tbody>
</table> <h3 id="MEDIA?MORELIKEMEDI-DUH!-Controllingplaybacks" class="diff-block-target diff-block-context" style="margin: 10px 0 0 0; font-size: 16px; line-height: 25px; margin: 30px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">Controlling playbacks</span> </h3> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">We've seen playbacks get stopped, but there are a lot more interesting operations that can be done on playbacks, such as reversing and fast-forwarding them. Within the context of recording a voicemail, these operations are pretty useless, so we will shift our focus now to the other side of voicemail: listening to recorded voicemails. This means defining a brand new state machine. To start with, we'll define a couple of states. The "preamble" state is a state where the current message number is played back to the listener. The "listening" state is where a voicemail message is played back to the listener. Here is the state machine we will be using:</span> </p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">In addition to the state transitions that are listed, there are several other DTMF keys that may be pressed while in the "listening" state that will affect the message being played but that will not result in a state transition. We will set it up as follows:</span> </p>
<ul class="diff-block-target diff-block-context" style="margin: 10px 0 0 0">
<li> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">DTMF '1': Reverse the current message playback.</span> </li>
<li> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">DTMF '2': Pause/unpause the current message playback.</span> </li>
<li> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">DTMF '3': Fast-forward the current message playback.</span> </li>
</ul> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">Here is the implementation of the application.</span> </p> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">js</span>
<table class="diff-macro diff-html-added diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ddfade;border-color: #93c49f;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">title</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">vm-playback</span>.py</td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse">language</td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse">py</td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse">collapse</td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse">true</td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">#!/usr/bin/env python
import ari
import logging
import <span class="diff-html-removed" id="removed-diff-105" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">time
</span><span class="diff-html-added" id="added-diff-90" style="font-size: 100%; background-color: #ddfade;">os
from state_machine </span>import <span class="diff-html-removed" id="removed-diff-106" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">os
</span><span class="diff-html-added" id="added-diff-91" style="font-size: 100%; background-color: #ddfade;">StateMachine
from ending_state import EndingState
from hungup_state import HungUpState
from listening_state import ListeningState
from preamble_state import PreambleState
from event import Event
</span>logging.basicConfig(level=logging.ERROR)
LOGGER = logging.getLogger(__name__)
client = ari.connect('http://<span class="diff-html-removed" id="removed-diff-107" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">localhost</span><span class="diff-html-added" id="added-diff-92" style="font-size: 100%; background-color: #ddfade;">10.24.20.249</span>:8088', 'asterisk', 'asterisk')
<span class="diff-html-removed" id="removed-diff-108" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">global</span>
<span class="diff-html-removed" id="removed-diff-109" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">DTMF_EVENT</span>
<span class="diff-html-added" id="added-diff-93" style="font-size: 100%; background-color: #ddfade;">class</span> <span class="diff-html-removed" id="removed-diff-110" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> </span><span class="diff-html-added" id="added-diff-94" style="font-size: 100%; background-color: #ddfade;">VoiceMailCall(object):</span>
<span class="diff-html-removed" id="removed-diff-111" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> </span> <span class="diff-html-added" id="added-diff-95" style="font-size: 100%; background-color: #ddfade;"> </span>def <span class="diff-html-removed" id="removed-diff-112" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">on</span>_<span class="diff-html-removed" id="removed-diff-113" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">dtmf(channel</span><span class="diff-html-added" id="added-diff-96" style="font-size: 100%; background-color: #ddfade;">_init__(self</span>, <span class="diff-html-removed" id="removed-diff-114" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">event</span><span class="diff-html-added" id="added-diff-97" style="font-size: 100%; background-color: #ddfade;">ari_client</span>, <span class="diff-html-removed" id="removed-diff-115" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">recording</span><span class="diff-html-added" id="added-diff-98" style="font-size: 100%; background-color: #ddfade;">channel</span>, mailbox):
<span class="diff-html-removed" id="removed-diff-116" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">global DTMF_EVENT</span><span class="diff-html-added" id="added-diff-99" style="font-size: 100%; background-color: #ddfade;"> self.client = ari_client</span>
<span class="diff-html-removed" id="removed-diff-117" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">digit</span> <span class="diff-html-removed" id="removed-diff-118" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">= event.get('digit'</span><span class="diff-html-added" id="added-diff-100" style="font-size: 100%; background-color: #ddfade;"> self.channel = channel
recordings = ari_client.recordings.listStored(</span>)
<span class="diff-html-removed" id="removed-diff-119" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">if</span> <span class="diff-html-removed" id="removed-diff-120" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">digit</span> <span class="diff-html-added" id="added-diff-101" style="font-size: 100%; background-color: #ddfade;"> self.voicemails </span>=<span class="diff-html-removed" id="removed-diff-121" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">=</span> <span class="diff-html-added" id="added-diff-102" style="font-size: 100%; background-color: #ddfade;">[rec.json[</span>'<span class="diff-html-removed" id="removed-diff-122" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">*</span><span class="diff-html-added" id="added-diff-103" style="font-size: 100%; background-color: #ddfade;">name</span>'<span class="diff-html-removed" id="removed-diff-123" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">:</span><span class="diff-html-added" id="added-diff-104" style="font-size: 100%; background-color: #ddfade;">]</span> <span class="diff-html-added" id="added-diff-105" style="font-size: 100%; background-color: #ddfade;">for</span> <span class="diff-html-added" id="added-diff-106" style="font-size: 100%; background-color: #ddfade;">rec</span> <span class="diff-html-added" id="added-diff-107" style="font-size: 100%; background-color: #ddfade;">in</span> <span class="diff-html-added" id="added-diff-108" style="font-size: 100%; background-color: #ddfade;">recordings</span> <span class="diff-html-added" id="added-diff-109" style="font-size: 100%; background-color: #ddfade;">if</span> <span class="diff-html-added" id="added-diff-110" style="font-size: 100%; background-color: #ddfade;">(</span>
<span class="diff-html-removed" id="removed-diff-124" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">print("Canceling</span> <span class="diff-html-removed" id="removed-diff-125" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">recording </span><span class="diff-html-added" id="added-diff-111" style="font-size: 100%; background-color: #ddfade;"> rec.json['name'].startswith('voicemail/</span>{0}<span class="diff-html-removed" id="removed-diff-126" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">"</span><span class="diff-html-added" id="added-diff-112" style="font-size: 100%; background-color: #ddfade;">'</span>.format(<span class="diff-html-removed" id="removed-diff-127" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">recording.json.get('name')))</span><span class="diff-html-added" id="added-diff-113" style="font-size: 100%; background-color: #ddfade;">mailbox)))]
self.current_voicemail = 0
self.setup_state_machine()
def setup_state_machine(self):</span>
<span class="diff-html-removed" id="removed-diff-128" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">recording.cancel(</span><span class="diff-html-added" id="added-diff-114" style="font-size: 100%; background-color: #ddfade;">hungup_state = HungUpState(self</span>)
<span class="diff-html-removed" id="removed-diff-129" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">DTMF_EVENT.close(</span><span class="diff-html-added" id="added-diff-115" style="font-size: 100%; background-color: #ddfade;">ending_state = EndingState(self</span>)
<span class="diff-html-removed" id="removed-diff-130" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">start_recording(channel, mailbox</span><span class="diff-html-added" id="added-diff-116" style="font-size: 100%; background-color: #ddfade;">listening_state = ListeningState(self</span>)
<span class="diff-html-removed" id="removed-diff-131" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">if</span> <span class="diff-html-removed" id="removed-diff-132" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">digit</span> <span class="diff-html-added" id="added-diff-117" style="font-size: 100%; background-color: #ddfade;"> preamble_state </span>=<span class="diff-html-removed" id="removed-diff-133" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">= '#':</span><span class="diff-html-added" id="added-diff-118" style="font-size: 100%; background-color: #ddfade;"> PreambleState(self)</span>
<span class="diff-html-removed" id="removed-diff-134" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">print("Stopping recording {0}".format(recording.json.get('name')))</span><span class="diff-html-added" id="added-diff-119" style="font-size: 100%; background-color: #ddfade;"> self.state_machine = StateMachine()
self.state_machine.add_transition(listening_state, Event.DTMF_FOUR,
preamble_state)
self.state_machine.add_transition(listening_state, Event.DTMF_SIX,
preamble_state)</span>
<span class="diff-html-removed" id="removed-diff-135" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">recording.stop(</span><span class="diff-html-added" id="added-diff-120" style="font-size: 100%; background-color: #ddfade;">self.state_machine.add_transition(listening_state, Event.HANGUP,
hungup_state</span>)
<span class="diff-html-removed" id="removed-diff-136" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">DTMF</span><span class="diff-html-added" id="added-diff-121" style="font-size: 100%; background-color: #ddfade;">self.state</span>_<span class="diff-html-removed" id="removed-diff-137" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">EVENT</span><span class="diff-html-added" id="added-diff-122" style="font-size: 100%; background-color: #ddfade;">machine</span>.<span class="diff-html-removed" id="removed-diff-138" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">close(</span><span class="diff-html-added" id="added-diff-123" style="font-size: 100%; background-color: #ddfade;">add_transition(listening_state, Event.DTMF_OCTOTHORPE,
ending_state</span>)
<span class="diff-html-removed" id="removed-diff-139" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">channel.hangup(</span><span class="diff-html-added" id="added-diff-124" style="font-size: 100%; background-color: #ddfade;">self.state_machine.add_transition(listening_state, Event.DTMF_STAR,
preamble_state</span>)
<span class="diff-html-removed" id="removed-diff-140" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> </span> <span class="diff-html-removed" id="removed-diff-141" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">def start_recording(channel, mailbox):</span><span class="diff-html-added" id="added-diff-125" style="font-size: 100%; background-color: #ddfade;"> self.state_machine.add_transition(listening_state, Event.EMPTY,
ending_state)</span>
<span class="diff-html-removed" id="removed-diff-142" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">global DTMF_EVENT</span><span class="diff-html-added" id="added-diff-126" style="font-size: 100%; background-color: #ddfade;"> self.state_machine.add_transition(preamble_state, Event.DTMF_OCTOTHORPE,</span>
<span class="diff-html-removed" id="removed-diff-143" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">path</span> <span class="diff-html-removed" id="removed-diff-144" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">= os.path.join('voicemail', mailbox, str(time.time()))</span><span class="diff-html-added" id="added-diff-127" style="font-size: 100%; background-color: #ddfade;"> listening_state)
self.state_machine.add_transition(preamble_state, Event.HANGUP,
hungup_state)
self.state_machine.start(preamble_state)
def next_message(self):</span>
<span class="diff-html-removed" id="removed-diff-145" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">recording</span> <span class="diff-html-removed" id="removed-diff-146" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">=</span> <span class="diff-html-removed" id="removed-diff-147" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">channel.record(name=path, format='wav', beep=True,
</span><span class="diff-html-added" id="added-diff-128" style="font-size: 100%; background-color: #ddfade;"> self.current_voicemail += 1
if self.current_voicemail == len(self.voicemails):</span>
<span class="diff-html-added" id="added-diff-129" style="font-size: 100%; background-color: #ddfade;">self.current_voicemail</span> <span class="diff-html-added" id="added-diff-130" style="font-size: 100%; background-color: #ddfade;">=</span> <span class="diff-html-added" id="added-diff-131" style="font-size: 100%; background-color: #ddfade;">0</span>
<span class="diff-html-added" id="added-diff-132" style="font-size: 100%; background-color: #ddfade;">def</span> <span class="diff-html-added" id="added-diff-133" style="font-size: 100%; background-color: #ddfade;">previous_message(self):</span>
<span class="diff-html-removed" id="removed-diff-148" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">ifExists='overwrite')
</span><span class="diff-html-added" id="added-diff-134" style="font-size: 100%; background-color: #ddfade;">self.current_voicemail -= 1</span>
<span class="diff-html-removed" id="removed-diff-149" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">print("Recording voicemail at {0}".format(path))</span><span class="diff-html-added" id="added-diff-135" style="font-size: 100%; background-color: #ddfade;"> if self.current_voicemail < 0:
self.current_voicemail = len(self.voicemails) - 1
def delete_message(self):</span>
<span class="diff-html-removed" id="removed-diff-150" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">DTMF_EVENT = channel.on_event('ChannelDtmfReceived', on_dtmf, recording, mailbox)
</span><span class="diff-html-added" id="added-diff-136" style="font-size: 100%; background-color: #ddfade;"> del self.voicemails[self.current_voicemail]
if len(self.voicemails) == 0:
return False
if self.current_voicemail == len(self.voicemails):
self.previous_message()
return True
def get_current(self):
return self.voicemails[self.current_voicemail]
</span>def stasis_start_cb(channel_obj, event):
channel = channel_obj['channel']
channel_name = channel.json.get('name')
mailbox = event.get('args')[0]
print("Channel {0} recording voicemail for {1}".format(
channel_name, mailbox))
channel.answer()
<span class="diff-html-removed" id="removed-diff-151" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">start_recording(</span><span class="diff-html-added" id="added-diff-137" style="font-size: 100%; background-color: #ddfade;">VoiceMailCall(client, </span>channel, mailbox)
<span class="diff-html-removed" id="removed-diff-152" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">
</span>client.on_channel_event('StasisStart', stasis_start_cb)
client.run(apps=<span class="diff-html-removed" id="removed-diff-153" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">'record-voicemail')
</span>
</pre> </td>
</tr>
</tbody>
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr></tr>
<tr>
<th style="background-color: transparent; text-align: left; font-weight: normal;"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">title</span></th>
<td style="padding: 0px; border-collapse: collapse"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">vm-record-retry.js</span></td>
</tr>
<tr>
<th style="background-color: transparent; text-align: left; font-weight: normal;"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">language</span></th>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-added" id="added-diff-138" style="font-size: 100%; background-color: #ddfade;">sys.argv[1])</span>
</pre> </td>
</tr>
</tbody>
</table> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">Quite a bit of this is similar to what we were using for </span><code style="font-family: monospace"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">vm-call.py</span></code><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"> previously. Aside from the new states, which will be introduced later, the real difference here is the new set of methods on the </span><code style="font-family: monospace"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">VoiceMailCall</span></code><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">.</span> </p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">First of the new states is the "preamble" state.</span> </p>
<table class="diff-macro diff-html-added diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ddfade;border-color: #93c49f;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">title</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">preamble_state.py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">language</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse">collapse</td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse">true</td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-removed" id="removed-diff-154" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">/*jshint node:true*/
'use strict';
var ari = require('ari-client');
var util = require('util');
var path = require('path');
ari.connect('http://localhost:8088', 'asterisk', 'asterisk', clientLoaded);
function clientLoaded(err, client) {
if (err) {</span><span class="diff-html-added" id="added-diff-139" style="font-size: 100%; background-color: #ddfade;">from event import Event
import uuid
class PreambleState(object):
state_name = "listening"
def __init__(self, call):
self.call = call
self.hangup_event = None
self.playback_finished = None
self.dtmf_event = None
self.playbacks = []
self.current_playback = 0
def enter(self):
print "Entering preamble 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.start_playbacks()
def start_playbacks(self):
message_playback = {
'id': str(uuid.uuid4()),
'media': 'sound:vm-message'
}
number_playback = {
'id': str(uuid.uuid4()),
</span> <span class="diff-html-removed" id="removed-diff-155" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">throw</span> <span class="diff-html-removed" id="removed-diff-156" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">err;</span> <span class="diff-html-added" id="added-diff-140" style="font-size: 100%; background-color: #ddfade;">'media':</span> <span class="diff-html-added" id="added-diff-141" style="font-size: 100%; background-color: #ddfade;">'number:{0}'.format(self.call.current_voicemail</span> <span class="diff-html-added" id="added-diff-142" style="font-size: 100%; background-color: #ddfade;">+</span> <span class="diff-html-added" id="added-diff-143" style="font-size: 100%; background-color: #ddfade;">1)
</span>}
<span class="diff-html-removed" id="removed-diff-157" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">client.on('StasisStart', stasisStart);</span><span class="diff-html-added" id="added-diff-144" style="font-size: 100%; background-color: #ddfade;"> self.playbacks.append(self.call.channel.playWithId(
playbackId=message_playback['id'],
media=message_playback['media']))</span>
<span class="diff-html-removed" id="removed-diff-158" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">function stasisStart(event, channel) {</span><span class="diff-html-added" id="added-diff-145" style="font-size: 100%; background-color: #ddfade;"> self.playbacks.append(self.call.channel.playWithId(
</span> <span class="diff-html-removed" id="removed-diff-159" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">var</span> <span class="diff-html-removed" id="removed-diff-160" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">mailbox</span> <span class="diff-html-removed" id="removed-diff-161" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">= event.args[0]</span><span class="diff-html-added" id="added-diff-146" style="font-size: 100%; background-color: #ddfade;"> playbackId=number_playback['id'],</span>
<span class="diff-html-removed" id="removed-diff-162" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">channel.answer(function(err) {</span><span class="diff-html-added" id="added-diff-147" style="font-size: 100%; background-color: #ddfade;"> media=number_playback['media']))
def stop_playbacks(self):
</span> <span class="diff-html-added" id="added-diff-148" style="font-size: 100%; background-color: #ddfade;">for</span> <span class="diff-html-added" id="added-diff-149" style="font-size: 100%; background-color: #ddfade;">playback</span> <span class="diff-html-added" id="added-diff-150" style="font-size: 100%; background-color: #ddfade;">in</span> <span class="diff-html-added" id="added-diff-151" style="font-size: 100%; background-color: #ddfade;">self.playbacks:</span>
<span class="diff-html-removed" id="removed-diff-163" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">if (err) {</span><span class="diff-html-added" id="added-diff-152" style="font-size: 100%; background-color: #ddfade;"> try:
playback.stop()
</span> <span class="diff-html-added" id="added-diff-153" style="font-size: 100%; background-color: #ddfade;">except:</span>
<span class="diff-html-removed" id="removed-diff-164" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">throw</span> <span class="diff-html-removed" id="removed-diff-165" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">err;</span> <span class="diff-html-added" id="added-diff-154" style="font-size: 100%; background-color: #ddfade;">pass</span>
<span class="diff-html-removed" id="removed-diff-166" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">}</span> <span class="diff-html-added" id="added-diff-155" style="font-size: 100%; background-color: #ddfade;">def</span> <span class="diff-html-added" id="added-diff-156" style="font-size: 100%; background-color: #ddfade;">cleanup(self):</span>
<span class="diff-html-removed" id="removed-diff-167" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">startRecording(channel, mailbox);</span><span class="diff-html-added" id="added-diff-157" style="font-size: 100%; background-color: #ddfade;"> self.playback_finished.close()
self.stop_playbacks()</span>
<span class="diff-html-removed" id="removed-diff-168" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">});</span><span class="diff-html-added" id="added-diff-158" style="font-size: 100%; background-color: #ddfade;">self.dtmf_event.close()</span>
<span class="diff-html-removed" id="removed-diff-169" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">}</span> <span class="diff-html-removed" id="removed-diff-170" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> function startRecording(</span><span class="diff-html-added" id="added-diff-159" style="font-size: 100%; background-color: #ddfade;">self.hangup_event.close()
def on_hangup(self, </span>channel, <span class="diff-html-removed" id="removed-diff-171" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">mailbox</span><span class="diff-html-added" id="added-diff-160" style="font-size: 100%; background-color: #ddfade;">event</span>)<span class="diff-html-added" id="added-diff-161" style="font-size: 100%; background-color: #ddfade;">:</span>
<span class="diff-html-removed" id="removed-diff-172" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">{</span> <span class="diff-html-added" id="added-diff-162" style="font-size: 100%; background-color: #ddfade;">self.cleanup()</span>
<span class="diff-html-removed" id="removed-diff-173" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">var</span> <span class="diff-html-removed" id="removed-diff-174" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">vm_path</span> <span class="diff-html-removed" id="removed-diff-175" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">= path.join('voicemail/', mailbox, Date.now().toString());</span><span class="diff-html-added" id="added-diff-163" style="font-size: 100%; background-color: #ddfade;"> self.call.state_machine.change_state(Event.HANGUP)
def on_playback_finished(self, event):
self.current_playback += 1
if self.current_playback == len(self.playbacks):
self.cleanup()</span>
<span class="diff-html-removed" id="removed-diff-176" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">console.log("Channel %s leaving voicemail for %s", channel.name, mailbox);</span><span class="diff-html-added" id="added-diff-164" style="font-size: 100%; background-color: #ddfade;"> self.call.state_machine.change_state(Event.DTMF_OCTOTHORPE)
def on_dtmf(self, channel, event):</span>
<span class="diff-html-removed" id="removed-diff-177" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">channel.record({name: vm_path, format: 'wav', beep: true}, function(err, recording) {</span><span class="diff-html-added" id="added-diff-165" style="font-size: 100%; background-color: #ddfade;">digit = event.get('digit')
if digit == '#':
self.cleanup()
self.call.state_machine.change_state(Event.DTMF_OCTOTHORPE)</span>
</pre> </td>
</tr>
</tbody>
</table> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <code style="font-family: monospace"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">PreambleState</span></code><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"> doesn't really introduce any new concepts. What is a bit different this time is that we demonstrate that multiple playbacks can be queued at once.</span> </p> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">Here is the "listening" state code</span> </p>
<table class="diff-macro diff-html-added diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;background-color: #ddfade;border-color: #93c49f;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<thead>
<tr>
<th class="diff-macro-title" style="background-color: transparent; text-align: left; font-weight: normal;padding: 5px;"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;"><span class="icon macro-placeholder-icon" style="background-color: ;line-height: 20px;"><img src="https://wiki.asterisk.org/wiki/s/en_GB/5635/60fd2eb45debbf4ede2b669f4c9b96b4ce40a937.48/_/plugins/servlet/confluence/placeholder/macro-icon?name=code" style="padding-right: 5px; vertical-align: text-bottom;" /> </span>Code Block</span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="diff-macro-properties" style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;padding: 0; border: 1px solid #dddddd;; padding: 0px; border-collapse: collapse">
<table style="border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">title</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">listening_state.py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">language</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">py</span></td>
</tr>
<tr>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">collapse</span></td>
<td style="background-color: #fafafa; padding: 0 0 0 5px; font-size: 12px; text-align: left;; padding: 0px; border-collapse: collapse"><span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">true</span></td>
</tr>
</tbody>
</table> </td>
</tr>
</tbody>
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-added" style="font-size: 100%; background-color: #ddfade;">from event import Event
import uuid
class ListeningState(object):
state_name = "listening"
def __init__(self, call):
</span> <span class="diff-html-added" id="added-diff-166" style="font-size: 100%; background-color: #ddfade;">self.call</span> <span class="diff-html-added" id="added-diff-167" style="font-size: 100%; background-color: #ddfade;">=</span> <span class="diff-html-added" id="added-diff-168" style="font-size: 100%; background-color: #ddfade;">call</span>
<span class="diff-html-removed" id="removed-diff-178" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">if (err) {</span><span class="diff-html-added" id="added-diff-169" style="font-size: 100%; background-color: #ddfade;"> self.playback_id = None
self.hangup_event = None
self.playback_finished = None
self.dtmf_event = None
self.playback = None
self.paused = False
def enter(self):
</span> <span class="diff-html-added" id="added-diff-170" style="font-size: 100%; background-color: #ddfade;">self.playback_id</span> <span class="diff-html-added" id="added-diff-171" style="font-size: 100%; background-color: #ddfade;">=</span> <span class="diff-html-added" id="added-diff-172" style="font-size: 100%; background-color: #ddfade;">str(uuid.uuid4())</span>
<span class="diff-html-removed" id="removed-diff-179" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">throw</span> <span class="diff-html-removed" id="removed-diff-180" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">err;</span> <span class="diff-html-added" id="added-diff-173" style="font-size: 100%; background-color: #ddfade;">print</span> <span class="diff-html-added" id="added-diff-174" style="font-size: 100%; background-color: #ddfade;">"Entering</span> <span class="diff-html-added" id="added-diff-175" style="font-size: 100%; background-color: #ddfade;">listening</span> <span class="diff-html-added" id="added-diff-176" style="font-size: 100%; background-color: #ddfade;">state"</span>
<span class="diff-html-removed" id="removed-diff-181" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">}</span><span class="diff-html-added" id="added-diff-177" style="font-size: 100%; background-color: #ddfade;">self.hangup_event</span> <span class="diff-html-added" id="added-diff-178" style="font-size: 100%; background-color: #ddfade;">=</span> <span class="diff-html-added" id="added-diff-179" style="font-size: 100%; background-color: #ddfade;">self.call.channel.on_event("ChannelHangupRequest",</span>
<span class="diff-html-removed" id="removed-diff-182" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">channel.on('ChannelDtmfReceived', function dtmfReceived(event, channel) {</span><span class="diff-html-added" id="added-diff-180" style="font-size: 100%; background-color: #ddfade;"> 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)
</span> <span class="diff-html-added" id="added-diff-181" style="font-size: 100%; background-color: #ddfade;">self.playback</span> <span class="diff-html-added" id="added-diff-182" style="font-size: 100%; background-color: #ddfade;">=</span> <span class="diff-html-added" id="added-diff-183" style="font-size: 100%; background-color: #ddfade;">self.call.channel.playWithId(</span>
<span class="diff-html-removed" id="removed-diff-183" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">var</span> <span class="diff-html-removed" id="removed-diff-184" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">digit = event.digit;</span><span class="diff-html-added" id="added-diff-184" style="font-size: 100%; background-color: #ddfade;"> playbackId=self.playback_id, media="recording:{0}".format(</span>
<span class="diff-html-removed" id="removed-diff-185" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">if (digit == '*') {</span><span class="diff-html-added" id="added-diff-185" style="font-size: 100%; background-color: #ddfade;"> self.call.get_current()))
def cleanup(self):
</span> <span class="diff-html-added" id="added-diff-186" style="font-size: 100%; background-color: #ddfade;">self.playback_finished.close()</span>
<span class="diff-html-added" id="added-diff-187" style="font-size: 100%; background-color: #ddfade;">if</span> <span class="diff-html-added" id="added-diff-188" style="font-size: 100%; background-color: #ddfade;">self.playback:</span>
<span class="diff-html-removed" id="removed-diff-186" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">console.log("Canceling recording", recording.name);</span><span class="diff-html-added" id="added-diff-189" style="font-size: 100%; background-color: #ddfade;"> self.playback.stop()
self.dtmf_event.close()</span>
<span class="diff-html-added" id="added-diff-190" style="font-size: 100%; background-color: #ddfade;">self.hangup_event.close()</span>
<span class="diff-html-added" id="added-diff-191" style="font-size: 100%; background-color: #ddfade;">def</span> <span class="diff-html-added" id="added-diff-192" style="font-size: 100%; background-color: #ddfade;">on_hangup(self,</span> <span class="diff-html-added" id="added-diff-193" style="font-size: 100%; background-color: #ddfade;">channel,</span> <span class="diff-html-added" id="added-diff-194" style="font-size: 100%; background-color: #ddfade;">event):</span>
<span class="diff-html-removed" id="removed-diff-187" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">recording.cancel</span><span class="diff-html-added" id="added-diff-195" style="font-size: 100%; background-color: #ddfade;"> self.cleanup</span>()<span class="diff-html-removed" id="removed-diff-188" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">;</span>
<span class="diff-html-added" id="added-diff-196" style="font-size: 100%; background-color: #ddfade;">self.call.state_machine.change_state(Event.HANGUP)</span>
<span class="diff-html-added" id="added-diff-197" style="font-size: 100%; background-color: #ddfade;">def</span> <span class="diff-html-added" id="added-diff-198" style="font-size: 100%; background-color: #ddfade;">on_playback_finished(self,</span> <span class="diff-html-added" id="added-diff-199" style="font-size: 100%; background-color: #ddfade;">event):</span>
<span class="diff-html-removed" id="removed-diff-189" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">channel.removeListener('ChannelDtmfReceived', dtmfReceived);</span><span class="diff-html-added" id="added-diff-200" style="font-size: 100%; background-color: #ddfade;"> if self.playback_id == event.get('playback').get('id'):</span>
<span class="diff-html-added" id="added-diff-201" style="font-size: 100%; background-color: #ddfade;">self.playback</span> <span class="diff-html-added" id="added-diff-202" style="font-size: 100%; background-color: #ddfade;">=</span> <span class="diff-html-added" id="added-diff-203" style="font-size: 100%; background-color: #ddfade;">None</span>
<span class="diff-html-removed" id="removed-diff-190" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">startRecording(</span><span class="diff-html-added" id="added-diff-204" style="font-size: 100%; background-color: #ddfade;">def on_dtmf(self, </span>channel, <span class="diff-html-removed" id="removed-diff-191" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">mailbox</span><span class="diff-html-added" id="added-diff-205" style="font-size: 100%; background-color: #ddfade;">event</span>)<span class="diff-html-removed" id="removed-diff-192" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">;</span><span class="diff-html-added" id="added-diff-206" style="font-size: 100%; background-color: #ddfade;">:</span>
<span class="diff-html-added" id="added-diff-207" style="font-size: 100%; background-color: #ddfade;">digit</span> <span class="diff-html-added" id="added-diff-208" style="font-size: 100%; background-color: #ddfade;">=</span> <span class="diff-html-added" id="added-diff-209" style="font-size: 100%; background-color: #ddfade;">event.get('digit')</span>
<span class="diff-html-removed" id="removed-diff-193" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">}</span> <span class="diff-html-removed" id="removed-diff-194" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">else</span> <span class="diff-html-added" id="added-diff-210" style="font-size: 100%; background-color: #ddfade;"> </span>if <span class="diff-html-removed" id="removed-diff-195" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">(</span>digit == '<span class="diff-html-removed" id="removed-diff-196" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">#</span><span class="diff-html-added" id="added-diff-211" style="font-size: 100%; background-color: #ddfade;">1</span>'<span class="diff-html-removed" id="removed-diff-197" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">)</span><span class="diff-html-added" id="added-diff-212" style="font-size: 100%; background-color: #ddfade;">:</span>
<span class="diff-html-removed" id="removed-diff-198" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">{</span> <span class="diff-html-added" id="added-diff-213" style="font-size: 100%; background-color: #ddfade;">if</span> <span class="diff-html-added" id="added-diff-214" style="font-size: 100%; background-color: #ddfade;">not</span> <span class="diff-html-added" id="added-diff-215" style="font-size: 100%; background-color: #ddfade;">self.playback:</span>
<span class="diff-html-removed" id="removed-diff-199" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">console.log("Stopping</span> <span class="diff-html-removed" id="removed-diff-200" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">recording",</span> <span class="diff-html-removed" id="removed-diff-201" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">recording.name);</span> <span class="diff-html-added" id="added-diff-216" style="font-size: 100%; background-color: #ddfade;">return</span>
<span class="diff-html-removed" id="removed-diff-202" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">recording</span><span class="diff-html-added" id="added-diff-217" style="font-size: 100%; background-color: #ddfade;">self</span>.<span class="diff-html-removed" id="removed-diff-203" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">stop();</span><span class="diff-html-added" id="added-diff-218" style="font-size: 100%; background-color: #ddfade;">playback.control(operation='reverse')</span>
<span class="diff-html-added" id="added-diff-219" style="font-size: 100%; background-color: #ddfade;">elif</span> <span class="diff-html-added" id="added-diff-220" style="font-size: 100%; background-color: #ddfade;">digit</span> <span class="diff-html-added" id="added-diff-221" style="font-size: 100%; background-color: #ddfade;">==</span> <span class="diff-html-added" id="added-diff-222" style="font-size: 100%; background-color: #ddfade;">'2':</span>
<span class="diff-html-removed" id="removed-diff-204" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">channel.removeListener('ChannelDtmfReceived', dtmfReceived);</span><span class="diff-html-added" id="added-diff-223" style="font-size: 100%; background-color: #ddfade;"> if not self.playback:
return
if self.paused:
self.playback.control(operation='unpause')</span>
<span class="diff-html-added" id="added-diff-224" style="font-size: 100%; background-color: #ddfade;">self.paused</span> <span class="diff-html-added" id="added-diff-225" style="font-size: 100%; background-color: #ddfade;">=</span> <span class="diff-html-removed" id="removed-diff-205" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> channel.hangup();</span><span class="diff-html-added" id="added-diff-226" style="font-size: 100%; background-color: #ddfade;">False
else:
self.playback.control(operation='pause')</span>
<span class="diff-html-removed" id="removed-diff-206" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">}</span><span class="diff-html-added" id="added-diff-227" style="font-size: 100%; background-color: #ddfade;">self.paused</span> <span class="diff-html-added" id="added-diff-228" style="font-size: 100%; background-color: #ddfade;">=</span> <span class="diff-html-added" id="added-diff-229" style="font-size: 100%; background-color: #ddfade;">True</span>
<span class="diff-html-added" id="added-diff-230" style="font-size: 100%; background-color: #ddfade;">elif</span> <span class="diff-html-added" id="added-diff-231" style="font-size: 100%; background-color: #ddfade;">digit</span> <span class="diff-html-removed" id="removed-diff-207" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">});
</span><span class="diff-html-added" id="added-diff-232" style="font-size: 100%; background-color: #ddfade;">== '3':</span>
<span class="diff-html-removed" id="removed-diff-208" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">});</span> <span class="diff-html-removed" id="removed-diff-209" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">}</span><span class="diff-html-added" id="added-diff-233" style="font-size: 100%; background-color: #ddfade;">if</span> <span class="diff-html-added" id="added-diff-234" style="font-size: 100%; background-color: #ddfade;">not</span> <span class="diff-html-added" id="added-diff-235" style="font-size: 100%; background-color: #ddfade;">self.playback:</span>
<span class="diff-html-removed" id="removed-diff-210" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">client.start('record-voicemail');
}
</span>
</pre> </td>
</tr>
</tbody>
</table> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">Instead of listing for a </span><code style="line-height: 1.4285715;; font-family: monospace"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">RecordingFinished</span></code><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> event, we instead listen for </span><code style="line-height: 1.4285715;; font-family: monospace"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">ChannelDtmfReceived</span></code><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">. This lets us act differently depending on which DTMF digit we receive from the channel. Notice that when we receive a DTMF digit that we care about, we stop listening for DTMF ({{DTMF_EVENT.stop()}} in Python, </span><code style="font-family: monospace"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">channel.removeListener()</span></code><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> in Javascript). This is so that if a user presses the '*' key multiple times while recording, we will not have the </span><code style="font-family: monospace"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">ChannelDtmfReceived</span></code><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> event fire multiple times for a single key press. Since we are having to listen for DTMF events, it also becomes easier for us to detect the '#' key press ourselves and leave off the </span><code style="font-family: monospace"><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">terminateOn</span></code><span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;"> option when recording the audio.</span> </p> <h3 id="MEDIA?MORELIKEMEDI-DUH!-Playingbackrecordings" class="diff-block-target diff-block-context" style="margin: 10px 0 0 0; font-size: 16px; line-height: 25px; margin: 30px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">Playing back recordings</span> </h3> <p class="diff-block-target diff-block-context" style="margin: 10px 0 0 0"> <span class="diff-html-removed" style="font-size: 100%; background-color: #ffe7e7; text-decoration: line-through;">Being able to cancel a voicemail while recording it is somewhat nice. What would be better, though, is if a caller were able to listen to the recorded voicemail and then be able to accept the current recording or discard it and re-record. We can modify the scripts to be able to play the stored voicemail back once the user has finished recording the initial voicemail.</span> </p> <p class="diff-context-placeholder" style="margin: 10px 0 0 0">...</p>
<table class="diff-macro diff-block-target diff-block-context" style="background-color: #f0f0f0;border: 1px solid #dddddd;margin: 10px 1px;padding: 0 2px 2px;width: 100%;; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #333">
<tbody>
<tr>
<td class="diff-macro-body" style="background-color: #fff;border: 1px solid #dddddd;padding: 10px;; padding: 0px; border-collapse: collapse"> <pre style="margin: 10px 0 0 0; margin-top: 0">
<span class="diff-html-added" id="added-diff-236" style="font-size: 100%; background-color: #ddfade;"> return
self.playback.control(operation='forward')
elif digit == '4':
self.cleanup()
self.call.previous_message()
self.call.state_machine.change_state(Event.DTMF_FOUR)
elif digit == '6':
self.cleanup()
self.call.next_message()
self.call.state_machine.change_state(Event.DTMF_SIX)
elif digit == '#':
self.cleanup()
self.call.state_machine.change_state(Event.DTMF_OCTOTHORPE)
elif digit == '*':
print ("Deleting stored recording {0}".format(
self.call.get_current()))
self.cleanup()
self.call.client.recordings.deleteStored(
recordingName=self.call.get_current())
if self.call.delete_message():
self.call.state_machine.change_state(Event.DTMF_STAR)
else:
self.call.state_machine.change_state(Event.EMPTY)</span>
</pre> </td>
</tr>
</tbody>
</table> </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/pages/viewpage.action?pageId=29396202&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/pages/viewpage.action?pageId=29396202&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/pages/viewpage.action?pageId=29396202&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/pages/viewpage.action?pageId=29396202&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=29396202&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=29396202&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>