1 /*
2 * vxml.cxx
3 *
4 * VXML engine for pwlib library
5 *
6 * Copyright (C) 2002 Equivalence Pty. Ltd.
7 *
8 * The contents of this file are subject to the Mozilla Public License
9 * Version 1.0 (the "License"); you may not use this file except in
10 * compliance with the License. You may obtain a copy of the License at
11 * http://www.mozilla.org/MPL/
12 *
13 * Software distributed under the License is distributed on an "AS IS"
14 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
15 * the License for the specific language governing rights and limitations
16 * under the License.
17 *
18 * The Original Code is Portable Windows Library.
19 *
20 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
21 *
22 * Contributor(s): ______________________________________.
23 *
24 * $Revision: 29069 $
25 * $Author: rjongbloed $
26 * $Date: 2013-02-11 23:11:12 -0600 (Mon, 11 Feb 2013) $
27 */
28
29 #ifdef __GNUC__
30 #pragma implementation "vxml.h"
31 #endif
32
33 #include <ptlib.h>
34
35 #define P_DISABLE_FACTORY_INSTANCES
36
37 #if P_VXML
38
39 #include <ptclib/vxml.h>
40 #include <ptclib/memfile.h>
41 #include <ptclib/random.h>
42 #include <ptclib/http.h>
43
44
45 static const bool DefaultBargeIn = true;
46
47
48 class PVXMLChannelPCM : public PVXMLChannel
49 {
50 PCLASSINFO(PVXMLChannelPCM, PVXMLChannel);
51
52 public:
53 PVXMLChannelPCM();
54
55 protected:
56 // overrides from PVXMLChannel
57 virtual PBoolean WriteFrame(const void * buf, PINDEX len);
58 virtual PBoolean ReadFrame(void * buffer, PINDEX amount);
59 virtual PINDEX CreateSilenceFrame(void * buffer, PINDEX amount);
60 virtual PBoolean IsSilenceFrame(const void * buf, PINDEX len) const;
61 virtual void GetBeepData(PBYTEArray & data, unsigned ms);
62 };
63
64
65 class PVXMLChannelG7231 : public PVXMLChannel
66 {
67 PCLASSINFO(PVXMLChannelG7231, PVXMLChannel);
68 public:
69 PVXMLChannelG7231();
70
71 // overrides from PVXMLChannel
72 virtual PBoolean WriteFrame(const void * buf, PINDEX len);
73 virtual PBoolean ReadFrame(void * buffer, PINDEX amount);
74 virtual PINDEX CreateSilenceFrame(void * buffer, PINDEX amount);
75 virtual PBoolean IsSilenceFrame(const void * buf, PINDEX len) const;
76 };
77
78
79 class PVXMLChannelG729 : public PVXMLChannel
80 {
81 PCLASSINFO(PVXMLChannelG729, PVXMLChannel);
82 public:
83 PVXMLChannelG729();
84
85 // overrides from PVXMLChannel
86 virtual PBoolean WriteFrame(const void * buf, PINDEX len);
87 virtual PBoolean ReadFrame(void * buffer, PINDEX amount);
88 virtual PINDEX CreateSilenceFrame(void * buffer, PINDEX amount);
89 virtual PBoolean IsSilenceFrame(const void * buf, PINDEX len) const;
90 };
91
92
93 static PVXMLNodeFactory::Worker<PVXMLNodeHandler> BlockNodeHandler("Block", true);
94
95 #define TRAVERSE_NODE(name) \
96 class PVXMLTraverse##name : public PVXMLNodeHandler { \
97 virtual bool Start(PVXMLSession & session, PXMLElement & element) const \
98 { return session.Traverse##name(element); } \
99 }; \
100 static PVXMLNodeFactory::Worker<PVXMLTraverse##name> name##NodeHandler(#name, true)
101
102 TRAVERSE_NODE(Audio);
103 TRAVERSE_NODE(Break);
104 TRAVERSE_NODE(Value);
105 TRAVERSE_NODE(SayAs);
106 TRAVERSE_NODE(Goto);
107 TRAVERSE_NODE(Grammar);
108 TRAVERSE_NODE(If);
109 TRAVERSE_NODE(Exit);
110 TRAVERSE_NODE(Var);
111 TRAVERSE_NODE(Submit);
112 TRAVERSE_NODE(Choice);
113 TRAVERSE_NODE(Property);
114 TRAVERSE_NODE(Disconnect);
115
116 #define TRAVERSE_NODE2(name) \
117 class PVXMLTraverse##name : public PVXMLNodeHandler { \
118 virtual bool Start(PVXMLSession & session, PXMLElement & element) const \
119 { return session.Traverse##name(element); } \
120 virtual bool Finish(PVXMLSession & session, PXMLElement & element) const \
121 { return session.Traversed##name(element); } \
122 }; \
123 static PVXMLNodeFactory::Worker<PVXMLTraverse##name> name##NodeHandler(#name, true)
124
125 TRAVERSE_NODE2(Menu);
126 TRAVERSE_NODE2(Form);
127 TRAVERSE_NODE2(Field);
128 TRAVERSE_NODE2(Transfer);
129 TRAVERSE_NODE2(Record);
130 TRAVERSE_NODE2(Prompt);
131
132 class PVXMLTraverseEvent : public PVXMLNodeHandler
133 {
Start(PVXMLSession &,PXMLElement & element) const134 virtual bool Start(PVXMLSession &, PXMLElement & element) const
135 {
136 return element.GetAttribute("fired") == "true";
137 }
138
Finish(PVXMLSession &,PXMLElement & element) const139 virtual bool Finish(PVXMLSession &, PXMLElement & element) const
140 {
141 element.SetAttribute("fired", "false");
142 return true;
143 }
144 };
145 static PVXMLNodeFactory::Worker<PVXMLTraverseEvent> FilledNodeHandler("Filled", true);
146 static PVXMLNodeFactory::Worker<PVXMLTraverseEvent> NoInputNodeHandler("NoInput", true);
147 static PVXMLNodeFactory::Worker<PVXMLTraverseEvent> NoMatchNodeHandler("NoMatch", true);
148 static PVXMLNodeFactory::Worker<PVXMLTraverseEvent> ErrorNodeHandler("Error", true);
149 static PVXMLNodeFactory::Worker<PVXMLTraverseEvent> CatchNodeHandler("Catch", true);
150
151 #if PTRACING
152 class PVXMLTraverseLog : public PVXMLNodeHandler {
Start(PVXMLSession & session,PXMLElement & node) const153 virtual bool Start(PVXMLSession & session, PXMLElement & node) const
154 {
155 unsigned level = node.GetAttribute("level").AsUnsigned();
156 if (level == 0)
157 level = 3;
158 PTRACE(level, "VXML-Log\t" + session.EvaluateExpr(node.GetAttribute("expr")));
159 return true;
160 }
161 };
162 static PVXMLNodeFactory::Worker<PVXMLTraverseLog> LogNodeHandler("Log", true);
163 #endif
164
165
166 #define new PNEW
167
168
169 #define SMALL_BREAK_MSECS 1000
170 #define MEDIUM_BREAK_MSECS 2500
171 #define LARGE_BREAK_MSECS 5000
172
173
174 //////////////////////////////////////////////////////////
175
GetContentType(const PFilePath & fn)176 static PString GetContentType(const PFilePath & fn)
177 {
178 PString type = fn.GetType();
179
180 if (type *= ".vxml")
181 return "text/vxml";
182
183 if (type *= ".wav")
184 return "audio/x-wav";
185
186 return PString::Empty();
187 }
188
189
190 ///////////////////////////////////////////////////////////////
191
PVXMLPlayable()192 PVXMLPlayable::PVXMLPlayable()
193 : m_vxmlChannel(NULL)
194 , m_subChannel(NULL)
195 , m_repeat(1)
196 , m_delay(0)
197 , m_sampleFrequency(8000)
198 , m_autoDelete(false)
199 , m_delayDone(false)
200 {
201 }
202
203
Open(PVXMLChannel & channel,const PString &,PINDEX delay,PINDEX repeat,PBoolean autoDelete)204 PBoolean PVXMLPlayable::Open(PVXMLChannel & channel, const PString &, PINDEX delay, PINDEX repeat, PBoolean autoDelete)
205 {
206 m_vxmlChannel = &channel;
207 m_delay = delay;
208 m_repeat = repeat;
209 m_autoDelete = autoDelete;
210 return true;
211 }
212
213
OnRepeat()214 bool PVXMLPlayable::OnRepeat()
215 {
216 if (PAssertNULL(m_vxmlChannel) == NULL)
217 return false;
218
219 if (m_repeat <= 1)
220 return false;
221
222 --m_repeat;
223 return true;
224 }
225
226
OnDelay()227 bool PVXMLPlayable::OnDelay()
228 {
229 if (m_delayDone)
230 return false;
231
232 m_delayDone = true;
233 if (m_delay == 0)
234 return false;
235
236 if (PAssertNULL(m_vxmlChannel) == NULL)
237 return false;
238
239 m_vxmlChannel->SetSilence(m_delay);
240 return true;
241 }
242
243
OnStop()244 void PVXMLPlayable::OnStop()
245 {
246 if (m_vxmlChannel == NULL || m_subChannel == NULL)
247 return;
248
249 if (m_vxmlChannel->GetReadChannel() == m_subChannel)
250 m_vxmlChannel->SetReadChannel(NULL, false, true);
251
252 delete m_subChannel;
253 }
254
255
256 ///////////////////////////////////////////////////////////////
257
OnStart()258 bool PVXMLPlayableStop::OnStart()
259 {
260 if (m_vxmlChannel == NULL)
261 return false;
262
263 m_vxmlChannel->SetSilence(500);
264 return false; // Return false so always stops
265 }
266
267
268 ///////////////////////////////////////////////////////////////
269
Open(PVXMLChannel & chan,const PString & fn,PINDEX delay,PINDEX repeat,PBoolean autoDelete)270 PBoolean PVXMLPlayableFile::Open(PVXMLChannel & chan, const PString & fn, PINDEX delay, PINDEX repeat, PBoolean autoDelete)
271 {
272 m_filePath = chan.AdjustWavFilename(fn);
273 if (!PFile::Exists(m_filePath)) {
274 PTRACE(2, "VXML\tPlayable file \"" << m_filePath << "\" not found.");
275 return false;
276 }
277
278 return PVXMLPlayable::Open(chan, fn, delay, repeat, autoDelete);
279 }
280
281
OnStart()282 bool PVXMLPlayableFile::OnStart()
283 {
284 if (PAssertNULL(m_vxmlChannel) == NULL)
285 return false;
286
287 PFile * file = NULL;
288
289 #if P_WAVFILE
290 // check the file extension and open a .wav or a raw (.sw or .g723) file
291 if (m_filePath.GetType() == ".wav") {
292 file = m_vxmlChannel->CreateWAVFile(m_filePath);
293 if (file == NULL) {
294 PTRACE(2, "VXML\tCannot open WAV file \"" << m_filePath << '"');
295 return false;
296 }
297 }
298 else
299 #endif // P_WAVFILE
300 {
301 // Assume file just has bytes of correct media format
302 file = new PFile(m_filePath);
303 if (!file->Open(PFile::ReadOnly)) {
304 PTRACE(2, "VXML\tCould not open audio file \"" << m_filePath << '"');
305 delete file;
306 return false;
307 }
308 }
309
310 PTRACE(3, "VXML\tPlaying file \"" << m_filePath << "\", " << file->GetLength() << " bytes");
311 m_subChannel = file;
312 return m_vxmlChannel->SetReadChannel(file, false);
313 }
314
315
OnRepeat()316 bool PVXMLPlayableFile::OnRepeat()
317 {
318 if (!PVXMLPlayable::OnRepeat())
319 return false;
320
321 PFile * file = dynamic_cast<PFile *>(m_subChannel);
322 return PAssert(file != NULL, PLogicError) && PAssertOS(file->SetPosition(0));
323 }
324
325
OnStop()326 void PVXMLPlayableFile::OnStop()
327 {
328 PVXMLPlayable::OnStop();
329
330 if (m_autoDelete && !m_filePath.IsEmpty()) {
331 PTRACE(3, "VXML\tDeleting file \"" << m_filePath << "\"");
332 PFile::Remove(m_filePath);
333 }
334 }
335
336
337 PFactory<PVXMLPlayable>::Worker<PVXMLPlayableFile> vxmlPlayableFilenameFactory("File");
338
339
340 ///////////////////////////////////////////////////////////////
341
PVXMLPlayableFileList()342 PVXMLPlayableFileList::PVXMLPlayableFileList()
343 : m_currentIndex(0)
344 {
345 }
346
347
Open(PVXMLChannel & chan,const PString & list,PINDEX delay,PINDEX repeat,PBoolean autoDelete)348 PBoolean PVXMLPlayableFileList::Open(PVXMLChannel & chan, const PString & list, PINDEX delay, PINDEX repeat, PBoolean autoDelete)
349 {
350 return Open(chan, list.Lines(), delay, repeat, autoDelete);
351 }
352
353
Open(PVXMLChannel & chan,const PStringArray & list,PINDEX delay,PINDEX repeat,PBoolean autoDelete)354 PBoolean PVXMLPlayableFileList::Open(PVXMLChannel & chan, const PStringArray & list, PINDEX delay, PINDEX repeat, PBoolean autoDelete)
355 {
356 for (PINDEX i = 0; i < list.GetSize(); ++i) {
357 PString fn = chan.AdjustWavFilename(list[i]);
358 if (PFile::Exists(fn))
359 m_fileNames.AppendString(fn);
360 else {
361 PTRACE(2, "VXML\tAudio file \"" << fn << "\" does not exist.");
362 }
363 }
364
365 if (m_fileNames.GetSize() == 0) {
366 PTRACE(2, "VXML\tNo files in list exist.");
367 return false;
368 }
369
370 m_currentIndex = 0;
371
372 return PVXMLPlayable::Open(chan, PString::Empty(), delay, ((repeat >= 0) ? repeat : 1) * m_fileNames.GetSize(), autoDelete);
373 }
374
375
OnStart()376 bool PVXMLPlayableFileList::OnStart()
377 {
378 if (!PAssert(!m_fileNames.IsEmpty(), PLogicError))
379 return false;
380
381 m_filePath = m_fileNames[m_currentIndex++ % m_fileNames.GetSize()];
382 return PVXMLPlayableFile::OnStart();
383 }
384
385
OnRepeat()386 bool PVXMLPlayableFileList::OnRepeat()
387 {
388 return PVXMLPlayable::OnRepeat() && OnStart();
389
390 }
391
392
OnStop()393 void PVXMLPlayableFileList::OnStop()
394 {
395 m_filePath.MakeEmpty();
396
397 PVXMLPlayableFile::OnStop();
398
399 if (m_autoDelete) {
400 for (PINDEX i = 0; i < m_fileNames.GetSize(); ++i) {
401 PTRACE(3, "VXML\tDeleting file \"" << m_fileNames[i] << "\"");
402 PFile::Remove(m_fileNames[i]);
403 }
404 }
405 }
406
407 PFactory<PVXMLPlayable>::Worker<PVXMLPlayableFileList> vxmlPlayableFilenameListFactory("FileList");
408
409
410 ///////////////////////////////////////////////////////////////
411
412 #if P_PIPECHAN
413
Open(PVXMLChannel & chan,const PString & cmd,PINDEX delay,PINDEX repeat,PBoolean autoDelete)414 PBoolean PVXMLPlayableCommand::Open(PVXMLChannel & chan, const PString & cmd, PINDEX delay, PINDEX repeat, PBoolean autoDelete)
415 {
416 if (cmd.IsEmpty()) {
417 PTRACE(2, "VXML\tEmpty command line.");
418 return false;
419 }
420
421 m_command = cmd;
422 return PVXMLPlayable::Open(chan, cmd, delay, repeat, autoDelete);
423 }
424
425
OnStart()426 bool PVXMLPlayableCommand::OnStart()
427 {
428 if (PAssertNULL(m_vxmlChannel) == NULL)
429 return false;
430
431 PString cmd = m_command;
432 cmd.Replace("%s", PString(PString::Unsigned, m_sampleFrequency));
433 cmd.Replace("%f", m_format);
434
435 // execute a command and send the output through the stream
436 PPipeChannel * pipe = new PPipeChannel;
437 if (!pipe->Open(cmd, PPipeChannel::ReadOnly)) {
438 PTRACE(2, "VXML\tCannot open command \"" << cmd << '"');
439 delete pipe;
440 return false;
441 }
442
443 if (!pipe->Execute()) {
444 PTRACE(2, "VXML\tCannot start command \"" << cmd << '"');
445 return false;
446 }
447
448 PTRACE(3, "VXML\tPlaying command \"" << cmd << '"');
449 m_subChannel = pipe;
450 return m_vxmlChannel->SetReadChannel(pipe, false);
451 }
452
453
OnStop()454 void PVXMLPlayableCommand::OnStop()
455 {
456 PPipeChannel * pipe = dynamic_cast<PPipeChannel *>(m_subChannel);
457 if (PAssert(pipe != NULL, PLogicError))
458 pipe->WaitForTermination();
459
460 PVXMLPlayable::OnStop();
461 }
462
463 PFactory<PVXMLPlayable>::Worker<PVXMLPlayableCommand> vxmlPlayableCommandFactory("Command");
464
465 #endif
466
467
468 ///////////////////////////////////////////////////////////////
469
Open(PVXMLChannel & chan,const PString & hex,PINDEX delay,PINDEX repeat,PBoolean autoDelete)470 PBoolean PVXMLPlayableData::Open(PVXMLChannel & chan, const PString & hex, PINDEX delay, PINDEX repeat, PBoolean autoDelete)
471 {
472 return PVXMLPlayable::Open(chan, hex, delay, repeat, autoDelete);
473 }
474
475
SetData(const PBYTEArray & data)476 void PVXMLPlayableData::SetData(const PBYTEArray & data)
477 {
478 m_data = data;
479 }
480
481
OnStart()482 bool PVXMLPlayableData::OnStart()
483 {
484 if (PAssertNULL(m_vxmlChannel) == NULL)
485 return false;
486
487 m_subChannel = new PMemoryFile(m_data);
488 PTRACE(3, "VXML\tPlaying " << m_data.GetSize() << " bytes of memory");
489 return m_vxmlChannel->SetReadChannel(m_subChannel, false);
490 }
491
492
OnRepeat()493 bool PVXMLPlayableData::OnRepeat()
494 {
495 if (!PVXMLPlayable::OnRepeat())
496 return false;
497
498 PMemoryFile * memfile = dynamic_cast<PMemoryFile *>(m_subChannel);
499 return PAssert(memfile != NULL, PLogicError) && PAssertOS(memfile->SetPosition(0));
500 }
501
502 PFactory<PVXMLPlayable>::Worker<PVXMLPlayableData> vxmlPlayableDataFactory("PCM Data");
503
504
505 ///////////////////////////////////////////////////////////////
506
507 #if P_DTMF
508
Open(PVXMLChannel & chan,const PString & toneSpec,PINDEX delay,PINDEX repeat,PBoolean autoDelete)509 PBoolean PVXMLPlayableTone::Open(PVXMLChannel & chan, const PString & toneSpec, PINDEX delay, PINDEX repeat, PBoolean autoDelete)
510 {
511 // populate the tone buffer
512 PTones tones;
513
514 if (!tones.Generate(toneSpec)) {
515 PTRACE(2, "VXML\tCOuld not generate tones with \"" << toneSpec << '"');
516 return false;
517 }
518
519 PINDEX len = tones.GetSize() * sizeof(short);
520 memcpy(m_data.GetPointer(len), tones.GetPointer(), len);
521
522 return PVXMLPlayable::Open(chan, toneSpec, delay, repeat, autoDelete);
523 }
524
525 PFactory<PVXMLPlayable>::Worker<PVXMLPlayableTone> vxmlPlayableToneFactory("Tone");
526
527 #endif // P_DTMF
528
529
530 ///////////////////////////////////////////////////////////////
531
Open(PVXMLChannel & chan,const PString & url,PINDEX delay,PINDEX repeat,PBoolean autoDelete)532 PBoolean PVXMLPlayableURL::Open(PVXMLChannel & chan, const PString & url, PINDEX delay, PINDEX repeat, PBoolean autoDelete)
533 {
534 if (!m_url.Parse(url)) {
535 PTRACE(2, "VXML\tInvalid URL \"" << url << '"');
536 return false;
537 }
538
539 return PVXMLPlayable::Open(chan, url, delay, repeat, autoDelete);
540 }
541
542
OnStart()543 bool PVXMLPlayableURL::OnStart()
544 {
545 if (PAssertNULL(m_vxmlChannel) == NULL)
546 return false;
547
548 // open the resource
549 PHTTPClient * client = new PHTTPClient;
550 client->SetPersistent(false);
551 PMIMEInfo outMIME, replyMIME;
552 int code = client->GetDocument(m_url, outMIME, replyMIME);
553 if ((code != 200) || (replyMIME(PHTTP::TransferEncodingTag()) *= PHTTP::ChunkedTag())) {
554 delete client;
555 return false;
556 }
557
558 m_subChannel = client;
559 return m_vxmlChannel->SetReadChannel(client, false);
560 }
561
562 PFactory<PVXMLPlayable>::Worker<PVXMLPlayableURL> vxmlPlayableURLFactory("URL");
563
564
565 ///////////////////////////////////////////////////////////////
566
PVXMLRecordable()567 PVXMLRecordable::PVXMLRecordable()
568 : m_finalSilence(3000)
569 , m_maxDuration(30000)
570 {
571 }
572
573
574 ///////////////////////////////////////////////////////////////
575
Open(const PString & arg)576 PBoolean PVXMLRecordableFilename::Open(const PString & arg)
577 {
578 m_fileName = arg;
579 return true;
580 }
581
582
OnStart(PVXMLChannel & outgoingChannel)583 bool PVXMLRecordableFilename::OnStart(PVXMLChannel & outgoingChannel)
584 {
585 PFile * file = NULL;
586
587 #if P_WAVFILE
588 // check the file extension and open a .wav or a raw (.sw or .g723) file
589 if (m_fileName.GetType() == ".wav") {
590 file = outgoingChannel.CreateWAVFile(m_fileName, true);
591 if (file == NULL) {
592 PTRACE(2, "VXML\tCannot open WAV file \"" << m_fileName << '"');
593 return false;
594 }
595 }
596 else
597 #endif // P_WAVFILE
598 {
599 file = new PFile(m_fileName);
600 if (!file->Open(PFile::WriteOnly)) {
601 PTRACE(2, "VXML\tCannot open audio file \"" << m_fileName << '"');
602 delete file;
603 return false;
604 }
605 }
606
607 PTRACE(3, "VXML\tRecording to file \"" << m_fileName << '"');
608 outgoingChannel.SetWriteChannel(file, true);
609
610 m_silenceTimer = m_finalSilence;
611 m_recordTimer = m_maxDuration;
612 return true;
613 }
614
615
OnFrame(PBoolean isSilence)616 PBoolean PVXMLRecordableFilename::OnFrame(PBoolean isSilence)
617 {
618 if (isSilence) {
619 if (m_silenceTimer.HasExpired()) {
620 PTRACE(4, "VXML\tRecording silence detected.");
621 return true;
622 }
623 }
624 else
625 m_silenceTimer = m_finalSilence;
626
627 if (m_recordTimer.HasExpired()) {
628 PTRACE(3, "VXML\tRecording finished due to max time exceeded.");
629 return true;
630 }
631
632 return false;
633 }
634
635
636 ///////////////////////////////////////////////////////////////
637
PVXMLCache(const PDirectory & _directory)638 PVXMLCache::PVXMLCache(const PDirectory & _directory)
639 : directory(_directory)
640 {
641 if (!directory.Exists())
642 directory.Create();
643 }
644
645
MD5AsHex(const PString & str)646 static PString MD5AsHex(const PString & str)
647 {
648 PMessageDigest::Result digest;
649 PMessageDigest5::Encode(str, digest);
650
651 PString hexStr;
652 const BYTE * data = digest.GetPointer();
653 for (PINDEX i = 0; i < digest.GetSize(); ++i)
654 hexStr.sprintf("%02x", (unsigned)data[i]);
655 return hexStr;
656 }
657
658
CreateFilename(const PString & prefix,const PString & key,const PString & fileType)659 PFilePath PVXMLCache::CreateFilename(const PString & prefix, const PString & key, const PString & fileType)
660 {
661 PString md5 = MD5AsHex(key);
662
663 return directory + ((prefix + "_") + md5 + fileType);
664 }
665
666
Get(const PString & prefix,const PString & key,const PString & fileType,PString & contentType,PFilePath & dataFn)667 PBoolean PVXMLCache::Get(const PString & prefix,
668 const PString & key,
669 const PString & fileType,
670 PString & contentType,
671 PFilePath & dataFn)
672 {
673 PWaitAndSignal m(*this);
674
675 dataFn = CreateFilename(prefix, key, "." + fileType);
676 PFilePath typeFn = CreateFilename(prefix, key, "_type.txt");
677 if (!PFile::Exists(dataFn) || !PFile::Exists(typeFn)) {
678 PTRACE(4, "VXML\tKey \"" << key << "\" not found in cache");
679 return false;
680 }
681
682 {
683 PFile file(dataFn, PFile::ReadOnly);
684 if (!file.IsOpen() || (file.GetLength() == 0)) {
685 PTRACE(4, "VXML\tDeleting empty cache file for key " << key);
686 PFile::Remove(dataFn, true);
687 PFile::Remove(typeFn, true);
688 return false;
689 }
690 }
691
692 PTextFile typeFile(typeFn, PFile::ReadOnly);
693 if (!typeFile.IsOpen()) {
694 PTRACE(4, "VXML\tCannot find type for cached key " << key << " in cache");
695 PFile::Remove(dataFn, true);
696 PFile::Remove(typeFn, true);
697 return false;
698 }
699
700 typeFile.ReadLine(contentType);
701 contentType.Trim();
702 if (contentType.IsEmpty())
703 contentType = GetContentType(dataFn);
704
705 return true;
706 }
707
708
Put(const PString & prefix,const PString & key,const PString & fileType,const PString & contentType,const PFilePath & fn,PFilePath & dataFn)709 void PVXMLCache::Put(const PString & prefix,
710 const PString & key,
711 const PString & fileType,
712 const PString & contentType,
713 const PFilePath & fn,
714 PFilePath & dataFn)
715 {
716 PWaitAndSignal m(*this);
717
718 // create the filename for the cache files
719 dataFn = CreateFilename(prefix, key, "." + fileType);
720 PFilePath typeFn = CreateFilename(prefix, key, "_type.txt");
721
722 // write the content type file
723 PTextFile typeFile(typeFn, PFile::WriteOnly);
724 if (contentType.IsEmpty())
725 typeFile.WriteLine(GetContentType(fn));
726 else
727 typeFile.WriteLine(contentType);
728
729 // rename the file to the correct name
730 PFile::Rename(fn, dataFn.GetFileName(), true);
731 }
732
733
GetResourceCache()734 PVXMLCache & PVXMLCache::GetResourceCache()
735 {
736 static PVXMLCache cache(PDirectory() + "cache");
737 return cache;
738 }
739
740
GetRandomFilename(const PString & prefix,const PString & fileType)741 PFilePath PVXMLCache::GetRandomFilename(const PString & prefix, const PString & fileType)
742 {
743 PFilePath fn;
744
745 // create a random temporary filename
746 PRandom r;
747 for (;;) {
748 fn = directory + psprintf("%s_%i.%s", (const char *)prefix, r.Generate() % 1000000, (const char *)fileType);
749 if (!PFile::Exists(fn))
750 break;
751 }
752
753 return fn;
754 }
755
756
757 //////////////////////////////////////////////////////////
758
PVXMLSession(PTextToSpeech * tts,PBoolean autoDelete)759 PVXMLSession::PVXMLSession(PTextToSpeech * tts, PBoolean autoDelete)
760 : m_textToSpeech(tts)
761 , m_autoDeleteTextToSpeech(autoDelete)
762 , m_vxmlThread(NULL)
763 , m_abortVXML(false)
764 , m_currentNode(NULL)
765 , m_xmlChanged(false)
766 , m_speakNodeData(true)
767 , m_bargeIn(DefaultBargeIn)
768 , m_bargingIn(false)
769 , m_grammar(NULL)
770 , m_defaultMenuDTMF('N') /// Disabled
771 , m_recordingStatus(NotRecording)
772 , m_recordStopOnDTMF(false)
773 , m_transferStatus(NotTransfering)
774 , m_transferStartTime(0)
775 {
776 SetVar("property.timeout" , "10s");
777 }
778
779
~PVXMLSession()780 PVXMLSession::~PVXMLSession()
781 {
782 Close();
783
784 if (m_autoDeleteTextToSpeech)
785 delete m_textToSpeech;
786 }
787
788
SetTextToSpeech(PTextToSpeech * tts,PBoolean autoDelete)789 PTextToSpeech * PVXMLSession::SetTextToSpeech(PTextToSpeech * tts, PBoolean autoDelete)
790 {
791 PWaitAndSignal mutex(m_sessionMutex);
792
793 if (m_autoDeleteTextToSpeech)
794 delete m_textToSpeech;
795
796 m_autoDeleteTextToSpeech = autoDelete;
797 return m_textToSpeech = tts;
798 }
799
800
SetTextToSpeech(const PString & ttsName)801 PTextToSpeech * PVXMLSession::SetTextToSpeech(const PString & ttsName)
802 {
803 PFactory<PTextToSpeech>::Key_T name = (const char *)ttsName;
804 if (ttsName.IsEmpty()) {
805 PFactory<PTextToSpeech>::KeyList_T engines = PFactory<PTextToSpeech>::GetKeyList();
806 if (engines.empty())
807 return SetTextToSpeech(NULL, false);
808
809 #ifdef _WIN32
810 name = "Microsoft SAPI";
811 if (std::find(engines.begin(), engines.end(), name) == engines.end())
812 #endif
813 name = engines[0];
814 }
815
816 return SetTextToSpeech(PFactory<PTextToSpeech>::CreateInstance(name), true);
817 }
818
819
Load(const PString & source)820 PBoolean PVXMLSession::Load(const PString & source)
821 {
822 // Lets try and guess what was passed, if file exists then is file
823 PFilePath file = source;
824 if (PFile::Exists(file))
825 return LoadFile(file);
826
827 // see if looks like URL
828 PINDEX pos = source.Find(':');
829 if (pos != P_MAX_INDEX) {
830 PString scheme = source.Left(pos);
831 if ((scheme *= "http") || (scheme *= "https") || (scheme *= "file"))
832 return LoadURL(source);
833 }
834
835 // See if is actual VXML
836 if (PCaselessString(source).Find("<vxml") != P_MAX_INDEX)
837 return LoadVXML(source);
838
839 return false;
840 }
841
842
LoadFile(const PFilePath & filename,const PString & firstForm)843 PBoolean PVXMLSession::LoadFile(const PFilePath & filename, const PString & firstForm)
844 {
845 PTRACE(4, "VXML\tLoading file: " << filename);
846
847 PTextFile file(filename, PFile::ReadOnly);
848 if (!file.IsOpen()) {
849 PTRACE(1, "VXML\tCannot open " << filename);
850 return false;
851 }
852
853 m_rootURL = PURL(filename);
854 return InternalLoadVXML(file.ReadString(P_MAX_INDEX), firstForm);
855 }
856
857
LoadURL(const PURL & url)858 PBoolean PVXMLSession::LoadURL(const PURL & url)
859 {
860 PTRACE(4, "VXML\tLoading URL " << url);
861
862 // retreive the document (may be a HTTP get)
863
864 PString xmlStr;
865 if (url.LoadResource(xmlStr)) {
866 m_rootURL = url;
867 return InternalLoadVXML(xmlStr, url.GetFragment());
868 }
869
870 PTRACE(1, "VXML\tCannot load document " << url);
871 return false;
872 }
873
874
LoadVXML(const PString & xmlText,const PString & firstForm)875 PBoolean PVXMLSession::LoadVXML(const PString & xmlText, const PString & firstForm)
876 {
877 m_rootURL = PString::Empty();
878 return InternalLoadVXML(xmlText, firstForm);
879 }
880
881
InternalLoadVXML(const PString & xmlText,const PString & firstForm)882 bool PVXMLSession::InternalLoadVXML(const PString & xmlText, const PString & firstForm)
883 {
884 {
885 PWaitAndSignal mutex(m_sessionMutex);
886
887 m_xmlChanged = true;
888 LoadGrammar(NULL);
889
890 // parse the XML
891 m_xml.RemoveAll();
892 if (!m_xml.Load(xmlText)) {
893 PTRACE(1, "VXML\tCannot parse root document: " << GetXMLError());
894 return false;
895 }
896
897 PXMLElement * root = m_xml.GetRootElement();
898 if (root == NULL) {
899 PTRACE(1, "VXML\tNo root element");
900 return false;
901 }
902
903 m_variableScope = m_variableScope.IsEmpty() ? "application" : "document";
904
905 {
906 PINDEX idx = 0;
907 PXMLElement * element;
908 while ((element = root->GetElement("var", idx++)) != NULL)
909 TraverseVar(*element);
910 }
911
912 // find the first form
913 if (!SetCurrentForm(firstForm, false)) {
914 PTRACE(1, "VXML\tNo form element");
915 m_xml.RemoveAll();
916 return false;
917 }
918 }
919
920 return Execute();
921 }
922
923
NormaliseResourceName(const PString & src)924 PURL PVXMLSession::NormaliseResourceName(const PString & src)
925 {
926 PURL url;
927 if (url.Parse(src, NULL))
928 return url;
929
930 if (m_rootURL.IsEmpty()) {
931 url.Parse(src, "file");
932 return url;
933 }
934
935 // relative to scheme/path in root document
936 url = m_rootURL;
937 PStringArray path = url.GetPath();
938 if (src[0] == '/' || path.IsEmpty()) {
939 url.SetPathStr(src);
940 return url;
941 }
942
943 PStringStream str;
944 for (PINDEX i = 0; i < path.GetSize()-1; i++)
945 str << path[i] << '/';
946 str << src;
947 url.SetPathStr(str);
948 return url;
949 }
950
951
RetreiveResource(const PURL & url,PString & contentType,PFilePath & dataFn,PBoolean useCache)952 PBoolean PVXMLSession::RetreiveResource(const PURL & url,
953 PString & contentType,
954 PFilePath & dataFn,
955 PBoolean useCache)
956 {
957 // files on the local file system get loaded locally
958 if (url.GetScheme() == "file" && url.GetHostName().IsEmpty()) {
959 dataFn = url.AsFilePath();
960 if (contentType.IsEmpty())
961 contentType = GetContentType(dataFn);
962 return true;
963 }
964
965 PString fileType;
966 {
967 const PStringArray & path = url.GetPath();
968 if (!path.IsEmpty())
969 fileType = PFilePath(path[path.GetSize()-1]).GetType();
970 }
971
972 if (useCache && PVXMLCache::GetResourceCache().Get("url", url.AsString(), fileType, contentType, dataFn))
973 return true;
974
975 // get a random filename
976 PFilePath newFn = PVXMLCache::GetResourceCache().GetRandomFilename("url", fileType);
977
978 // get the resource header information
979 PHTTPClient client;
980 PMIMEInfo outMIME, replyMIME;
981 if (!client.GetDocument(url, outMIME, replyMIME)) {
982 PTRACE(2, "VXML\tCannot load resource " << url);
983 return false;
984 }
985
986 // Get the body of the response in a PBYTEArray (might be binary data)
987 PBYTEArray incomingData;
988 client.ReadContentBody(replyMIME, incomingData);
989 contentType = replyMIME(PHTTPClient::ContentTypeTag());
990
991 // write the data in the file
992 PFile cacheFile(newFn, PFile::WriteOnly);
993 cacheFile.Write(incomingData.GetPointer(), incomingData.GetSize());
994
995 // if we have a cache and we are using it, then save the data
996 if (useCache)
997 PVXMLCache::GetResourceCache().Put("url", url.AsString(), fileType, contentType, newFn, dataFn);
998
999 // data is loaded
1000 return true;
1001 }
1002
1003
SetCurrentForm(const PString & searchId,bool fullURI)1004 bool PVXMLSession::SetCurrentForm(const PString & searchId, bool fullURI)
1005 {
1006 PString id = searchId;
1007
1008 if (fullURI) {
1009 if (searchId.IsEmpty()) {
1010 PTRACE(3, "VXML\tFull URI required for this form/menu search");
1011 return false;
1012 }
1013
1014 if (searchId[0] != '#') {
1015 PTRACE(4, "VXML\tSearching form/menu \"" << searchId << '"');
1016 return LoadURL(NormaliseResourceName(searchId));
1017 }
1018
1019 id = searchId.Mid(1);
1020 }
1021
1022 // Only handle search of top level nodes for <form>/<menu> element
1023 // NOTE: should have some flag to know if it is loaded
1024 PXMLElement * root = m_xml.GetRootElement();
1025 if (root != NULL) {
1026 for (PINDEX i = 0; i < root->GetSize(); i++) {
1027 PXMLObject * xmlObject = root->GetElement(i);
1028 if (xmlObject->IsElement()) {
1029 PXMLElement * xmlElement = (PXMLElement*)xmlObject;
1030 if (
1031 (xmlElement->GetName() == "form" || xmlElement->GetName() == "menu") &&
1032 (id.IsEmpty() || (xmlElement->GetAttribute("id") *= id))
1033 ) {
1034 PTRACE(3, "VXML\tFound <" << xmlElement->GetName() << " id=\"" << xmlElement->GetAttribute("id") << "\">");
1035
1036 if (m_currentNode != NULL) {
1037 PXMLElement * element = m_currentNode->GetParent();
1038 while (element != NULL) {
1039 PCaselessString nodeType = element->GetName();
1040 PVXMLNodeHandler * handler = PVXMLNodeFactory::CreateInstance(nodeType);
1041 if (handler != NULL) {
1042 handler->Finish(*this, *element);
1043 PTRACE(4, "VXML\tProcessed VoiceXML element: <" << nodeType << '>');
1044 }
1045 element = element->GetParent();
1046 }
1047 }
1048
1049 m_currentNode = xmlObject;
1050 return true;
1051 }
1052 }
1053 }
1054 }
1055
1056 PTRACE(3, "VXML\tNo form/menu with id \"" << searchId << '"');
1057 return false;
1058 }
1059
1060
GetAndLockVXMLChannel()1061 PVXMLChannel * PVXMLSession::GetAndLockVXMLChannel()
1062 {
1063 m_sessionMutex.Wait();
1064 if (IsOpen())
1065 return GetVXMLChannel();
1066
1067 m_sessionMutex.Signal();
1068 return NULL;
1069 }
1070
1071
Open(const PString & mediaFormat)1072 PBoolean PVXMLSession::Open(const PString & mediaFormat)
1073 {
1074 PVXMLChannel * chan = PFactory<PVXMLChannel>::CreateInstance(mediaFormat);
1075 if (chan == NULL) {
1076 PTRACE(1, "VXML\tCannot create VXML channel with format " << mediaFormat);
1077 return false;
1078 }
1079
1080 if (!chan->Open(this)) {
1081 delete chan;
1082 return false;
1083 }
1084
1085 // set the underlying channel
1086 if (!PIndirectChannel::Open(chan, chan))
1087 return false;
1088
1089 return Execute();
1090 }
1091
1092
Execute()1093 PBoolean PVXMLSession::Execute()
1094 {
1095 PWaitAndSignal mutex(m_sessionMutex);
1096
1097 if (IsLoaded()) {
1098 if (m_vxmlThread == NULL)
1099 m_vxmlThread = PThread::Create(PCREATE_NOTIFIER(VXMLExecute), "VXML");
1100 else
1101 Trigger();
1102 }
1103
1104 return true;
1105 }
1106
1107
Close()1108 PBoolean PVXMLSession::Close()
1109 {
1110 PThread * thread = NULL;
1111
1112 m_sessionMutex.Wait();
1113
1114 LoadGrammar(NULL);
1115
1116 if (PThread::Current() != m_vxmlThread) {
1117 thread = m_vxmlThread;
1118 m_vxmlThread = NULL;
1119 }
1120
1121 m_sessionMutex.Signal();
1122
1123 if (thread != NULL) {
1124 PTRACE(3, "VXML\tClosing session, fast forwarding through script");
1125
1126 // Stop condition for thread
1127 m_abortVXML = true;
1128 Trigger();
1129
1130 PAssert(thread->WaitForTermination(10000), "VXML thread did not exit in time.");
1131 delete thread;
1132 }
1133
1134 return PIndirectChannel::Close();
1135 }
1136
1137
VXMLExecute(PThread &,INT)1138 void PVXMLSession::VXMLExecute(PThread &, INT)
1139 {
1140 PTRACE(4, "VXML\tExecution thread started");
1141
1142 m_sessionMutex.Wait();
1143
1144 while (!m_abortVXML) {
1145 // process current node in the VXML script
1146 if (ProcessNode()) {
1147 /* wait for something to happen, usually output of some audio. But under
1148 some circumstances we want to abort the script, but we have to make
1149 sure the script has been run to the end so submit actions etc. can be
1150 performed. Record and audio and other user interaction commands can
1151 be skipped, so we don't wait for them */
1152 do {
1153 while (ProcessEvents())
1154 ;
1155 } while (NextNode(true));
1156 }
1157 else {
1158 // Wait till node finishes
1159 while (ProcessEvents())
1160 ;
1161
1162 NextNode(false);
1163 }
1164
1165 // Determine if we should quit
1166 if (m_currentNode != NULL)
1167 continue;
1168
1169 PTRACE(3, "VXML\tEnd of VoiceXML elements.");
1170
1171 m_sessionMutex.Signal();
1172 OnEndDialog();
1173 m_sessionMutex.Wait();
1174
1175 // Wait for anything OnEndDialog plays to complete.
1176 while (ProcessEvents())
1177 ;
1178
1179 if (m_currentNode == NULL)
1180 m_abortVXML = true;
1181 }
1182
1183 m_sessionMutex.Signal();
1184
1185 OnEndSession();
1186
1187 PTRACE(4, "VXML\tExecution thread ended");
1188 }
1189
1190
OnEndDialog()1191 void PVXMLSession::OnEndDialog()
1192 {
1193 }
1194
1195
OnEndSession()1196 void PVXMLSession::OnEndSession()
1197 {
1198 }
1199
1200
ProcessEvents()1201 bool PVXMLSession::ProcessEvents()
1202 {
1203 // m_sessionMutex already locked
1204
1205 if (m_abortVXML)
1206 return false;
1207
1208 char ch;
1209
1210 m_userInputMutex.Wait();
1211 if (m_userInputQueue.empty())
1212 ch = '\0';
1213 else {
1214 ch = m_userInputQueue.front();
1215 m_userInputQueue.pop();
1216 PTRACE(3, "VXML\tHandling user input " << ch);
1217 }
1218 m_userInputMutex.Signal();
1219
1220 if (ch != '\0') {
1221 if (m_recordStopOnDTMF)
1222 EndRecording();
1223
1224 if (m_bargeIn && IsOpen()) {
1225 PTRACE(4, "VXML\tBarging in");
1226 m_bargingIn = true;
1227 GetVXMLChannel()->FlushQueue();
1228 }
1229
1230 if (m_grammar != NULL)
1231 m_grammar->OnUserInput(ch);
1232 }
1233
1234 if (IsOpen() && GetVXMLChannel()->IsPlaying()) {
1235 PTRACE(4, "VXML\tIs playing, awaiting event");
1236 }
1237 else if (IsOpen() && GetVXMLChannel()->IsRecording()) {
1238 PTRACE(4, "VXML\tIs recording, awaiting event");
1239 }
1240 else if (m_grammar != NULL && m_grammar->GetState() == PVXMLGrammar::Started) {
1241 PTRACE(4, "VXML\tAwaiting input, awaiting event");
1242 }
1243 else if (m_transferStatus == TransferInProgress) {
1244 PTRACE(4, "VXML\tTransfer in progress, awaiting event");
1245 }
1246 else {
1247 PTRACE(4, "VXML\tNothing happening, processing next node");
1248 return false;
1249 }
1250
1251 m_sessionMutex.Signal();
1252 m_waitForEvent.Wait();
1253 m_sessionMutex.Wait();
1254
1255 if (!m_xmlChanged)
1256 return true;
1257
1258 PTRACE(4, "VXML\tXML changed, flushing queue");
1259
1260 // Clear out any audio being output, so can start fresh on new VXML.
1261 if (IsOpen())
1262 GetVXMLChannel()->FlushQueue();
1263
1264 return false;
1265 }
1266
1267
NextNode(bool processChildren)1268 bool PVXMLSession::NextNode(bool processChildren)
1269 {
1270 // m_sessionMutex already locked
1271
1272 if (m_abortVXML)
1273 return false;
1274
1275 // No more nodes
1276 if (m_currentNode == NULL)
1277 return false;
1278
1279 if (m_xmlChanged)
1280 return false;
1281
1282 PXMLElement * element = dynamic_cast<PXMLElement *>(m_currentNode);
1283 if (element != NULL) {
1284 // if the current node has children, then process the first child
1285 if (processChildren && (m_currentNode = element->GetSubObject(0)) != NULL)
1286 return false;
1287 }
1288 else {
1289 // Data node
1290 PXMLObject * sibling = m_currentNode->GetNextObject();
1291 if (sibling != NULL) {
1292 m_currentNode = sibling;
1293 return false;
1294 }
1295 if ((element = m_currentNode->GetParent()) == NULL) {
1296 m_currentNode = NULL;
1297 return false;
1298 }
1299 }
1300
1301 // No children, move to sibling
1302 do {
1303 PCaselessString nodeType = element->GetName();
1304 PVXMLNodeHandler * handler = PVXMLNodeFactory::CreateInstance(nodeType);
1305 if (handler != NULL) {
1306 if (!handler->Finish(*this, *element)) {
1307 PTRACE(4, "VXML\tContinue processing VoiceXML element: <" << nodeType << '>');
1308 return true;
1309 }
1310 PTRACE(4, "VXML\tProcessed VoiceXML element: <" << nodeType << '>');
1311 }
1312
1313 if ((m_currentNode = element->GetNextObject()) != NULL)
1314 break;
1315
1316 } while ((element = element->GetParent()) != NULL);
1317
1318 return false;
1319 }
1320
1321
ProcessGrammar()1322 bool PVXMLSession::ProcessGrammar()
1323 {
1324 if (m_grammar == NULL) {
1325 PTRACE(4, "VXML\tNo grammar was created!");
1326 return true;
1327 }
1328
1329 switch (m_grammar->GetState()) {
1330 case PVXMLGrammar::Idle :
1331 m_grammar->Start();
1332 return false;
1333
1334 case PVXMLGrammar::Started :
1335 return false;
1336
1337 default :
1338 PTRACE_IF(4, m_bargingIn, "VXML\tEnding barge in");
1339 m_bargingIn = false;
1340
1341 PVXMLGrammar * grammar = m_grammar;
1342 m_grammar = NULL;
1343 PTRACE(2, "VXML\tProcessing grammar " << *grammar);
1344 bool nextNode = grammar->Process();
1345 delete grammar;
1346 return nextNode;
1347 }
1348 }
1349
1350
ProcessNode()1351 bool PVXMLSession::ProcessNode()
1352 {
1353 // m_sessionMutex already locked
1354
1355 if (m_abortVXML)
1356 return false;
1357
1358 if (m_currentNode == NULL)
1359 return false;
1360
1361 if (m_bargingIn)
1362 return false;
1363
1364 m_xmlChanged = false;
1365
1366 PXMLData * nodeData = dynamic_cast<PXMLData *>(m_currentNode);
1367 if (nodeData != NULL) {
1368 if (m_speakNodeData)
1369 PlayText(nodeData->GetString().Trim());
1370 return true;
1371 }
1372
1373 m_speakNodeData = true;
1374
1375 PXMLElement * element = (PXMLElement*)m_currentNode;
1376 PCaselessString nodeType = element->GetName();
1377 PVXMLNodeHandler * handler = PVXMLNodeFactory::CreateInstance(nodeType);
1378 if (handler == NULL) {
1379 PTRACE(2, "VXML\tUnknown/unimplemented VoiceXML element: <" << nodeType << '>');
1380 return false;
1381 }
1382
1383 PTRACE(3, "VXML\tProcessing VoiceXML element: <" << nodeType << '>');
1384 bool started = handler->Start(*this, *element);
1385 PTRACE_IF(4, !started, "VXML\tSkipping VoiceXML element: <" << nodeType << '>');
1386 return started;
1387 }
1388
1389
OnUserInput(const PString & str)1390 void PVXMLSession::OnUserInput(const PString & str)
1391 {
1392 {
1393 PWaitAndSignal mutex(m_userInputMutex);
1394 for (PINDEX i = 0; i < str.GetLength(); i++)
1395 m_userInputQueue.push(str[i]);
1396 }
1397 Trigger();
1398 }
1399
1400
TraverseRecord(PXMLElement &)1401 PBoolean PVXMLSession::TraverseRecord(PXMLElement &)
1402 {
1403 m_recordingStatus = NotRecording;
1404 return true;
1405 }
1406
1407
TraversedRecord(PXMLElement & element)1408 PBoolean PVXMLSession::TraversedRecord(PXMLElement & element)
1409 {
1410 if (m_abortVXML)
1411 return true;
1412
1413 switch (m_recordingStatus) {
1414 case RecordingInProgress :
1415 return false;
1416
1417 case RecordingComplete :
1418 return GoToEventHandler(element, "filled");
1419
1420 default :
1421 break;
1422 }
1423
1424 // see if we need a beep
1425 if (element.GetAttribute("beep").ToLower() *= "true") {
1426 PBYTEArray beepData;
1427 GetBeepData(beepData, 1000);
1428 if (beepData.GetSize() != 0)
1429 PlayData(beepData);
1430 }
1431
1432 // Get the destination filename (dest)
1433 PURL destURL;
1434 if (element.HasAttribute("dest"))
1435 destURL = element.GetAttribute("dest");
1436
1437 if (destURL.IsEmpty())
1438 destURL.Parse("recording_" + PTime().AsString("yyyyMMdd_hhmmss") + ".wav", "file");
1439
1440 // Get max record time (maxtime)
1441 PTimeInterval maxTime = StringToTime(element.GetAttribute("maxtime"), INT_MAX);
1442
1443 // Get terminating silence duration (finalsilence)
1444 PTimeInterval termTime = StringToTime(element.GetAttribute("finalsilence"), 3000);
1445
1446 // Get dtmf term (dtmfterm)
1447 PBoolean dtmfTerm = true;
1448 if (element.HasAttribute("dtmfterm"))
1449 dtmfTerm = !(element.GetAttribute("dtmfterm").ToLower() *= "false");
1450
1451 // create a semaphore, and then wait for the recording to terminate
1452 return !StartRecording(destURL.AsFilePath(), dtmfTerm, maxTime, termTime);
1453 }
1454
1455
GetXMLError() const1456 PString PVXMLSession::GetXMLError() const
1457 {
1458 return psprintf("(%i:%i) ", m_xml.GetErrorLine(), m_xml.GetErrorColumn()) + m_xml.GetErrorString();
1459 }
1460
1461
EvaluateExpr(const PString & expr)1462 PString PVXMLSession::EvaluateExpr(const PString & expr)
1463 {
1464 // Should be full ECMAScript but ...
1465 // We only support expressions of the form 'literal'+variable or all digits
1466
1467 PString result;
1468
1469 PINDEX pos = 0;
1470 while (pos < expr.GetLength()) {
1471 if (expr[pos] == '\'') {
1472 PINDEX quote = expr.Find('\'', ++pos);
1473 PTRACE_IF(2, quote == P_MAX_INDEX, "VXML\tMismatched quote, ignoring transfer");
1474 result += expr(pos, quote-1);
1475 pos = quote+1;
1476 }
1477 else if (isalpha(expr[pos])) {
1478 PINDEX span = expr.FindSpan("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.$", pos);
1479 result += GetVar(expr(pos, span-1));
1480 pos = span;
1481 }
1482 else if (isdigit(expr[pos])) {
1483 PINDEX span = expr.FindSpan("0123456789", pos);
1484 result += GetVar(expr(pos, span-1));
1485 pos = span;
1486 }
1487 else if (expr[pos] == '+' || isspace(expr[pos]))
1488 pos++;
1489 else {
1490 PTRACE(2, "VXML\tOnly '+' operator supported.");
1491 break;
1492 }
1493 }
1494
1495 return result;
1496 }
1497
1498
GetVar(const PString & varName) const1499 PCaselessString PVXMLSession::GetVar(const PString & varName) const
1500 {
1501 PString fullVarName = varName;
1502 if (varName.Find('.') == P_MAX_INDEX)
1503 fullVarName = m_variableScope+'.'+varName;
1504
1505 return m_variables(fullVarName);
1506 }
1507
1508
SetVar(const PString & varName,const PString & value)1509 void PVXMLSession::SetVar(const PString & varName, const PString & value)
1510 {
1511 PString fullVarName = varName;
1512 if (varName.Find('.') == P_MAX_INDEX)
1513 fullVarName = m_variableScope+'.'+varName;
1514
1515 m_variables.SetAt(fullVarName, value);
1516 }
1517
1518
PlayFile(const PString & fn,PINDEX repeat,PINDEX delay,PBoolean autoDelete)1519 PBoolean PVXMLSession::PlayFile(const PString & fn, PINDEX repeat, PINDEX delay, PBoolean autoDelete)
1520 {
1521 return IsOpen() && GetVXMLChannel()->QueueFile(fn, repeat, delay, autoDelete);
1522 }
1523
1524
PlayCommand(const PString & cmd,PINDEX repeat,PINDEX delay)1525 PBoolean PVXMLSession::PlayCommand(const PString & cmd, PINDEX repeat, PINDEX delay)
1526 {
1527 return IsOpen() && GetVXMLChannel()->QueueCommand(cmd, repeat, delay);
1528 }
1529
1530
PlayData(const PBYTEArray & data,PINDEX repeat,PINDEX delay)1531 PBoolean PVXMLSession::PlayData(const PBYTEArray & data, PINDEX repeat, PINDEX delay)
1532 {
1533 return IsOpen() && GetVXMLChannel()->QueueData(data, repeat, delay);
1534 }
1535
1536
PlayTone(const PString & toneSpec,PINDEX repeat,PINDEX delay)1537 PBoolean PVXMLSession::PlayTone(const PString & toneSpec, PINDEX repeat, PINDEX delay)
1538 {
1539 return IsOpen() && GetVXMLChannel()->QueuePlayable("Tone", toneSpec, repeat, delay, true);
1540 }
1541
1542
PlayElement(PXMLElement & element)1543 PBoolean PVXMLSession::PlayElement(PXMLElement & element)
1544 {
1545 PString str = element.GetAttribute("src").Trim();
1546 if (str.IsEmpty()) {
1547 str = EvaluateExpr(element.GetAttribute("expr"));
1548 if (str.IsEmpty()) {
1549 PTRACE(2, "VXML\tNo src attribute to play element.");
1550 return false;
1551 }
1552 }
1553
1554 if (str[0] == '|')
1555 return PlayCommand(str.Mid(1));
1556
1557 // get a normalised name for the resource
1558 bool safe = GetVar("caching") == "safe" || (element.GetAttribute("caching") *= "safe");
1559
1560 // load the resource from the cache
1561 PString contentType;
1562 PFilePath fn;
1563 if (RetreiveResource(NormaliseResourceName(str), contentType, fn, !safe))
1564 return PlayFile(fn, 0, 0, safe); // make sure we delete the file if not cacheing
1565
1566 return false;
1567 }
1568
1569
GetBeepData(PBYTEArray & data,unsigned ms)1570 void PVXMLSession::GetBeepData(PBYTEArray & data, unsigned ms)
1571 {
1572 if (IsOpen())
1573 GetVXMLChannel()->GetBeepData(data, ms);
1574 }
1575
1576
PlaySilence(const PTimeInterval & timeout)1577 PBoolean PVXMLSession::PlaySilence(const PTimeInterval & timeout)
1578 {
1579 return PlaySilence((PINDEX)timeout.GetMilliSeconds());
1580 }
1581
1582
PlaySilence(PINDEX msecs)1583 PBoolean PVXMLSession::PlaySilence(PINDEX msecs)
1584 {
1585 PBYTEArray nothing;
1586 return IsOpen() && GetVXMLChannel()->QueueData(nothing, 1, msecs);
1587 }
1588
1589
PlayStop()1590 PBoolean PVXMLSession::PlayStop()
1591 {
1592 return IsOpen() && GetVXMLChannel()->QueuePlayable(new PVXMLPlayableStop());
1593 }
1594
1595
PlayResource(const PURL & url,PINDEX repeat,PINDEX delay)1596 PBoolean PVXMLSession::PlayResource(const PURL & url, PINDEX repeat, PINDEX delay)
1597 {
1598 return IsOpen() && GetVXMLChannel()->QueueResource(url, repeat, delay);
1599 }
1600
1601
LoadGrammar(PVXMLGrammar * grammar)1602 PBoolean PVXMLSession::LoadGrammar(PVXMLGrammar * grammar)
1603 {
1604 PTRACE_IF(2, m_grammar != NULL && grammar == NULL, "VXML\tGrammar cleared from " << *m_grammar);
1605
1606 delete m_grammar;
1607 m_grammar = grammar;
1608
1609 PTRACE_IF(2, grammar != NULL, "VXML\tGrammar set to " << *grammar);
1610 return true;
1611 }
1612
1613
PlayText(const PString & textToPlay,PTextToSpeech::TextType type,PINDEX repeat,PINDEX delay)1614 PBoolean PVXMLSession::PlayText(const PString & textToPlay,
1615 PTextToSpeech::TextType type,
1616 PINDEX repeat,
1617 PINDEX delay)
1618 {
1619 if (!IsOpen() || textToPlay.IsEmpty())
1620 return false;
1621
1622 PTRACE(2, "VXML\tConverting \"" << textToPlay << "\" to speech");
1623
1624 PStringArray list;
1625 bool useCache = GetVar("caching") != "safe";
1626 if (!ConvertTextToFilenameList(textToPlay, type, list, useCache) || (list.GetSize() == 0)) {
1627 PTRACE(1, "VXML\tCannot convert text to speech");
1628 return false;
1629 }
1630
1631 PVXMLPlayableFileList * playable = new PVXMLPlayableFileList;
1632 if (!playable->Open(*GetVXMLChannel(), list, delay, repeat, !useCache)) {
1633 delete playable;
1634 PTRACE(1, "VXML\tCannot create playable for filename list");
1635 return false;
1636 }
1637
1638 if (!GetVXMLChannel()->QueuePlayable(playable))
1639 return false;
1640
1641 PTRACE(2, "VXML\tQueued filename list for playing");
1642
1643 return true;
1644 }
1645
1646
ConvertTextToFilenameList(const PString & _text,PTextToSpeech::TextType type,PStringArray & filenameList,PBoolean useCache)1647 PBoolean PVXMLSession::ConvertTextToFilenameList(const PString & _text, PTextToSpeech::TextType type, PStringArray & filenameList, PBoolean useCache)
1648 {
1649 PString prefix = psprintf("tts%i", type);
1650
1651 PStringArray lines = _text.Trim().Lines();
1652 for (PINDEX i = 0; i < lines.GetSize(); i++) {
1653
1654 PString text = lines[i].Trim();
1655 if (text.IsEmpty())
1656 continue;
1657
1658 PBoolean spoken = false;
1659 PFilePath dataFn;
1660
1661 // see if we have converted this text before
1662 PString contentType = "audio/x-wav";
1663 if (useCache)
1664 spoken = PVXMLCache::GetResourceCache().Get(prefix, contentType + '\t' + text, "wav", contentType, dataFn);
1665
1666 // if not cached, then use the text to speech converter
1667 if (spoken) {
1668 PTRACE(3, "VXML\tUsing cached audio file for " << _text);
1669 } else {
1670 PFilePath tmpfname;
1671 if (m_textToSpeech != NULL) {
1672 tmpfname = PVXMLCache::GetResourceCache().GetRandomFilename("tts", "wav");
1673 if (m_textToSpeech->OpenFile(tmpfname)) {
1674 spoken = m_textToSpeech->Speak(text, type);
1675 PTRACE(3, "VXML\tCreated new audio file for " << _text);
1676 }
1677 else {
1678 PTRACE(2, "VXML\tcannot open file " << tmpfname);
1679 }
1680 m_textToSpeech->Close();
1681 if (useCache)
1682 PVXMLCache::GetResourceCache().Put(prefix, text, "wav", contentType, tmpfname, dataFn);
1683 else
1684 dataFn = tmpfname;
1685 }
1686 }
1687
1688 if (!spoken) {
1689 PTRACE(2, "VXML\tcannot speak text using TTS engine");
1690 } else
1691 filenameList.AppendString(dataFn);
1692 }
1693
1694 return filenameList.GetSize() > 0;
1695 }
1696
1697
SetPause(PBoolean pause)1698 void PVXMLSession::SetPause(PBoolean pause)
1699 {
1700 if (IsOpen())
1701 GetVXMLChannel()->SetPause(pause);
1702 }
1703
1704
StartRecording(const PFilePath & recordFn,PBoolean recordDTMFTerm,const PTimeInterval & recordMaxTime,const PTimeInterval & recordFinalSilence)1705 PBoolean PVXMLSession::StartRecording(const PFilePath & recordFn,
1706 PBoolean recordDTMFTerm,
1707 const PTimeInterval & recordMaxTime,
1708 const PTimeInterval & recordFinalSilence)
1709 {
1710 if (!IsOpen())
1711 return false;
1712
1713 if (recordFn.IsEmpty()) {
1714 PTRACE(1, "VXML\tNo destination file location");
1715 return true;
1716 }
1717
1718 PFile::Remove(recordFn);
1719
1720 m_recordStopOnDTMF = recordDTMFTerm;
1721
1722 if (!GetVXMLChannel()->StartRecording(recordFn,
1723 (unsigned)recordFinalSilence.GetMilliSeconds(),
1724 (unsigned)recordMaxTime.GetMilliSeconds()))
1725 return false;
1726
1727 m_recordingStatus = RecordingInProgress;
1728 return true;
1729 }
1730
1731
EndRecording()1732 PBoolean PVXMLSession::EndRecording()
1733 {
1734 return m_recordingStatus == RecordingInProgress && IsOpen() && GetVXMLChannel()->EndRecording();
1735 }
1736
1737
TraverseAudio(PXMLElement & element)1738 PBoolean PVXMLSession::TraverseAudio(PXMLElement & element)
1739 {
1740 return !PlayElement(element);
1741 }
1742
1743
TraverseBreak(PXMLElement & element)1744 PBoolean PVXMLSession::TraverseBreak(PXMLElement & element)
1745 {
1746 // msecs is VXML 1.0
1747 if (element.HasAttribute("msecs"))
1748 return PlaySilence(element.GetAttribute("msecs").AsInteger());
1749
1750 // time is VXML 2.0
1751 if (element.HasAttribute("time"))
1752 return PlaySilence(StringToTime(element.GetAttribute("time"), 1000));
1753
1754 if (element.HasAttribute("size")) {
1755 PString size = element.GetAttribute("size");
1756 if (size *= "none")
1757 return true;
1758 if (size *= "small")
1759 return PlaySilence(SMALL_BREAK_MSECS);
1760 if (size *= "large")
1761 return PlaySilence(LARGE_BREAK_MSECS);
1762 return PlaySilence(MEDIUM_BREAK_MSECS);
1763 }
1764
1765 // default to medium pause
1766 return PlaySilence(MEDIUM_BREAK_MSECS);
1767 }
1768
1769
TraverseValue(PXMLElement & element)1770 PBoolean PVXMLSession::TraverseValue(PXMLElement & element)
1771 {
1772 PString className = element.GetAttribute("class");
1773 PString value = EvaluateExpr(element.GetAttribute("expr"));
1774 PString voice = element.GetAttribute("voice");
1775 if (voice.IsEmpty())
1776 voice = GetVar("voice");
1777 SayAs(className, value, voice);
1778 return true;
1779 }
1780
1781
TraverseSayAs(PXMLElement & element)1782 PBoolean PVXMLSession::TraverseSayAs(PXMLElement & element)
1783 {
1784 SayAs(element.GetAttribute("class"), element.GetData());
1785 return true;
1786 }
1787
1788
TraverseGoto(PXMLElement & element)1789 PBoolean PVXMLSession::TraverseGoto(PXMLElement & element)
1790 {
1791 bool fullURI = false;
1792 PString target;
1793
1794 if (element.HasAttribute("nextitem"))
1795 target = element.GetAttribute("nextitem");
1796 else if (element.HasAttribute("expritem"))
1797 target = EvaluateExpr(element.GetAttribute("expritem"));
1798 else if (element.HasAttribute("expr")) {
1799 fullURI = true;
1800 target = EvaluateExpr(element.GetAttribute("expr"));
1801 }
1802 else if (element.HasAttribute("next")) {
1803 fullURI = true;
1804 target = element.GetAttribute("next");
1805 }
1806
1807 if (SetCurrentForm(target, fullURI))
1808 return ProcessNode();
1809
1810 // LATER: throw "error.semantic" or "error.badfetch" -- lookup which
1811 return false;
1812 }
1813
1814
TraverseGrammar(PXMLElement & element)1815 PBoolean PVXMLSession::TraverseGrammar(PXMLElement & element)
1816 {
1817 // LATER: A bunch of work to do here!
1818
1819 // For now we only support the builtin digits type and do not parse any grammars.
1820
1821 // NOTE: For now we will process both <grammar> and <field> here.
1822 // NOTE: Later there needs to be a check for <grammar> which will pull
1823 // out the text and process a grammar like '1 | 2'
1824
1825 // Right now we only support one active grammar.
1826 if (m_grammar != NULL) {
1827 PTRACE(2, "VXML\tWarning: can only process one grammar at a time, ignoring previous grammar");
1828 LoadGrammar(NULL);
1829 }
1830
1831 m_speakNodeData = false;
1832
1833 PCaselessString attrib = element.GetAttribute("mode");
1834 if (!attrib.IsEmpty() && attrib != "dtmf") {
1835 PTRACE(2, "VXML\tOnly DTMF mode supported for grammar");
1836 return false;
1837 }
1838
1839 attrib = element.GetAttribute("type");
1840 if (!attrib.IsEmpty() && attrib != "X-OPAL/digits") {
1841 PTRACE(2, "VXML\tOnly \"digits\" type supported for grammar");
1842 return false;
1843 }
1844
1845 PTRACE(4, "VXML\tLoading new grammar");
1846 PStringToString tokens;
1847 PURL::SplitVars(element.GetData(), tokens, ';', '=');
1848 return LoadGrammar(new PVXMLDigitsGrammar(*this,
1849 *element.GetParent(),
1850 tokens("minDigits", "1").AsUnsigned(),
1851 tokens("maxDigits", "10").AsUnsigned(),
1852 tokens("terminators", "#")));
1853 }
1854
1855
1856 // Finds the proper event hander for 'noinput', 'filled', 'nomatch' and 'error'
1857 // by searching the scope hiearchy from the current from
GoToEventHandler(PXMLElement & element,const PString & eventName)1858 bool PVXMLSession::GoToEventHandler(PXMLElement & element, const PString & eventName)
1859 {
1860 PXMLElement * level = &element;
1861 PXMLElement * handler = NULL;
1862
1863 int actualCount = 1; // Need to increment this with state stored ... somewhere
1864
1865 // Look in all the way up the tree for a handler either explicitly or in a catch
1866 for (;;) {
1867 for (int testCount = actualCount; testCount >= 0; --testCount) {
1868 // Check for an explicit hander - i.e. <error>, <filled>, <noinput>, <nomatch>, <help>
1869 PINDEX index = 0;
1870 if ((handler = level->GetElement(eventName)) != NULL &&
1871 handler->GetAttribute("count").AsInteger() == testCount)
1872 goto gotHandler;
1873
1874 // Check for a <catch>
1875 index = 0;
1876 while ((handler = level->GetElement("catch", index++)) != NULL) {
1877 if ((handler->GetAttribute("event") *= eventName) &&
1878 handler->GetAttribute("count").AsInteger() == testCount)
1879 goto gotHandler;
1880 }
1881 }
1882
1883 level = level->GetParent();
1884 if (level == NULL) {
1885 PTRACE(4, "VXML\tNo event handler found for \"" << eventName << '"');
1886 return true;
1887 }
1888 }
1889
1890 gotHandler:
1891 handler->SetAttribute("fired", "true");
1892 m_currentNode = handler;
1893 PTRACE(4, "VXML\tSetting event handler to node " << handler << " for \"" << eventName << '"');
1894 return false;
1895 }
1896
1897
SayAs(const PString & className,const PString & text)1898 void PVXMLSession::SayAs(const PString & className, const PString & text)
1899 {
1900 SayAs(className, text, GetVar("voice"));
1901 }
1902
1903
SayAs(const PString & className,const PString & textToSay,const PString & voice)1904 void PVXMLSession::SayAs(const PString & className, const PString & textToSay, const PString & voice)
1905 {
1906 if (m_textToSpeech != NULL)
1907 m_textToSpeech->SetVoice(voice);
1908
1909 PString text = textToSay.Trim();
1910 if (!text.IsEmpty()) {
1911 PTextToSpeech::TextType type = PTextToSpeech::Literal;
1912
1913 if (className *= "digits")
1914 type = PTextToSpeech::Digits;
1915
1916 else if (className *= "literal")
1917 type = PTextToSpeech::Literal;
1918
1919 else if (className *= "number")
1920 type = PTextToSpeech::Number;
1921
1922 else if (className *= "currency")
1923 type = PTextToSpeech::Currency;
1924
1925 else if (className *= "time")
1926 type = PTextToSpeech::Time;
1927
1928 else if (className *= "date")
1929 type = PTextToSpeech::Date;
1930
1931 else if (className *= "phone")
1932 type = PTextToSpeech::Phone;
1933
1934 else if (className *= "ipaddress")
1935 type = PTextToSpeech::IPAddress;
1936
1937 else if (className *= "duration")
1938 type = PTextToSpeech::Duration;
1939
1940 PlayText(text, type);
1941 }
1942 }
1943
1944
StringToTime(const PString & str,int dflt)1945 PTimeInterval PVXMLSession::StringToTime(const PString & str, int dflt)
1946 {
1947 if (str.IsEmpty())
1948 return dflt;
1949
1950 PCaselessString units = str.Mid(str.FindSpan("0123456789")).Trim();
1951 if (units == "s")
1952 return PTimeInterval(0, str.AsInteger());
1953 else if (units == "m")
1954 return PTimeInterval(0, 0, str.AsInteger());
1955 else if (units == "h")
1956 return PTimeInterval(0, 0, 0, str.AsInteger());
1957
1958 return str.AsInt64();
1959 }
1960
1961
TraverseIf(PXMLElement & element)1962 PBoolean PVXMLSession::TraverseIf(PXMLElement & element)
1963 {
1964 // If 'cond' parameter evaluates to true, enter child entities, else
1965 // go to next element.
1966
1967 PString condition = element.GetAttribute("cond");
1968
1969 // Find comparison type
1970 PINDEX location = condition.Find("==");
1971 if (location == P_MAX_INDEX) {
1972 PTRACE(1, "VXML\t<if> element contains condition with operator other than ==, not implemented" );
1973 return false;
1974 }
1975
1976 // Find var name
1977 PString varname = condition.Left(location);
1978
1979 // Find value, skip '=' signs
1980 PString cond_value = condition.Mid(location + 3);
1981
1982 // check if var value equals value from condition and if not skip child elements
1983 PCaselessString value = GetVar(varname);
1984 if (value == cond_value) {
1985 PTRACE(3, "VXML\tCondition matched \"" << condition << '"');
1986 }
1987 else {
1988 PTRACE(3, "VXMLSess\t\tCondition \"" << condition << "\"did not match, " << varname << " == " << value);
1989 if (element.HasSubObjects())
1990 // Step to last child element (really last element is NULL?)
1991 m_currentNode = element.GetElement(element.GetSize() - 1);
1992 }
1993
1994 return true;
1995 }
1996
1997
TraverseExit(PXMLElement &)1998 PBoolean PVXMLSession::TraverseExit(PXMLElement &)
1999 {
2000 PTRACE(2, "VXML\tExiting, fast forwarding through script");
2001 m_abortVXML = true;
2002 Trigger();
2003 return true;
2004 }
2005
2006
TraverseSubmit(PXMLElement & element)2007 PBoolean PVXMLSession::TraverseSubmit(PXMLElement & element)
2008 {
2009 PURL url;
2010
2011 if (element.HasAttribute("expr"))
2012 url.Parse(EvaluateExpr(element.GetAttribute("expr")));
2013 else if (element.HasAttribute("next"))
2014 url.Parse(element.GetAttribute("next"));
2015 else {
2016 PTRACE(1, "VXML\t<submit> does not contain \"next\" or \"expr\" attribute.");
2017 return false;
2018 }
2019 if (url.IsEmpty()) {
2020 PTRACE(1, "VXML\t<submit> has an invalid URL.");
2021 return false;
2022 }
2023
2024 bool urlencoded;
2025 PCaselessString str = element.GetAttribute("enctype");
2026 if (str.IsEmpty() || str == "x-www-form-urlencoded")
2027 urlencoded = true;
2028 else if (str == "multipart/form-data")
2029 urlencoded = false;
2030 else {
2031 PTRACE(1, "VXML\t<submit> has unknown \"enctype\" attribute of \"" << str << '"');
2032 return false;
2033 }
2034
2035 bool get;
2036 str = element.GetAttribute("method");
2037 if (str.IsEmpty())
2038 get = urlencoded;
2039 else if (str == "GET")
2040 get = true;
2041 else if (str == "POST")
2042 get = false;
2043 else {
2044 PTRACE(1, "VXML\t<submit> has unknown \"method\" attribute of \"" << str << '"');
2045 return false;
2046 }
2047
2048 PHTTPClient client("PTLib VXML");
2049 client.SetReadTimeout(StringToTime(element.GetAttribute("fetchtimeout"), 10000));
2050
2051 PStringArray namelist = element.GetAttribute("namelist").Tokenise(" \t", false);
2052
2053 if (get) {
2054 if (namelist.IsEmpty())
2055 url.SetQueryVars(GetVariables());
2056 else {
2057 for (PINDEX i = 0; i < namelist.GetSize(); ++i)
2058 url.SetQueryVar(namelist[i], GetVar(namelist[i]));
2059 }
2060
2061 PMIMEInfo replyMIME;
2062 if (client.GetDocument(url, replyMIME) && client.ReadContentBody(replyMIME))
2063 return true;
2064
2065 PTRACE(1, "VXML\t<submit> GET " << url << " failed with "
2066 << client.GetLastResponseCode() << ' ' << client.GetLastResponseInfo());
2067 return false;
2068 }
2069
2070 if (urlencoded) {
2071 PStringToString vars;
2072 if (namelist.IsEmpty())
2073 vars = GetVariables();
2074 else {
2075 for (PINDEX i = 0; i < namelist.GetSize(); ++i)
2076 vars.SetAt(namelist[i], GetVar(namelist[i]));
2077 }
2078
2079 if (client.PostData(url, vars))
2080 return true;
2081
2082 PTRACE(1, "VXML\t<submit> POST " << url << " failed with "
2083 << client.GetLastResponseCode() << ' ' << client.GetLastResponseInfo());
2084 return false;
2085 }
2086
2087 PMIMEInfo sendMIME;
2088
2089 // Put in boundary
2090 PString boundary = "--------012345678901234567890123458VXML";
2091 sendMIME.SetAt( PHTTP::ContentTypeTag(), "multipart/form-data; boundary=" + boundary);
2092
2093 // After this all boundaries have a "--" prepended
2094 boundary.Splice("--", 0, 0);
2095
2096 PStringStream entityBody;
2097
2098 for (PINDEX i = 0; i < namelist.GetSize(); ++i) {
2099 if (GetVar(namelist[i] + ".type") != "audio/x-wav" ) {
2100 PTRACE(1, "VXML\t<submit> does not (yet) support submissions of types other than \"audio/x-wav\"");
2101 continue;
2102 }
2103
2104 PFile file(GetVar(namelist[i] + ".filename"), PFile::ReadOnly);
2105 if (!file.IsOpen()) {
2106 PTRACE(1, "VXML\t<submit> could not find file \"" << file.GetFilePath() << '"');
2107 continue;
2108 }
2109
2110 PMIMEInfo part1, part2;
2111 part1.Set(PMIMEInfo::ContentTypeTag, "audio/wav");
2112 part1.Set(PMIMEInfo::ContentDispositionTag,
2113 "form-data; name=\"voicemail\"; filename=\"" + file.GetFilePath().GetFileName() + '"');
2114 // Make PHP happy?
2115 // Anyway, this shows how to add more variables, for when namelist containes more elements
2116 part2.Set(PMIMEInfo::ContentDispositionTag, "form-data; name=\"MAX_FILE_SIZE\"\r\n\r\n3000000");
2117
2118 entityBody << "--" << boundary << "\r\n"
2119 << part1 << "\r\n"
2120 << file.ReadString(file.GetLength())
2121 << "--" << boundary << "\r\n"
2122 << part2
2123 << "\r\n";
2124 }
2125
2126 if (entityBody.IsEmpty()) {
2127 PTRACE(1, "VXML\t<submit> could not find anything to send using \"" << setfill(',') << namelist << '"');
2128 return false;
2129 }
2130
2131 if (client.PostData(url, sendMIME, entityBody))
2132 return true;
2133
2134 PTRACE(1, "VXML\t<submit> POST " << url << " failed with "
2135 << client.GetLastResponseCode() << ' ' << client.GetLastResponseInfo());
2136 return false;
2137 }
2138
2139
TraverseProperty(PXMLElement & element)2140 PBoolean PVXMLSession::TraverseProperty(PXMLElement & element)
2141 {
2142 if (element.HasAttribute("name"))
2143 SetVar("property." + element.GetAttribute("name"), element.GetAttribute("value"));
2144
2145 return true;
2146 }
2147
2148
TraverseTransfer(PXMLElement &)2149 PBoolean PVXMLSession::TraverseTransfer(PXMLElement &)
2150 {
2151 m_transferStatus = NotTransfering;
2152 return true;
2153 }
2154
2155
TraversedTransfer(PXMLElement & element)2156 PBoolean PVXMLSession::TraversedTransfer(PXMLElement & element)
2157 {
2158 if (m_transferStatus == NotTransfering) {
2159 TransferType type = BridgedTransfer;
2160 if (element.GetAttribute("bridge") *= "false")
2161 type = BlindTransfer;
2162 else {
2163 PCaselessString typeStr = element.GetAttribute("type");
2164 if (typeStr == "blind")
2165 type = BlindTransfer;
2166 else if (typeStr == "consultation")
2167 type = ConsultationTransfer;
2168 }
2169
2170 m_transferStartTime.SetCurrentTime();
2171
2172 bool started = false;
2173 if (element.HasAttribute("dest"))
2174 started = OnTransfer(element.GetAttribute("dest"), type);
2175 else if (element.HasAttribute("destexpr"))
2176 started = OnTransfer(EvaluateExpr(element.GetAttribute("destexpr")), type);
2177
2178 if (started) {
2179 m_transferStatus = TransferInProgress;
2180 return false;
2181 }
2182
2183 m_transferStatus = TransferFailed;
2184 }
2185 else {
2186 PString name = element.GetAttribute("name");
2187 if (!name.IsEmpty())
2188 SetVar(name + "$.duration", PString(PString::Unsigned, (PTime() - m_transferStartTime).GetSeconds()));
2189 }
2190
2191 return GoToEventHandler(element, m_transferStatus == TransferSuccessful ? "filled" : "error");
2192 }
2193
2194
SetTransferComplete(bool state)2195 void PVXMLSession::SetTransferComplete(bool state)
2196 {
2197 PTRACE(3, "VXML\tTransfer " << (state ? "completed" : "failed"));
2198 m_transferStatus = state ? TransferSuccessful : TransferFailed;
2199 Trigger();
2200 }
2201
2202
TraverseMenu(PXMLElement & element)2203 PBoolean PVXMLSession::TraverseMenu(PXMLElement & element)
2204 {
2205 LoadGrammar(new PVXMLMenuGrammar(*this, element));
2206 m_defaultMenuDTMF = (element.GetAttribute("dtmf") *= "true") ? '1' : 'N';
2207 return true;
2208 }
2209
2210
TraversedMenu(PXMLElement &)2211 PBoolean PVXMLSession::TraversedMenu(PXMLElement &)
2212 {
2213 return ProcessGrammar();
2214 }
2215
2216
TraverseChoice(PXMLElement & element)2217 PBoolean PVXMLSession::TraverseChoice(PXMLElement & element)
2218 {
2219 if (!element.HasAttribute("dtmf") && m_defaultMenuDTMF <= '9')
2220 element.SetAttribute("dtmf", PString(m_defaultMenuDTMF++));
2221
2222 return true;
2223 }
2224
2225
TraverseVar(PXMLElement & element)2226 PBoolean PVXMLSession::TraverseVar(PXMLElement & element)
2227 {
2228 PString name = element.GetAttribute("name");
2229 PString expr = element.GetAttribute("expr");
2230
2231 if (name.IsEmpty() || expr.IsEmpty()) {
2232 PTRACE(1, "VXML\t<var> must have both \"name=\" and \"expr=\" attributes." );
2233 return false;
2234 }
2235
2236 SetVar(name, EvaluateExpr(expr));
2237 return true;
2238 }
2239
2240
TraverseDisconnect(PXMLElement &)2241 PBoolean PVXMLSession::TraverseDisconnect(PXMLElement &)
2242 {
2243 m_currentNode = NULL;
2244 return true;
2245 }
2246
2247
TraverseForm(PXMLElement &)2248 PBoolean PVXMLSession::TraverseForm(PXMLElement &)
2249 {
2250 m_variableScope = "dialog";
2251 return true;
2252 }
2253
2254
TraversedForm(PXMLElement &)2255 PBoolean PVXMLSession::TraversedForm(PXMLElement &)
2256 {
2257 m_variableScope = "application";
2258 return true;
2259 }
2260
2261
TraversePrompt(PXMLElement & element)2262 PBoolean PVXMLSession::TraversePrompt(PXMLElement & element)
2263 {
2264 // LATER:
2265 // check 'cond' attribute to see if the children of this node should be processed
2266 // check 'count' attribute to see if this node should be processed
2267
2268 // Update timeout of current recognition (if 'timeout' attribute is set)
2269 if (m_grammar != NULL)
2270 m_grammar->SetTimeout(StringToTime(element.GetAttribute("timeout")));
2271
2272 m_bargeIn = !(element.GetAttribute("bargein") *= "false"); // Defaults to true
2273 return true;
2274 }
2275
2276
TraversedPrompt(PXMLElement &)2277 PBoolean PVXMLSession::TraversedPrompt(PXMLElement &)
2278 {
2279 m_bargeIn = DefaultBargeIn;
2280 return true;
2281 }
2282
2283
TraverseField(PXMLElement &)2284 PBoolean PVXMLSession::TraverseField(PXMLElement &)
2285 {
2286 return true;
2287 }
2288
2289
TraversedField(PXMLElement &)2290 PBoolean PVXMLSession::TraversedField(PXMLElement &)
2291 {
2292 return ProcessGrammar();
2293 }
2294
2295
OnEndRecording()2296 void PVXMLSession::OnEndRecording()
2297 {
2298 //SetVar(channelName + ".size", PString(incomingChannel->GetWAVFile()->GetDataLength() ) );
2299 //SetVar(channelName + ".type", "audio/x-wav" );
2300 //SetVar(channelName + ".filename", incomingChannel->GetWAVFile()->GetName() );
2301 m_recordingStatus = RecordingComplete;
2302 Trigger();
2303 }
2304
2305
Trigger()2306 void PVXMLSession::Trigger()
2307 {
2308 PTRACE(4, "VXML\tEvent triggered");
2309 m_waitForEvent.Signal();
2310 }
2311
2312
2313
2314 /////////////////////////////////////////////////////////////////////////////////////////
2315
PVXMLGrammar(PVXMLSession & session,PXMLElement & field)2316 PVXMLGrammar::PVXMLGrammar(PVXMLSession & session, PXMLElement & field)
2317 : m_session(session)
2318 , m_field(field)
2319 , m_state(Idle)
2320 , m_timeout(PVXMLSession::StringToTime(session.GetVar("property.timeout"), 10000))
2321 {
2322 m_timer.SetNotifier(PCREATE_NOTIFIER(OnTimeout));
2323 }
2324
2325
SetTimeout(const PTimeInterval & timeout)2326 void PVXMLGrammar::SetTimeout(const PTimeInterval & timeout)
2327 {
2328 if (timeout > 0) {
2329 m_timeout = timeout;
2330 if (m_timer.IsRunning())
2331 m_timer = timeout;
2332 }
2333 }
2334
2335
Start()2336 void PVXMLGrammar::Start()
2337 {
2338 m_state = Started;
2339 m_timer = m_timeout;
2340 PTRACE(3, "VXML\tStarted grammar " << *this << ", timeout=" << m_timeout);
2341 }
2342
2343
OnTimeout(PTimer &,INT)2344 void PVXMLGrammar::OnTimeout(PTimer &, INT)
2345 {
2346 PTRACE(3, "VXML\tTimeout for grammar " << *this);
2347 m_mutex.Wait();
2348
2349 if (m_state == Started) {
2350 m_state = NoInput;
2351 m_session.Trigger();
2352 }
2353
2354 m_mutex.Signal();
2355 }
2356
2357
Process()2358 bool PVXMLGrammar::Process()
2359 {
2360 // Figure out what happened
2361 switch (m_state) {
2362 case Filled:
2363 if (m_field.HasAttribute("name"))
2364 m_session.SetVar(m_field.GetAttribute("name"), m_value);
2365 return m_session.GoToEventHandler(m_field, "filled");
2366
2367 case PVXMLGrammar::NoInput:
2368 return m_session.GoToEventHandler(m_field, "noinput");
2369
2370 case PVXMLGrammar::NoMatch:
2371 return m_session.GoToEventHandler(m_field, "nomatch");
2372
2373 default:
2374 break; //ERROR - unexpected grammar state
2375 }
2376
2377 return true; // Next node
2378 }
2379
2380
2381 //////////////////////////////////////////////////////////////////
2382
PVXMLMenuGrammar(PVXMLSession & session,PXMLElement & field)2383 PVXMLMenuGrammar::PVXMLMenuGrammar(PVXMLSession & session, PXMLElement & field)
2384 : PVXMLGrammar(session, field)
2385 {
2386 }
2387
2388
OnUserInput(const char ch)2389 void PVXMLMenuGrammar::OnUserInput(const char ch)
2390 {
2391 m_mutex.Wait();
2392
2393 m_value = ch;
2394 m_state = PVXMLGrammar::Filled;
2395
2396 m_mutex.Signal();
2397 }
2398
2399
Process()2400 bool PVXMLMenuGrammar::Process()
2401 {
2402 if (m_state == Filled) {
2403 PXMLElement * choice;
2404 PINDEX index = 0;
2405 while ((choice = m_field.GetElement("choice", index++)) != NULL) {
2406 // Check if DTMF value for grammarResult matches the DTMF value for the choice
2407 if (choice->GetAttribute("dtmf") == m_value) {
2408 PTRACE(3, "VXML\tMatched menu choice: " << m_value);
2409 PString next = choice->GetAttribute("next");
2410 if (next.IsEmpty())
2411 next = m_session.EvaluateExpr(choice->GetAttribute("expr"));
2412 if (m_session.SetCurrentForm(next, true))
2413 return false;
2414
2415 return m_session.GoToEventHandler(m_field, choice->GetAttribute("event"));
2416 }
2417 }
2418
2419 m_state = NoMatch;
2420 }
2421
2422 return PVXMLGrammar::Process();
2423 }
2424
2425
2426 //////////////////////////////////////////////////////////////////
2427
PVXMLDigitsGrammar(PVXMLSession & session,PXMLElement & field,PINDEX minDigits,PINDEX maxDigits,PString terminators)2428 PVXMLDigitsGrammar::PVXMLDigitsGrammar(PVXMLSession & session,
2429 PXMLElement & field,
2430 PINDEX minDigits,
2431 PINDEX maxDigits,
2432 PString terminators)
2433 : PVXMLGrammar(session, field)
2434 , m_minDigits(minDigits)
2435 , m_maxDigits(maxDigits)
2436 , m_terminators(terminators)
2437 {
2438 PAssert(minDigits <= maxDigits, PInvalidParameter);
2439 }
2440
2441
OnUserInput(const char ch)2442 void PVXMLDigitsGrammar::OnUserInput(const char ch)
2443 {
2444 PWaitAndSignal mutex(m_mutex);
2445
2446 // Ignore any keys until we are running
2447 if (m_state != Started)
2448 return;
2449
2450 PINDEX len = m_value.GetLength();
2451
2452 // is this char the terminator?
2453 if (m_terminators.Find(ch) != P_MAX_INDEX) {
2454 m_state = (len >= m_minDigits && len <= m_maxDigits) ? Filled : NoMatch;
2455 return;
2456 }
2457
2458 // Otherwise add to the grammar and check to see if we're done
2459 m_value += ch;
2460 if (++len >= m_maxDigits)
2461 m_state = PVXMLGrammar::Filled; // the grammar is filled!
2462 }
2463
2464
2465 //////////////////////////////////////////////////////////////////
2466
PVXMLChannel(unsigned frameDelay,PINDEX frameSize)2467 PVXMLChannel::PVXMLChannel(unsigned frameDelay, PINDEX frameSize)
2468 : PDelayChannel(DelayReadsAndWrites, frameDelay, frameSize)
2469 , m_vxmlSession(NULL)
2470 , m_sampleFrequency(8000)
2471 , m_closed(false)
2472 , m_paused(false)
2473 , m_totalData(0)
2474 , m_recordable(NULL)
2475 , m_currentPlayItem(NULL)
2476 {
2477 }
2478
2479
Open(PVXMLSession * session)2480 PBoolean PVXMLChannel::Open(PVXMLSession * session)
2481 {
2482 m_currentPlayItem = NULL;
2483 m_vxmlSession = session;
2484 m_silenceTimer.SetInterval(500); // 1/2 a second delay before we start outputting stuff
2485 PTRACE(4, "VXML\tOpening channel " << this);
2486 return true;
2487 }
2488
2489
~PVXMLChannel()2490 PVXMLChannel::~PVXMLChannel()
2491 {
2492 Close();
2493 }
2494
2495
IsOpen() const2496 PBoolean PVXMLChannel::IsOpen() const
2497 {
2498 return !m_closed;
2499 }
2500
2501
Close()2502 PBoolean PVXMLChannel::Close()
2503 {
2504 if (!m_closed) {
2505 PTRACE(4, "VXML\tClosing channel " << this);
2506
2507 EndRecording();
2508 FlushQueue();
2509
2510 m_closed = true;
2511
2512 PDelayChannel::Close();
2513 }
2514
2515 return true;
2516 }
2517
2518
AdjustWavFilename(const PString & ofn)2519 PString PVXMLChannel::AdjustWavFilename(const PString & ofn)
2520 {
2521 if (wavFilePrefix.IsEmpty())
2522 return ofn;
2523
2524 PString fn = ofn;
2525
2526 // add in suffix required for channel format, if any
2527 PINDEX pos = ofn.FindLast('.');
2528 if (pos == P_MAX_INDEX) {
2529 if (fn.Right(wavFilePrefix.GetLength()) != wavFilePrefix)
2530 fn += wavFilePrefix;
2531 }
2532 else {
2533 PString basename = ofn.Left(pos);
2534 PString ext = ofn.Mid(pos+1);
2535 if (basename.Right(wavFilePrefix.GetLength()) != wavFilePrefix)
2536 basename += wavFilePrefix;
2537 fn = basename + "." + ext;
2538 }
2539 return fn;
2540 }
2541
2542
2543 #if P_WAVFILE
2544
CreateWAVFile(const PFilePath & fn,PBoolean recording)2545 PWAVFile * PVXMLChannel::CreateWAVFile(const PFilePath & fn, PBoolean recording)
2546 {
2547 PWAVFile * wav = new PWAVFile;
2548 if (!wav->SetFormat(mediaFormat)) {
2549 PTRACE(1, "VXML\tWAV file format " << mediaFormat << " not known");
2550 delete wav;
2551 return NULL;
2552 }
2553
2554 wav->SetAutoconvert();
2555 if (!wav->Open(fn,
2556 recording ? PFile::WriteOnly : PFile::ReadOnly,
2557 PFile::ModeDefault))
2558 PTRACE(2, "VXML\tCould not open WAV file " << wav->GetName());
2559
2560 else if (recording) {
2561 wav->SetChannels(1);
2562 wav->SetSampleRate(8000);
2563 wav->SetSampleSize(16);
2564 return wav;
2565 }
2566
2567 else if (!wav->IsValid())
2568 PTRACE(2, "VXML\tWAV file header invalid for " << wav->GetName());
2569
2570 else if (wav->GetSampleRate() != GetSampleFrequency())
2571 PTRACE(2, "VXML\tWAV file has unsupported sample frequency " << wav->GetSampleRate());
2572
2573 else if (wav->GetChannels() != 1)
2574 PTRACE(2, "VXML\tWAV file has unsupported channel count " << wav->GetChannels());
2575
2576 else {
2577 wav->SetAutoconvert(); /// enable autoconvert
2578 PTRACE(3, "VXML\tOpened WAV file " << wav->GetName());
2579 return wav;
2580 }
2581
2582 delete wav;
2583 return NULL;
2584 }
2585
2586 #endif // P_WAVFILE
2587
2588
Write(const void * buf,PINDEX len)2589 PBoolean PVXMLChannel::Write(const void * buf, PINDEX len)
2590 {
2591 if (m_closed)
2592 return false;
2593
2594 m_channelWriteMutex.Wait();
2595
2596 // let the recordable do silence detection
2597 if (m_recordable != NULL && m_recordable->OnFrame(IsSilenceFrame(buf, len)))
2598 EndRecording();
2599
2600 m_channelWriteMutex.Signal();
2601
2602 // write the data and do the correct delay
2603 if (WriteFrame(buf, len))
2604 m_totalData += lastWriteCount;
2605 else {
2606 EndRecording();
2607 lastWriteCount = len;
2608 Wait(len, nextWriteTick);
2609 }
2610
2611 return true;
2612 }
2613
2614
StartRecording(const PFilePath & fn,unsigned _finalSilence,unsigned _maxDuration)2615 PBoolean PVXMLChannel::StartRecording(const PFilePath & fn, unsigned _finalSilence, unsigned _maxDuration)
2616 {
2617 PVXMLRecordableFilename * recordable = new PVXMLRecordableFilename();
2618 if (!recordable->Open(fn)) {
2619 delete recordable;
2620 return false;
2621 }
2622
2623 recordable->SetFinalSilence(_finalSilence);
2624 recordable->SetMaxDuration(_maxDuration);
2625 return QueueRecordable(recordable);
2626 }
2627
2628
QueueRecordable(PVXMLRecordable * newItem)2629 PBoolean PVXMLChannel::QueueRecordable(PVXMLRecordable * newItem)
2630 {
2631 m_totalData = 0;
2632
2633 // shutdown any existing recording
2634 EndRecording();
2635
2636 // insert the new recordable
2637 PWaitAndSignal mutex(m_channelWriteMutex);
2638 m_recordable = newItem;
2639 m_totalData = 0;
2640 SetReadTimeout(frameDelay);
2641 return newItem->OnStart(*this);
2642 }
2643
2644
EndRecording()2645 PBoolean PVXMLChannel::EndRecording()
2646 {
2647 PWaitAndSignal mutex(m_channelWriteMutex);
2648
2649 if (m_recordable == NULL)
2650 return false;
2651
2652 PTRACE(3, "VXML\tFinished recording " << m_totalData << " bytes");
2653 m_recordable->OnStop();
2654 delete m_recordable;
2655 m_recordable = NULL;
2656 m_vxmlSession->OnEndRecording();
2657
2658 return true;
2659 }
2660
2661
Read(void * buffer,PINDEX amount)2662 PBoolean PVXMLChannel::Read(void * buffer, PINDEX amount)
2663 {
2664 for (;;) {
2665 if (m_closed)
2666 return false;
2667
2668 if (m_paused || m_silenceTimer.IsRunning())
2669 break;
2670
2671 // if the read succeeds, we are done
2672 if (ReadFrame(buffer, amount)) {
2673 m_totalData += lastReadCount;
2674 return true; // Already done real time delay
2675 }
2676
2677 // if a timeout, send silence, try again in a bit
2678 if (GetErrorCode(LastReadError) == Timeout)
2679 break;
2680
2681 // Other errors mean end of the playable
2682 PWaitAndSignal mutex(m_channelReadMutex);
2683
2684 // if current item still active, check for trailing actions
2685 if (m_currentPlayItem != NULL) {
2686 PTRACE(3, "VXML\tFinished playing " << *m_currentPlayItem << ", " << m_totalData << " bytes");
2687
2688 if (m_currentPlayItem->OnRepeat())
2689 continue;
2690
2691 // see if end of queue delay specified
2692 if (m_currentPlayItem->OnDelay())
2693 break;
2694
2695 // stop the current item
2696 m_currentPlayItem->OnStop();
2697 delete m_currentPlayItem;
2698 m_currentPlayItem = NULL;
2699 m_vxmlSession->Trigger();
2700 }
2701
2702 for (;;) {
2703 // check the queue for the next action, if none, send silence
2704 m_currentPlayItem = m_playQueue.Dequeue();
2705 if (m_currentPlayItem == NULL)
2706 goto double_break;
2707
2708 // start the new item
2709 if (m_currentPlayItem->OnStart())
2710 break;
2711
2712 delete m_currentPlayItem;
2713 }
2714
2715 PTRACE(4, "VXML\tStarted playing " << *m_currentPlayItem);
2716 SetReadTimeout(frameDelay);
2717 m_totalData = 0;
2718 }
2719
2720 double_break:
2721 lastReadCount = CreateSilenceFrame(buffer, amount);
2722 Wait(lastReadCount, nextReadTick);
2723 return true;
2724 }
2725
2726
SetSilence(unsigned msecs)2727 void PVXMLChannel::SetSilence(unsigned msecs)
2728 {
2729 PTRACE(3, "VXML\tPlaying silence for " << msecs << "ms");
2730 m_silenceTimer.SetInterval(msecs);
2731 }
2732
2733
QueuePlayable(const PString & type,const PString & arg,PINDEX repeat,PINDEX delay,PBoolean autoDelete)2734 PBoolean PVXMLChannel::QueuePlayable(const PString & type,
2735 const PString & arg,
2736 PINDEX repeat,
2737 PINDEX delay,
2738 PBoolean autoDelete)
2739 {
2740 if (repeat <= 0)
2741 repeat = 1;
2742
2743 PVXMLPlayable * item = PFactory<PVXMLPlayable>::CreateInstance(type);
2744 if (item == NULL) {
2745 PTRACE(2, "VXML\tCannot find playable of type " << type);
2746 return false;
2747 }
2748
2749 if (item->Open(*this, arg, delay, repeat, autoDelete)) {
2750 PTRACE(3, "VXML\tEnqueueing playable " << type << " with arg \"" << arg
2751 << "\" for playing " << repeat << " times, followed by " << delay << "ms silence");
2752 return QueuePlayable(item);
2753 }
2754
2755 delete item;
2756 return false;
2757 }
2758
2759
QueuePlayable(PVXMLPlayable * newItem)2760 PBoolean PVXMLChannel::QueuePlayable(PVXMLPlayable * newItem)
2761 {
2762 if (!IsOpen()) {
2763 delete newItem;
2764 return false;
2765 }
2766
2767 newItem->SetSampleFrequency(GetSampleFrequency());
2768 m_channelReadMutex.Wait();
2769 m_playQueue.Enqueue(newItem);
2770 m_channelReadMutex.Signal();
2771 return true;
2772 }
2773
2774
QueueResource(const PURL & url,PINDEX repeat,PINDEX delay)2775 PBoolean PVXMLChannel::QueueResource(const PURL & url, PINDEX repeat, PINDEX delay)
2776 {
2777 if (url.GetScheme() *= "file")
2778 return QueuePlayable("File", url.AsFilePath(), repeat, delay, false);
2779 else
2780 return QueuePlayable("URL", url.AsString(), repeat, delay);
2781 }
2782
2783
QueueData(const PBYTEArray & data,PINDEX repeat,PINDEX delay)2784 PBoolean PVXMLChannel::QueueData(const PBYTEArray & data, PINDEX repeat, PINDEX delay)
2785 {
2786 PTRACE(3, "VXML\tEnqueueing " << data.GetSize() << " bytes for playing, followed by " << delay << "ms silence");
2787 PVXMLPlayableData * item = PFactory<PVXMLPlayable>::CreateInstanceAs<PVXMLPlayableData>("PCM Data");
2788 if (item == NULL) {
2789 PTRACE(2, "VXML\tCannot find playable of type 'PCM Data'");
2790 delete item;
2791 return false;
2792 }
2793
2794 if (!item->Open(*this, "", delay, repeat, true)) {
2795 PTRACE(2, "VXML\tCannot open playable of type 'PCM Data'");
2796 delete item;
2797 return false;
2798 }
2799
2800 item->SetData(data);
2801
2802 return QueuePlayable(item);
2803 }
2804
2805
FlushQueue()2806 void PVXMLChannel::FlushQueue()
2807 {
2808 PTRACE(4, "VXML\tFlushing playable queue");
2809
2810 PWaitAndSignal mutex(m_channelReadMutex);
2811
2812 PVXMLPlayable * qItem;
2813 while ((qItem = m_playQueue.Dequeue()) != NULL) {
2814 qItem->OnStop();
2815 delete qItem;
2816 }
2817
2818 if (m_currentPlayItem != NULL) {
2819 m_currentPlayItem->OnStop();
2820 delete m_currentPlayItem;
2821 m_currentPlayItem = NULL;
2822 }
2823
2824 m_silenceTimer.Stop();
2825
2826 PTRACE(4, "VXML\tFlushed playable queue");
2827 }
2828
2829
2830 ///////////////////////////////////////////////////////////////
2831
2832 PFactory<PVXMLChannel>::Worker<PVXMLChannelPCM> pcmVXMLChannelFactory(VXML_PCM16);
2833
PVXMLChannelPCM()2834 PVXMLChannelPCM::PVXMLChannelPCM()
2835 : PVXMLChannel(10, 160)
2836 {
2837 mediaFormat = VXML_PCM16;
2838 wavFilePrefix = PString::Empty();
2839 }
2840
2841
WriteFrame(const void * buf,PINDEX len)2842 PBoolean PVXMLChannelPCM::WriteFrame(const void * buf, PINDEX len)
2843 {
2844 return PDelayChannel::Write(buf, len);
2845 }
2846
2847
ReadFrame(void * buffer,PINDEX amount)2848 PBoolean PVXMLChannelPCM::ReadFrame(void * buffer, PINDEX amount)
2849 {
2850 PINDEX len = 0;
2851 while (len < amount) {
2852 if (!PDelayChannel::Read(len + (char *)buffer, amount-len))
2853 return false;
2854 len += GetLastReadCount();
2855 }
2856
2857 return true;
2858 }
2859
2860
CreateSilenceFrame(void * buffer,PINDEX amount)2861 PINDEX PVXMLChannelPCM::CreateSilenceFrame(void * buffer, PINDEX amount)
2862 {
2863 memset(buffer, 0, amount);
2864 return amount;
2865 }
2866
2867
IsSilenceFrame(const void * buf,PINDEX len) const2868 PBoolean PVXMLChannelPCM::IsSilenceFrame(const void * buf, PINDEX len) const
2869 {
2870 // Calculate the average signal level of this frame
2871 int sum = 0;
2872
2873 const short * pcm = (const short *)buf;
2874 const short * end = pcm + len/2;
2875 while (pcm != end) {
2876 if (*pcm < 0)
2877 sum -= *pcm++;
2878 else
2879 sum += *pcm++;
2880 }
2881
2882 // calc average
2883 unsigned level = sum / (len / 2);
2884
2885 return level < 500; // arbitrary level
2886 }
2887
2888
2889 static short beepData[] = { 0, 18784, 30432, 30400, 18784, 0, -18784, -30432, -30400, -18784 };
2890
2891
GetBeepData(PBYTEArray & data,unsigned ms)2892 void PVXMLChannelPCM::GetBeepData(PBYTEArray & data, unsigned ms)
2893 {
2894 data.SetSize(0);
2895 while (data.GetSize() < (PINDEX)(ms * 16)) {
2896 PINDEX len = data.GetSize();
2897 data.SetSize(len + sizeof(beepData));
2898 memcpy(len + data.GetPointer(), beepData, sizeof(beepData));
2899 }
2900 }
2901
2902 ///////////////////////////////////////////////////////////////
2903
2904 PFactory<PVXMLChannel>::Worker<PVXMLChannelG7231> g7231VXMLChannelFactory(VXML_G7231);
2905
PVXMLChannelG7231()2906 PVXMLChannelG7231::PVXMLChannelG7231()
2907 : PVXMLChannel(30, 0)
2908 {
2909 mediaFormat = VXML_G7231;
2910 wavFilePrefix = "_g7231";
2911 }
2912
2913
2914 static const PINDEX g7231Lens[] = { 24, 20, 4, 1 };
2915
WriteFrame(const void * buffer,PINDEX actualLen)2916 PBoolean PVXMLChannelG7231::WriteFrame(const void * buffer, PINDEX actualLen)
2917 {
2918 PINDEX len = g7231Lens[(*(BYTE *)buffer)&3];
2919 if (len > actualLen)
2920 return false;
2921
2922 return PDelayChannel::Write(buffer, len);
2923 }
2924
2925
ReadFrame(void * buffer,PINDEX)2926 PBoolean PVXMLChannelG7231::ReadFrame(void * buffer, PINDEX /*amount*/)
2927 {
2928 if (!PDelayChannel::Read(buffer, 1))
2929 return false;
2930
2931 PINDEX len = g7231Lens[(*(BYTE *)buffer)&3];
2932 if (len != 1) {
2933 if (!PIndirectChannel::Read(1+(BYTE *)buffer, len-1))
2934 return false;
2935 lastReadCount++;
2936 }
2937
2938 return true;
2939 }
2940
2941
CreateSilenceFrame(void * buffer,PINDEX)2942 PINDEX PVXMLChannelG7231::CreateSilenceFrame(void * buffer, PINDEX /* len */)
2943 {
2944 ((BYTE *)buffer)[0] = 2;
2945 memset(((BYTE *)buffer)+1, 0, 3);
2946 return 4;
2947 }
2948
2949
IsSilenceFrame(const void * buf,PINDEX len) const2950 PBoolean PVXMLChannelG7231::IsSilenceFrame(const void * buf, PINDEX len) const
2951 {
2952 if (len == 4)
2953 return true;
2954 if (buf == NULL)
2955 return false;
2956 return ((*(const BYTE *)buf)&3) == 2;
2957 }
2958
2959 ///////////////////////////////////////////////////////////////
2960
2961 PFactory<PVXMLChannel>::Worker<PVXMLChannelG729> g729VXMLChannelFactory(VXML_G729);
2962
PVXMLChannelG729()2963 PVXMLChannelG729::PVXMLChannelG729()
2964 : PVXMLChannel(10, 0)
2965 {
2966 mediaFormat = VXML_G729;
2967 wavFilePrefix = "_g729";
2968 }
2969
2970
WriteFrame(const void * buf,PINDEX)2971 PBoolean PVXMLChannelG729::WriteFrame(const void * buf, PINDEX /*len*/)
2972 {
2973 return PDelayChannel::Write(buf, 10);
2974 }
2975
ReadFrame(void * buffer,PINDEX)2976 PBoolean PVXMLChannelG729::ReadFrame(void * buffer, PINDEX /*amount*/)
2977 {
2978 return PDelayChannel::Read(buffer, 10); // No silence frames so always 10 bytes
2979 }
2980
2981
CreateSilenceFrame(void * buffer,PINDEX)2982 PINDEX PVXMLChannelG729::CreateSilenceFrame(void * buffer, PINDEX /* len */)
2983 {
2984 memset(buffer, 0, 10);
2985 return 10;
2986 }
2987
2988
IsSilenceFrame(const void *,PINDEX) const2989 PBoolean PVXMLChannelG729::IsSilenceFrame(const void * /*buf*/, PINDEX /*len*/) const
2990 {
2991 return false;
2992 }
2993
2994
2995 //////////////////////////////////////////////////////////////////////////////////
2996
2997 class TextToSpeech_Sample : public PTextToSpeech
2998 {
2999 public:
3000 TextToSpeech_Sample();
3001 PStringArray GetVoiceList();
3002 PBoolean SetVoice(const PString & voice);
3003 PBoolean SetRate(unsigned rate);
3004 unsigned GetRate();
3005 PBoolean SetVolume(unsigned volume);
3006 unsigned GetVolume();
3007 PBoolean OpenFile (const PFilePath & fn);
3008 PBoolean OpenChannel(PChannel * chanel);
IsOpen()3009 PBoolean IsOpen() { return opened; }
3010 PBoolean Close();
3011 PBoolean Speak(const PString & text, TextType hint = Default);
3012 PBoolean SpeakNumber(unsigned number);
3013
3014 PBoolean SpeakFile(const PString & text);
3015
3016 protected:
3017 //PTextToSpeech * defaultEngine;
3018
3019 PMutex mutex;
3020 PBoolean opened;
3021 PBoolean usingFile;
3022 PString text;
3023 PFilePath path;
3024 unsigned volume, rate;
3025 PString voice;
3026
3027 std::vector<PFilePath> filenames;
3028 };
3029
3030
TextToSpeech_Sample()3031 TextToSpeech_Sample::TextToSpeech_Sample()
3032 {
3033 PWaitAndSignal m(mutex);
3034 usingFile = opened = false;
3035 rate = 8000;
3036 volume = 100;
3037 }
3038
3039
GetVoiceList()3040 PStringArray TextToSpeech_Sample::GetVoiceList()
3041 {
3042 PStringArray r;
3043 return r;
3044 }
3045
3046
SetVoice(const PString & v)3047 PBoolean TextToSpeech_Sample::SetVoice(const PString & v)
3048 {
3049 voice = v;
3050 return true;
3051 }
3052
3053
SetRate(unsigned v)3054 PBoolean TextToSpeech_Sample::SetRate(unsigned v)
3055 {
3056 rate = v;
3057 return true;
3058 }
3059
3060
GetRate()3061 unsigned TextToSpeech_Sample::GetRate()
3062 {
3063 return rate;
3064 }
3065
3066
SetVolume(unsigned v)3067 PBoolean TextToSpeech_Sample::SetVolume(unsigned v)
3068 {
3069 volume = v;
3070 return true;
3071 }
3072
3073
GetVolume()3074 unsigned TextToSpeech_Sample::GetVolume()
3075 {
3076 return volume;
3077 }
3078
3079
OpenFile(const PFilePath & fn)3080 PBoolean TextToSpeech_Sample::OpenFile(const PFilePath & fn)
3081 {
3082 PWaitAndSignal m(mutex);
3083
3084 Close();
3085 usingFile = true;
3086 path = fn;
3087 opened = true;
3088
3089 PTRACE(3, "TTS\tWriting speech to " << fn);
3090
3091 return true;
3092 }
3093
3094
OpenChannel(PChannel *)3095 PBoolean TextToSpeech_Sample::OpenChannel(PChannel * /*chanel*/)
3096 {
3097 PWaitAndSignal m(mutex);
3098
3099 Close();
3100 usingFile = false;
3101 opened = false;
3102
3103 return true;
3104 }
3105
3106
Close()3107 PBoolean TextToSpeech_Sample::Close()
3108 {
3109 PWaitAndSignal m(mutex);
3110
3111 if (!opened)
3112 return true;
3113
3114 PBoolean stat = true;
3115
3116 #if P_WAVFILE
3117 if (usingFile) {
3118 PWAVFile outputFile("PCM-16", path, PFile::WriteOnly);
3119 if (!outputFile.IsOpen()) {
3120 PTRACE(1, "TTS\tCannot create output file " << path);
3121 stat = false;
3122 }
3123 else {
3124 std::vector<PFilePath>::const_iterator r;
3125 for (r = filenames.begin(); r != filenames.end(); ++r) {
3126 PFilePath f = *r;
3127 PWAVFile file;
3128 file.SetAutoconvert();
3129 if (!file.Open(f, PFile::ReadOnly)) {
3130 PTRACE(1, "TTS\tCannot open input file " << f);
3131 stat = false;
3132 } else {
3133 PTRACE(1, "TTS\tReading from " << f);
3134 BYTE buffer[1024];
3135 for (;;) {
3136 if (!file.Read(buffer, 1024))
3137 break;
3138 outputFile.Write(buffer, file.GetLastReadCount());
3139 }
3140 }
3141 }
3142 }
3143 filenames.erase(filenames.begin(), filenames.end());
3144 }
3145 #endif // P_WAVFILE
3146
3147 opened = false;
3148 return stat;
3149 }
3150
3151
SpeakNumber(unsigned number)3152 PBoolean TextToSpeech_Sample::SpeakNumber(unsigned number)
3153 {
3154 return Speak(PString(PString::Signed, number), Number);
3155 }
3156
3157
Speak(const PString & text,TextType hint)3158 PBoolean TextToSpeech_Sample::Speak(const PString & text, TextType hint)
3159 {
3160 // break into lines
3161 PStringArray lines = text.Lines();
3162 PINDEX i;
3163 for (i = 0; i < lines.GetSize(); ++i) {
3164
3165 PString line = lines[i].Trim();
3166 if (line.IsEmpty())
3167 continue;
3168
3169 PTRACE(3, "TTS\tAsked to speak " << text << " with type " << hint);
3170
3171 if (hint == DateAndTime) {
3172 PTRACE(3, "TTS\tSpeaking date and time");
3173 Speak(text, Date);
3174 Speak(text, Time);
3175 continue;
3176 }
3177
3178 if (hint == Date) {
3179 PTime time(line);
3180 if (time.IsValid()) {
3181 PTRACE(4, "TTS\tSpeaking date " << time);
3182 SpeakFile(time.GetDayName(time.GetDayOfWeek(), PTime::FullName));
3183 SpeakNumber(time.GetDay());
3184 SpeakFile(time.GetMonthName(time.GetMonth(), PTime::FullName));
3185 SpeakNumber(time.GetYear());
3186 }
3187 continue;
3188 }
3189
3190 if (hint == Time) {
3191 PTime time(line);
3192 if (time.IsValid()) {
3193 PTRACE(4, "TTS\tSpeaking time " << time);
3194 int hour = time.GetHour();
3195 if (hour < 13) {
3196 SpeakNumber(hour);
3197 SpeakNumber(time.GetMinute());
3198 SpeakFile(PTime::GetTimeAM());
3199 }
3200 else {
3201 SpeakNumber(hour-12);
3202 SpeakNumber(time.GetMinute());
3203 SpeakFile(PTime::GetTimePM());
3204 }
3205 }
3206 continue;
3207 }
3208
3209 if (hint == Default) {
3210 PBoolean isTime = false;
3211 PBoolean isDate = false;
3212
3213 for (i = 0; !isDate && i < 7; ++i) {
3214 isDate = isDate || (line.Find(PTime::GetDayName((PTime::Weekdays)i, PTime::FullName)) != P_MAX_INDEX);
3215 isDate = isDate || (line.Find(PTime::GetDayName((PTime::Weekdays)i, PTime::Abbreviated)) != P_MAX_INDEX);
3216 PTRACE(4, "TTS\t " << isDate << " - " << PTime::GetDayName((PTime::Weekdays)i, PTime::FullName) << "," << PTime::GetDayName((PTime::Weekdays)i, PTime::Abbreviated));
3217 }
3218 for (i = 1; !isDate && i <= 12; ++i) {
3219 isDate = isDate || (line.Find(PTime::GetMonthName((PTime::Months)i, PTime::FullName)) != P_MAX_INDEX);
3220 isDate = isDate || (line.Find(PTime::GetMonthName((PTime::Months)i, PTime::Abbreviated)) != P_MAX_INDEX);
3221 PTRACE(4, "TTS\t " << isDate << " - " << PTime::GetMonthName((PTime::Months)i, PTime::FullName) << "," << PTime::GetMonthName((PTime::Months)i, PTime::Abbreviated));
3222 }
3223
3224 if (!isTime)
3225 isTime = line.Find(PTime::GetTimeSeparator()) != P_MAX_INDEX;
3226 if (!isDate)
3227 isDate = line.Find(PTime::GetDateSeparator()) != P_MAX_INDEX;
3228
3229 if (isDate && isTime) {
3230 PTRACE(4, "TTS\tDefault changed to DateAndTime");
3231 Speak(line, DateAndTime);
3232 continue;
3233 }
3234 if (isDate) {
3235 PTRACE(4, "TTS\tDefault changed to Date");
3236 Speak(line, Date);
3237 continue;
3238 }
3239 else if (isTime) {
3240 PTRACE(4, "TTS\tDefault changed to Time");
3241 Speak(line, Time);
3242 continue;
3243 }
3244 }
3245
3246 PStringArray tokens = line.Tokenise("\t ", false);
3247 for (PINDEX j = 0; j < tokens.GetSize(); ++j) {
3248 PString word = tokens[j].Trim();
3249 if (word.IsEmpty())
3250 continue;
3251 PTRACE(4, "TTS\tSpeaking word " << word << " as " << hint);
3252 switch (hint) {
3253
3254 case Time:
3255 case Date:
3256 case DateAndTime:
3257 PAssertAlways("Logic error");
3258 break;
3259
3260 default:
3261 case Default:
3262 case Literal:
3263 {
3264 PBoolean isDigits = true;
3265 PBoolean isIpAddress = true;
3266
3267 PINDEX k;
3268 for (k = 0; k < word.GetLength(); ++k) {
3269 if (word[k] == '.')
3270 isDigits = false;
3271 else if (!isdigit(word[k]))
3272 isDigits = isIpAddress = false;
3273 }
3274
3275 if (isIpAddress) {
3276 PTRACE(4, "TTS\tDefault changed to IPAddress");
3277 Speak(word, IPAddress);
3278 } else if (isDigits) {
3279 PTRACE(4, "TTS\tDefault changed to Number");
3280 Speak(word, Number);
3281 } else {
3282 PTRACE(4, "TTS\tDefault changed to Spell");
3283 Speak(word, Spell);
3284 }
3285 }
3286 break;
3287
3288 case Spell:
3289 PTRACE(4, "TTS\tSpelling " << text);
3290 for (PINDEX i = 0; i < text.GetLength(); ++i)
3291 SpeakFile(PString(text[i]));
3292 break;
3293
3294 case Phone:
3295 case Digits:
3296 PTRACE(4, "TTS\tSpeaking digits " << text);
3297 for (PINDEX i = 0; i < text.GetLength(); ++i) {
3298 if (isdigit(text[i]))
3299 SpeakFile(PString(text[i]));
3300 }
3301 break;
3302
3303 case Duration:
3304 case Currency:
3305 case Number:
3306 {
3307 int number = atoi(line);
3308 PTRACE(4, "TTS\tSpeaking number " << number);
3309 if (number < 0) {
3310 SpeakFile("negative");
3311 number = -number;
3312 }
3313 else if (number == 0) {
3314 SpeakFile("0");
3315 }
3316 else {
3317 if (number >= 1000000) {
3318 int millions = number / 1000000;
3319 number = number % 1000000;
3320 SpeakNumber(millions);
3321 SpeakFile("million");
3322 }
3323 if (number >= 1000) {
3324 int thousands = number / 1000;
3325 number = number % 1000;
3326 SpeakNumber(thousands);
3327 SpeakFile("thousand");
3328 }
3329 if (number >= 100) {
3330 int hundreds = number / 100;
3331 number = number % 100;
3332 SpeakNumber(hundreds);
3333 SpeakFile("hundred");
3334 }
3335 if (!SpeakFile(PString(PString::Signed, number))) {
3336 int tens = number / 10;
3337 number = number % 10;
3338 if (tens > 0)
3339 SpeakFile(PString(PString::Signed, tens*10));
3340 if (number > 0)
3341 SpeakFile(PString(PString::Signed, number));
3342 }
3343 }
3344 }
3345 break;
3346
3347 case IPAddress:
3348 {
3349 PIPSocket::Address addr(line);
3350 PTRACE(4, "TTS\tSpeaking IP address " << addr);
3351 for (PINDEX i = 0; i < 4; ++i) {
3352 int octet = addr[i];
3353 if (octet < 100)
3354 SpeakNumber(octet);
3355 else
3356 Speak(octet, Digits);
3357 if (i != 3)
3358 SpeakFile("dot");
3359 }
3360 }
3361 break;
3362 }
3363 }
3364 }
3365
3366 return true;
3367 }
3368
3369
SpeakFile(const PString & text)3370 PBoolean TextToSpeech_Sample::SpeakFile(const PString & text)
3371 {
3372 PFilePath f = PDirectory(voice) + (text.ToLower() + ".wav");
3373 if (!PFile::Exists(f)) {
3374 PTRACE(2, "TTS\tUnable to find explicit file for " << text);
3375 return false;
3376 }
3377 filenames.push_back(f);
3378 return true;
3379 }
3380
3381 PFactory<PTextToSpeech>::Worker<TextToSpeech_Sample> sampleTTSFactory("sampler", false);
3382
3383
3384 #endif // P_VXML
3385
3386
3387 ///////////////////////////////////////////////////////////////
3388