0


web 语音通话 jssip

先把封装好的地址安上(非本人封装):webrtc-webphone: 基于JsSIP开发的webrtc软电话

jssip中文文档:jssip中文开发文档(完整版) - 简书

jssip使用文档:(我没有运行过,但是他写的很清楚,反正比我好)jssip+webrtc+freeswitch实现电话网页及遇到的488状态码问题_freeswitch 488_weixin_39715323的博客-CSDN博客

正常使用由于web限制应该在https上使用,但是http也不是不可以,我放到下一篇文章了,这就说怎么使用

我这用的是webrtc+jssip

webrtc-webphone已经实现了我的需求,所以我没有使用原生jssip

特主要实现的功能有:注册,拨叫,接听,保持,恢复

我的项目中需要静音(指我不能说话,通话人可以讲话)

所以我将hold(保持)和unhold(恢复)改成了mute和unmute

下面是完整代码和方法说明:

1.init(注册citbar)config中需要使用ip地址、端口号、extNo拨号人、extPwd密码

2.handleAgentBarBtnClick(通话状态更改)

    makecall(拨号)

    hangup(挂断)

    hold(静音)

    unhold(取消静音)

3.onbeforeunload (通话中刷新对讲群组中没有退出,导致群组中有多个同一个人)

4.beforeDestroy(切换页面后没有退出群组,刷新不走这个方法)

注意:

1.不要重复拨号,状态卡住后就能同一个设备对话了bug

2.如果想切换页面还能通话就不要beforeDestroy,但是在其他页面在回来时会导致状态不一致,而且容易出现卡状态,所以我将通话操作放在index最顶层里面

<template>
  <div class="top">
   
    <div style="padding: 0 30px;">
      <div style="display: flex;padding-top: 3px;">

        <!-- 设备树插件 -->
        <organizationTree ref="organizationTree"></organizationTree>

        <div style="width:77vw;height:80vh; margin-top: 5px;">
          <div class="photo_date" style="position:relative">
            <div style="display:flex;min-width:30%">
              <div class="text_type photo_deviceName " v-show="groupName" style="text-align: center;">
                {{groupName}}</div>
              <div class="text_type" :class="meetingStatus==2?' meetingName1':' meetingName'"
                style="width: 125px;text-align: center;">
                {{meetingStatus==2?meetingName:meetingName1}}</div>
            </div>
            <div style="position: absolute;right: 0;" v-show="createBy==userName">

              <div v-show="meetingStatus==2" class="btn_type photo_meeting_btn" @click="meetingAll"
                style="position:absolute; right:135px;width: 140px;height: 35px;line-height: 35px;">
                邀请全部成员</div>
              <div class="bg_btn_type photo_meeting_btn" @click="meeting"
                style="position:absolute; right:0;width: 125px;height: 35px;line-height: 35px;">
                {{meetingStatus==1?'开始会议':'结束会议'}}</div>

            </div>
          </div>

          <div class="">
            <div class="deviceList_title">
              <div class="deviceList_title_text" style="width:15%">名称</div>
              <div class="deviceList_title_text" style="width:15%">imei</div>
              <div class="deviceList_title_text" style="width:15%">类型</div>
              <div class="deviceList_title_text" style="width:15%">状态</div>
              <div class="deviceList_title_text" style="width:15%">会议状态</div>
              <div class="deviceList_title_text" style="width:24%">操作</div>
              <!-- <div class="deviceList_title_text">操作</div> -->
            </div>
            <div v-show="deviceList" class="deviceList">

              <div class="deviceList_list" :class="item.id==itemId?'deviceList_list1':''"
                v-for="(item,index) in deviceList" :key="item.id" @mouseover="mouseover(item.id)"
                @mouseleave="mouseout()">
                <!-- <el-tooltip :content="item.name" placement="bottom" effect="light"> -->
                <div class="deviceList_title_text" style="width:15%">{{item.devName}}</div>
                <!-- </el-tooltip> -->
                <div class="deviceList_title_text" style="width:15%">{{item.imei}}</div>
                <div class="deviceList_title_text" style="width:15%">{{item.devTypeName}}
                </div>
                <div class="deviceList_title_text" style="width:15%">
                  {{dictionary(item.devStatus,'dev_status',item.imei)}}
                </div>
                <div class="deviceList_title_text" style="width:15%">
                  {{dictionary(item.memberStatus,'meeting_member_status')}}
                </div>
                <div style="width:24%;">
                  <div v-if="meetingStatus==2">
                    <div v-if="item.imei == createBy&&item.imei == userName">
                      <div v-if="item.devStatus==3||item.devStatus==4||item.devStatus==5||item.devStatus==1">
                        <div style="width:100%;justify-content: center;" class="deviceList_operate">
                          <div class="deviceList_title_text1" @click="speak('request')"
                            v-if="item.memberStatus!=3&&item.memberStatus!=1&&item.memberStatus!=4&&item.memberStatus!=5">
                            <div>开始发言</div>
                          </div>
                          <div class="deviceList_title_text1" @click="speak('request')" v-if="item.memberStatus==3">
                            <div>结束发言</div>
                          </div>
                          <div class="deviceList_title_text1" @click="outMeeting" v-if="item.memberStatus==2">离开会议
                          </div>
                          <div class="deviceList_title_text1" @click="inMeeting" v-if="item.memberStatus==1">进入会议
                          </div>
                        </div>
                      </div>
                    </div>
                    <div v-if="item.imei != createBy">
                      <div v-if="item.devStatus==3||item.devStatus==4||item.devStatus==5||item.devStatus==1">
                        <div style="width:100%;justify-content: center;" class="deviceList_operate"
                          v-show="createBy==userName&&isInOrOutMeeting&&deviceList[0].memberStatus==2">
                          <div class="deviceList_title_text1" @click="speak('call',item.imei,'start')"
                            v-if="item.memberStatus!=3&&item.memberStatus!=1&&item.memberStatus!=4&&item.memberStatus!=5">
                            <div>点名发言</div>
                          </div>
                          <div class="deviceList_title_text1" @click="speak('call',item.imei,'end')"
                            v-if="item.memberStatus==3">
                            <div>结束发言</div>
                          </div>
                          <div class="deviceList_title_text1" @click="inOrOut('out',item.imei)"
                            v-if="item.memberStatus==2">请离会议</div>
                          <div class="deviceList_title_text1" @click="inOrOut('in',item.imei)"
                            v-if="item.memberStatus==1">拉入会议</div>
                        </div>
                      </div>
                    </div>
                  </div>

                </div>
              </div>
            </div>
          </div>
        </div>

      </div>

    </div>
  </div>

</template>
<script>
  import Header from "../home/header/index";
  import Footer from "../home/footer/index";
  import webSocketClass from "@/utils/webSocket";
  import { postWarnStatus } from "@/api/AlarmRecord";
  import organizationTree from "./deviceTree/organizationTree"
  import { devicetree } from "@/api/system/deviceTree";
  import { timestampToTime } from "../../../utils/time.js"
  import { addGroup, delGroup, getGroup, listGroup, updateGroup, updateMeetingStatus, selectDeviceGroupDetailList, deviceRequestTalking, inOrOutMeeting } from "@/api/system/group";
  import { listData } from "@/api/system/dict/data";
  import Ctibar from './AgentBar/ctibar.js';
  var audio = document.getElementById('audio');

  var constraints = {
    audio: true,
    video: true,
    mandatory: {
      maxWidth: 640,
      maxHeight: 360
    }
  };
  URL = window.URL || window.webkitURL;

  var eventHandlers = {
    'progress': function (e) {
      console.log('call is in progress');
    },
    'failed': function (e) {
      console.log('call failed: ', e);
    },
    'ended': function (e) {
      console.log('call ended : ', e);
    },
    'confirmed': function (e) {
      console.log('call confirmed');
    }
  };
  export default {
    dicts: ["warn_type"],
    components: {
      Header,
      Footer,
      organizationTree
    },
    data() {
    
      return {
     
        // 以下群组
        isGroup: false,
        createBy: '',
        isInOrOutMeeting: false,
        isSpeak: false,
        itemId: '',
        meetingStatus: '',
        groupDetail: {},
        isMike: false,
        deviceList: null,
        meetingName1: "会议未开始",
        meetingName: "会议中",
        groupName: "",
        dataList: null,
        userId: JSON.parse(sessionStorage.getItem("userInfo")).userId,
        userName: JSON.parse(sessionStorage.getItem("userInfo")).userName,
        // 查询参数
        queryParams: {
          pageNum: 1,
          pageSize: 10,
          deptId: null,
          planName: null,
          status: null,
        },
        total: 0,
        deviceTreeList: [],
        title: "",
        num: 1,
        groupId: '',
        dictionaryList: [],
        websocket: null,

        //初始化SDK所需要的配置
        config: {
          host: '39.152.2.103',
          port: '5066',
          proto: false,
          extNo: '',
          extPwd: '20181231',
          autoRegister: true,
          debug: true,
          //stunServer: 'stun.1.google.com',   可自行修改使用stun服务器地址
          stateEventListener: this.stateEventListener
        },
        //坐席分机号
        agentNo: '',
        //客户号码
        customerNo: '',
        //拨号弹窗
        showDial: false,
        //转接弹窗
        showTransferDial: false,
        //转接号码
        transNum: '',
        numList: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#',],
        agentStatus: 'DISCONNECTED',
        statusMap: {
          CONNECTED: '已连接',
          DISCONNECTED: '网络断开',
          REGISTERED: '已注册',
          UNREGISTERED: '未注册',
          REGISTER_FAILED: '注册失败',
          IN_CALL: '通话中',
          INCOMING_CALL: '来电振铃',
          OUTGOING_CALL: '外呼振铃',
          HOLD: '保持中',
          CALL_END: '通话结束'
        },
        timer: null,
        timerString: '00:00:00',
        outNum: '',
        isHold: true
      };
    },
    computed: {
      classObject() {
        const bool1 = this.alarmArr.length > 1;
        const bool2 = this.alarmArr.length === 1;
        return {
          tanchuangbox: true,
          "tanchuangbox-height-multi": bool1,
          "tanchuangbox-height-single": bool2,
        };
      },
    },
    mounted() {

      this.init()
  
    },
    methods: {

      // 以下群组
      inMeeting() {
        this.loadingFun()
        var body = {
          devImei: this.userName,
          flag: 'in',
          groupId: this.groupId,
        }

        inOrOutMeeting(body).then(response => {
          if (response.data.success) {
          } else {
            this.$modal.msgError(response.data.message);
          }
          this.loading.close();
        },
          error => {
            this.loading.close();
          });

      },
      outMeeting() {
        this.loadingFun()
        var body = {
          devImei: this.userName,
          flag: 'out',
          groupId: this.groupId,
        }

        inOrOutMeeting(body).then(response => {

          if (response.data.message == '已离开!') {

          } else {
            this.$modal.msgError(response.data.message);
          }
          this.loading.close();
        },
          error => {
            this.loading.close();
          });

      },

      inOrOut(flag, devImei) {
        this.loadingFun()
        var body = {
          devImei: devImei,
          flag: flag,
          groupId: this.groupId,
        }
        inOrOutMeeting(body).then(response => {
          console.log("123123123", response)
          if (response.data.success) {
            this.$modal.msgSuccess(response.data.message);
          } else {
            this.$modal.msgError(response.data.message);
          }
          this.loading.close();
        },
          error => {
            this.loading.close();
          });
      },
      loadingFun() {
        this.loading = this.$loading({
          lock: true,
          text: 'Loading',
          spinner: 'el-icon-loading',
          background: 'rgba(0, 0, 0, 0.7)'
        });
      },
      speak(type, devImei, flag) {
        this.loadingFun()
        var flagg = ''
        if (type == 'request') {
          flagg = !this.isSpeak ? 'start' : 'end'
          devImei = this.userName
        } else {
          flagg = flag
        }
        var body = {
          devImei: devImei,
          flag: flagg,
          groupId: this.groupId,
          type: type,
        }
        deviceRequestTalking(body).then(response => {
          if (response.data.success) {
            if (type == 'request') {

            }
            this.$modal.msgSuccess(response.data.message);
            //恢复发言
          } else {
            this.$modal.msgError(response.data.message);
            this.hold()
          }
          this.loading.close();
        },
          error => {
            this.loading.close();
          });
      },
      // 1、进入元素
      mouseover(index) {
        this.itemId = index
      },
      // 4、离开元素
      mouseout() {
        this.itemId = ''
      },
      ws() {
        console.log('{"groupId":' + this.groupId + '}')
        this.websocket.webSocketSendMsg('{"groupId":' + this.groupId + '}')

      },
      devStatus(devStatus) {
        for (const i in this.deviceList) {
          if (this.deviceList[i].devStatus == devStatus) {
            this.deviceList[i].devStatus = devStatus
          }
        }
      },
      dictionary(e, type, imei) {
        for (const i in this.dictionaryList) {
          if (this.createBy == imei && e == 4) {
            return '在线'
          }
          if (this.dictionaryList[i].dictValue == e && this.dictionaryList[i].dictType == type) {
            return this.dictionaryList[i].dictLabel
          }
        }
      },
      handleAdd() {
        // this.reset();
        // this.open = true;
        this.title = "添加群组";
      },

      tableRowClassName({ row, rowIndex }) {

        return 'photo';
      },
      rowClass({ row, rowIndex }) {

        return 'text-align: center;background-color: #1A1D30;color: #fff'

      },
      meetingAll() {
        var meetingStatusAll = 2
        var params = {
          groupId: this.groupId,
          meetingStatus: meetingStatusAll
        }
        updateMeetingStatus(params).then(response => {
          if (response.code == 200) {

            this.$modal.msgSuccess(response.msg);
          } else {
            this.$modal.msgError(response.msg);

          }
        });
      },

      meeting() {
        this.loadingFun()
        if (this.deviceList != null) {
          console.log('meetingStatusmeetingStatus', this.meetingStatus)
          var params = {
            groupId: this.groupId,
            meetingStatus: this.meetingStatus == 1 ? 2 : 1
          }
          updateMeetingStatus(params).then(response => {
            setTimeout(() => {
              if (response.code == 200) {
                if (this.meetingStatus == 1) {
                  if (sessionStorage.getItem('groupId') == this.groupId) {
                    this.handleAgentBarBtnClick('hangup')
                    sessionStorage.setItem('groupId', '')
                  }
                  this.isInOrOutMeeting = false
                  this.isHold = true

                  this.loading.close()
                }
              }
            }, 5000);

          });
        }
      },
      //孙组件向父组件传递数据
      wsMeetingStatus(isInterface) {
        console.log('isMeeting状态:', isInterface)
        this.meetingStatus = isInterface
      },
      groupDeviceItemClick(item) {
        // this.handleAgentBarBtnClick('hangup')
        this.isSpeak = false
        this.deviceList = []
        this.groupId = item.id
        this.meetingStatus = item.meetingStatus
        this.groupName = item.groupName
        this.createBy = item.createBy
        this.item = item
        var params = { groupInfoId: item.id, pageNum: 0, pageSize: 0, }
        selectDeviceGroupDetailList(params).then(response => {
          this.deviceList = response.rows
          this.total = this.deviceList.length
          this.ws()
          if (this.deviceList[0].memberStatus == 3 &&
            this.deviceList[0].imei == this.userName) {
            this.isSpeak = true;
          }
          if (
            this.deviceList[0].memberStatus == 2 &&
            this.deviceList[0].imei == this.userName
          ) {
            if (sessionStorage.getItem("groupId") == this.groupId) {
              this.isInOrOutMeeting = true
              return
            }
            if (sessionStorage.getItem("groupId")) {
            } else {
              this.call()
            }
          }
        });

      },

      onClickDialOutside(event) {
        console.log(event)
        this.showDial = false
      },
      onClickTransDialOutside(event) {
        this.showTransferDial = false;
      },
      handleAgentBarBtnClick(name) {
        console.log(name + '当前')
        if (name === 'login') {
          this.login();
        } else if (name === 'logout') {
          this.logout();
        } else if (name === 'answer') {
          this.answer();
        } else if (name === 'hangup') {
          this.hangup();
        } else if (name === 'makecall') {
          this.makeCall('9*' + this.groupId)
        } else if (name === 'hold') {
          this.hold();
        } else if (name === 'unhold') {
          this.unhold();
        } else if (name === 'transfer') {
          this.transfer(this.transNum)
        }
      },

      login() {
        this.init()
        Ctibar.register()
      },
      logout() {
        Ctibar.unregister()
      },
      makeCall(phone) {
        if (phone === "" || phone === undefined) {
          console.error('无效的号码,请重新输入!');
          return
        }
        Ctibar.makecall(phone);

      },
      hold() {
        Ctibar.hold();

      },
      unhold() {
        Ctibar.unhold();
      },
      answer() {
        Ctibar.answer();
      },
      hangup() {
        Ctibar.hangup();
      },
      transfer(phone) {
        console.info("触发转接", phone)
        Ctibar.transfer(phone);
      },
      //外呼拨号盘
      handleDialBtnClick(val) {
        this.outNum += val;
        this.$refs.outNumInput.focus();
      },
      //转接拨号盘
      handleTransDialBtnClick(val) {
        this.transNum += val;
        this.$refs.transNumInput.focus();
      },

      //参数为时间差秒数,返回这两个时间差并格式化
      computeTimeDiff(diff) {
        diff = Math.round(diff / 1000);
        let hour = Math.floor(diff / 3600).toString().padStart(2, '0');
        let min = Math.floor((diff - hour * 3600) / 60).toString().padStart(2, '0');
        let sec = (diff % 60).toString().padStart(2, '0');
        return hour + ':' + min + ':' + sec;
      },
      //重置时间
      restoreTime(origin) {
        clearInterval(this.timer);
        this.timerString = '00:00:00';
        this.timer = setInterval(() => {
          this.timerString = this.computeTimeDiff(new Date().getTime() - origin);
        }, 1000);
      },
      //状态变更回调
      stateEventListener(event, data) {
        //debug使用
        console.log('当前event为: ' + event + ', 当前data为: ' + JSON.stringify(data))

        this.agentStatus = event
        let origin = new Date().getTime();
        switch (event) {
          case "CONNECTED":
            this.agentNo = data.localAgent
            this.restoreTime(origin);
            break;

          case "DISCONNECTED":
            this.restoreTime(origin);
            break;

          case "UNREGISTERED":
            this.restoreTime(origin);
            break;

          case "OUTGOING_CALL":
            this.customerNo = data.otherLegNumber;
            this.restoreTime(origin);
            break;

          case "INCOMING_CALL":
            this.customerNo = data.otherLegNumber;
            //播放来电振铃音
            this.playRingMedia();
            this.restoreTime(origin);
            break;

          case "IN_CALL":
            this.stopPlayRingMedia();
            this.restoreTime(origin);

            // this.timer = setInterval(() => {
            // }, 1000);
            console.log('当前是否hold', this.isHold)
            if (this.isHold) {
              setTimeout(() => {
                this.handleAgentBarBtnClick('hold')
                this.isHold = false
                // 方法区
              }, 500);
            }

            break;

          case "CALL_END":
            this.stopPlayRingMedia();
            //挂机铃声
            this.playHangupMedia()
            this.restoreTime(origin);
            break;

          default:

        }
      },

      //播放挂机铃声
      playHangupMedia() {
        const _this = this;
        var hangupAudio = document.getElementById("hangupMediaAudioId")
        if (!hangupAudio) {
          hangupAudio = document.createElement('audio');
          hangupAudio.id = 'hangupMediaAudioId';
          hangupAudio.hidden = true;
          hangupAudio.src = 'wav/hangup.wav'
          document.body.appendChild(hangupAudio);
        }
        hangupAudio.play();
      },
      //播放来电振铃
      playRingMedia() {
        const _this = this;
        _this.stopPlayRingMedia();

        var ringAudio = document.getElementById("ringMediaAudioId")
        if (!ringAudio) {
          ringAudio = document.createElement('audio');
          ringAudio.id = 'ringMediaAudioId';
          ringAudio.hidden = true;
          ringAudio.src = 'wav/ring.wav';
          ringAudio.loop = 'loop';
          document.body.appendChild(ringAudio);
        }
        ringAudio.play();
      },
      //停止播放来电振铃
      stopPlayRingMedia() {
        const _this = this;
        var ringAudio = document.getElementById("ringMediaAudioId");
        if (ringAudio) {
          document.body.removeChild(ringAudio);
        }
      },
      //初始化方法
      init() {
        this.config.extNo = this.userName
        if (sessionStorage.getItem("freeSwitchWs") != null) {
          this.config.host = sessionStorage.getItem("freeSwitchWs").split(":")[0]
          this.config.port = sessionStorage.getItem("freeSwitchWs").split(":")[1]
        }
        Ctibar.initSDK(this.config)

        let url = `/ws/` + this.userId + '/group/device/push';
        this.websocket = new webSocketClass(url)
        this.websocket.getWebSocketMsg(evt => {
          // 客户端接收服务端返回的数据
          var data = JSON.parse(evt.data);
          console.log("websocket返回的数据123:", data);
          switch (data.flag) {
            case "group"://会议状态
              this.$refs.organizationTree.setGroupList(data, this.groupId)
              console.log("websocket返回的数据123:", data);
              break
            case "memberStatus"://成员状态
              for (const i in this.deviceList) {
                console.log('123123132', data.member);
                if (this.deviceList[i].imei == data.member) {
                  this.deviceList[i].devStatus = data.memberStatus;
                }
              }
              break
            case "memberMeetingStatus"://成员会议状态
              for (const i in this.deviceList) {
                if (this.deviceList[i].imei == data.member) {
                  this.deviceList[i].memberStatus = data.memberMeetingStatus;
                }
              }
              if (this.groupId == data.groupId && this.userName == data.member && this.createBy == data.member) {
                if (data.memberMeetingStatus == 3) {
                  this.isSpeak = true;
                  this.unhold()
                  console.log('数据this.isInOrOutMeeting1111', this.isSpeak)
                } else {
                  this.isSpeak = false;
                  this.hold()
                }
              }

              if (data.memberMeetingStatus == 4) {
                this.loading.close()
              }
              if (!this.isInOrOutMeeting && this.groupId == data.groupId && this.userName == data.member && this.createBy == data.member && data.memberMeetingStatus == 2) {
                this.hold()
                if (sessionStorage.getItem("groupId") &&
                  sessionStorage.getItem("groupId") !== this.groupId) {
                } else {
                  console.log('this.isInOrOutMeeting1111', data.memberMeetingStatus)
                  this.call()
                }
              }
              if (this.createBy == data.member && data.memberMeetingStatus == 1 && this.userName == data.member) {
                this.handleAgentBarBtnClick('hangup')
                sessionStorage.setItem("groupId", '')
                this.isInOrOutMeeting = false
                this.isSpeak = false
                this.isHold = true
              }
              break

          }
        })
      },
      setGroup(group) {
        this.isGroup = group
     
   
      },
      call() {
        this.handleAgentBarBtnClick('makecall')
        sessionStorage.setItem("groupId", this.groupId)
        this.isInOrOutMeeting = true
        if (this.deviceList[0].imei != this.createBy) {
          // this.groupDeviceItemClick(this.item)
        }
        var that = this
        window.onbeforeunload = (e) => {
          console.log('this.isInOrOutMeeting', that.isInOrOutMeeting)
          if (that.isInOrOutMeeting) {
            that.handleAgentBarBtnClick('hangup')
            sessionStorage.setItem("groupId", '')
            that.websocket.closeSocket()
            window.onbeforeunload = null
          }
        }
        this.loading.close()
        this.isInOrOutMeeting = true
        this.isHold = true
      }
    },
    beforeDestroy() {  //进行监听销毁
      console.log('1231232131232132131,', this.isInOrOutMeeting)
      if (this.isInOrOutMeeting) {
        this.handleAgentBarBtnClick('hangup')
        sessionStorage.setItem("groupId", '')
        this.websocket.closeSocket()
        window.onbeforeunload = null
      }
    },

  };
</script>
标签: 前端 js vue

本文转载自: https://blog.csdn.net/qq_38744614/article/details/131326901
版权归原作者 悅悅~ 所有, 如有侵权,请联系我们删除。

“web 语音通话 jssip”的评论:

还没有评论