📄 vxml.cxx
字号:
}
if (strDest.IsEmpty()) {
PTime now;
strDest = GetVar("session.telephone.dnis" ) + "_" + GetVar( "session.telephone.ani" ) + "_" + now.AsString( "yyyyMMdd_hhmmss") + ".wav";
}
// For some reason, if the file is there the create
// seems to fail.
PFile::Remove(strDest);
PFilePath file(strDest);
// Get max record time (maxtime)
PTimeInterval maxTime = PMaxTimeInterval;
if (element->HasAttribute("maxtime"))
maxTime = StringToTime(element->GetAttribute("maxtime"));
// Get terminating silence duration (finalsilence)
PTimeInterval termTime(3000);
if (element->HasAttribute("finalsilence"))
termTime = StringToTime(element->GetAttribute("finalsilence"));
// Get dtmf term (dtmfterm)
BOOL dtmfTerm = TRUE;
if (element->HasAttribute("dtmfterm"))
dtmfTerm = !(element->GetAttribute("dtmfterm").ToLower() *= "false");
// create a semaphore, and then wait for the recording to terminate
StartRecording(file, dtmfTerm, maxTime, termTime);
recordSync.Wait(maxTime);
if (!recordSync.Wait(maxTime)) {
// The Wait() has timed out, to signal that the record timed out.
// This is VXML version 2 property, but nice.
// So it's possible to detect if the record timed out from within the
// VXML script
SetVar(strName + "$.maxtime", "true");
}
else {
// Normal hangup before timeout
SetVar( strName + "$.maxtime", "false");
}
// when this returns, we are done
EndRecording();
}
return TRUE;
}
PString PVXMLSession::GetXMLError() const
{
return psprintf("(%i:%i) ", xmlFile.GetErrorLine(), xmlFile.GetErrorColumn()) + xmlFile.GetErrorString();
}
PString PVXMLSession::EvaluateExpr(const PString & oexpr)
{
PString expr = oexpr.Trim();
// see if all digits
PINDEX i;
BOOL allDigits = TRUE;
for (i = 0; i < expr.GetLength(); i++) {
allDigits = allDigits && isdigit(expr[i]);
}
if (allDigits)
return expr;
return GetVar(expr);
}
PString PVXMLSession::GetVar(const PString & ostr) const
{
PString str = ostr;
PString scope;
// get scope
PINDEX pos = str.Find('.');
if (pos != P_MAX_INDEX) {
scope = str.Left(pos);
str = str.Mid(pos+1);
}
// process session scope
if (scope.IsEmpty() || (scope *= "session")) {
if (sessionVars.Contains(str))
return sessionVars(str);
}
// assume any other scope is actually document or application
return documentVars(str);
}
void PVXMLSession::SetVar(const PString & ostr, const PString & val)
{
PString str = ostr;
PString scope;
// get scope
PINDEX pos = str.Find('.');
if (pos != P_MAX_INDEX) {
scope = str.Left(pos);
str = str.Mid(pos+1);
}
// do session scope
if (scope.IsEmpty() || (scope *= "session")) {
sessionVars.SetAt(str, val);
return;
}
PTRACE(3, "PVXML\tDocument: " << str << " = \"" << val << "\"");
// assume any other scope is actually document or application
documentVars.SetAt(str, val);
}
BOOL PVXMLSession::PlayFile( const PString & id, const PString & fn, PINDEX repeat, PINDEX delay, BOOL autoDelete)
{
if (vxmlChannel == NULL || !vxmlChannel->QueueFile(id, fn, repeat, delay, autoDelete))
return FALSE;
AllowClearCall();
return TRUE;
}
BOOL PVXMLSession::PlayCommand(const PString & cmd, PINDEX repeat, PINDEX delay)
{
if (vxmlChannel == NULL || !vxmlChannel->QueueCommand(cmd, repeat, delay))
return FALSE;
AllowClearCall();
return TRUE;
}
BOOL PVXMLSession::PlayData(const PString & id, const PBYTEArray & data, PINDEX repeat, PINDEX delay)
{
if (vxmlChannel == NULL || !vxmlChannel->QueueData( id, data, repeat, delay))
return FALSE;
AllowClearCall();
return TRUE;
}
void PVXMLSession::GetBeepData(PBYTEArray & data, unsigned ms)
{
if (vxmlChannel != NULL)
vxmlChannel->GetBeepData(data, ms);
}
BOOL PVXMLSession::PlaySilence(const PTimeInterval & timeout)
{
return PlaySilence((PINDEX)timeout.GetMilliSeconds());
}
BOOL PVXMLSession::PlaySilence(PINDEX msecs)
{
PBYTEArray nothing;
if (vxmlChannel == NULL || !vxmlChannel->QueueData( "silence", nothing, 1, msecs))
return FALSE;
AllowClearCall();
return TRUE;
}
BOOL PVXMLSession::PlayResource( const PString & id, const PURL & url, PINDEX repeat, PINDEX delay)
{
if (vxmlChannel == NULL || !vxmlChannel->QueueResource(id, url, repeat, delay))
return FALSE;
AllowClearCall();
return TRUE;
}
BOOL PVXMLSession::LoadGrammar(PVXMLGrammar * grammar)
{
if (activeGrammar != NULL) {
delete activeGrammar;
activeGrammar = FALSE;
}
activeGrammar = grammar;
return TRUE;
}
BOOL PVXMLSession::PlayText(const PString & _text,
PTextToSpeech::TextType type,
PINDEX repeat,
PINDEX delay)
{
PStringArray list;
BOOL useCache = !(GetVar("caching") *= "safe");
if (!ConvertTextToFilenameList(_text, type, list, useCache) || (list.GetSize() == 0)) {
PTRACE(1, "PVXML\tCannot convert text to speech");
return FALSE;
}
PVXMLPlayableFilenameList * playable = new PVXMLPlayableFilenameList;
if (!playable->Open(*vxmlChannel, list, delay, repeat, !useCache)) {
delete playable;
PTRACE(1, "PVXML\tCannot create playable for filename list");
return FALSE;
}
return vxmlChannel->QueuePlayable(playable);
}
BOOL PVXMLSession::ConvertTextToFilenameList(const PString & _text, PTextToSpeech::TextType type, PStringArray & filenameList, BOOL useCache)
{
PString prefix = psprintf("tts%i", type);
PStringArray lines = _text.Trim().Lines();
for (PINDEX i = 0; i < lines.GetSize(); i++) {
PString text = lines[i].Trim();
if (text.IsEmpty())
continue;
BOOL spoken = FALSE;
PFilePath dataFn;
// see if we have converted this text before
PString contentType;
if (useCache)
spoken = PVXMLCache::GetResourceCache().Get(prefix, text, "wav", contentType, dataFn);
// if not cached, then use the text to speech converter
if (!spoken) {
PFilePath tmpfname;
if (textToSpeech != NULL) {
tmpfname = PVXMLCache::GetResourceCache().GetRandomFilename("tts", "wav");
if (!textToSpeech->OpenFile(tmpfname)) {
PTRACE(2, "PVXML\tcannot open file " << tmpfname);
} else {
spoken = textToSpeech->Speak(text, type);
if (!textToSpeech->Close()) {
PTRACE(2, "PVXML\tcannot close TTS engine");
}
}
textToSpeech->Close();
if (useCache)
PVXMLCache::GetResourceCache().Put(prefix, text, "wav", contentType, tmpfname, dataFn);
else
dataFn = tmpfname;
}
}
if (!spoken) {
PTRACE(2, "PVXML\tcannot speak text using TTS engine");
} else
filenameList.AppendString(dataFn);
}
return filenameList.GetSize() > 0;
}
void PVXMLSession::SetPause(BOOL _pause)
{
if (vxmlChannel != NULL)
vxmlChannel->SetPause(_pause);
}
BOOL PVXMLSession::IsPlaying() const
{
return (vxmlChannel != NULL) && vxmlChannel->IsPlaying();
}
BOOL PVXMLSession::StartRecording(const PFilePath & _recordFn,
BOOL _recordDTMFTerm,
const PTimeInterval & _recordMaxTime,
const PTimeInterval & _recordFinalSilence)
{
recording = TRUE;
recordFn = _recordFn;
recordDTMFTerm = _recordDTMFTerm;
recordMaxTime = _recordMaxTime;
recordFinalSilence = _recordFinalSilence;
if (vxmlChannel != NULL) {
PXMLElement* element = (PXMLElement*) currentNode;
if ( element->HasAttribute("name")) {
PString chanName = element->GetAttribute("name");
vxmlChannel->SetName(chanName);
}
return vxmlChannel->StartRecording(recordFn, (unsigned )recordFinalSilence.GetMilliSeconds());
}
return FALSE;
}
void PVXMLSession::RecordEnd()
{
if (recording)
recordSync.Signal();
}
BOOL PVXMLSession::EndRecording()
{
if (recording) {
recording = FALSE;
if (vxmlChannel != NULL)
return vxmlChannel->EndRecording();
}
return FALSE;
}
BOOL PVXMLSession::IsRecording() const
{
return (vxmlChannel != NULL) && vxmlChannel->IsRecording();
}
PWAVFile * PVXMLSession::CreateWAVFile(const PFilePath & fn, PFile::OpenMode mode, int opts, unsigned fmt)
{
if (!fn.IsEmpty())
return new PWAVFile(fn, mode, opts, fmt);
return new PWAVFile(mode, opts, fmt);
}
void PVXMLSession::AllowClearCall()
{
allowFinish = TRUE;
}
BOOL PVXMLSession::TraverseAudio()
{
if (!currentNode->IsElement()) {
PlayText(((PXMLData *)currentNode)->GetString());
}
else {
PXMLElement * element = (PXMLElement *)currentNode;
if (element->GetName() *= "value") {
PString className = element->GetAttribute("class");
PString value = EvaluateExpr(element->GetAttribute("expr"));
SayAs(className, value);
}
else if (element->GetName() *= "sayas") {
PString className = element->GetAttribute("class");
PXMLObject * object = element->GetElement();
if (!object->IsElement()) {
PString text = ((PXMLData *)object)->GetString();
SayAs(className, text);
}
}
else if (element->GetName() *= "break") {
// msecs is VXML 1.0
if (element->HasAttribute("msecs"))
PlaySilence(element->GetAttribute("msecs").AsInteger());
// time is VXML 2.0
else if (element->HasAttribute("time")) {
PTimeInterval time = StringToTime(element->GetAttribute("time"));
PlaySilence(time);
}
else if (element->HasAttribute("size")) {
PString size = element->GetAttribute("size");
if (size *= "none")
;
else if (size *= "small")
PlaySilence(SMALL_BREAK_MSECS);
else if (size *= "large")
PlaySilence(LARGE_BREAK_MSECS);
else
PlaySilence(MEDIUM_BREAK_MSECS);
}
// default to medium pause
else {
PlaySilence(MEDIUM_BREAK_MSECS);
}
}
else if (element->GetName() *= "audio") {
BOOL loaded = FALSE;
if (element->HasAttribute("src")) {
PString str = element->GetAttribute("src").Trim();
if (!str.IsEmpty() && (str[0] == '|')) {
loaded = TRUE;
PlayCommand(str.Mid(1));
}
else {
// get a normalised name for the resource
PFilePath fn;
PURL url = NormaliseResourceName(str);
// load the resource from the cache
PString contentType;
BOOL useCache = !(GetVar("caching") *= "safe") && !(element->GetAttribute("caching") *= "safe");
if (RetreiveResource(url, contentType, fn, useCache)) {
PWAVFile * wavFile = vxmlChannel->CreateWAVFile(fn);
if (wavFile == NULL)
PTRACE(3, "PVXML\tCannot create audio file " + fn);
else if (!wavFile->IsOpen())
delete wavFile;
else {
loaded = TRUE;
PlayFile(fn, 0, 0, !useCache); // make sure we delete the file if not cacheing
}
}
}
if (loaded) {
// skip to the next node
if (element->HasSubObjects())
currentNode = element->GetElement(element->GetSize() - 1);
}
}
}
else
PTRACE(3, "PVXML\tUnknown audio tag " << element->GetName() << " encountered");
}
return TRUE;
}
BOOL PVXMLSession::TraverseGoto() // <goto>
{
PAssert(currentNode != NULL, "ProcessGotoElement(): Expected valid node");
if (currentNode == NULL)
return FALSE;
// LATER: handle expr, expritem, fetchaudio, fetchhint, fetchtimeout, maxage, maxstale
PAssert(currentNode->IsElement(), "ProcessGotoElement(): Expected element");
// nextitem
PString nextitem = ((PXMLElement*)currentNode)->GetAttribute("nextitem");
if (!nextitem.IsEmpty()) {
// LATER: Take out the optional #
currentForm = FindForm(nextitem);
currentNode = currentForm;
if (currentForm == NULL) {
// LATER: throw "error.semantic" or "error.badfetch" -- lookup which
return FALSE;
}
return TRUE;
}
// next
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -