使用 WebRTC 和 H5、JavaSprict 开发一套简单的语音客服系统,.NET 语音客服系统
H5 语音客服系统可以实现实时的语音通话功能,使客户能够直接与客服人员进行语音交流,通过语音通话,用户可以更直接地表达问题,客服人员也能更准确地理解用户需求,从而更快速地提供解决方案。
·
前言
H5 语音客服系统可以实现实时的语音通话功能,使客户能够直接与客服人员进行语音交流,通过语音通话,用户可以更直接地表达问题,客服人员也能更准确地理解用户需求,从而更快速地提供解决方案。相比于传统的文字聊天客服系统,语音客服系统更贴近真实的沟通方式,能够更好地理解用户需求和问题,提供更个性化、人性化的服务,提升用户满意度和体验提供更加直接和高效的沟通方式。
H5 语音客服系统可以在不同的设备和操作系统上运行,包括 PC、手机、平板等,具有良好的跨平台兼容性,能够满足不同用户的需求。
软件定制开发请联系 QQ:993014309或869119955
HTML 代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<meta content="telephone=no" name="format-detection">
<title>语音通话</title>
<link href="/h5/css/mui.min.css" rel="stylesheet" />
<link href="/h5/css/iconfont.css" rel="stylesheet" />
<link href="/h5/css/app.css" rel="stylesheet" />
<link href="/h5/css/app-h5.css" rel="stylesheet" />
<style>
body {
width: 100%;
height: 100%;
background: url(/h5/img/backgroud.jpg) no-repeat 50%;
background-size: cover;
-webkit-overflow-scrolling: touch;
backdrop-filter: blur(8px);
}
#call-status {
color: #ffffff;
text-align: center;
font-size: 1rem;
margin-top: 3rem;
}
#operation {
width: 100%;
position: absolute;
bottom: 3rem;
text-align: center;
}
#call-icon {
display: inline-block;
width: 6rem;
height: 6rem;
border-radius: 100%;
background-color: #5de758;
background-image: url(/h5/img/audio_video_voice.png);
background-size: 70%;
background-repeat: no-repeat;
background-position-x: 50%;
background-position-y: 50%;
}
#call-icon:active {
background-color: #43b13f;
}
#call-text {
color: #ffffff;
font-size: 1rem;
}
#hangup-icon {
display: inline-block;
width: 6rem;
height: 6rem;
border-radius: 100%;
background-color: #e75e58;
background-image: url(/h5/img/audio_video_hang.png);
background-size: 70%;
background-repeat: no-repeat;
background-position-x: 50%;
background-position-y: 50%;
}
#hangup-icon:active {
background-color: #b94a45;
}
#hangup-text {
color: #ffffff;
font-size: 1rem;
}
#headpic {
margin-top: 10rem;
text-align: center;
}
#headpic img {
width: 8rem;
height: 8rem;
border-radius: 100%;
}
#name {
color: #ffffff;
font-size: 1.6rem;
text-align: center;
margin-top: 0.5rem;
}
</style>
</head>
<body>
<audio style="display: none" id="localAudio" autoplay></audio>
<audio style="display: none" id="remoteAudio" autoplay></audio>
<!-- 消息提醒音频 -->
<audio id="call-audio" loop>
<source src="/h5/audio/call.mp3" type="audio/mpeg" />
</audio>
<div>
<div style="position: absolute; width: 100%; top: 0px;">
<p id="call-status">正在连接服务器</p>
</div>
<!-- 显示的头像 -->
<div id="headpic">
<img src="/h5/img/cs-all-headpic.png" />
</div>
<!-- 显示的名称 -->
<p id="name">客服热线</p>
<div id="operation">
<!-- 拨号 -->
<div id="call">
<div id="call-icon"></div>
<div id="call-text">呼叫</div>
</div>
<!-- 取消/挂断 -->
<div id="hangup" style="display: none;">
<div id="hangup-icon"></div>
<div id="hangup-text">取消</div>
</div>
</div>
</div>
</body>
</html>
<script src="/h5/js/jquery-3.4.1.min.js"></script>
<script src="/h5/js/system-1.0.5.js"></script>
<script src="/h5/js/md5.js"></script>
<script src="/h5/js/mui.min.js"></script>
<script src="/h5/js/mui.zoom.js"></script>
<script src="/h5/js/mui.picker.js"></script>
<script src="/h5/js/mui.poppicker.js"></script>
<script src="/h5/js/mui.previewimage.js"></script>
<script src="/h5/js/common.js?v=1.0.8"></script>
<script src="/h5/js/chat.im.js?v=1.0.8"></script>
<script src="/h5/js/adapter-latest.js"></script>
<script src="/h5/js/audio-call.js?v=1.0.8"></script>
<script>
console.log(adapter.browserDetails.browser);
init();
</script>
JavaSprict 代码
// webrtc 连接配置信息
var configuration = {
"iceServers": [
{
// stun 服务器地址、用户名及密码。
"urls": "stun:xxx.xxx.xxx.xxx:3478",
"username": "xxx",
"credential": "xxx"
},
{
// turn 服务器地址、用户名及密码。
"urls": "turn:xxx.xxx.xxx.xxx:3478",
"username": "xxx",
"credential": "xxx"
}
],
// 默认使用 relay 方式传输数据。
//"iceTransportPolicy": "relay",
//"iceCandidatePoolSize": "0"
}
var status = 0;
var statusEnum = {
normal: 0,
// 呼叫中。
call: 1,
// 人工座席繁忙。
busySeats: 2,
// 链接中。
connecting: 3,
// 已接通。
connected: 4
};
var connecting = true;
// 本地音视频流。
var localStream = null;
// 本地音频控件。
var localAudio = document.getElementById("localAudio");
// 远程端音视频流。
var remoteStream = null;
// 远程端音频控件。
var remoteAudio = document.getElementById("remoteAudio");
var callStatus = $("#call-status");
var callAudio = document.getElementById("call-audio");
var peerConnection = null;
var onLocalMediaStreamCall = null;
var onLocalMediaStreamErrorCall = null;
function getAduioCallStatus() {
return status;
}
/*
* 离开语音通话房间。
*/
function stopAudioCall() {
console.log("停止语音通话");
// 挂断。
hangup();
// 关闭本地媒体流。
closeLocalMedia();
// 清理倒计时。
stopCallConnected();
// 清空远程语音播放。
remoteAudio.srcObject = null;
}
// 打开音视频设备。
function startAudioCall() {
console.log("启动语音通话");
// 检测是否支持音视频设备。
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
$.alert("当前设备不支持语音通话", null, true);
return false;
}
// navigator.getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);
if (localStream == null) {
console.log("尝试获取本地音频设备");
// 获取本地音频设备。
navigator.mediaDevices.getUserMedia({ audio: true, video: false })
// 响应成功。
.then(onLocalMediaStream)
// 打开音视频设备失败回调函数。
.catch(function (e) {
console.error("获取音频设备失败,异常信息:", e);
$.alert("音频设备访问失败,未授权或发生其他错误", null, true);
if (onLocalMediaStreamErrorCall) {
onLocalMediaStreamErrorCall();
}
return;
});
} else if (onLocalMediaStreamCall) {
onLocalMediaStreamCall();
}
return true;
}
/*
* 打开音频设备成功时的回调函数。
*/
function onLocalMediaStream(stream) {
console.log("获取本地音频设备成功");
//console.log("onLocalMediaStream", stream);
// 从设备上获取到的音频 track 添加到 localStream 中。
if (localStream) {
console.log("添加新的音频数据流");
// 包含流中所有的音轨。
stream.getAudioTracks().forEach(function (track) {
localStream.addTrack(track);
stream.removeTrack(track);
});
} else {
localStream = stream;
}
// 绑定本地音频控件。
//localAudio.src = window.URL.createObjectURL(localStream);
//remoteAudio.srcObject = localStream;
if (onLocalMediaStreamCall) {
console.log("回调 onLocalMediaStreamCall");
onLocalMediaStreamCall();
}
}
/*
* 接收远程端音视频流。
*/
function onRemoteStream(e) {
console.log("收到远程端发送的音视频流");
remoteStream = e.streams[0];
// 给音频控件设置数据源。
//remoteAudio.src = window.URL.createObjectURL(remoteStream);
remoteAudio.srcObject = remoteStream;
// 显示已接通的状态。
showCallStatus(statusEnum.connected);
// 开始显示通话计时。
startCallConnected();
}
/*
* 通过调用 createOffer 生成 Offer SDP 描述符的回调函数。
*/
function onOffer(desc) {
console.log("onOffer: " + JSON.stringify(desc));
// 设置本地 SDP。
peerConnection.setLocalDescription(desc);
// 将 Offer SDP 发送给远程端。
sendMessage(chatIm.messageBodyType.offer, JSON.stringify(desc));
}
/*
* 通过调用 createAnswer 生成 Answer SDP 描述符的回调函数。
*/
function onAnswer(desc) {
console.log("onAnswer: " + JSON.stringify(desc));
// 设置本地 SDP。
peerConnection.setLocalDescription(desc);
// 将 Answer SDP 发送给远程端。
sendMessage(chatIm.messageBodyType.answer, JSON.stringify(desc));
}
/*
* 对方接接受的语音通话邀请。
*/
function receiveAccept() {
// 停止播放。
callAudio.pause();
// 隐藏呼叫按钮。
$("#call").hide();
// 显示取消按钮。
$("#hangup").show();
// 创建连接。
createPeerConnection();
var offerOptions = {
offerToReceiveAudio: 1,
offerToReceiveVideo: 0
};
// 创建 offer。
peerConnection.createOffer(offerOptions)
.then(onOffer)
.catch(function (e) {
console.error("Failed to create offer: ", e);
});
}
/*
* 挂断。
*/
function hangup() {
if (peerConnection == null) {
return;
}
peerConnection.close();
peerConnection = null;
//
console.log("已关闭 peerConnection 对象。");
}
/*
* 创建 PeerConnection 对象。
*/
function createPeerConnection() {
showCallStatus(statusEnum.connecting);
if (peerConnection == null) {
console.log("创建 peerConnection 对象。");
peerConnection = new RTCPeerConnection(configuration);
// 当前收集到 Candidate 后触发的响应事件。
peerConnection.onicecandidate = function (e) {
console.log("响应 onicecandidate");
// 检测 candidate 是否存在。
if (e.candidate) {
console.log("candidate: " + e.candidate);
// 发送给远程端。
sendMessage(chatIm.messageBodyType.candidate, JSON.stringify(e.candidate));
}
}
// 监听远程端发送的音视频流。
peerConnection.ontrack = onRemoteStream;
// 将本地音频轨添加到 PeerConnection 对象中。
localStream.getTracks().forEach(function (track) {
peerConnection.addTrack(track, localStream);
console.log("添加本地音视频流到远程端");
});
//
console.log("peerConnection 对象初始化完成。");
}
}
/*
* 关闭本地媒体流。
*/
function closeLocalMedia() {
if (localStream == null) {
return;
}
// 遍历每个 track,并将其关闭。
localStream.getTracks().forEach(function (track) {
track.stop();
});
localStream = null;
console.log("已关闭 localStream 对象。");
}
/*
* 通过 webSocket 向远程端发送数据。
* data: 需要发送的数据对象。
*/
function sendMessage(type, data) {
console.log("发送消息,type: " + type, data);
var pack = {
// 聊天类型。
bodyType: type,
// 内容。
content: data
};
// 向服务器发送数据。
chatIm.send(pack);
}
function onConnect(event) {
}
function onConnectStatus(v) {
connecting = !v;
// 显示当前状态。
showCallStatus(status);
}
/*
* 响应 webSocket 发送过来的消息。
*/
function onMessage(data) {
console.log(data);
if (data.type == chatIm.messageReportType.heartbeat) {
return;
}
if (data.type == chatIm.messageReportType.roomDisband || data.type == chatIm.messageReportType.roomDisband) {
if (status != statusEnum.normal)
$.alert("通话意外中断,请重新呼叫", clientHangup);
return;
}
if (data.type != chatIm.messageReportType.receive) {
return;
}
// 获取到消息内容.
var message = data.value[0];
switch (message.bodyType) {
// 有用户接受语音通话邀请了。
case chatIm.messageBodyType.acceptJoinRoom:
receiveAccept();
break;
// 对方退出了。
case chatIm.messageBodyType.quitRoom:
$.alert("对方已挂断");
hangup();
hangupStatus();
break;
// 远程端发送的 offer sdp。
case chatIm.messageBodyType.offer:
if (peerConnection == null) {
console.error("peerConnection is null.");
return;
}
var json = JSON.parse(message.content);
console.log("设置远程端发送的 offer", json);
// 设置媒体协商。
peerConnection.setRemoteDescription(new RTCSessionDescription(json)).then(function (e) {
console.log("设置远程端发送的 offer 成功", e);
// 创建 answer。
peerConnection.createAnswer().then(onAnswer).catch(function (e) {
console.error("Failed to create answer: ", e);
});
}).catch(function (e) {
console.log("设置远程端发送的 offer 失败", e);
});
break;
// 远程端发送的 answer sdp。
case chatIm.messageBodyType.answer:
if (peerConnection == null) {
console.error("peerConnection is null.");
return;
}
var json = JSON.parse(message.content);
console.log("设置远程端发送的 answer", json);
// 设置媒体协商。
peerConnection.setRemoteDescription(new RTCSessionDescription(json)).then(function (e) {
console.log("设置远程端发送的 answer 成功", e);
}).catch(function (e) {
console.log("设置远程端发送的 answer 失败", e);
});
break;
// 收到远程端发送 Candidate 消息。
case chatIm.messageBodyType.candidate:
if (peerConnection == null) {
console.error("peerConnection is null.");
return;
}
var json = JSON.parse(message.content);
console.log("设置远程端发送的 candidate", json);
// 将远程端 Candidate 消息添加到 PeerConnection 中。
peerConnection.addIceCandidate(new RTCIceCandidate(json));
break;
// 人工坐席繁忙。
case chatIm.messageBodyType.busySeats:
status = statusEnum.busySeats;
setTimeout(function () {
if (status == statusEnum.busySeats) {
$.alert("人工座席繁忙,请稍候再拨");
// 取消。
clientHangup();
}
}, 3000);
break;
}
}
function showCallStatus(v) {
status = v;
if (connecting && status == 0) {
callStatus.text("正在连接服务器");
} else if (connecting) {
callStatus.text("当前网络不稳定");
} else if (status == statusEnum.call) {
callStatus.text("呼叫中");
} else if (status == statusEnum.connecting) {
callStatus.text("正在与对方建立连接");
}
else if (status == statusEnum.connected) {
callStatus.text("已接通");
}
else {
callStatus.text("");
}
}
function init() {
// 获取 id、gid。
var id = system.getCookie("audio-id");
var gid = system.getCookie("audio-gid");
// 设置登录状态参数。
var signInState = ("?chatsa=" + encodeURIComponent(id) + "&chatlt=AYUKLOP&gid=" + encodeURIComponent(gid));
var host = (urlConfig.chatApiUrl + signInState);
chatIm.init(host, {
onConnect: onConnect,
onConnectStatus: onConnectStatus,
onMessage: onMessage,
onMessageSendFail: function (id) {
console.log(id + " 发送失败");
}
});
onLocalMediaStreamCall = function () {
// 创建聊天室。
var pack = {
// 创建聊天室。
bodyType: chatIm.messageBodyType.createRoom,
// 内容。
content: null
};
// 向服务器发送数据。
chatIm.send(pack);
}
onLocalMediaStreamErrorCall = hangupStatus;
// 给呼叫按钮设置点击事件。
$("#call").bind("click", clientCall);
$("#hangup").bind("click", clientHangup);
}
function clientCall() {
// 启动语音通话。
if (!startAudioCall()) {
return;
}
callAudio.load();
// 播放呼叫音频。
callAudio.play();
// 隐藏呼叫按钮。
$("#call").hide();
// 显示取消按钮。
$("#hangup").show();
// 设置为正在呼叫的状态。
showCallStatus(statusEnum.call);
}
function clientHangup() {
console.log("主动挂断");
hangupStatus();
// 停止语音通话。
stopAudioCall();
//
sendMessage(chatIm.messageBodyType.quitRoom, null);
}
// 取消/挂断状态。
function hangupStatus() {
callAudio.pause();
$("#hangup").hide();
$("#call").show();
stopCallConnected();
showCallStatus(statusEnum.normal);
}
var callConnectedTime = 0;
var callConnectedInterval;
// 启动已通话的时间。
function startCallConnected() {
if (callConnectedInterval != null) {
clearInterval(callConnectedInterval);
}
callConnectedTime = 0;
callConnectedInterval = setInterval(function () {
callConnectedTime++;
callStatus.text(parseInt((callConnectedTime / 60)).toString().padLeft(2, '0') + ":" + (callConnectedTime % 60).toString().padLeft(2, '0'));
}, 1000);
}
function stopCallConnected() {
if (callConnectedInterval != null) {
clearInterval(callConnectedInterval);
}
callStatus.text("");
}
总结
以上就在 H5 中实现语音客服系统的部分代码,需要完整的项目源代码可以在微信公众号中私信我。
微信公众号:KeisoftCN
更多推荐
已为社区贡献1条内容
所有评论(0)