基于jssip封装的软电话功能模块SoftPhoneForWebrtc.ts

SoftPhoneForWebrtc.ts  :
 
import JsSIP from 'jssip'
// 自定义数据进行通信的socket事件
function selfSocketEvent(data) {
  if (window.$ak.socketMyself) {
    window.$ak.socketMyself.emit('selfEvent', {
      externalNo: `webrtc-${localStorage.getItem('clientPhone')}`, //客户号
      internalNo: localStorage.getItem('seatCode'), //坐席号
      msgContent: data,
    })
  }
}
let vm = null
// JsSIP.debug.enable('JsSIP:Transport JsSIP:RTCSession*')
import eventBus from '@/util/Bus'
const sendThis = (_this) => {
  vm = _this
}

function SoftPhone() {
  this.version = '0.1'
  this.session = null
  this.ua = null
  this.logEnable = true
  this.URI = null
  this.SIP_SCHEME = 'sip'
  this.localStream = null
  this.remoteMedia = null
  this.localMedia = null
  this._contactUri = ''
  this.socket = null
  this.messager = null
  this.flowList = []
  this._init()
}
export { sendThis, SoftPhone }

SoftPhone.prototype = {
  _init() {
    this._initSoftPhoneObject()
  },
  _initSoftPhoneObject() {
    //JSSIP参数修改,FreeSWITCH要求session_expires会话间隔大于120s
    // Session-Expires这个字段的值必须要大于Min-SE,否则就认为会话结束。
    // 所以你最初应该将Session-Expires设置一个比较大的值,或者就直接不用Session-Expires字段
    JsSIP.C.SESSION_EXPIRES = 3600
    JsSIP.C.MIN_SESSION_EXPIRES = 3600
  },
  _bindUAEvent(ua) {
    ua.on('registered', (data) => this._UAEventHandler('registered', data))
    ua.on('unregistered', (data) => this._UAEventHandler('unregistered', data))
    ua.on('registrationFailed', (data) =>
      this._UAEventHandler('registrationFailed', data)
    )
    ua.on('registrationExpiring', () => this._registrationExpiring())
    ua.on('connecting', (data) => this._UAEventHandler('connecting', data))
    ua.on('connected', (data) => this._UAEventHandler('connected', data))
    ua.on('disconnected', (data) => this._UAEventHandler('disconnected', data))
    ua.on('newRTCSession', (data) => this._newRTCSessionHandler(data))
    ua.on('newMessage', (data) => this._UAEventHandler('newMessage', data))
    ua.on('sipEvent', (data) => this._UAEventHandler('sipEvent', data))
    ua.on('newInfo', (data) => this._UAEventHandler('newInfo', data))
  },
  _UAEventHandler(status, data) {
    if (this.logEnable && console && typeof console.log == 'function') {
      console.log('%c=======' + status + '=======', 'color:blue')
    }
    if (status == 'connected') {
      console.log('软电话已经连接')
      this.session = data.session
    }
    if (status == 'newInfo') {
      console.log('来新消息了')
    }
    if (status == 'sipEvent') {
      console.log('触发了sipEvent事件')
    }
  },
  _newRTCSessionHandler(data) {
    this.session = data.session
    this._bindRTCSessionEvent(this.session)
  },
  _registrationExpiring() {
    console.log('注册超时,重新再注册一次')
    this.ua.register()
  },
  _bindRTCSessionEvent(session) {
    ;[
      'newInfo',
      'peerconnection',
      'connecting',
      'sending',
      'progress',
      'accepted',
      'confirmed',
      'ended', //已建立的通话结束时触发。
      'failed',
      'newDTMF',
      'hold',
      'unhold',
      'muted',
      'unmuted',
      'reinvite',
      'update',
      'refer',
      'replaces',
      'sdp',
      'getusermediafailed',
      'peerconnection:createofferfailed',
      'peerconnection:createanswerfailed',
      'peerconnection:setlocaldescriptionfailed',
      'peerconnection:setremotedescriptionfailed',
    ].forEach((key) =>
      session.on(key, (data) => {
        this._RTCSessionEventHandler(key, data)
      })
    )
  },
  _RTCSessionEventHandler(status, data) {
    if (this.logEnable && console && typeof console.log == 'function') {
      console.log('%c*******' + status + '*******', 'color:green')
      this.flowList.push(status)
    }
    switch (status) {
      case 'peerconnection:createanswerfailed':
        console.log('peerconnection:createanswerfailed')
        break
      case 'peerconnection':
        this._peerconnectionEventHandler(data)
        break
      case 'accepted':
        this._acceptedEventHandler(data)
        break
      case 'progress':
        // 客户端的呼叫只有走到了progress阶段,坐席侧的振铃才应该被弹出来
        selfSocketEvent('progress')
        console.log('========progress=======')
        break
      case 'failed':
        vm.onCallFail()
        selfSocketEvent('')
        this.remoteMedia.pause()
        this.localMedia.pause()
        break
      case 'confirmed':
        // 设置坐席状态为通话中
        console.log('%c*******' + '通话中' + '*******', 'color:green')
        vm.isTaking = true
        eventBus.$emit('_isTaking', true)
        break
      case 'ended':
        // 设置坐席状态为未在通话中
        vm.isTaking = false
        eventBus.$emit('_isTaking', false)
        this._endedEventHandler(data)
        break
      case 'muted':
        eventBus.$emit('_muteSucess')
        break
      case 'unmuted':
        eventBus.$emit('_unmutedSucess')
        break
    }
  },
  // 已建立的通话结束时触发。
  _endedEventHandler(data) {
    console.log('已挂机')
    vm.callEnded()
  },
  _peerconnectionEventHandler(data) {},
  _acceptedEventHandler() {
    var self = this
    this.remoteMedia.srcObject = this.session.connection.getRemoteStreams()[0]
    this.localMedia.srcObject = this.session.connection.getRemoteStreams()[0]
    let constraints = {
      audio: true,
      video: false,
    }
    navigator.mediaDevices
      .getUserMedia(constraints)
      .then(function(stream) {
        self.getLocalMedia(stream)
      })
      .catch(function(err) {
        console.log(err)
      })
  },
  _createMediaDOM(id, dom) {
    var div = document.createElement('div')
    div.style.width = '0'
    div.style.height = '0'
    var audio = document.createElement('AUDIO')
    audio.id = id
    audio.setAttribute('autoplay', 'autoplay')
    div.appendChild(audio)
    document.body.appendChild(div)
    this[dom] = audio
  },
  setMediaObj(remote, local) {
    remote && (this.remoteMedia = remote)
    local && (this.localMedia = local)
    !this.remoteMedia && this._createMediaDOM('_remoteMedia', 'remoteMedia')
    !this.localMedia && this._createMediaDOM('_localMedia', 'localMedia')
  },
  getLocalMedia(stream) {
    this.session.connection.addStream(stream)
  },
  toSipURI(number) {
    return new JsSIP.URI(this.SIP_SCHEME, number, this.URI.host).toString()
  },
  toContactURI(number) {
    return new JsSIP.URI(this.SIP_SCHEME, number, this.URI.host, null, {
      transport: 'ws',
    }).toString()
  },
  captureLocalMedia() {
    let constraints = {
      audio: true,
      video: false,
    }
    navigator.mediaDevices
      .getUserMedia(constraints)
      .then((stream) => console.log(stream))
      .catch((err) => console.log(err))
  },
  /**
   * 注册
   * @param sipUser 用户名
   * @param sipPwd 密码
   * @param sipServer 服务地址
   */
  register(sipUser, sipPwd, sipServer) {
    return new Promise((resolve, reject) => {
      this.setMediaObj()
      if (sipUser == '' || sipPwd == '' || sipServer == '') {
        return
      }
      this.URI = JsSIP.Grammar.parse(sipServer, 'Request_URI')
      this._contactUri = this.toContactURI(sipUser)
      var _sipUser = this.toSipURI(sipUser)
      var socket = new JsSIP.WebSocketInterface(sipServer)
      this.socket = socket
      var configuration = {
        sockets: [socket],
        uri: _sipUser,
        password: sipPwd,
        register: false,
        contact_uri: this._contactUri,
        register_expires: 3600, //注册有效时间
        connection_recovery_max_interval: 3600 * 24 * 1000,
        connection_recovery_min_interval: 3600 * 24 * 1000,
        no_answer_timeout: 120,
      }
      this.ua = new JsSIP.UA(configuration)
      // console.log(this.ua._configuration, 'ua配置')
      this._bindUAEvent(this.ua)
      this.ua.start()
      this.ua.register()
      this.ua.on('registered', (data) => resolve(data))
      this.ua.on('registrationFailed', (data) => reject(data))
    })
  },
  /**
   * 注销
   */
  unregister() {
    if (this.ua) {
      this.ua.unregister()
      this.ua.stop()
    }
    return true
  },
  /**
   * 外呼
   * @param dtmfNumber 被叫号码
   */
  placeCall(seatNumber) {
    this.flowList = []
    var sipNumber = this.toSipURI(seatNumber)
    var eventHandlers = {
      confirmed: function(data) {
        console.log('电话接通confirmed')
        vm.onCallReceive()
      },
      ended: function(data) {
        console.log('通话结束ended')
      },
    }
    var options = {
      eventHandlers: eventHandlers,
      mediaConstraints: { audio: true, video: false },
    }
    this.session = this.ua.call(sipNumber, options)
  },
  /**
   * 接听
   */
  answer() {
    this.remoteMedia.loop = true
    this.localMedia.loop = true
    this.remoteMedia.volume = 1
    this.localMedia.volume = 1
    this.remoteMedia.play()
    this.localMedia.play()
    var options = {
      mediaConstraints: { audio: true, video: false },
    }
    this.session.answer(options)
  },
  /**
   * 挂机
   */
  reject() {
    if (this.session) {
      this.session.terminate()
    }
  },
  /**
   * 保持
   */
  hold() {
    this.session.hold()
  },
  /**
   * 拾回
   */
  retrieveHold() {
    this.session.unhold()
  },
  /**
   * 静音
   */
  mute(e) {
    this.session.mute()
    if (e === '坐席') {
      this.remoteMedia.pause()
    }
    if (e === '客户') {
      this.localMedia.pause()
    }
  },
  /**
   * 取消静音
   */
  unmute(e) {
    this.session.unmute()
    if (e === '坐席') {
      this.remoteMedia.play()
    }
    if (e === '客户') {
      this.localMedia.play()
    }
  },
  // 清空flowList
  clearFlow() {
    this.flowList = []
  },
  /**
   * 二次拨号
   * @param dtmfNumber 二次拨号号码
   */
  dtmf(dtmfNumber) {
    console.log(dtmfNumber)
    this.session.sendDTMF(dtmfNumber)
  },
  /**
   * 设置在线
   */
  setReady() {
    if (this.ua && !this.ua.isRegistered()) {
      this.ua.register()
    }
  },
  /**
   * 设置离线
   */
  setNoDisturb() {
    if (this.ua && this.ua.isRegistered()) {
      this.ua.unregister()
    }
  },
  /**
   * 电话转接
   */
  transfer(number) {
    var sipNumber = this.toSipURI(number)
    this.session.refer(sipNumber)
  },
}
posted @ 2023-01-30 17:37  菌子乐水  阅读(1041)  评论(0编辑  收藏  举报