интеграция WebRTC с CUBE (SBC)
-
![cucm_asterisk_sip_trunk_status.PNG]
Установка Asterisk Шаг 1: Установите зависимости
sudo apt-get update
sudo apt-get install -y build-essential libxml2-dev libncurses5-dev libssl-dev libjansson-dev libsqlite3-dev uuid-dev Шаг 2: Загрузите исходный код
Перейдите в /usr/src и загрузите последнюю версию Asterisk 22:
sudo wget
/forum/uploads/files/cisco/962babdd6a4020d998c3e730330738797dd36060.gz cd /usr/src
tar -xzvf asterisk-22-current.tar.gz Шаг 3: Скомпилируйте Asterisk
sudo tar -xvzf asterisk-21-current.tar.gz
cd asterisk-22*/
sudo contrib/scripts/install_prereq install
sudo ./configure
sudo make menuselect
sudo make && sudo make install
sudo make samples
sudo make config Шаг 4:
sudo systemctl start asterisk
systemctl status asterisk
sudo asterisk -rvvv Шаг 5: Проверьте, работает ли
Asterisk Запустите Asterisk в фоновом режиме, чтобы убедиться, что он работает: sudo asterisk -vvvvvvvvgc Шаг 6: Остановите Asterisk
В командной строке Asterisk остановите процесс: cli core stop now Шаг 7: Запустите Asterisk в фоновом режиме
Запустите Asterisk в качестве фонового процесса: sudo asterisk Шаг 8: Подключитесь к консоли
Asterisk Чтобы подключиться к запущенной консоли Asterisk:
sudo asterisk -r
Вы можете нажать ctrl z, чтобы выйти Шаг 9 Создание сертификатов sudo openssl req -nodes -newkey rsa:2048 -keyout /etc/asterisk/keys//asterisk.key -out /etc/asterisk/keys/asterisk.csr
введите свое общее имя и другие данные, когда будет предложено Шаг 10:
предоставление разрешения
sudo chown asterisk:asterisk /etc/asterisk/keys/asterisk.crt
sudo chown asterisk:asterisk /etc/asterisk/keys/asterisk.pem
sudo chmod 644 /etc/asterisk/keys/asterisk.crt
sudo chmod 600 /etc/asterisk/keys/asterisk.pem Шаг 11: опционально (ссылка:
https://vitalpbx.com/blog/asterisk-webrtc-from-scratch/?srsltid=AfmBOoqsJzR4lmLpmihFfwLDBXxPqwiAcTjdMNz11rcrckxodCfPl4Em
) Создайте пользователя asterisk и добавьте его в группу
groupadd asterisk
useradd -r -d /var/lib/asterisk -g asterisk asterisk
usermod -aG audio,dialout asterisk
chown asterisk. -R /etc/asterisk
chown asterisk. -R /var/{lib,log,spool}/asterisk
chown -R asterisk.asterisk /usr/lib64/asterisk Шаг 12: измените файл http.conf
sudo nano /etc/asterisk/http.conf
добавьте следующее
[general]
enabled=yes
bindaddr=0.0.0.0
bindport=8088
tlsenable=yes
tlsbindaddr=0.0.0.0:8089
tlscertfile=/etc/asterisk/keys/asterisk.cer (замените на свой сертификат)
tlsprivatekey=/etc/asterisk/keys/asterisk.key (замените на свой сертификат) Шаг 13: измените файл pjsip.conf для webrtc и cucm-trunk.
Вы можете заменить cucm-trunk на cube
ipопределение безопасного транспорта websocket*
[transport-wss]
type=transport
protocol=wss
bind=0.0.0.0:8089
cert_file=/etc/asterisk/keys/asterisk.cer
priv_key_file=/etc/asterisk/keys/asterisk.key ** раздел webrtc**
[webcall]
type=endpoint
context=default
disallow=all
direct_media=no
allow=ulaw,alaw,gsm,h264,VP8
webrtc=yes
aors=webcall
auth=webcall_auth ; Заставить Asterisk доверять CallerID от клиента
trust_id_inbound=yes
trust_id_outbound=yes
send_pai=yes
send_rpid=yes dtls_auto_generate_cert=yes
rtp_engine=asterisk
use_avpf=yes
ice_support=yes
force_rport=yes
rewrite_contact=yes [webcall_auth]
type=auth
auth_type=userpass
password=ваш пароль
username=webcall [webcall]
type=aor
max_contacts=100 [CUCM-Trunk]
type=endpoint
transport=transport-udp ; или transport-tcp, если CUCM требует TCP
context=from-external
disallow=all
allow=ulaw,alaw,gsm,h264,VP8
aors=CUCM-Trunk
outbound_auth=CUCM-Auth
direct_media=no [CUCM-Trunk]
type=aor
contact=sip:IP:5060 ; IP-адрес CUCM или CUBE и порт SIP [CUCM-Auth]
type=auth
auth_type=userpass
username=
password= ; Должно соответствовать конфигурации SIP-транка CUCM ИЛИ CUBE Шаг 14: измените extensions.conf
[default]
exten => 7100,1,NoOp(Переадресация вызова на CUCM IVR)
same => n,Dial(PJSIP/7100@CUCM-Trunk)
same => n,Hangup
шаг 15:
Подключитесь к консоли
Atserik sudo asterisk -r
pjsip reload
dialplan reload
module reload http
Шаг 16: проверьте, работает ли Asterik на 8089
sudo ss -tulnp | grep -E '8088|8089|5060' шаг 17. Настройка клиента webrtc с помощью javascript.
Измените на свои реальные параметры
<!DOCTYPE html><html>
<head>
<title>Collnetwork Care</title>
<script src="
https://webrtc.github.io/adapter/adapter-latest.js
"></script>
<link href="{{ url_for('static', filename='favicon.ico') }}" rel="icon" type="image/x-icon"/>
<link href="
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css
" rel="stylesheet"/>
<script src="{{ url_for('static', filename='sip.min.js') }}"></script>
<style>
body {
background-color: #f0f2f5;
font-family: sans-serif;
display: flex;
flex-direction: column;
align-items: center;
margin: 0;
padding: 20px;
} .keypad {
display: grid;
grid-template-columns: repeat(3, 80px);
gap: 15px;
justify-content: center;
margin-top: 20px;
} .keypad button {
width: 80px;
height: 80px;
font-size: 24px;
font-weight: bold;
border-radius: 50%;
border: none;
background-color: #007bff;
color: white;
cursor: pointer;
transition: background-color 0.3s;
} .keypad button:hover {
background-color: #0056b3;
} /* Наложение клавиатуры на видео
/#dtmfKeypad {
position: absolute;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
background: rgba(255, 255, 255, 0.9);
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
z-index: 20;
} .header {
margin-bottom: 20px;
} .logo {
height: 50px;
} .call-container {
position: relative;
width: 100%;
max-width: 900px;
background-color: #fff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-radius: 8px;
padding: 20px;
} .main-video-wrapper {
width: 100%;
height: 500px;
background-color: #000;
border-radius: 8px;
overflow: hidden;
} #remoteVideo {
width: 100%;
height: 100%;
object-fit: contain;
} .local-video-wrapper {
position: absolute;
top: 30px;
right: 30px;
width: 200px;
height: 150px;
border: 2px solid #fff;
border-radius: 8px;
overflow: hidden;
z-index: 10;
} #localVideo {
width: 100%;
height: 100%;
object-fit: cover;
} .controls-container {
display: flex;
justify-content: center;
margin-top: 20px;
gap: 15px;
} .controls-container button {
background-color: #555;
color: #fff;
border: none;
border-radius: 50%;
width: 50px;
height: 50px;
cursor: pointer;
font-size: 20px;
transition: background-color 0.3s;
} .controls-container button:hover {
background-color: #777;
} .call-btn {
background-color: #28a745 !important;
} .call-btn:hover {
background-color: #218838 !important;
} .hangup-btn {
background-color: #dc3545 !important;
} .hangup-btn:hover {
background-color: #c82333 !important;
} .welcome-message {
height: 500px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
background-color: #f8f9fa;
color: #495057;
border-radius: 8px;
padding: 20px;
}
</style>
</head>
<body>
<div class="header">
<img alt="Логотип Collnetwork" class="logo" src="{{ url_for('static', filename='logo.png') }}"/>
</div> <div class="call-container">
<div class="welcome-message" id="welcomeMessage">
<h2>Добро пожаловать в Collnetwork Care!</h2>
<p>Введите свои данные, чтобы начать видеозвонок с нашим представителем.</p> <form id="callForm" style="margin-top: 20px;">
<div style="margin-bottom: 10px;">
<label for="callerName">Ваше имя:</label>
<input id="callerName" name="callerName" placeholder="например, Джейн Доу" required="" type="text"/>
</div>
<div style="margin-bottom: 20px;">
<label for="callerPhone">Номер телефона:</label>
<input id="callerPhone" name="callerPhone" placeholder="например, +15551234567" required="" type="tel"/>
</div>
</form> <button class="call-btn" id="callButton">
<i class="fas fa-phone"></i> Начать звонок
</button>
</div> <div class="main-video-wrapper" style="display:none;">
<video autoplay="" id="remoteVideo" playsinline=""></video>
</div>
<div class="local-video-wrapper" style="display:none;">
<video autoplay="" id="localVideo" muted="" playsinline=""></video>
</div> <div class="controls-container">
<button class="hangup-btn" id="hangupButton" style="display:none;">
<i class="fas fa-phone-slash"></i>
</button>
<button id="muteButton" style="display:none;" title="Отключить/включить звук">
<i class="fas fa-microphone"></i>
</button>
<button id="videoButton" style="display:none;" title="Видео вкл./выкл.">
<i class="fas fa-video"></i>
</button>
<button id="screenShareButton" style="display:none;" title="Поделиться экраном">
<i class="fas fa-desktop"></i>
</button>
<button id="dtmfButton" style="display:none;" title="Клавиатура">
<i class="fas fa-th"></i>
</button>
</div> <div id="dtmfKeypad" style="display:none;">
<div class="keypad">
<button class="dtmf" data-digit="1">1</button>
<button class="dtmf" data-digit="2">2</button>
<button class="dtmf" data-digit="3">3</button> <button class="dtmf" data-digit="4">4</button>
<button class="dtmf" data-digit="5">5</button>
<button class="dtmf" data-digit="6">6</button> <button class="dtmf" data-digit="7">7</button>
<button class="dtmf" data-digit="8">8</button>
<button class="dtmf" data-digit="9">9</button> <button class="dtmf" data-digit="">*</button>
<button class="dtmf" data-digit="0">0</button>
<button class="dtmf" data-digit="#">#</button>
</div>
</div> </div>
</body>
<script>
let userAgent;
let registerer;
let session;
let localStream;
let uaReady = false;
let screenSharing = false; // Переключить вызов
window.toggleCall = async function toggleCall {
if (session) {
session.dispose;
session = null;
resetUI;
return;
} const callerName = document.getElementById('callerName').value || 'WebRTC Client';
const callerPhone = document.getElementById('callerPhone').value || 'anonymous';
const combinedDisplayName =${callerName} (${callerPhone}); if (!userAgent || userAgent.isTerminated) {
userAgent = new SIP.UserAgent({
uri: SIP.UserAgent.makeURI(sip:webcall@172.16.48.3),
transportOptions: { server: 'wss://172.16.48.3:8089/ws', traceSip: true },
displayName: combinedDisplayName,
authorizationUsername: 'webcall',
authorizationPassword: 'your password as you defined in pjsip'
}); userAgent.delegate = {
onTransportError: => {
console.warn('WebSocket закрыт неожиданно');
alert('Соединение WebSocket закрыто. Обновите страницу.');
},
onInvite: (invitation) => {
handleSession(invitation);
invitation.accept;
}
}; try {
await userAgent.start;
registerer = new SIP.Registerer(userAgent);
await registerer.register;
console.log('SIP UA registered successfully');
uaReady = true;
} catch (err) {
console.error('Failed to connect/register', err);
alert('Failed to connect/register to SIP server');
return;
}
} const target = SIP.UserAgent.makeURI('sip:0000@cucm ip');
if (!target) {
alert('Неверный SIP URI для target');
return;
} const inviter = new SIP.Inviter(userAgent, target, {
sessionDescriptionHandlerOptions: {
constraints: { audio: true, video: true }
}
}); try {
await inviter.invite;
handleSession(inviter); // Обновление пользовательского
интерфейса document.getElementById('welcomeMessage').style.display = 'none';
document.querySelector('.main-video-wrapper').style.display = 'block';
document.querySelector('.local-video-wrapper').style.display = 'block';
document.getElementById('callButton').style.display = 'none';
document.getElementById('hangupButton').style.display = 'inline-block';
document.getElementById('muteButton').style.display = 'inline-block';
document.getElementById('videoButton').style.display = 'inline-block';
document.getElementById('screenShareButton').style.display = 'inline-block';
document.getElementById('dtmfButton').style.display = 'inline-block';
} catch (e) {
console.error('Failed to make call', e);
alert('Failed to make call');
}
}; document.addEventListener('DOMContentLoaded', async => {
await startLocalVideo; document.getElementById('callButton').addEventListener('click', (e) => {
e.preventDefault;
const form = document.getElementById('callForm');
if (!form.checkValidity) {
form.reportValidity;
return;
}
window.toggleCall;
}); document.getElementById('hangupButton').addEventListener('click', => window.toggleCall);
document.getElementById('screenShareButton').addEventListener('click', toggleScreenShare);
document.getElementById('muteButton').addEventListener('click', toggleMute);
document.getElementById('videoButton').addEventListener('click', toggleVideo);
document.getElementById('dtmfButton').addEventListener('click', toggleDTMFKeypad); document.querySelectorAll('.dtmf').forEach(button => {
button.addEventListener('click', function {
sendDTMF(this.getAttribute('data-digit'));
});
}); window.addEventListener('beforeunload', => {
if (userAgent) userAgent.stop.catch(=>{});
});
}); async function startLocalVideo {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
localStream = stream;
const localVideo = document.getElementById('localVideo');
if (localVideo) localVideo.srcObject = stream;
} catch (error) {
console.error("Ошибка доступа к мультимедийным устройствам:", error);
alert("Невозможно получить доступ к вашей камере и микрофону.");
}
} function handleSession(newSession) {
session = newSession; session.stateChange.addListener((st) => {
if (st === SIP.SessionState.Terminated) {
session = null;
resetUI;
}
}); session.sessionDescriptionHandler.peerConnection.ontrack = (ev) => {
const remoteVideo = document.getElementById('remoteVideo');
if (remoteVideo.srcObject !== ev.streams[0]) {
remoteVideo.srcObject = ev.streams[0];
}
}; localStream.getTracks.forEach((t) =>
session.sessionDescriptionHandler.peerConnection.addTrack(t, localStream)
);
} function resetUI {
// Сбросить интерфейс пользователя в состояние
приветствия document.getElementById('welcomeMessage').style.display = 'flex';
document.querySelector('.main-video-wrapper').style.display = 'none';
document.querySelector('.local-video-wrapper').style.display = 'none';
document.getElementById('callButton').style.display = 'inline-block';
document.getElementById('hangupButton').style.display = 'none';
document.getElementById('muteButton').style.display = 'none';
document.getElementById('videoButton').style.display = 'none';
document.getElementById('screenShareButton').style.display = 'none';
document.getElementById('dtmfButton').style.display = 'none';
document.getElementById('dtmfKeypad').style.display = 'none'; document.getElementById('remoteVideo').srcObject = null;
document.getElementById('callButton').textContent = 'Call'; // Повторно включить ввод данных в
форму document.getElementById('callerName').disabled = false;
document.getElementById('callerPhone').disabled = false;
} function toggleMute {
if (!session) return;
const sender = session.sessionDescriptionHandler.peerConnection.getSenders.find(s => s.track && s.track.kind === 'audio');
if (!sender) return;
sender.track.enabled = !sender.track.enabled;
const icon = document.querySelector('#muteButton i');
if (sender.track.enabled) {
icon.classList.remove('fa-microphone-slash');
icon.classList.add('fa-microphone');
} else {
icon.classList.remove('fa-microphone');
icon.classList.add('fa-microphone-slash');
}
} function toggleVideo {
if (!session) return;
const sender = session.sessionDescriptionHandler.peerConnection.getSenders.find(s => s.track && s.track.kind === 'video');
if (!sender) return;
sender.track.enabled = !sender.track.enabled;
const icon = document.querySelector('#videoButton i');
const localEl = document.getElementById('localVideo');
if (sender.track.enabled) {
icon.classList.remove('fa-video-slash');
icon.classList.add('fa-video');
localEl.style.display = 'block';
} else {
icon.classList.remove('fa-video');
icon.classList.add('fa-video-slash');
localEl.style.display = 'none';
}
} async function toggleScreenShare {
if (!session) return;
if (screenSharing) {
const camTrack = localStream.getVideoTracks[0];
const sender = session.sessionDescriptionHandler.peerConnection.getSenders.find(s => s.track && s.track.kind === 'video');
if (sender && camTrack) await sender.replaceTrack(camTrack);
screenSharing = false;
document.querySelector('#screenShareButton i').classList.remove('fa-compress');
document.querySelector('#screenShareButton i').classList.add('fa-desktop');
return;
}
try {
const screenStream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: true });
const screenTrack = screenStream.getVideoTracks[0];
const sender = session.sessionDescriptionHandler.peerConnection.getSenders.find(s => s.track && s.track.kind === 'video');
if (sender && screenTrack) await sender.replaceTrack(screenTrack);
screenSharing = true;
document.querySelector('#screenShareButton i').classList.remove('fa-desktop');
document.querySelector('#screenShareButton i').classList.add('fa-compress');
} catch (e) {
console.error('Screen share failed', e);
alert('Failed to start screen sharing.');
}
} function toggleDTMFKeypad {
const keypad = document.getElementById('dtmfKeypad');
if (keypad.style.display === 'none' || !keypad.style.display) {
keypad.style.display = 'block';
} else {
keypad.style.display = 'none';
}
} function sendDTMF(digit) {
if (session) {
// Найти RTCRtpSender для аудиодорожки
const audioSender = session.sessionDescriptionHandler.peerConnection
.getSenders
.find(sender => sender.track && sender.track.kind === 'audio'); // Проверить, существует ли отправитель и имеет ли он объект
DTMF-отправителя if (audioSender && audioSender.dtmf) {
try {
// Свойство .dtmf ЯВЛЯЕТСЯ объектом
RTCDTMFSender audioSender.dtmf.insertDTMF(digit, 100, 50);
console.log(Отправлен DTMF-тон: ${digit});
} catch (error) {
console.error('Ошибка при отправке DTMF-тона:', error);
}
} else {
console.warn("RTCDTMFSender недоступен для аудиодорожки.");
}
} else {
console.warn("Нет активной сессии для отправки DTMF.");
}}
</script>
</html>
шаг 18:
https://asteriskip:8089/ws
, чтобы принять самоподписанный сертификат (это не нужно делать, если у вас есть публичный сертификат).
Откройте файл html и попробуйте выполнить вызов.
http://downloads.asterisk.org/pub/telephony/asterisk/asterisk-22-current.tar.gz
-
Здравствуйте, удалось ли вам интегрировать Webrtc с CUBE? Если да, то не могли бы вы поделиться инструкциями.
-
Каков вариант использования? 2.12.0.0 ![Response Signature]

-
Я хочу развернуть Webrtc, чтобы пользователи могли использовать браузер и звонить агентам uccx. Я пытался настроить его с помощью opensips в качестве шлюза Webrtc, но медиа не проходит. В результате я начал думать о CUBE, чтобы посмотреть, может ли он обрабатывать звонки Webrtc и перенаправлять их в cucm. Таким образом, пользователь будет совершать звонки webrtc в cucm через CUBE.
-
Наконец, мне удалось интегрировать webrtc с CUCM через шлюз Asterisk SIP.
-
Вау! Поздравляю. Не могли бы вы поделиться информацией о том, как это настроено в CUBE, и ссылками на документацию по настройке?
-
Я не знаю, поддерживает ли CUBE wss, но я уверен, что CUBE поддерживает sip-звонки.
Asterisk и некоторые другие SIP-серверы поддерживают WSS-звонки.
Поэтому вы можете установить Asterisk на Ubuntu и настроить SIP-транк для CUBE с помощью
плана набора номера.
Как только Asterisk получит WebRTC-звонки, предназначенные для конечной точки в CUBE, он
направит эти звонки на эту конечную точку. Сообщите мне, если вы хотите
установить Asterisk в качестве SIP-шлюза, чтобы я мог поделиться с вами шагами, которые я использовал. -
Не могли бы вы поделиться более подробной информацией о вашей установке и конфигурациях? ![Response Signature]

-
Насколько я знаю, Cube не поддерживает WebRTC. Во всех найденных мной источниках упоминается шлюз WebRTC-SIP, который интегрирован с SBC с помощью SIP-транка для последующего подключения к Communication Manager. ![Response Signature]

-
Да, и я использовал Asterisk в качестве SIP-шлюза. Есть и другие SIP-шлюзы, такие как opensips и kamailio.
-
Хорошо. Я их соберу и свяжусь с вами. А пока вам нужно установить Ubuntu. Я установил Ubuntu версии 25.
Здравствуйте! Похоже, вам интересна эта беседа, но у вас пока нет учетной записи.
Вы устали просматривать одни и те же посты каждый раз, когда заходите на сайт? После регистрации, вам не придётся искать обсуждения в которых вы принимали участие, настройте уведомления о новых сообщениях так как вам это удобно (по электронной почте или уведомлением). У вас появится возможность сохранять закладки и ставить лайки постам, чтобы выразить свою благодарность другим участникам сообщества.
С вашими комментариями этот пост может стать ещё лучше 💗
Зарегистрироваться Войти