You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用ESL从FreeSWITCH获取通话时长失败,求可行实现方案

解决FreeSWITCH ESL获取通话时长的实现方案

问题根因

你当前只监听CHANNEL_EXECUTE_COMPLETE事件的逻辑存在偏差:这个事件仅在originate API执行完成时触发,此时呼叫还处于发起/振铃阶段,尚未接通也未结束,自然不会携带variable_answer_epoch、variable_end_epoch这类运行时变量。

实现方案

1. 调整ESL事件订阅范围

你需要额外订阅两类核心事件:

  • CHANNEL_ANSWER:呼叫接通时触发,会携带variable_answer_epoch变量
  • CHANNEL_HANGUP_COMPLETE:呼叫完全挂断后触发,会携带variable_end_epoch、以及FreeSWITCH已自动计算好的时长变量variable_billsec(计费通话时长,单位:秒,即从接通到挂断的有效通话时长)、variable_duration(呼叫总时长,从发起创建到挂断的总时长,单位:秒)
    订阅示例代码:
// ESL连接建立后订阅所需事件
connection.subscribe(['CHANNEL_EXECUTE_COMPLETE', 'CHANNEL_ANSWER', 'CHANNEL_HANGUP_COMPLETE'])

2. 基于呼叫UUID关联事件

你调用originate接口返回的callUid就是A腿(主叫侧)的唯一通道UUID,后续所有该呼叫相关事件都会携带对应UUID作为Channel-Unique-ID头部,你可以用这个ID作为键存储呼叫中间状态:

// 全局存储呼叫状态,生产环境可替换为Redis/数据库
const callMap = new Map()

connection.on('esl::event', (event) => {
  const eventName = event.getHeader('Event-Name')
  const channelUuid = event.getHeader('Channel-Unique-ID')
  
  // 处理originate返回后的状态初始化
  if (eventName === 'CHANNEL_EXECUTE_COMPLETE' && event.getHeader('Application') === 'originate') {
    callMap.set(callUid, {
      status: 'initiated'
    })
    return
  }

  // 处理呼叫接通
  if (eventName === 'CHANNEL_ANSWER' && callMap.has(channelUuid)) {
    const callInfo = callMap.get(channelUuid)
    callInfo.answerEpoch = event.getHeader('variable_answer_epoch')
    callInfo.status = 'answered'
    callMap.set(channelUuid, callInfo)
    return
  }

  // 处理呼叫挂断,计算时长
  if (eventName === 'CHANNEL_HANGUP_COMPLETE' && callMap.has(channelUuid)) {
    const callInfo = callMap.get(channelUuid)
    // 方案1:直接取FreeSWITCH已计算好的计费时长,单位秒
    const billSec = event.getHeader('variable_billsec')
    console.log('有效通话时长(秒):', billSec)
    
    // 方案2:用你原有逻辑自行计算
    const endEpoch = event.getHeader('variable_end_epoch')
    const duration = calculateDuration(callInfo.answerEpoch, endEpoch)
    console.log('自行计算的通话时长(毫秒):', duration)
    
    // 清理已结束的呼叫状态
    callMap.delete(channelUuid)
    return
  }
})

3. 多腿呼叫关联优化(可选)

由于你是桥接两个外线号码,存在A、B两条呼叫腿,可在originate时添加自定义变量关联两条腿,避免事件匹配混乱:

// originate时添加自定义唯一标识
const customCallId = Date.now().toString()
let arg1 = `{ignore_early_media=true,origination_caller_id_number=${callerId},x_custom_call_id=${customCallId}}${app_args}`;

后续A、B腿的所有事件都会携带variable_x_custom_call_id头部,可通过该字段将两次通道的事件匹配到同一次点击呼叫任务中。


内容的提问来源于stack exchange,提问作者venalyn sudaria

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 07:30:01