NMS v3.21基于WHIP协议(WebRTC-HTTP Ingestion Protocol)重新实现WebRTC推流功能。
[webrtc]
# 使用whip和whep标准协议实现webrtc直接推流和播放
# ip 服务部署在公网,设置公网IP地址,内网就填内网ip,使用此IP地址直接进行通讯, 如不设置使用stun识别
ip =
# port 监听端口, 设置后开启服务
port = 10000
# ice_tcp ice是否通过tcp传输, Chrome支持\OBS不支持
ice_tcp = 0
# stun 服务器地址, 多个服务器地址用 | 分割
stun = stun:stun.l.google.com:19302
关键参数说明:
ip
:当明确设置固定公网ip地址时,WebRTC使用1:1 的NAT通讯,像RTMP等协议一样进行快速直连。port
:WebRTC的通讯端口,目前直接使用TCP传输,在公网通讯中,不掉包、不乱序、不花屏、不卡顿。stun
:stun服务器,如果不配置固定公网ip,则通过stun识别公网地址。https://server_name:8443/live/stream.whip
http://server_ip:8000/live/stream.whip
location
字段,值为资源id
,以HTTP DELETE请求该地址时,就可以立即结束推流。<video id="localVideo" playsinline autoplay muted>video>
<div class="box">
<button id="startButton">Startbutton>
<button id="callButton">Callbutton>
<button id="hangupButton">Hang Upbutton>
div>
const startButton = document.getElementById('startButton');
const callButton = document.getElementById('callButton');
const hangupButton = document.getElementById('hangupButton');
callButton.disabled = true;
hangupButton.disabled = true;
startButton.addEventListener('click', start);
callButton.addEventListener('click', call);
hangupButton.addEventListener('click', hangup);
const localVideo = document.getElementById('localVideo');
localVideo.addEventListener('loadedmetadata', function () {
console.log(`Local video videoWidth: ${this.videoWidth}px, videoHeight: ${this.videoHeight}px`);
});
let localStream;
let pc1;
let whipLocation;
const offerOptions = {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
};
const configuration = {
iceServers: [
{
urls: "stun:stun.l.google.com:19302"
}
]
};
async function start() {
console.log('Requesting local stream');
startButton.disabled = true;
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: { width: 1280, height: 720, frameRate: 30 } });
console.log('Received local stream');
localVideo.srcObject = stream;
localStream = stream;
callButton.disabled = false;
} catch (e) {
alert(`getUserMedia() error: ${e.name}`);
}
}
async function call() {
callButton.disabled = true;
hangupButton.disabled = false;
console.log('Starting call');
const videoTracks = localStream.getVideoTracks();
const audioTracks = localStream.getAudioTracks();
if (videoTracks.length > 0) {
console.log(`Using video device: ${videoTracks[0].label}`);
}
if (audioTracks.length > 0) {
console.log(`Using audio device: ${audioTracks[0].label}`);
}
console.log('RTCPeerConnection configuration:', configuration);
pc1 = new RTCPeerConnection(configuration);
console.log('Created local peer connection object pc1');
// pc1.addEventListener('icecandidate', e => onIceCandidate(pc1, e));
pc1.onicegatheringstatechange = gatheringStateChange;
pc1.addEventListener('iceconnectionstatechange', e => onIceStateChange(pc1, e));
localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));
console.log('Added local stream to pc1');
try {
console.log('pc1 createOffer start');
const offer = await pc1.createOffer(offerOptions);
await pc1.setLocalDescription(offer);
const answer = await postSDPOffer("https://192.168.0.2:8443/live/demo.whip", pc1.localDescription.sdp);
await pc1.setRemoteDescription(answer);
whipLocation = answer.location;
// await onCreateOfferSuccess(offer);
} catch (e) {
onCreateSessionDescriptionError(e);
}
}
async function postSDPOffer(url, sdp) {
let result = {
status: 0,
type: 'answer',
};
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/sdp',
"video-bitrate": 2_000_000,
"video-keyint": 3,
"audio-bitrate": 64_000
},
body: sdp
});
result.status = response.status;
result.sdp = await response.text();
result.location = response.headers.get("Location");
} catch (error) {
result.error = error;
}
return result;
}
function hangup() {
console.log('Ending call');
pc1.close();
pc1 = null;
hangupButton.disabled = true;
callButton.disabled = false;
fetch(whipLocation, { method: "DELETE" });
}
注意:
NodeMedia Webrtc WHIP 推流 Demo(右键可查看完整源码)