打电话! 如何在一小时内组装网络拨号器



朋友,今天我想和你谈电话。 对于某些人来说,这是一个全新的话题。 对于其他人来说,这纯粹是“可以将Skype发送给我吗?” 第三,突然需要生活。 最后一个选项是我们的选项。


在本文中,我将向您展示一个很小但非常可行的实现示例,该示例将使您能够真正地制作自己的WEB拨号器,并直接从浏览器中调用数十个javascript行的膝盖来直接呼叫朋友。


关于技术和协议




院子里有2019年,令我们高兴的是,已经有一个现成的工具,用于实现Web的实时通讯(RTC),即WebRTC 。 几年前,他正在积极发展。 该API仍在最终确定中,但是该技术已成为事实上的标准,并且在大多数流行的浏览器中都受支持。 在本文中,我们不会只讨论技术本身,您可以在开发人员网站上阅读更多内容,或在中心上查找文章。 例如, 这里


但是在我们开始之前,我想澄清几点。


  1. 首先,WebRTC在一系列协议的基础上运行,甚至对于p2p通信,您都将需要某种服务器,客户端可以通过该服务器找到彼此的朋友。 我们的示例将使用SIP协议,您可以在此处了解更多信息。
  2. 您将需要一台支持以上所有内容的服务器-例如FreeSwitch或Asterisk。

这些内容不在本文讨论范围之内。 我们将假定您和我们一样幸运,并且您已经可以配置VoIP电话。


好吧,本文最长的部分在后面,让我们编写代码!


页面布局




首先,我们需要一个用于调用的页面,用于输入用户名,密码,电话号码和几个按钮的字段。 在最简单的版本中,它将如下所示:


<div class="container"> <div class="input-group mb-6"> <div class="input-group-prepend"> <span class="input-group-text">Login</span> </div> <input id="loginText" type="text" class="form-control"> <div class="input-group-prepend"> <span class="input-group-text">Password</span> </div> <input id="passwordText" type="password" class="form-control"> <button id="loginButton" type="button" class="btn btn-primary" onclick="login()">Login</button> <button id="logOutButton" type="button" class="btn btn-primary d-none" onclick="logout()">LogOut</button> </div> <div class="input-group mb-6 d-none" id="callPanel"> <input id="callNumberText" type="text" class="form-control" placeholder="Call number"> <button id="callNumberButton" type="button" class="btn btn-success" onclick="call()">Call</button> <button id="hangUpButton" type="button" class="btn btn-danger d-none" onclick="hangUp()">Hang Up</button> </div> <audio id="localAudio" autoPlay muted></audio> <audio id="remoteAudio" autoPlay></audio> <audio id="sounds" autoPlay></audio> </div> 

音频元素将“发送”和“接收”声音,并通过声音播放拨号声音以达到美丽的目的。


用户界面已准备就绪,您无法找到UX的问题,让它正常工作。


我们紧固JSSIP




我们将使用一个库,其中已实现了所需的所有内容-JSSIP 。 您可以查看文档:一切都已详细描述,甚至有现成的实现示例。 也就是说,我们实际上不需要执行任何操作-只需尽可能简化所有操作并弄清楚是什么。


输入登录名/密码(必须在电话服务器上注册)后,您需要登录到服务器。 我们这样做:


 socket = new JsSIP.WebSocketInterface("wss://webrtcserver:port/ws"); _ua = new JsSIP.UA( { uri: "sip:" + this.loginText.val() + "@webrtcserver", password: this.passwordText.val(), display_name: this.loginText.val(), sockets: [socket] }); 

在此过程中,您可以订阅连接事件和连接事件,并在那里做一些有用的事情。 但是,让我们继续进行注册事件:


 his._ua.on('registered', () => { console.log("UA registered"); this.loginButton.addClass('d-none'); this.logOutButton.removeClass('d-none'); this.loginText.prop('disabled', true); this.passwordText.prop('disabled', true); $("#callPanel").removeClass('d-none'); }); 

在这里,我们只需要更改按钮的状态:显示必要的内容,隐藏不必要的内容。 如果登录突然出现问题,我们将错误记录在日志中:


 this._ua.on('registrationFailed', (data) => { console.error("UA registrationFailed", data.cause); }); 

这足以登录。 仍然需要用
this._ua.start();


如果正确指定了服务器并且接受了您的用户名/密码,则会出现用于输入电话和“呼叫”按钮的字段。


对于日志记录,您需要调用this._ua.stop(),一切都很简单。


打个电话


现在-最重要的是:您需要拨打输入的号码。


 this.session = this._ua.call(number, { pcConfig: { hackStripTcp: true, //   ,       rtcpMuxPolicy: 'negotiate', //   ,   multiplexing.       . iceServers: [] }, mediaConstraints: { audio: true, //    video: false }, rtcOfferConstraints: { offerToReceiveAudio: 1, //    offerToReceiveVideo: 0 } }); 

请注意:我们明确启用了多路复用,还必须在您的服务器上启用此设置。 对于星号,在sip.conf设置中为rtcp_mux = yes。


进一步的交互是基于回调的,在回调中,我们必须确保音频视频流指向相应的页面元素的方向,并以正确的顺序将必要的消息发送到服务器。


 //    this.session.on('progress', () => { console.log("UA session progress"); playSound("ringback.ogg", true); }); //      this.session.on('connecting', () => { console.log("UA session connecting"); playSound("ringback.ogg", true); //          ,     let peerconnection = this.session.connection; let localStream = peerconnection.getLocalStreams()[0]; // Handle local stream if (localStream) { // Clone local stream this._localClonedStream = localStream.clone(); console.log('UA set local stream'); let localAudioControl = document.getElementById("localAudio"); localAudioControl.srcObject = this._localClonedStream; } //       ,        peerconnection.addEventListener('addstream', (event) => { console.log("UA session addstream"); let remoteAudioControl = document.getElementById("remoteAudio"); remoteAudioControl.srcObject = event.stream; }); }); //   , ,    this.session.on('failed', (data) => { console.log("UA session failed"); stopSound("ringback.ogg"); playSound("rejected.mp3", false); this.callButton.removeClass('d-none'); this.hangUpButton.addClass('d-none'); }); // ,  this.session.on('ended', () => { console.log("UA session ended"); playSound("rejected.mp3", false); JsSIP.Utils.closeMediaStream(this._localClonedStream); this.callButton.removeClass('d-none'); this.hangUpButton.addClass('d-none'); }); //  ,    this.session.on('accepted', () => { console.log("UA session accepted"); stopSound("ringback.ogg"); playSound("answered.mp3", false); }); 

总的来说,一切都很合乎逻辑。 拨打['progress']时-播放拨号声音。 在我们的示例中,我们播放自己的声音,但正如pvsur正确指出的那样,您也可以从被叫方获取声音,并听到自动通知者的响应,例如“在哔声后留下消息”(如果有)。
一旦我通过['accepted']-播放已回答的声音。 订户拿起电话后,我们将获取其声音流并将其放入remoteAudio ['connecting'和'addstream']元素中。
在调用结束时,请执行closeMediaStream。 你可以放松一下


关于操作的一点点


在测试期间,发现了两件事。


  1. 在Chrome中,拨号开始时会延迟几秒钟,这很烦人。 我们从日志中发现他去了冰服务器,因为我们有自己的服务器,所以根本不需要冰服务器。 因此,在JSSIP的配置中,我们只是删除了它们,并立即变得更漂亮。 请参阅pcConfig.iceServers和pcConfig.hackStripTcp。
  2. 我们的星号具有WSS协议,该协议具有用于SIP的加密。 浏览器在使用HTTPS网站时需要这样做。 但是星号使用基于联系参数的WS,其中JSSIP库包含一个硬编码的WS描述符。 图书馆的开发人员同时指出了实际上对此标准没有要求的标准。 翠菊的同事们一直不愿修复任何东西。 一般来说,死胡同。 好吧,这时我们在源代码中找到了this._configuration.contact_uri = new URI(...)行,将transport:'ws'更改为transport:'wss'并继续享受生活。

通常,该示例已准备就绪,您可以接听并致电。 无需放置任何软件电话或自行开发。 无需担心将此软件部署到客户端汽车上。 只需打开浏览器并致电。


另一个库允许您在音频模式下拨打其他号码。 也就是说,您可以很好地致电例如银行的呼叫中心,然后转到语音菜单上的所需项目。 为此,只需运行以下命令:


 this._call.sendDTMF('. ') 

关于法卡皮




有几点确实让我感到紧张。


我将这一部分放在本文的讨论范围之外,但是除了拨出电话以外,我们还需要接受拨入电话。 有一段时间,我不得不蹲下一个打来的电话,但后来打断了。 一切都由上面提到的rtcpMuxPolicy设置决定,并在星号上启用了多路复用,但是我们在相当长的一段时间里都很愚蠢。


而且,在同一台计算机上拨打电话和拨打电话时,仍然存在拨号自己的问题。 我记不清了,但是连接已成功建立,也没有错误,也没有声音:)时间用完了,所以我们在这种特殊效果上得分。 但是请记住,在单独的汽车上测试来电更好。


结论


最后,我想指出的是,我们在呼叫中心测试了JSSIP + Asterisk捆绑包,一切正常,至少在chrome中可以正常使用,这完全适合我们。 最主要的是允许浏览器访问媒体设备并在拨号服务器上注册用户。


您可以在github上看到完成的示例。


有用的链接


关于威宝
关于SIP: tytstyts
关于星号
Jssip库

Source: https://habr.com/ru/post/zh-CN448266/


All Articles