From 17e2416f680cd15be8a23d4b119642ad0e305f26 Mon Sep 17 00:00:00 2001 From: Mark Spencer Date: Sat, 13 Mar 2004 06:00:41 +0000 Subject: [PATCH] Merge queue changes from Bug #214 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@2415 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- CHANGES | 2 + Makefile | 2 +- apps/app_queue.c | 304 +++++++++++++++++++++++++++++++--- configs/queues.conf.sample | 34 ++++ sounds.txt | 12 ++ sounds/queue-callswaiting.gsm | Bin 0 -> 3366 bytes sounds/queue-holdtime.gsm | Bin 0 -> 4026 bytes sounds/queue-minutes.gsm | Bin 0 -> 1056 bytes sounds/queue-thankyou.gsm | Bin 0 -> 2739 bytes sounds/queue-thereare.gsm | Bin 0 -> 3234 bytes sounds/queue-youarenext.gsm | Bin 0 -> 8283 bytes 11 files changed, 333 insertions(+), 21 deletions(-) create mode 100755 sounds/queue-callswaiting.gsm create mode 100755 sounds/queue-holdtime.gsm create mode 100755 sounds/queue-minutes.gsm create mode 100755 sounds/queue-thankyou.gsm create mode 100755 sounds/queue-thereare.gsm create mode 100755 sounds/queue-youarenext.gsm diff --git a/CHANGES b/CHANGES index 58728c7c7f..f2a13716b5 100755 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,5 @@ + -- ADPCM Standardization + -- Numerous bug fixes -- Add IAX2 Firmware Support -- Add G.726 support -- Add ices/icecast support diff --git a/Makefile b/Makefile index da0aab927d..e8fbe76616 100755 --- a/Makefile +++ b/Makefile @@ -269,7 +269,7 @@ datafiles: all exit 1; \ fi; \ done - for x in sounds/vm-* sounds/transfer* sounds/pbx-* sounds/ss-* sounds/beep* sounds/dir-* sounds/conf-* sounds/agent-* sounds/invalid* sounds/tt-* sounds/auth-* sounds/privacy-*; do \ + for x in sounds/vm-* sounds/transfer* sounds/pbx-* sounds/ss-* sounds/beep* sounds/dir-* sounds/conf-* sounds/agent-* sounds/invalid* sounds/tt-* sounds/auth-* sounds/privacy-* sounds/queue-*; do \ if grep -q "^%`basename $$x`%" sounds.txt; then \ install -m 644 $$x $(DESTDIR)$(ASTVARLIBDIR)/sounds ; \ else \ diff --git a/apps/app_queue.c b/apps/app_queue.c index 2f762497d9..8094904b06 100755 --- a/apps/app_queue.c +++ b/apps/app_queue.c @@ -7,6 +7,21 @@ * * Mark Spencer * + * These features added by David C. Troy : + * - Per-queue holdtime calculation + * - Estimated holdtime announcement + * - Position announcement + * - Abandoned/completed call counters + * - Failout timer passed as optional app parameter + * - Optional monitoring of calls, started when call is answered + * + * Patch Version 1.07 2003-12-24 01 + * + * Added servicelevel statistic by Michiel Betel + * + * Fixed ot work with CVS as of 2004-02-25 and released as 1.07a + * by Matthew Enger + * * This program is free software, distributed under the terms of * the GNU General Public License */ @@ -25,6 +40,7 @@ #include #include /* JDG */ #include +#include #include #include #include @@ -67,7 +83,7 @@ static char *app = "Queue"; static char *synopsis = "Queue a call for a call queue"; static char *descrip = -" Queue(queuename[|options[|URL][|announceoverride]]):\n" +" Queue(queuename[|options[|URL][|announceoverride][|timeout]]):\n" "Queues an incoming call in a particular call queue as defined in queues.conf.\n" " This application returns -1 if the originating channel hangs up, or if the\n" "call is bridged and either of the parties in the bridge terminate the call.\n" @@ -81,7 +97,9 @@ static char *descrip = " In addition to transferring the call, a call may be parked and then picked\n" "up by another user.\n" " The optional URL will be sent to the called party if the channel supports\n" -"it.\n"; +"it.\n" +" The timeout will cause the queue to fail out after a specified number of\n" +"seconds, checked between each queues.conf 'timeout' and 'retry' cycle.\n"; // [PHM 06/26/03] static char *app_aqm = "AddQueueMember" ; @@ -127,12 +145,15 @@ LOCAL_USER_DECL; struct queue_ent { struct ast_call_queue *parent; /* What queue is our parent */ char moh[80]; /* Name of musiconhold to be used */ - char announce[80]; /* Announcement to play */ + char announce[80]; /* Announcement to play for member when call is answered */ char context[80]; /* Context when user exits queue */ int pos; /* Where we are in the queue */ + int last_pos_said; /* Last position we told the user */ + time_t last_pos; /* Last time we told the user their position */ int opos; /* Where we started in the queue */ int handled; /* Whether our call was handled */ time_t start; /* When we started holding */ + int queuetimeout; /* How many seconds before timing out of queue */ struct ast_channel *chan; /* Our channel */ struct queue_ent *next; /* The next queue entry */ }; @@ -151,10 +172,24 @@ struct ast_call_queue { ast_mutex_t lock; char name[80]; /* Name of the queue */ char moh[80]; /* Name of musiconhold to be used */ - char announce[80]; /* Announcement to play */ - char context[80]; /* Announcement to play */ + char announce[80]; /* Announcement to play when call is answered */ + char context[80]; /* Context for this queue */ int strategy; /* Queueing strategy */ - int announcetimeout; /* How often to announce their position */ + int announcefrequency; /* How often to announce their position */ + int announceholdtime; /* When to announce holdtime: 0 = never, -1 = every announcement, 1 = only once */ + int holdtime; /* Current avg holdtime for this queue, based on recursive boxcar filter */ + int callscompleted; /* Number of queue calls completed */ + int callsabandoned; /* Number of queue calls abandoned */ + int servicelevel; /* seconds setting for servicelevel*/ + int callscompletedinsl; /* Number of queue calls answererd with servicelevel*/ + char monfmt[8]; /* Format to use when recording calls */ + char sound_next[80]; /* Sound file: "Your call is now first in line" (def. queue-youarenext) */ + char sound_thereare[80]; /* Sound file: "There are currently" (def. queue-thereare) */ + char sound_calls[80]; /* Sound file: "calls waiting to speak to a representative." (def. queue-callswaiting)*/ + char sound_holdtime[80]; /* Sound file: "The current estimated total holdtime is" (def. queue-holdtime) */ + char sound_minutes[80]; /* Sound file: "minutes." (def. queue-minutes) */ + char sound_thanks[80]; /* Sound file: "Thank you for your patience." (def. queue-thankyou) */ + int count; /* How many entries are in the queue */ int maxlen; /* Max number of entries in queue */ @@ -288,6 +323,102 @@ static void destroy_queue(struct ast_call_queue *q) free(q); } +static int play_file(struct ast_channel *chan, char *filename) +{ + int res; + + ast_stopstream(chan); + res = ast_streamfile(chan, filename, chan->language); + + if (!res) + res = ast_waitstream(chan, ""); + else + res = 0; + + if (res) { + ast_log(LOG_WARNING, "ast_streamfile failed on %s \n", chan->name); + res = 0; + } + ast_stopstream(chan); + + return res; +} + +static int say_position(struct queue_ent *qe) +{ + int res = 0, avgholdmins; + time_t now; + + /* Check to see if this is ludicrous -- if we just announced position, don't do it again*/ + time(&now); + if ( (now - qe->last_pos) < 15 ) + return -1; + + /* If either our position has changed, or we are over the freq timer, say position */ + if ( (qe->last_pos_said == qe->pos) && ((now - qe->last_pos) < qe->parent->announcefrequency) ) + return -1; + + ast_moh_stop(qe->chan); + /* Say we're next, if we are */ + if (qe->pos == 1) { + res += play_file(qe->chan, qe->parent->sound_next); + goto posout; + } else { + res += play_file(qe->chan, qe->parent->sound_thereare); + res += ast_say_number(qe->chan, qe->pos, AST_DIGIT_ANY, qe->chan->language); + res += play_file(qe->chan, qe->parent->sound_calls); + } + + /* Round hold time to nearest minute */ + avgholdmins = ( (qe->parent->holdtime + 30) - (now - qe->start) ) / 60; + ast_verbose(VERBOSE_PREFIX_3 "Hold time for %s is %d minutes\n", qe->parent->name, avgholdmins); + + /* If the hold time is >1 min, if it's enabled, and if it's not + supposed to be only once and we have already said it, say it */ + if (avgholdmins > 1 && (qe->parent->announceholdtime) && (!(qe->parent->announceholdtime==1 && qe->last_pos)) ) { + res += play_file(qe->chan, qe->parent->sound_holdtime); + res += ast_say_number(qe->chan, avgholdmins, AST_DIGIT_ANY, qe->chan->language); + res += play_file(qe->chan, qe->parent->sound_minutes); + } + + posout: + /* Set our last_pos indicators */ + qe->last_pos = now; + qe->last_pos_said = qe->pos; + + ast_verbose(VERBOSE_PREFIX_3 "Told %s in %s their queue position (which was %d)\n", qe->chan->name, qe->parent->name, qe->pos); + res += play_file(qe->chan, qe->parent->sound_thanks); + ast_moh_start(qe->chan, qe->moh); + + return (res>0); +} + +static void record_abandoned(struct queue_ent *qe) +{ + ast_mutex_lock(&qe->parent->lock); + qe->parent->callsabandoned++; + ast_mutex_unlock(&qe->parent->lock); +} + +static void recalc_holdtime(struct queue_ent *qe) +{ + int oldvalue, newvalue; + + /* Calculate holdtime using a recursive boxcar filter */ + /* Thanks to SRT for this contribution */ + /* 2^2 (4) is the filter coefficient; a higher exponent would give old entries more weight */ + + newvalue = time(NULL) - qe->start; + + ast_mutex_lock(&qe->parent->lock); + if (newvalue <= qe->parent->servicelevel) + qe->parent->callscompletedinsl++; + oldvalue = qe->parent->holdtime; + qe->parent->holdtime = (((oldvalue << 2) - oldvalue) + newvalue) >> 2; + ast_mutex_unlock(&qe->parent->lock); +} + + static void leave_queue(struct queue_ent *qe) { struct ast_call_queue *q; @@ -317,6 +448,7 @@ ast_log(LOG_NOTICE, "Queue '%s' Leave, Channel '%s'\n", q->name, qe->chan->name else q->head = cur->next; } else { + /* Renumber the people after us in the queue based on a new count */ cur->pos = ++pos; prev = cur; } @@ -611,12 +743,29 @@ static int wait_our_turn(struct queue_ent *qe) { struct queue_ent *ch; int res = 0; + time_t now; + + /* This is the holding pen for callers 2 through maxlen */ for (;;) { /* Atomically read the parent head -- does not need a lock */ ch = qe->parent->head; + /* If we are now at the top of the head, break out */ - if (qe->parent->head == qe) + if (qe == ch) + break; + + /* If we have timed out, break out */ + if ( qe->queuetimeout ) { + time(&now); + if ( (now - qe->start) >= qe->queuetimeout ) break; + } + + /* Make a position announcement, if enabled */ + if (qe->parent->announcefrequency) + say_position(qe); + + /* Wait a second before checking again */ res = ast_waitfordigit(qe->chan, RECHECK * 1000); if (res) @@ -640,6 +789,7 @@ static int update_queue(struct ast_call_queue *q, struct localuser *user) } cur = cur->next; } + q->callscompleted++; ast_mutex_unlock(&q->lock); return 0; } @@ -793,6 +943,7 @@ static int try_calling(struct queue_ent *qe, char *options, char *announceoverri if (!peer) { if (to) { /* Musta gotten hung up */ + record_abandoned(qe); res = -1; } else { if (digit && valid_exit(qe, digit)) @@ -817,6 +968,7 @@ static int try_calling(struct queue_ent *qe, char *options, char *announceoverri ast_channel_setoption(peer,AST_OPTION_TONE_VERIFY,&zapx,sizeof(char),0); } /* Update parameters for the queue */ + recalc_holdtime(qe); update_queue(qe->parent, lpeer); hanguptree(outgoing, peer); /* Stop music on hold */ @@ -849,6 +1001,10 @@ static int try_calling(struct queue_ent *qe, char *options, char *announceoverri ast_hangup(peer); return -1; } + /* Begin Monitoring */ + if (qe->parent->monfmt && *qe->parent->monfmt) { + ast_monitor_start( peer, qe->parent->monfmt, qe->chan->cdr->uniqueid, 1 ); + } /* Drop out of the queue at this point, to prepare for next caller */ leave_queue(qe); /* JDG: sendurl */ @@ -1120,6 +1276,7 @@ static int queue_exec(struct ast_channel *chan, void *data) char *options = NULL; char *url = NULL; char *announceoverride = NULL; + char *queuetimeoutstr = NULL; /* whether to exit Queue application after the timeout hits */ int go_on = 0; @@ -1134,6 +1291,9 @@ static int queue_exec(struct ast_channel *chan, void *data) } LOCAL_USER_ADD(u); + + /* Setup our queue entry */ + memset(&qe, 0, sizeof(qe)); /* Parse our arguments XXX Check for failure XXX */ strncpy(info, (char *)data, strlen((char *)data) + AST_MAX_EXTENSION-1); @@ -1151,25 +1311,39 @@ static int queue_exec(struct ast_channel *chan, void *data) if (announceoverride) { *announceoverride = '\0'; announceoverride++; + queuetimeoutstr = strchr(announceoverride, '|'); + if (queuetimeoutstr) { + *queuetimeoutstr = '\0'; + queuetimeoutstr++; + qe.queuetimeout = atoi(queuetimeoutstr); + } else { + qe.queuetimeout = 0; } } } } - if (option_debug) - ast_log(LOG_DEBUG, "queue: %s, options: %s, url: %s, announce: %s\n", - queuename, options, url, announceoverride); - /* Setup our queue entry */ - memset(&qe, 0, sizeof(qe)); + } + + printf("queue: %s, options: %s, url: %s, announce: %s, timeout: %d\n", + queuename, options, url, announceoverride, qe.queuetimeout); + + qe.chan = chan; qe.start = time(NULL); + qe.last_pos_said = 0; + qe.last_pos = 0; if (!join_queue(queuename, &qe)) { ast_queue_log(queuename, chan->uniqueid, "NONE", "ENTERQUEUE", "%s|%s", url ? url : "", chan->callerid ? chan->callerid : ""); /* Start music on hold */ ast_moh_start(chan, qe.moh); for (;;) { + /* This is the wait loop for callers 2 through maxlen */ + res = wait_our_turn(&qe); /* If they hungup, return immediately */ if (res < 0) { + /* Record this abandoned call */ + record_abandoned(&qe); ast_queue_log(queuename, chan->uniqueid, "NONE", "ABANDON", "%d|%d|%ld", qe.pos, qe.opos, (long)time(NULL) - qe.start); if (option_verbose > 2) { ast_verbose(VERBOSE_PREFIX_3 "User disconnected while waiting their turn\n"); @@ -1186,6 +1360,22 @@ static int queue_exec(struct ast_channel *chan, void *data) } if (!res) { for (;;) { + /* This is the wait loop for the head caller*/ + /* To exit, they may get their call answered; */ + /* they may dial a digit from the queue context; */ + /* or, they may may timeout. */ + + /* Leave if we have exceeded our queuetimeout */ + if (qe.queuetimeout && ( (time(NULL) - qe.start) >= qe.queuetimeout) ) { + res = 0; + break; + } + + /* Make a position announcement, if enabled */ + if (qe.parent->announcefrequency) + say_position(&qe); + + /* Try calling all queue members for 'timeout' seconds */ res = try_calling(&qe, options, announceoverride, url, &go_on); if (res) { if (res < 0) { @@ -1195,6 +1385,14 @@ static int queue_exec(struct ast_channel *chan, void *data) ast_queue_log(queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%c|%d", res, qe.pos); break; } + + /* Leave if we have exceeded our queuetimeout */ + if (qe.queuetimeout && ( (time(NULL) - qe.start) >= qe.queuetimeout) ) { + res = 0; + break; + } + + /* OK, we didn't get anybody; wait for 'retry' seconds; may get a digit to exit with */ res = wait_a_bit(&qe); if (res < 0) { ast_queue_log(queuename, chan->uniqueid, "NONE", "ABANDON", "%d|%d|%ld", qe.pos, qe.opos, (long)time(NULL) - qe.start); @@ -1208,18 +1406,20 @@ static int queue_exec(struct ast_channel *chan, void *data) ast_queue_log(queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%c|%d", res, qe.pos); break; } - /* exit after a timeout if 'n' option enabled */ + /* exit after 'timeout' cycle if 'n' option enabled */ if (go_on) { ast_queue_log(queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos); res = 0; break; } + } } /* Don't allow return code > 0 */ if (res > 0 && res != AST_PBX_KEEPALIVE) { res = 0; ast_moh_stop(chan); + ast_stopstream(chan); } leave_queue(&qe); } else { @@ -1281,10 +1481,24 @@ static void reload_queues(void) q->retry = 0; q->timeout = -1; q->maxlen = 0; + q->announcefrequency = 0; + q->announceholdtime = 0; + q->holdtime = 0; + q->callscompleted = 0; + q->callsabandoned = 0; + q->callscompletedinsl = 0; + q->servicelevel = 0; free_members(q, 0); strcpy(q->moh, ""); strcpy(q->announce, ""); strcpy(q->context, ""); + strcpy(q->monfmt, ""); + strcpy(q->sound_next, "queue-youarenext"); + strcpy(q->sound_thereare, "queue-thereare"); + strcpy(q->sound_calls, "queue-callswaiting"); + strcpy(q->sound_holdtime, "queue-holdtime"); + strcpy(q->sound_minutes, "queue-minutes"); + strcpy(q->sound_thanks, "queue-thankyou"); prev = q->members; if (prev) { /* find the end of any dynamic members */ @@ -1329,10 +1543,30 @@ static void reload_queues(void) strncpy(q->context, var->value, sizeof(q->context) - 1); } else if (!strcasecmp(var->name, "timeout")) { q->timeout = atoi(var->value); + } else if (!strcasecmp(var->name, "monitor-format")) { + strncpy(q->monfmt, var->value, sizeof(q->monfmt) - 1); + } else if (!strcasecmp(var->name, "queue-youarenext")) { + strncpy(q->sound_next, var->value, sizeof(q->sound_next) - 1); + } else if (!strcasecmp(var->name, "queue-thereare")) { + strncpy(q->sound_thereare, var->value, sizeof(q->sound_thereare) - 1); + } else if (!strcasecmp(var->name, "queue-callswaiting")) { + strncpy(q->sound_calls, var->value, sizeof(q->sound_calls) - 1); + } else if (!strcasecmp(var->name, "queue-holdtime")) { + strncpy(q->sound_holdtime, var->value, sizeof(q->sound_holdtime) - 1); + } else if (!strcasecmp(var->name, "queue-minutes")) { + strncpy(q->sound_minutes, var->value, sizeof(q->sound_minutes) - 1); + } else if (!strcasecmp(var->name, "queue-thankyou")) { + strncpy(q->sound_thanks, var->value, sizeof(q->sound_thanks) - 1); + } else if (!strcasecmp(var->name, "announce-frequency")) { + q->announcefrequency = atoi(var->value); + } else if (!strcasecmp(var->name, "announce-holdtime")) { + q->announceholdtime = (!strcasecmp(var->value,"once")) ? 1 : ast_true(var->value); } else if (!strcasecmp(var->name, "retry")) { q->retry = atoi(var->value); } else if (!strcasecmp(var->name, "maxlen")) { q->maxlen = atoi(var->value); + } else if (!strcasecmp(var->name, "servicelevel")) { + q->servicelevel= atoi(var->value); } else if (!strcasecmp(var->name, "strategy")) { q->strategy = strat2int(var->value); if (q->strategy < 0) { @@ -1390,6 +1624,8 @@ static int __queues_show(int fd, int argc, char **argv, int queue_show) time_t now; char max[80]; char calls[80]; + float sl = 0; + time(&now); if ((!queue_show && argc != 2) || (queue_show && argc != 3)) return RESULT_SHOWUSAGE; @@ -1420,7 +1656,11 @@ static int __queues_show(int fd, int argc, char **argv, int queue_show) snprintf(max, sizeof(max), "%d", q->maxlen); else strcpy(max, "unlimited"); - ast_cli(fd, "%-12.12s has %d calls (max %s) in '%s' strategy\n", q->name, q->count, max, int2strat(q->strategy)); + sl = 0; + if(q->callscompleted > 0) + sl = 100*((float)q->callscompletedinsl/(float)q->callscompleted); + ast_cli(fd, "%-12.12s has %d calls (max %s) in '%s' strategy (%ds holdtime), C:%d, A:%d, SL:%2.1f%% within %ds\n", + q->name, q->count, max, int2strat(q->strategy), q->holdtime, q->callscompleted, q->callsabandoned,sl,q->servicelevel); if (q->members) { ast_cli(fd, " Members: \n"); for (mem = q->members; mem; mem = mem->next) { @@ -1502,6 +1742,8 @@ static int manager_queues_status( struct mansession *s, struct message *m ) char idText[256] = ""; struct ast_call_queue *q; struct queue_ent *qe; + float sl = 0; + struct member *mem; astman_send_ack(s, m, "Queue status will follow"); time(&now); ast_mutex_lock(&qlock); @@ -1511,21 +1753,43 @@ static int manager_queues_status( struct mansession *s, struct message *m ) } while(q) { ast_mutex_lock(&q->lock); + + /* List queue properties */ + if(q->callscompleted > 0) + sl = 100*((float)q->callscompletedinsl/(float)q->callscompleted); ast_cli(s->fd, "Event: QueueParams\r\n" "Queue: %s\r\n" "Max: %d\r\n" "Calls: %d\r\n" + "Holdtime: %d\r\n" + "Completed: %d\r\n" + "Abandoned: %d\r\n" + "ServiceLevel: %d\r\n" + "ServicelevelPerf: %2.1f\r\n" "%s" "\r\n", - q->name, q->maxlen, q->count,idText); -#if 0 - /* Do we care about queue members? */ + q->name, q->maxlen, q->count, q->holdtime, q->callscompleted, + q->callsabandoned, q->servicelevel, sl, idText); + + /* List Queue Members */ for (mem = q->members; mem; mem = mem->next) - ast_cli(fd, " %s/%s\n", mem->tech, mem->loc); -#endif + ast_cli(s->fd, "Event: QueueMember\r\n" + "Queue: %s\r\n" + "Location: %s/%s\r\n" + "Membership: %s\r\n" + "Penalty: %d\r\n" + "CallsTaken: %d\r\n" + "LastCall: %ld\r\n" + "%s" + "\r\n", + q->name, mem->tech, mem->loc, mem->dynamic ? "dynamic" : "static", + mem->penalty, mem->calls, mem->lastcall, idText); + + /* List Queue Entries */ + pos = 1; for (qe = q->head; qe; qe = qe->next) - ast_cli(s->fd, "Event: QueueMember\r\n" + ast_cli(s->fd, "Event: QueueEntry\r\n" "Queue: %s\r\n" "Position: %d\r\n" "Channel: %s\r\n" diff --git a/configs/queues.conf.sample b/configs/queues.conf.sample index dff5da17e2..75dabab540 100755 --- a/configs/queues.conf.sample +++ b/configs/queues.conf.sample @@ -2,6 +2,11 @@ ; ; Global settings for call queues ; (none exist currently) +; +; Note that a timeout to fail out of a queue may be passed as part of application call +; from extensions.conf: +; Queue(queuename|[options]|[optionalurl]|[announceoverride]|[timeout]) +; example: Queue(dave|t|||45) [default] ; @@ -53,6 +58,35 @@ ; ;maxlen = 0 ; +; +; How often to announce queue position and/or estimated holdtime to caller (0=off) +; +;announce-frequency = 90 +; +; Should we include estimated hold time in position announcements? +; Either yes, no, or only once; hold time will not be announced if <1 minute +; +;announce-holdtime = yes|no|once +; +; Use these sound files in making position/holdtime announcements. The +; defaults are as listed below -- change only if you need to. +; +;queue-youarenext = queue-youarenext ("You are now first in line.") +;queue-thereare = queue-thereare ("There are") +;queue-callswaiting = queue-callswaiting ("calls waiting.") +;queue-holdtime = queue-holdtime ("The current est. holdtime is") +;queue-minutes = queue-minutes ("minutes.") +;queue-thankyou = queue-thankyou ("Thank you for your patience.") +; +; Calls may be recorded using Asterisk's monitor resource +; This can be enabled from within the Queue application, starting recording +; when the call is actually picked up; thus, only successful calls are +; recorded, and you are not recording while people are listening to MOH. +; To enable monitoring, simply specify "monitor-format"; it will be disabled +; otherwise. +; +; monitor-format = gsm|wav|wav49 +; ; Each member of this call queue is listed on a separate line in ; the form technology/dialstring. "member" means a normal member of a ; queue. An optional penalty may be specified after a comma, such that diff --git a/sounds.txt b/sounds.txt index dea1946428..833f8805ee 100755 --- a/sounds.txt +++ b/sounds.txt @@ -72,6 +72,18 @@ %pbx-transfer.gsm%Transfer. +%queue-callswaiting.gsm%Waiting to speak with a representative + +%queue-holdtime.gsm%The estimated hold time is currently + +%queue-minutes.gsm%Minutes + +%queue-thankyou.gsm%Thank you for your patience + +%queue-thereare.gsm%You are currently caller number + +%queue-youarenext.gsm%Your call is now first in line and will be answered by the next available representative. + %ss-noservice.gsm%The number you have dialed is not in service. Please check the number and try again. %transfer.gsm%Please hold while I try that extension. diff --git a/sounds/queue-callswaiting.gsm b/sounds/queue-callswaiting.gsm new file mode 100755 index 0000000000000000000000000000000000000000..60596fd8b9ed51d289bcd8e3b34cb9aa38460f7f GIT binary patch literal 3366 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 YZM{H@E`U!91T#Ir6}}#csmH+o1&x&p6951J literal 0 HcmV?d00001 diff --git a/sounds/queue-minutes.gsm b/sounds/queue-minutes.gsm new file mode 100755 index 0000000000000000000000000000000000000000..e7053f7de9293075c6b386afdf3934d4250f6b48 GIT binary patch literal 1056 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 literal 0 HcmV?d00001