jssip(呼叫,接听,重新协商等功能实现)
<template>
<div class="head">
<a-button type="primary" @click="call">call</a-button>
<a-button type="primary">answer</a-button>
<a-button type="primary" @click="renegotiate">renegotiate</a-button>
<a-button type="primary" @click="terminate">terminate</a-button>
<a-button type="primary" @click="peerBtn">对等连接</a-button>
</div>
<div class="main">
<div>
<video src="" id="selfVideo" controls></video>
<video src="" id="remoteVideo" controls></video>
<audio src="" id="audioElement" controls></audio>
</div>
<div class="message"></div>
</div>
</template>
<script setup>
import JsSIP from "jssip";
import { onMounted } from "vue";
let currentSession;
let userAgent;
let peer;
onMounted(() => {
sip_init();
});
var msg_log;
function sip_init() {
msg_log = {
el: document.querySelector(".message"),
log(msg) {
console.log("msg", msg);
this.el.innerHTML += `<span class="success">${new Date().toLocaleTimeString()}:${msg}</span></br>`;
},
error(msg) {
console.log("error", msg);
this.el.innerHTML += `<span class="error">${new Date().toLocaleTimeString()}:${msg}</span></br>`;
},
};
const name = location.search.slice(6);
if (!name) {
return msg_log.error("location.search获取不到信息");
}
const selfVideo = document.querySelector("#selfVideo");
const remoteVideo = document.querySelector("#remoteVideo");
// 本地加载完成 对端加载完成
const socket = new JsSIP.WebSocketInterface("ws://101.200.183.204:5062");
const configuration = {
sockets: [socket],
uri: `sip:${name === "offer" ? 2001 : 2002}@172.24.66.100;transport=ws`,
password: "67975111",
register: true,
session_timers: false,
};
var ua = new JsSIP.UA(configuration);
ua.on("connected", () => msg_log.log("连线中"));
ua.on("connecting", () => msg_log.log("接线中"));
ua.on("disconnected", () => msg_log.error("取消连线"));
ua.on("registered", () =>
msg_log.log(`--${name === "offer" ? 2001 : 2002}注册成功`)
);
ua.on("registrationExpiring", () => msg_log.log("注册即将到期,重新注册"));
ua.on("registrationFailed", () => msg_log.error("注册失败"));
ua.on("unregistered", () => msg_log.log("取消注册"));
ua.on("sipEvent", () => msg_log.log("sipEvent"));
ua.on("newRTCSession", function (data) {
const { session, request, originator } = data;
if (originator === "remote") {
msg_log.log("对方打电话过来了~~~");
} else {
msg_log.log("拨打电话中~~~");
}
currentSession = session;
session.on("accepted", () => msg_log.log("通话接受时候触发"));
session.on("connecting", () => msg_log.log("通话连线时候触发"));
session.on("sdp", () => msg_log.log("交换sdp信令事件触发"));
session.on("failed", () => msg_log.log("通话失败事件触发"));
session.on("reinvite", () => {
openLocalCamera();
msg_log.log("重新协商事件触发");
audioElement.srcObject = null;
if (session._connection.getLocalStreams().length > 0) {
// 接听后,判断localStream
selfVideo.srcObject = session?._connection.getLocalStreams()[0];
selfVideo.play();
}
if (session?._connection.getRemoteStreams().length > 0) {
remoteVideo.srcObject = session?._connection.getRemoteStreams()[0];
remoteVideo.play();
}
});
session.on("progress", () => {
if (originator === "remote") {
msg_log.log("电话过来拉~~~~~~~~~··");
// var flag = confirm("是否接听?");
// if (!flag) {
// ua?.terminateSessions();
// return;
// }
session.answer({
mediaConstraints: { audio: true, video: true },
// mediaStream: localStream,
});
msg_log.log("我接听了");
}
msg_log.log("接听事件在progress中触发");
});
session.on("confirmed", () => {
msg_log.log("呼叫确认--设置媒体流到音视频中");
selfVideo.srcObject = null;
remoteVideo.srcObject = null;
const stream = new MediaStream();
const receivers = currentSession.connection?.getReceivers();
if (receivers)
receivers.forEach((receiver) => stream.addTrack(receiver.track));
audioElement.srcObject = stream;
// 最后都要播放
audioElement.oncanplay = () => {
audioElement.play();
};
});
session.on("peerconnection", (data) => {
msg_log.log("对等连接事件触发");
});
session.on("connecting", (data) => {
peer = session._connection;
console.log(peer, data, "wewewewewewewew");
msg_log.log("对等连接建立,connecting");
});
session.on("ended", () => msg_log.log("通话结束"));
});
userAgent = ua;
ua.start();
}
const 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");
},
};
function call() {
const opt = {
mediaConstraints: {
audio: true,
video: false,
},
eventHandlers,
};
msg_log.log("1001 呼叫");
userAgent.call("sip:2002@101.200.183.204", opt);
}
// 重新协商
function renegotiate() {
// openLocalCamera();
var options = {
useUpdate: false,
rtcOfferConstraints: {
mandatory: {
OfferToReceiveAudio: true,
OfferToReceiveVideo: true,
},
},
pcConfig: {
rtcpMuxPolicy: "negotiate",
iceServers: [{ urls: ["stun:stun.l.google.com:19302"] }],
},
};
currentSession.renegotiate(options, () => {
audioElement.srcObject = null;
if (currentSession._connection.getLocalStreams().length > 0) {
// 接听后,判断localStream
selfVideo.srcObject = currentSession?._connection.getLocalStreams()[0];
selfVideo.play();
}
if (currentSession?._connection.getRemoteStreams().length > 0) {
remoteVideo.srcObject = currentSession?._connection.getRemoteStreams()[0];
remoteVideo.play();
}
console.log("我重新协商成功了~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
});
}
function terminate() {
currentSession.terminate();
}
//开启本地摄像头,并把当前的媒体流添加到peerConnection中
function openLocalCamera() {
// 1.获取本地音视频流
// 调用 getUserMedia API 获取音视频流
let constraints = {
video: true,
// audio: true,
// audio: {
// // 设置回音消除
// noiseSuppression: true,
// // 设置降噪
// echoCancellation: true,
// }
};
navigator.mediaDevices
.getUserMedia(constraints)
.then(gotLocalMediaStream)
.catch((err) => {
console.log("getUserMedia 错误", err);
});
}
async function gotLocalMediaStream(mediaStream) {
selfVideo.srcObject = mediaStream;
selfVideo.play();
const mediaStreamTrack = mediaStream.getTracks()[0];
const localStream = mediaStream;
localStream.getTracks().forEach((track) => {
peer.addTrack(track, mediaStream);
});
// 这里每次开启本地视频流都要重新发送offer到对端
// await peer.setLocalDescription(offer);
}
//关闭本地的视频流
function closeLocalMedia() {
peerConnection.removeTrack(this.rtpSender);
}
async function peerBtn() {
console.log(peer, "peer");
// const await navigator.
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
stream.getTracks().forEach((track) => {
peer.addTrack(track, stream);
});
selfVideo.srcObject = stream;
selfVideo.play();
// openLocalCamera();
}
// 在关闭视频流之后,video标签会显示最后一帧的画面,如果想使video标签展示初始状态,可以调用video.load()
// video.load(); //视频回到初始状态
</script>
<style lang="less">
.head {
height: 50px;
background-color: #ccc;
display: flex;
align-items: center;
margin-bottom: 20px;
button {
margin: 0 10px;
}
}
.main {
display: flex;
.message {
margin-left: 100px;
background-color: rgba(0, 0, 0, 0.729);
width: 500px;
border: 2px solid #000;
span.success {
color: green;
}
span.error {
color: red;
}
}
}
</style>
http://localhost:8080/?type=offer 发送方
http://localhost:8080/?type=answer 接听方