Telefoneado! Cómo armar tu marcador web en una hora



Amigos, hoy quiero hablar con ustedes sobre las llamadas. Para algunos, este es un tema completamente nuevo. Para otros, es pura diversión de los fanáticos a nivel de "pero ¿debería enviarme mi Skype?" Para el tercero, una repentina necesidad de vida. La última opción es nuestra opción.


En este artículo, le mostraré un ejemplo de implementación pequeño pero muy viable que le permitirá literalmente hacer su propio marcador WEB y llamar a un amigo directamente desde el navegador, literalmente, en el extremo de varias docenas de líneas de JavaScript.


Sobre tecnologías y protocolos




Es 2019, y para nuestro deleite, ya existe una herramienta preparada para implementar R ealTime C ommunication (RTC) para la web, a saber, WebRTC . Hace unos años estaba en desarrollo activo. La API aún se está finalizando, pero la tecnología se ha convertido en el estándar de facto y es compatible con los navegadores más populares. En este artículo, no nos detendremos en la tecnología en sí; puede leer más en el sitio web de los desarrolladores o buscar artículos en el centro Por ejemplo, aquí .


Pero antes de comenzar, quiero aclarar un par de puntos.


  1. En primer lugar, WebRTC se ejecuta sobre un conjunto de protocolos, e incluso para la comunicación p2p necesitará algún tipo de servidor a través del cual sus clientes puedan encontrarse y hacerse amigos entre sí. Nuestro ejemplo utilizará el protocolo SIP, del cual puede leer más, por ejemplo, aquí .
  2. Necesitará un servidor con soporte para todo lo anterior, como FreeSwitch o Asterisk.

Dejamos estas cosas fuera del alcance del artículo. Asumimos que usted es tan afortunado como nosotros y que ya ha configurado la telefonía VoIP a su disposición.


Bueno, la parte más larga del artículo está detrás, ¡codifiquemos!


Diseño de página




Primero, necesitamos una página con la que llamaremos, campos para ingresar un nombre de usuario, contraseña, número de teléfono y un par de botones. En la versión más simple, se verá más o menos así:


<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> 

Los elementos de audio "enviarán" y "recibirán" sonido, y por belleza, a través de sonidos, reproducirán sonidos de marcación.


La interfaz de usuario está lista, no puede encontrar fallas en UX, hagamos que funcione.


Sujetamos JSSIP




Utilizaremos una biblioteca en la que todo lo que se requiere ya está implementado: JSSIP . Puede consultar la documentación: todo se describe con suficiente detalle allí e incluso hay un ejemplo de implementación listo para usar. Es decir, prácticamente no necesitamos hacer nada: simplemente simplifique todo lo más posible y descubra qué es qué.


Después de ingresar el nombre de usuario / contraseña (debe estar registrado en su servidor de telefonía) debe iniciar sesión en el servidor. Hacemos esto:


 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] }); 

En el camino, puede suscribirse a los eventos conectados y conectados y hacer algo útil allí. Pero pasemos al evento de registro:


 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'); }); 

Aquí solo necesitamos cambiar el estado de los botones: mostrar lo necesario, ocultar lo innecesario. Y si de repente algo salió mal con el inicio de sesión, escupimos el error en el registro:


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

Esto es suficiente para iniciar sesión. Queda por obtener el órgano de barril con
this._ua.start ();


Si el servidor se especifica correctamente y se acepta su nombre de usuario / contraseña, aparecerá un campo para ingresar el teléfono y aparecerá el botón Llamar.


Para iniciar sesión, debe llamar a this._ua.stop (), todo es simple.


Hacer una llamada


Ahora, lo más importante: debe llamar al número ingresado.


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

Tenga en cuenta: habilitamos explícitamente la multiplexación, esta configuración también debe estar habilitada en su servidor. En el caso de un asterisco, esto es rtcp_mux = yes en la configuración sip.conf.


La interacción adicional se basa en devoluciones de llamada, en las que debemos asegurar la dirección del flujo de audio y video al elemento de página correspondiente y enviar los mensajes necesarios en el orden correcto al servidor.


 //    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); }); 

En general, todo es bastante lógico. Mientras marca ['progreso']: reproduce los sonidos de marcación. En nuestro ejemplo, reproducimos nuestro propio sonido, pero como pvsur comentó correctamente, también puede obtenerlo del lado llamado y escuchar la respuesta del informador automático como "deje un mensaje después del pitido", si hay uno.
Tan pronto como llegué ['aceptado'] - reproducir el sonido respondió. Tan pronto como el suscriptor levante el teléfono, obtendremos su flujo de sonido y lo colocaremos en el elemento remoteAudio ['connect' y 'addstream'].
Al final de la llamada, cierre closeMediaStream. Puedes relajarte


Un poco sobre la operación.


Durante las pruebas, se descubrieron dos cosas.


  1. En Chrome, hubo un retraso de varios segundos al comienzo de la marcación, lo cual fue muy molesto. Descubrimos por los registros que fue al servidor de hielo, que no era necesario en absoluto, ya que tenemos nuestro propio servidor. Por lo tanto, en la configuración de JSSIP, simplemente los eliminamos e inmediatamente nos volvimos más bonitos. Consulte pcConfig.iceServers y pcConfig.hackStripTcp.
  2. Nuestro asterisco tiene un protocolo WSS con encriptación para SIP. El navegador lo requiere al usar el sitio web HTTPS. Pero el asterisco usa WS en función de los parámetros de contacto en los que la biblioteca JSSIP contiene un descriptor WS codificado. Los desarrolladores de la biblioteca al mismo tiempo señalan estándares en los que realmente no hay requisitos sobre este tema. Y los colegas del aster persistentemente no quieren arreglar nada. En general, un callejón sin salida. Bueno, en este momento encontramos en la fuente la línea this._configuration.contact_uri = new URI (...), cambie transport: 'ws' a transport: 'wss' y continúe disfrutando de la vida.

En general, el ejemplo está listo, puede tomar y llamar. No es necesario poner ningún softphone o desarrollar uno propio. No necesita preocuparse por implementar este software en los automóviles de los clientes. Simplemente abra el navegador y llame.


Otra biblioteca le permite marcar números adicionales en modo tono. Es decir, puede llamar, por ejemplo, al centro de llamadas del banco y acceder al elemento deseado en el menú de voz. Para hacer esto, simplemente ejecute el siguiente comando:


 this._call.sendDTMF('. ') 

Sobre Fakapy




Hubo varios puntos que realmente me pusieron nervioso.


Dejé esta parte fuera del alcance del artículo, pero además de las llamadas salientes, también necesitábamos aceptar llamadas entrantes. Y durante algún tiempo tuve que ponerme en cuclillas con una llamada entrante, que llegó y luego se interrumpió. Todo fue decidido por la configuración rtcpMuxPolicy mencionada anteriormente y que permitió la multiplexación en el asterisco, pero fuimos estúpidos durante bastante tiempo.


Y todavía hay problemas para marcar usted mismo, cuando se realiza una llamada y una llamada en la misma máquina. No recuerdo exactamente, pero la conexión se estableció con éxito, no hubo errores ni sonido :) El tiempo se estaba acabando, por lo que obtuvimos un puntaje en este efecto especial. Pero tenga en cuenta que probar las llamadas entrantes es mejor en un automóvil separado.


Conclusión


Finalmente, quiero señalar que probamos el paquete JSSIP + Asterisk en nuestro centro de llamadas, todo funciona bien, al menos en cromo, lo que nos conviene por completo. Lo principal es permitir que el navegador acceda a dispositivos multimedia y registre usuarios en el servidor de marcado.


Puedes ver el ejemplo terminado en el github .


Enlaces utiles


Sobre webrct
Sobre SIP: tyts , tyts
Sobre el asterisco
Biblioteca Jssip

Source: https://habr.com/ru/post/448266/


All Articles