我是 webrtc 的新手,当我执行我的演示时,显示这样的错误消息:
未捕获(承诺)DOMException:无法在“RTCPeerConnection”上执行“addIceCandidate”:远程描述为空
未捕获(承诺)DOMException:无法在“RTCPeerConnection”上执行“setRemoteDescription”:无法设置远程应答 sdp:在错误状态下调用:稳定
请帮忙!!!如果你能告诉我代码。非常感谢。
执行顺序不对吗?
在线演示: https://139.198.176.37/charclient/webrtc/p2p.html?type=offer 或 type=answer
我的服务器代码:
package com.demo.socketio;
import com.corundumstudio.socketio.SocketConfig;
import com.corundumstudio.socketio.SocketIONamespace;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.Transport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SocketioConfig {
private static final Logger log = LoggerFactory.getLogger(SocketioConfig.class);
@Bean
public SocketIOServer socketIOServer(SocketioServerProperties config) {
config.setOrigin(null); // 注意如果开放跨域设置,需要设置为null而不是"*"
config.setTransports(Transport.POLLING, Transport.WEBSOCKET);
SocketConfig socketConfig = config.getSocketConfig();
socketConfig.setReuseAddress(true);
final SocketIOServer server = new SocketIOServer(config);
server.start();
return server;
}
/**
* @param server
* @return
*/
@Bean
public SocketIONamespace socketIOChats(SocketIOServer server) {
SocketIONamespace n = server.addNamespace("/chattest");
n.addConnectListener(client -> {
log.info("**********connection success:" + client.getHandshakeData().getSingleUrlParam("roomid"));
});
n.addEventListener("message", Object.class, (client, data, ackRequest) -> { //new DataListener<MessageBase>() {
log.info("message client: {}", data);
n.getBroadcastOperations().sendEvent("message", data);
});
n.addDisconnectListener(client -> {
log.info("Disconnect success", client.getSessionId());
});
return n;
}}
我的 html 客户端代码:
<script src="../js/adapter.js"></script>
<script src="../js/socket.io/socket.io.js"></script>
<video id="remote-video"></video>
<video id="local-video" muted></video>
<button class="start-button" onclick="startLive()">start</button>
<script>
const message = {
el: document.querySelector('.logger'),
log (msg) {
this.el.innerHTML += `<span>${new Date().toLocaleTimeString()}:${msg}</span><br/>`;
},
error (msg) {
this.el.innerHTML += `<span class="error">${new Date().toLocaleTimeString()}:${msg}</span><br/>`;
}
};
const pcConfig = {
'iceServers': [{
'urls': 'turn:stun.ukerd.com:3478',// 免费直接可用的iceserver列表 https://gist.github.com/yetithefoot/7592580,也可以自建
'credential': "123456",
'username': "lvming"
}]
};
const target = location.search.slice(6);
console.log('target is :'+target)
const localVideo = document.querySelector('#local-video');
const remoteVideo = document.querySelector('#remote-video');
const button = document.querySelector('.start-button');
localVideo.onloadeddata = () => {
message.log('播放本地视频');
localVideo.play();
}
remoteVideo.onloadeddata = () => {
message.log('播放对方视频');
remoteVideo.play();
}
document.title = target === 'offer' ? '发起方' : '接收方';
message.log('第一步:信令通道(WebSocket)创建中......');
const socket = io.connect('https://139.198.176.37/chattest', {
transports: ['websocket', 'xhr-polling', 'jsonp-polling'],
path: "/socket.ix/"
});
socket.on('connect', function () {
message.log('第二步:信令通道创建成功!');
target === 'offer' && (button.style.display = 'block');
});
socket.on('error', function (e) {
message1.error('信令通道创建失败!'+e);
});
socket.on('message', function (e) {
const { type, sdp, iceCandidate } = JSON.parse(e) //e.data
console.log('type is :'+type)
console.log('sdp is :'+sdp)
console.log('iceCandidate is :'+iceCandidate)
console.log('start and end')
if (type === 'answer') {
peer.setRemoteDescription(new RTCSessionDescription({ type, sdp }));
} else if (type === 'answer_ice') {
peer.addIceCandidate(iceCandidate);
} else if (type === 'offer') {
startLive(new RTCSessionDescription({ type, sdp }));
} else if (type === 'offer_ice') {
peer.addIceCandidate(iceCandidate);
}
});
const peer = new RTCPeerConnection(pcConfig);
peer.ontrack = e => {
if (e && e.streams) {
message.log('收到对方音频/视频流数据...');
remoteVideo.srcObject = e.streams[0];
}
};
peer.onicecandidate = e => {
if (e.candidate) {
message.log('搜集并发送候选人');
socket.send(JSON.stringify({
type: `${target}_ice`,
iceCandidate: e.candidate
}));
} else {
message.log('候选人收集完成!');
}
};
async function startLive (offerSdp) {
target === 'offer' && (button.style.display = 'none');
let stream;
try {
message.log('尝试调取本地摄像头/麦克风');
stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
message.log('摄像头/麦克风获取成功!');
localVideo.srcObject = stream;
} catch(err) {
console.log(err);
message.error('摄像头/麦克风获取失败!');
return;
}
message.log(`------ WebRTC ${target === 'offer' ? '发起方' : '接收方'}流程开始 ------`);
message.log('将媒体轨道添加到轨道集');
stream.getTracks().forEach(track => {
peer.addTrack(track, stream);
});
//发起方 offer
if (!offerSdp) {
message.log('创建本地SDP');
const offer = await peer.createOffer();
await peer.setLocalDescription(offer);
message.log(`传输发起方本地SDP`);
socket.send(JSON.stringify(offer));
} else {//接收方
message.log('接收到发送方SDP');
await peer.setRemoteDescription(offerSdp);
message.log('创建接收方(应答)SDP');
const answer = await peer.createAnswer();
message.log(`传输接收方(应答)SDP`);
socket.send(JSON.stringify(answer));
await peer.setLocalDescription(answer);
}
}
</script>