Работа RTCPeerConnection
getUserMedia() very simple demo #WebRTC
var constraints = { video: true, audio: false };
var localMediaStream, remoteMediaStream; var leftLocalVideoElement, leftRemoteVideoElement; var rightLocalVideoElement, rightRemoteVideoElement;
var localRtcPeerConnection, remoteRtcPeerConnection; var localRtcDescription, remoteRtcDescription; var localIceCandidate, remoteIceCandidate;
function initPeerConnection(peerConnection) { peerConnection = new RTCPeerConnection();
peerConnection.ondatachannel = function (event) {console.log(event)};
peerConnection.onidentityresult = function (event) {console.log(event)};
peerConnection.onidpassertionerror = function (event) {console.log(event)};
peerConnection.onidpvalidationerror = function (event) {console.log(event)};
peerConnection.onnegotiationneeded = function (event) {console.log(event)};
peerConnection.onpeeridentity = function (event) {console.log(event)};
peerConnection.onremovestream = function (event) {console.log(event)};
return peerConnection;
}
function reportError(errMessage) { log_error("REPORT ERROR " + errMessage.name + ": " + errMessage.message); }
function start() { leftLocalVideoElement = document.getElementById('leftLocalVideoElement'); leftRemoteVideoElement = document.getElementById('leftRemoteVideoElement');
rightLocalVideoElement = document.getElementById('rightLocalVideoElement');
rightRemoteVideoElement = document.getElementById('rightRemoteVideoElement');
navigator.mediaDevices.getUserMedia(constraints)
.then(function (stream) {
localMediaStream = stream;
console.log('--- Подключаю локальный видеоисточник к левому локальному элементу video');
leftLocalVideoElement.srcObject = localMediaStream;
console.log('--- Инициирую локальный RTCPeerConnection');
localRtcPeerConnection = initPeerConnection(localRtcPeerConnection);
if (localRtcPeerConnection.connectionState) console.log('Локальный connectionState: ' + localRtcPeerConnection.connectionState);
console.log('Локальный iceConnectionState: ' + localRtcPeerConnection.iceConnectionState);
console.log('Локальный iceGatheringState: ' + localRtcPeerConnection.iceGatheringState);
console.log('Локальный signalingState: ' + localRtcPeerConnection.signalingState);
localRtcPeerConnection.onconnectionstatechange = function (event) {
console.log('Локальный connectionState: ' + localRtcPeerConnection.connectionState);
console.log(event);
};
localRtcPeerConnection.oniceconnectionstatechange = function (event) {
console.log('Локальный iceConnectionState: ' + localRtcPeerConnection.iceConnectionState);
console.log(event);
};
localRtcPeerConnection.onicegatheringstatechange = function (event) {
console.log('Локальный iceGatheringState: ' + localRtcPeerConnection.iceGatheringState);
console.log(event);
};
localRtcPeerConnection.onsignalingstatechange = function (event) {
console.log('Локальный signalingState: ' + localRtcPeerConnection.signalingState);
console.log(event);
};
localRtcPeerConnection.onicecandidate = function(event) {
console.log("*** Событие onicecandidate локального RTCPeerConnection");
console.log('onicecandidate: localRtcPeerConnection.signalingState: ' + localRtcPeerConnection.signalingState);
if (event.candidate) {
console.log("Получаем строку локального кандидата ICE: " + event.candidate.candidate);
console.log(event);
console.log('Из него создаём удалённого кандидата ICE.');
remoteIceCandidate = new RTCIceCandidate(event.candidate);
// Метод addIceCandidate предоставляет удалённого кандидата агенту ICE (ICE Agent).
remoteRtcPeerConnection.addIceCandidate(remoteIceCandidate)
.catch(reportError);
}
};
localRtcPeerConnection.ontrack = function (event) {
console.log("*** Событие ontrack локального RTCPeerConnection");
console.log('--- Подключаем поток к элементу video с идентификатором leftRemoteVideoElement');
console.log(event);
leftRemoteVideoElement.srcObject = event.streams[0];
};
// console.log(localRtcPeerConnection);
localMediaStream.getTracks().forEach(function (track) {
localRtcPeerConnection.addTrack(track, localMediaStream); // Возвращает экземпляр RTCRtpSender
// Создаёт событие negotiationneeded.
});
console.log('--- Создаю локальное описание SDP RTCSessionDescription');
return localRtcPeerConnection.createOffer();
})
.then(function(offer) {
console.log('--- Добавляю локальное описание SDP RTCSessionDescription в локальный RTCPeerConnection');
console.log(offer);
localRtcDescription = offer;
// Создаёт событие signalingstatechange (Event)
// Создаёт событие icegatheringstatechange (Event)
// Создаёт событие icecandidate (RTCPeerConnectionIceEvent) 3 раза
// Создаёт событие icegatheringstatechange (Event)
// Создаёт событие icecandidate (RTCPeerConnectionIceEvent) 2 раза
return localRtcPeerConnection.setLocalDescription(localRtcDescription);
})
.then(function() {
console.log('--- RTCSessionDescription в localDescription добавлен.');
console.log('--- Инициирую удалённый RTCPeerConnection');
remoteRtcPeerConnection = initPeerConnection(remoteRtcPeerConnection);
if (remoteRtcPeerConnection.connectionState) console.log('Удалённый connectionState: ' + remoteRtcPeerConnection.connectionState);
console.log('Удалённый iceConnectionState: ' + remoteRtcPeerConnection.iceConnectionState);
console.log('Удалённый iceGatheringState: ' + remoteRtcPeerConnection.iceGatheringState);
console.log('Удалённый signalingState: ' + remoteRtcPeerConnection.signalingState);
remoteRtcPeerConnection.onconnectionstatechange = function (event) {
console.log('Удалённый connectionState: ' + remoteRtcPeerConnection.connectionState);
console.log(event);
};
remoteRtcPeerConnection.oniceconnectionstatechange = function (event) {
console.log('Удалённый iceConnectionState: ' + remoteRtcPeerConnection.iceConnectionState);
console.log(event);
};
remoteRtcPeerConnection.onicegatheringstatechange = function (event) {
console.log('Удалённый iceGatheringState: ' + remoteRtcPeerConnection.iceGatheringState);
console.log(event);
};
remoteRtcPeerConnection.onsignalingstatechange = function (event) {
console.log('Удалённый signalingState: ' + remoteRtcPeerConnection.signalingState);
console.log(event);
};
remoteRtcPeerConnection.onicecandidate = function(event) {
console.log('onicecandidate: remoteRtcPeerConnection.signalingState: ' + remoteRtcPeerConnection.signalingState);
if (event.candidate) {
console.log("Получаем строку удалённого кандидата ICE: " + event.candidate.candidate);
console.log(event);
console.log('Из него создаём локального кандидата ICE.');
localIceCandidate = new RTCIceCandidate(event.candidate);
// Метод addIceCandidate предоставляет удалённого кандидата агенту ICE (ICE Agent).
localRtcPeerConnection.addIceCandidate(localIceCandidate)
.catch(reportError);
}
};
remoteRtcPeerConnection.ontrack = function (event) {
console.log("*** Событие Track");
console.log('Подключаем поток к элементу video с идентификатором rightRemoteVideoElement');
console.log(event);
rightRemoteVideoElement.srcObject = event.streams[0];
};
console.log("--- Удалённый узел записывает полученное описание сессии в remoteDescription");
return remoteRtcPeerConnection.setRemoteDescription(localRtcDescription);
})
.then(function () {
return navigator.mediaDevices.getUserMedia(constraints);
})
.then(function (stream) {
remoteMediaStream = stream;
rightLocalVideoElement.srcObject = remoteMediaStream;
remoteMediaStream.getTracks().forEach(function (track) {
remoteRtcPeerConnection.addTrack(track, remoteMediaStream);
});
return remoteRtcPeerConnection.createAnswer();
})
.then(function (answer) {
remoteRtcDescription = answer;
return remoteRtcPeerConnection.setLocalDescription(answer);
})
.then(function() {
localRtcPeerConnection.setRemoteDescription(remoteRtcDescription);
console.log('LOCAL TRANSCIEVERS');
console.log(localRtcPeerConnection.getTransceivers());
console.log('LOCAL SENDERS');
console.log(localRtcPeerConnection.getSenders());
console.log('LOCAL RECEIVERS');
console.log(localRtcPeerConnection.getReceivers());
console.log('REMOTE TRANSCIEVERS');
console.log(remoteRtcPeerConnection.getTransceivers());
console.log('REMOTE SENDERS');
console.log(remoteRtcPeerConnection.getSenders());
console.log('REMOTE RECEIVERS');
console.log(remoteRtcPeerConnection.getReceivers());
})
.catch(function (error) {
console.log(error);
});
}
Данный пример демонстрирует настройку и создание связи между двумя объектами RTCPeerConnection на одной странице. Всё по честному, только без использования сигнального сервера. Требуемые описания SDP и кандидаты ICE передаются напрямую между объектами. Чуть позднее опишу всё подключение пошагово. Особо любопытные могут посмотреть вывод консоли и код JS.
Начать трансляцию## Образец SDP из приглашения
Врезки начинающиеся с символа # в описании SDP вставлены мной.
v=0
# v= (protocol version) - версия протокола. Тут всегда стоит 0.
o=- 4562398619592453647 2 IN IP4 127.0.0.1
# o= (originator and session identifier) - создатель сессии и её номер
# o=<username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address>
s=-
# s= (session name) - название сессии
t=0 0
# t= (time the session is active) - время активности сессии
# t=<start-time> <stop-time>
a=group:BUNDLE video
a=msid-semantic: WMS 6PN6rSNX0tmmlUFV07tzu5W9QPf2IhAwQMx8
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 122 127 121 125 107 108 109 124 120 123 119 114
# m= (media name and transport address)
# m=<media> <port> <proto> <fmt> ...
c=IN IP4 0.0.0.0
# c= (connection information) - информация о подключении (почему стоят нули?)
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:vP2q
a=ice-pwd:b5DkIXEqoSmXSGaPb35UyoNL
a=ice-options:trickle
a=fingerprint:sha-256 D9:92:BA:98:AF:80:ED:4B:06:38:89:8D:BC:FA:FC:FF:A0:C9:B1:6E:0A:C8:3B:7B:1A:3D:20:98:51:1B:D7:77
a=setup:actpass
a=mid:video
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:4 urn:3gpp:video-orientation
a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=sendrecv
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 x-google-profile-id=0
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 H264/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:102 H264/90000
a=rtcp-fb:102 goog-remb
a=rtcp-fb:102 transport-cc
a=rtcp-fb:102 ccm fir
a=rtcp-fb:102 nack
a=rtcp-fb:102 nack pli
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
a=rtpmap:122 rtx/90000
a=fmtp:122 apt=102
a=rtpmap:127 H264/90000
a=rtcp-fb:127 goog-remb
a=rtcp-fb:127 transport-cc
a=rtcp-fb:127 ccm fir
a=rtcp-fb:127 nack
a=rtcp-fb:127 nack pli
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:121 rtx/90000
a=fmtp:121 apt=127
a=rtpmap:125 H264/90000
a=rtcp-fb:125 goog-remb
a=rtcp-fb:125 transport-cc
a=rtcp-fb:125 ccm fir
a=rtcp-fb:125 nack
a=rtcp-fb:125 nack pli
a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:107 rtx/90000
a=fmtp:107 apt=125
a=rtpmap:108 H264/90000
a=rtcp-fb:108 goog-remb
a=rtcp-fb:108 transport-cc
a=rtcp-fb:108 ccm fir
a=rtcp-fb:108 nack
a=rtcp-fb:108 nack pli
a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d0032
a=rtpmap:109 rtx/90000
a=fmtp:109 apt=108
a=rtpmap:124 H264/90000
a=rtcp-fb:124 goog-remb
a=rtcp-fb:124 transport-cc
a=rtcp-fb:124 ccm fir
a=rtcp-fb:124 nack
a=rtcp-fb:124 nack pli
a=fmtp:124 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032
a=rtpmap:120 rtx/90000
a=fmtp:120 apt=124
a=rtpmap:123 red/90000
a=rtpmap:119 rtx/90000
a=fmtp:119 apt=123
a=rtpmap:114 ulpfec/90000
a=ssrc-group:FID 293764515 294041450
a=ssrc:293764515 cname:sSnZk2YDsY1wR11y
a=ssrc:293764515 msid:6PN6rSNX0tmmlUFV07tzu5W9QPf2IhAwQMx8 28c903e6-1a01-4e1d-a6cf-3e3dcda13384
a=ssrc:293764515 mslabel:6PN6rSNX0tmmlUFV07tzu5W9QPf2IhAwQMx8
a=ssrc:293764515 label:28c903e6-1a01-4e1d-a6cf-3e3dcda13384
a=ssrc:294041450 cname:sSnZk2YDsY1wR11y
a=ssrc:294041450 msid:6PN6rSNX0tmmlUFV07tzu5W9QPf2IhAwQMx8 28c903e6-1a01-4e1d-a6cf-3e3dcda13384
a=ssrc:294041450 mslabel:6PN6rSNX0tmmlUFV07tzu5W9QPf2IhAwQMx8
a=ssrc:294041450 label:28c903e6-1a01-4e1d-a6cf-3e3dcda13384
</unicast-address></addrtype></nettype></sess-version></sess-id></username>
Комментарии
Пока комментариев нет.