{"id":366871,"date":"2024-05-21T03:09:15","date_gmt":"2024-05-21T03:09:15","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=366871"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=366871","title":{"rendered":"<span>\u041f\u043e\u0433\u0440\u0443\u0436\u0435\u043d\u0438\u0435 \u0432 Web RTC \u0438\u043b\u0438 \u043f\u0438\u0448\u0435\u043c SFU \u0441\u0432\u043e\u0438\u043c\u0438 \u0441\u0438\u043b\u0430\u043c\u0438<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/027\/051\/42c\/02705142c45e93d3d93ce29464e05992.jpeg\" width=\"1920\" height=\"1080\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/027\/051\/42c\/02705142c45e93d3d93ce29464e05992.jpeg\" data-blurred=\"true\"\/><\/figure>\n<blockquote>\n<p><a href=\"https:\/\/webrtc.org\" rel=\"noopener noreferrer nofollow\">WebRTC\u00a0<\/a>\u2014 \u044d\u0442\u043e \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043d\u0430\u044f \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f, \u043f\u0440\u0435\u0434\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u0430\u044f \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u043f\u043e\u0442\u043e\u043a\u043e\u0432\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u043c\u0435\u0436\u0434\u0443 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430\u043c\u0438 \u0438\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u043c\u0438 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0438 \u0434\u0432\u0443\u0445\u0442\u043e\u0447\u0435\u0447\u043d\u043e\u0439 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438\u00a0<em>(point-to-point transmission)<\/em>.<\/p>\n<\/blockquote>\n<p>Web RTC \u0443\u0436\u0435 \u0434\u0430\u0432\u043d\u043e \u0438\u043c\u0435\u0435\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u0432 \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043e\u0432, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043e\u0431\u0445\u043e\u0434\u0438\u0442\u044c \u0441\u0442\u043e\u0440\u043e\u043d\u043e\u0439 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044e \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0433\u043b\u0443\u043f\u043e. \u0412\u043e\u0442 \u0438 \u044f \u0442\u0430\u043a \u043f\u043e\u0434\u0443\u043c\u0430\u043b, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0435\u0442-\u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0440\u0435\u0448\u0438\u043b \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c sfu-\u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0430 golang. <\/p>\n<h2>\u041e \u0441\u0430\u043c\u043e\u043c Web RTC <\/h2>\n<p>\u0417\u0434\u0435\u0441\u044c \u044f \u043a\u0440\u0430\u0442\u043a\u043e \u043f\u0440\u043e\u0431\u0435\u0433\u0443\u0441\u044c \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0440\u0430\u0431\u043e\u0442\u044b Web RTC, \u043a\u043e\u043c\u0443 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043b\u0435\u0437\u0442\u044c \u0447\u0443\u0442\u044c \u043f\u043e\u0433\u043b\u0443\u0431\u0436\u0435 \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e \u0441\u0441\u044b\u043b\u043a\u0443 <a href=\"https:\/\/webrtcforthecurious.com\" rel=\"noopener noreferrer nofollow\">\u0437\u0434\u0435\u0441\u044c<\/a>.  <\/p>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0434\u0432\u0430 \u043f\u0438\u0440\u0430 \u0441\u043c\u043e\u0433\u043b\u0438 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0441\u0435\u0431\u0435 RTCPeerConnection, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b SDP (Session Description Protocol). \u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0438\u043c\u0435\u0435\u0442 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 &#171;\u043a\u043b\u044e\u0447-\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435&#187; \u0438 \u043f\u043e \u0441\u0443\u0442\u0438 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0432\u0437\u044f\u0442\u043e\u0433\u043e \u043f\u0438\u0440\u0430 (\u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0441\u0430\u043c\u043e \u0437\u0430 \u0441\u0435\u0431\u044f \u0433\u043e\u0432\u043e\u0440\u0438\u0442). <\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 SessionDescription <\/p>\n<pre><code>v=0 o=- 0 0 IN IP4 127.0.0.1 s=- c=IN IP4 127.0.0.1 t=0 0 m=audio 4000 RTP\/AVP 111 a=rtpmap:111 OPUS\/48000\/2 m=video 4002 RTP\/AVP 96 a=rtpmap:96 VP8\/90000<\/code><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e \u043a\u0430\u043a \u043f\u0438\u0440 \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043b \u043d\u0430\u043c SessionDescription (\u0434\u0430\u043b\u0435\u0435 \u043f\u0440\u043e\u0441\u0442\u043e SD), \u043f\u0438\u0440 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0435\u0433\u043e \u0434\u0440\u0443\u0433\u043e\u043c\u0443 \u043f\u0438\u0440\u0443 \u0432 \u0432\u0438\u0434\u0435 \u043e\u0444\u0444\u0435\u0440\u0430. <\/p>\n<p>\u0417\u0434\u0435\u0441\u044c \u0441\u0440\u0430\u0437\u0443 \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u0435\u0442 \u0432\u043e\u043f\u0440\u043e\u0441: &#171;\u0410 \u043a\u0443\u0434\u0430, \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c?&#187; \u0418 \u0432\u043e\u043f\u0440\u043e\u0441 \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439, \u0441\u0430\u043c \u043f\u043e \u0441\u0435\u0431\u0435 \u043f\u0438\u0440 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0437\u043d\u0430\u0442\u044c ip-\u0430\u0434\u0440\u0435\u0441 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u043f\u0438\u0440\u0430. \u0422\u0443\u0442 \u043d\u0430 \u043f\u043e\u043c\u043e\u0449\u044c \u043d\u0430\u043c \u043f\u0440\u0438\u0445\u043e\u0434\u044f\u0442: turn &amp; signal \u0441\u0435\u0440\u0432\u0435\u0440\u0430. <\/p>\n<p>Signal &#8212; \u044d\u0442\u043e \u0441\u0435\u0440\u0432\u0435\u0440, \u043a \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u044e\u0442\u0441\u044f \u043f\u0438\u0440\u044b \u0434\u043b\u044f \u043e\u0431\u043c\u0435\u043d\u0430 SD. \u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c\u0441\u044f \u043f\u043e\u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043d\u0430 \u043d\u0435\u043c \u0447\u0443\u0442\u044c \u043f\u043e\u0437\u0436\u0435.  <\/p>\n<p>Turn \u0441\u0435\u0440\u0432\u0435\u0440 \u0440\u0435\u0448\u0430\u0435\u0442 \u0434\u0440\u0443\u0433\u0443\u044e \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443. \u041d\u0430\u0448\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0447\u0430\u0441\u0442\u043e \u0432\u044b\u0445\u043e\u0434\u044f\u0442 \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u0447\u0435\u0440\u0435\u0437 wifi, \u0430 \u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043e\u043d\u0438 \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0432\u043d\u0443\u0442\u0440\u0438 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e NAT \u0432\u0430\u0448\u0435\u0433\u043e \u0440\u043e\u0443\u0442\u0435\u0440\u0430 \u0438 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e ip-\u0430\u0434\u0440\u0435\u0441\u0430 \u0443 \u043d\u0438\u0445 \u043d\u0435\u0442. \u041d\u043e \u0434\u043b\u044f turn \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u044d\u0442\u043e \u043d\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430. \u0421 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0441\u0432\u043e\u0438\u0445 &#171;Magic Cookie&#187; \u043e\u043d \u0432\u0441\u0435 \u0440\u0430\u0432\u043d\u043e \u0434\u043e\u0431\u0435\u0440\u0435\u0442\u0441\u044f \u0434\u043e \u0432\u0430\u0448\u0435\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430. <\/p>\n<p>\u0422\u0430\u043a, SD \u043c\u044b \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043b\u0438, \u0443\u0437\u043d\u0430\u043b\u0438 \u043d\u0430\u0448 IP, \u0434\u0430\u0436\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043b\u0438 \u043d\u0430\u0448 SD \u0432 \u0432\u0438\u0434\u0435 offer \u043d\u0430 signal \u0441\u0435\u0440\u0432\u0435\u0440. \u0414\u0430\u043b\u0435\u0435 \u0432\u0442\u043e\u0440\u043e\u0439 \u043f\u0438\u0440 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u0441\u0435\u0431\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u043d\u0430\u043c\u0438 SD, \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u0441\u0432\u043e\u0439 SD \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0435\u0433\u043e signal \u0441\u0435\u0440\u0432\u0435\u0440\u0443 \u0432 \u0432\u0438\u0434\u0435 answer. \u041f\u0435\u0440\u0432\u044b\u0439 \u043f\u0438\u0440 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 SD \u0432 \u0432\u0438\u0434\u0435 answer \u043a \u0441\u0435\u0431\u0435.<\/p>\n<blockquote>\n<p>\u0412\u00a0\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043a\u00a0\u043e\u0431\u043c\u0435\u043d\u0443 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0435\u0439 \u043e\u00a0\u043c\u0435\u0434\u0438\u0430(\u043e\u0431\u0441\u0443\u0436\u0434\u0451\u043d\u043d\u043e\u0439 \u0432\u044b\u0448\u0435 \u0432\u00a0offer\/answer \u0438 SDP), \u0443\u0437\u043b\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043e\u0431\u043c\u0435\u043d\u044f\u0442\u044c\u0441\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0435\u0439 \u043e\u00a0\u0441\u0435\u0442\u0435\u0432\u043e\u043c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0438. \u041e\u0431\u00a0\u044d\u0442\u043e\u043c \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e \u043a\u0430\u043a\u00a0\u043e\u00a0ICE\u2011\u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0435 \u0438 \u0434\u0435\u0442\u0430\u043b\u044f\u0445 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043c\u0435\u0442\u043e\u0434\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u0443\u0437\u0435\u043b \u043c\u043e\u0436\u0435\u0442 \u043e\u0431\u0449\u0430\u0442\u044c\u0441\u044f (\u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0438\u043b\u0438\u00a0\u0447\u0435\u0440\u0435\u0437 TURN\u2011\u0441\u0435\u0440\u0432\u0435\u0440).ICE\u2011\u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430\u043c\u0438 \u043f\u0438\u0440\u044b \u0442\u0430\u043a\u0436\u0435 \u0434\u0435\u043b\u044f\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 signal \u0441\u0435\u0440\u0432\u0435\u0440. <\/p>\n<\/blockquote>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/547\/2cf\/0a5\/5472cf0a5d59a51b1732df767798467f.png\" width=\"641\" height=\"559\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/547\/2cf\/0a5\/5472cf0a5d59a51b1732df767798467f.png\"\/><\/figure>\n<p>\u0418 \u0432\u043e\u0442, \u043f\u043e\u0441\u043b\u0435 \u0432\u0441\u0435\u0445 \u0442\u0430\u043d\u0446\u0435\u0432 \u0441 \u0431\u0443\u0431\u043d\u043e\u043c, \u043c\u044b \u043d\u0430\u043a\u043e\u043d\u0435\u0446 \u043c\u043e\u0436\u0435\u043c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043d\u0430\u0448 RTCPeerConnection. <\/p>\n<h2>\u0410 \u0437\u0430\u0447\u0435\u043c \u043d\u0430\u043c \u043d\u0443\u0436\u0435\u043d SFU ?<\/h2>\n<p>Select Forwarding Unit \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043d\u0430 \u043f\u043e\u043c\u043e\u0449\u044c \u0442\u043e\u0433\u0434\u0430, \u043a\u043e\u0433\u0434\u0430 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u0438\u0440\u043e\u0432 \u0432 \u043e\u0434\u043d\u043e\u0439 \u0441\u0435\u0441\u0441\u0438\u0438 \u0434\u043e\u0441\u0442\u0438\u0433\u0430\u0435\u0442 7+. \u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043f\u0440\u0438\u043c\u0435\u0440: <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/be9\/db5\/b23\/be9db5b23399f406182bd7dc75ea350f.png\" width=\"1338\" height=\"1266\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/be9\/db5\/b23\/be9db5b23399f406182bd7dc75ea350f.png\"\/><\/figure>\n<p>\u041c\u044b \u0438\u043c\u0435\u0435\u043c 7 \u043f\u0438\u0440\u043e\u0432, \u043a\u0430\u0436\u0434\u044b\u0439 \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0432\u0438\u0434\u0435\u043e \u0438 \u0430\u0443\u0434\u0438\u043e \u0442\u0440\u0435\u043a\u0438. \u0412 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 p2p \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043c: 6+5+4+3+2+1=21 Peer \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435. \u0410 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0442\u0440\u0435\u043a\u043e\u0432 \u0431\u0443\u0434\u0435\u0442 \u0431\u043e\u043b\u044c\u0448\u0435 \u0432 4 \u0440\u0430\u0437\u0430(\u043e\u0442 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0442\u0430\u043a\u043e\u0433\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u0441 \u043e\u0431\u0435\u0438\u0445 \u0441\u0442\u043e\u0440\u043e\u043d \u0438\u0434\u0435\u0442 \u0432\u0438\u0434\u0435\u043e \u0438 \u0430\u0443\u0434\u0438\u043e \u0434\u043e\u0440\u043e\u0436\u043a\u0430).<\/p>\n<p>\u041d\u0430\u043b\u0438\u0446\u043e \u0430\u0431\u0441\u043e\u043b\u044e\u0442\u043d\u043e \u043d\u0435\u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u044b \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043e\u0442 \u043f\u043e\u043b\u043d\u043e\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u043e\u0439 \u0442\u043e\u043f\u043e\u043b\u043e\u0433\u0438\u0438 \u043a \u0437\u0432\u0435\u0437\u0434\u043e\u0447\u043d\u043e\u0439: <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/de5\/c29\/b74\/de5c29b74ee378fde728c0193152cba8.png\" width=\"1244\" height=\"1134\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/de5\/c29\/b74\/de5c29b74ee378fde728c0193152cba8.png\"\/><\/figure>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u0438\u043c\u0435\u0435\u043c 7 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439 PeerConnection, \u0433\u0434\u0435 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u0438\u0440\u0430 \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c 2 \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0438 12 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0440\u0435\u043a\u043e\u0432. \u0425\u043e\u0442\u044c Web RTC \u0438 \u0431\u044b\u043b \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u043f\u0440\u0435\u0434\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d \u0434\u043b\u044f \u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043e\u0432 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e, \u0438\u043c\u0435\u043d\u043d\u043e \u0432 \u0441\u0432\u044f\u0437\u043a\u0435 \u0441 SFU \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u043e \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u043c\u0443 \u043f\u0440\u043e\u0447\u0443\u0432\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u043c\u043e\u0449\u044c \u044d\u0442\u043e\u0439 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0438. <\/p>\n<p>\u0421\u043b\u0435\u0434\u0443\u0435\u0442 \u0442\u0430\u043a\u0436\u0435 \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u0438\u043c\u0435\u0435\u0442\u0441\u044f \u0435\u0449\u0435 \u043e\u0434\u0438\u043d \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f &#8212; MCU (Multipoint Control Unit). \u041d\u043e \u0442\u043e\u0433\u0434\u0430 \u0432 \u043e\u0431\u044f\u0437\u0430\u043d\u043d\u043e\u0441\u0442\u0438 \u043d\u0430\u0448\u0435\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0431\u0443\u0434\u0435\u0442 \u0435\u0449\u0435 \u0432\u0445\u043e\u0434\u0438\u0442\u044c \u0443\u043f\u0430\u043a\u043e\u0432\u043a\u0430 \u0432\u0441\u0435\u0445 \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0440\u0435\u043a\u043e\u0432 \u043a \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u043f\u0438\u0440\u0443 \u0432 \u0435\u0434\u0438\u043d\u044b\u0439 MediaStream. \u041e\u0434\u043d\u0430\u043a\u043e \u0441\u043e \u0441\u0442\u043e\u0440\u043e\u043d\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0431\u0443\u0434\u0435\u0442 \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u043c \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0441 \u044d\u0442\u0438\u043c\u0438 \u043f\u043e\u0442\u043e\u043a\u0430\u043c\u0438. <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/18e\/348\/170\/18e3481705a5d003beab2b2ba01aede3.png\" width=\"600\" height=\"450\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/18e\/348\/170\/18e3481705a5d003beab2b2ba01aede3.png\"\/><\/figure>\n<p>\u041f\u0440\u0438\u0432\u0435\u0434\u0443 \u043f\u0440\u0438\u043c\u0435\u0440: Zoom \u0438 \u0435\u0433\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0437\u0430\u0433\u043b\u0443\u0448\u0430\u0442\u044c, \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u0442\u044c \u043f\u043b\u0438\u0442\u043a\u0438 \u0441 \u0432\u0438\u0434\u0435\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439. \u0418\u043c\u0435\u043d\u043d\u043e \u0431\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u0442\u043e\u043c\u0443, \u0447\u0442\u043e \u043a\u0430\u0436\u0434\u0430\u044f \u043f\u043b\u0438\u0442\u043a\u0430 &#8212; \u044d\u0442\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 MediaStream, \u0441 \u043d\u0435\u0439 \u043c\u043e\u0436\u043d\u043e \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c. \u0415\u0441\u043b\u0438 \u0431\u044b \u043c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b\u0438 MCU, \u0442\u043e \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 \u0431\u044b \u043d\u0435 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u043f\u043b\u0438\u0442\u043e\u043a \u0441 \u0432\u0438\u0434\u0435\u043e, \u0430 \u0432 \u0435\u0434\u0438\u043d\u044b\u0439 \u0431\u043b\u043e\u043a \u0432\u0438\u0434\u0435\u043e \u043f\u043e\u0442\u043e\u043a\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 MCU \u0437\u0430\u0431\u043e\u0442\u043b\u0438\u0432\u043e \u043d\u0430\u043c \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u043b. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u043f\u043e\u0432\u044b\u0448\u0430\u044f \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u0432 \u0440\u0430\u0437\u044b, \u043c\u044b \u0443\u0440\u0435\u0437\u0430\u0435\u043c \u043c\u043d\u043e\u0433\u0438\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u0414\u0430, \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0442\u043e\u0433\u0434\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0441\u043e\u0439\u0434\u0435\u0442 \u043d\u0430 \u043d\u0435\u0442, \u043d\u043e \u043f\u043e\u043a\u0440\u044b\u0432\u0430\u044e\u0442 \u043b\u0438 \u044d\u0442\u0438 \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432\u0430 \u043f\u043e\u044f\u0432\u0438\u0432\u0448\u0438\u0435\u0441\u044f \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043a\u0438. \u042f \u0434\u0443\u043c\u0430\u044e, \u0447\u0442\u043e \u043d\u0435\u0442. <\/p>\n<h2>\u041f\u0440\u0438\u0441\u0442\u0443\u043f\u0430\u0435\u043c<\/h2>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u043c \u0432\u043e\u043f\u043b\u043e\u0449\u0430\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442 \u0432 \u0436\u0438\u0437\u043d\u044c. \u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c\u0441\u044f \u0441\u043e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u043e\u0439 \u043d\u0430\u0448\u0435\u0433\u043e sfu.<\/p>\n<figure class=\"float full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ae0\/454\/b32\/ae0454b324286e233c151ffb1f212f62.png\" width=\"1438\" height=\"1490\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ae0\/454\/b32\/ae0454b324286e233c151ffb1f212f62.png\"\/><\/figure>\n<p>\u041d\u0430\u0448 \u0441\u0435\u0440\u0432\u0435\u0440 \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u0442\u044c \u0438\u0437 \u0434\u0432\u0443\u0445 \u0447\u0430\u0441\u0442\u0435\u0439: Signal &amp; Coordinator. \u041f\u0435\u0440\u0432\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0442\u044c \u043e\u0431\u043c\u0435\u043d SD &amp; ICE-\u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430\u043c\u0438, \u0432\u0442\u043e\u0440\u043e\u0439 \u0431\u0443\u0434\u0435\u0442 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0438 \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u043f\u043e\u0442\u043e\u043a\u0438.<\/p>\n<h2>Peer<\/h2>\n<p>Peer \u0431\u0443\u0434\u0435\u0442 \u044f\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0440\u043d\u043e\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u043e\u0439 \u0438 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f <\/p>\n<pre><code class=\"go\">type Peer struct { id         string connection *webrtc.PeerConnection streams    map[string]*webrtc.TrackRemote mutex      sync.RWMutex socket     *websocket.Conn }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043f\u043e\u043a\u0430 \u0432\u0441\u0435 \u043f\u0440\u043e\u0441\u0442\u043e, \u043a\u043b\u0430\u0441\u0441 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0432 \u0441\u0435\u0431\u0435 \u0441\u043e\u043a\u0435\u0442, \u0441\u0430\u043c\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0438  \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u043e\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0442\u0440\u0435\u043a\u0438 <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u043f\u0438\u0448\u0435\u043c \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0438\u0440\u0430 <\/p>\n<pre><code class=\"go\">type PeerInterface interface { SetSocket(ws_conn *websocket.Conn) AddRemoteTrack(track *webrtc.TrackRemote) RemoveRemoteTrack(track *webrtc.TrackRemote) SetPeerConnection(conn *webrtc.PeerConnection) ReactOnOffer(offer webrtc.SessionDescription) ReactOnAnswer(answer_str string) }<\/code><\/pre>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043f\u043e\u043a\u0430 \u0432\u0441\u0435 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e, \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0435\u0442\u043e\u0434\u043e\u0432 \u0434\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u0435\u0439 Peer. \u0415\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435, \u043d\u0430 \u0447\u0435\u043c \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0441\u044f: ReactOnAnswer&amp; ReactOnOffer:<\/p>\n<pre><code class=\"go\">func (peer *Peer) ReactOnOffer(offer_str string) (webrtc.SessionDescription, error) { peer.mutex.Lock() defer peer.mutex.Unlock()  offer := webrtc.SessionDescription{ Type: webrtc.SDPTypeOffer, SDP:  offer_str, } err := peer.connection.SetRemoteDescription(offer) if err != nil { fmt.Println(err) return offer, err } fmt.Println(\"Remote Description was set for peer \", peer.id) answer, err := peer.connection.CreateAnswer(nil) _ = peer.connection.SetLocalDescription(answer) fmt.Println(\"Local Description was set for peer \", peer.id) if err != nil { return offer, err } fmt.Println(\"Answer was created in peer \", peer.id) return answer, nil  }<\/code><\/pre>\n<p>\u041a\u043e\u0433\u0434\u0430 \u043e\u0442 signal-\u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c Offer \u043e\u0442 \u0434\u0440\u0443\u0433\u043e \u043f\u0438\u0440\u0430, \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u0435\u0431\u0435 \u043f\u0440\u0438\u0448\u0435\u0434\u0448\u0438\u0439 SD, \u0434\u043b\u044f \u043d\u0430\u0441 \u044d\u0442\u043e \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0439 SD, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 SetRemoteDescription() \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u043d\u0430\u043c \u0432 \u043d\u0430\u0448\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0435. \u0414\u0430\u043b\u0435\u0435, \u043a\u0430\u043a \u0443\u0436\u0435 \u0431\u044b\u043b\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u043e \u0432 \u0442\u0435\u043e\u0440\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0447\u0430\u0441\u0442\u0438, \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c Answer, \u0447\u0442\u043e\u0431\u044b \u0434\u0440\u0443\u0433\u043e\u0439 \u043f\u0438\u0440 \u0442\u043e\u0436\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b RemoteDescription, \u043d\u043e \u043f\u0435\u0440\u0435\u0434 \u044d\u0442\u0438\u043c \u043c\u044b \u0442\u0430\u043a\u0436\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0441\u0435\u0431\u0435 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 LocalDescription<\/p>\n<pre><code class=\"go\">func (peer *Peer) ReactOnAnswer(answer_str string) error { peer.mutex.Lock() defer peer.mutex.Unlock() answer := webrtc.SessionDescription{ Type: webrtc.SDPTypeAnswer, SDP:  answer_str, } err := peer.connection.SetRemoteDescription(answer) if err != nil { fmt.Println(err) return err } return nil } <\/code><\/pre>\n<p>\u0421\u0438\u0442\u0443\u0430\u0446\u0438\u044f \u0438\u0434\u0435\u043d\u0442\u0438\u0447\u043d\u0430\u044f, \u043f\u0440\u043e\u0441\u0442\u043e \u0442\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u043d\u0430 \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u0431\u0430\u0440\u0440\u0438\u043a\u0430\u0434. \u041c\u044b \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043b\u0438 Offer, \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 Answer, \u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0438 RemoteDescription.<\/p>\n<h2>Room <\/h2>\n<p>\u041f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u043c\u0441\u044f \u0447\u0443\u0442\u044c \u0432\u044b\u0448\u0435. \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 Room \u0431\u0443\u0434\u0435\u0442 \u044f\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u043e\u0434\u043d\u043e\u0439 \u0441\u0435\u0441\u0441\u0438\u0435\u0439 \u0432\u0438\u0434\u0435\u043e \u0438\/\u0438\u043b\u0438 \u0430\u0443\u0434\u0438\u043e \u043a\u043e\u043d\u0444\u0435\u0440\u0435\u043d\u0446\u0438\u0438. <\/p>\n<pre><code class=\"go\">type Room struct { id     string mutex  sync.RWMutex peers  map[string]*Peer tracks map[string]*webrtc.TrackLocalStaticRTP }<\/code><\/pre>\n<p>\u041e\u043d\u0430 \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0432 \u0441\u0435\u0431\u0435 \u0432\u0441\u0435 \u043f\u0438\u0440\u044b \u0432 \u0434\u0430\u043d\u043d\u043e\u0439 \u043a\u043e\u043c\u043d\u0430\u0442\u0435, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0442\u0440\u0435\u043a\u0438 \u043a \u0441\u0435\u0431\u0435 \u0432 \u043f\u043e\u043b\u0435 tracks  \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0438\u0445 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u043c \u043f\u0438\u0440\u0430\u043c. <\/p>\n<p>\u041e\u043f\u0438\u0448\u0435\u043c \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435:<\/p>\n<pre><code class=\"go\">type RoomInterface interface { JoinRoom(id string) AddPeer(peer *Peer) RemovePeer(peer_id string) AddTrack(track *webrtc.TrackRemote) RemoveTrack(track *webrtc.TrackRemote) SendAnswer(message webrtc.SessionDescription, peer_id string) SendOffer(message webrtc.SessionDescription, peer_id string) SendICE(message *webrtc.ICECandidate, peer_id string) Signal() }<\/code><\/pre>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u0439, \u043f\u0440\u043e\u0431\u0435\u0436\u0438\u043c\u0441\u044f \u043f\u043e \u043d\u0438\u043c: <\/p>\n<ol>\n<li>\n<p>AddPeer(peer *Peer) &#8212; \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432 \u043f\u043e\u043b\u0435 room.peers<\/p>\n<\/li>\n<li>\n<p>RemovePeer(peer_id string) &#8212; \u0443\u0434\u0430\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u0437 \u043a\u043e\u043c\u043d\u0430\u0442\u044b, \u0435\u0441\u043b\u0438 \u0442\u0430\u043a\u043e\u0439 \u0438\u043c\u0435\u0435\u0442\u0441\u044f <\/p>\n<\/li>\n<li>\n<p>AddTrack(track *webrtc.TrackRemote) &#8212; \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u043d\u043e\u0432\u044b\u0439 \u0442\u0440\u0435\u043a \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u0442 \u0435\u0433\u043e \u043a \u0441\u0435\u0431\u0435 \u0432 room.tracks<\/p>\n<\/li>\n<li>\n<p>RemoveTrack(track *webrtc.TrackRemote) &#8212; \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0443\u0434\u0430\u043b\u044f\u0435\u0442 \u0442\u0440\u0435\u043a<\/p>\n<\/li>\n<li>\n<p>SendAnswer, SendOffer, SendICE &#8212; \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0442 Offer, Answer &amp; Ice-\u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0432\u0441\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u0435, \u043a\u0440\u043e\u043c\u0435 \u043f\u0438\u0440\u0430 \u0441\u043e id=peer_id.<\/p>\n<\/li>\n<\/ol>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0435\u0440\u0435\u0439\u0434\u0435\u043c \u043a \u0441\u0430\u043c\u043e\u043c\u0443 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u043c\u0443: room.Signal()<\/p>\n<pre><code class=\"go\">func (room *Room) Signal() { room.mutex.Lock() defer room.mutex.Unlock() attemptSync := func() (again bool) { for _, peer := range room.peers {            \/\/ 1) Check if peer is already closed if peer.connection.ConnectionState() == webrtc.PeerConnectionStateClosed { fmt.Println(\"Peer with peer_id\", peer.id, \"was disconnected\") room.RemovePeer(peer.id) return true }             \/\/ 2)  existingSenders := map[string]bool{} for _, sender := range peer.connection.GetSenders() { if sender.Track() == nil { continue }                 \/\/ 3) existingSenders[sender.Track().ID()] = true \/\/ If we have a RTPSender that doesn't map to a existing track remove and signal if _, ok := room.tracks[sender.Track().ID()]; !ok { if err := peer.connection.RemoveTrack(sender); err != nil { fmt.Println(\"Track\", sender.Track().ID(), \"was removed\") return true } } }  \/\/ 4) Don't receive videos we are sending, make sure we don't have loopback for _, receiver := range peer.connection.GetReceivers() { if receiver.Track() == nil { continue }  existingSenders[receiver.Track().ID()] = true } \/\/ 5) Add all track we aren't sending yet to the PeerConnection for trackID := range room.tracks { if _, ok := existingSenders[trackID]; !ok { if _, err := peer.connection.AddTrack(room.tracks[trackID]); err == nil { fmt.Println(\"New track are sending for peer\", peer.id) return true } else { fmt.Println(err) } } }             \/\/ 6) if peer.connection.PendingLocalDescription() != nil { fmt.Println(peer.connection.PendingLocalDescription()) offer, err := peer.connection.CreateOffer(&amp;webrtc.OfferOptions{ OfferAnswerOptions: webrtc.OfferAnswerOptions{}, ICERestart:         true, }) if err != nil { fmt.Println(\"Error in CreateOffer: \", err) return true } if err = peer.connection.SetLocalDescription(offer); err != nil { fmt.Println(\"Offer: \", offer) fmt.Println(\"Cannot set LocalDescription: \", err) return false }  offerString, err := json.Marshal(offer) if err != nil { fmt.Println(\"Marshalling failed: \", err) return true }  if err = peer.socket.WriteJSON(&amp;WsMessage{ Event: \"offer\", Data:  string(offerString), }); err != nil { fmt.Println(\"Cannot write message in WsMessage: \", err) return true } }  } return }     \/\/ 7) for syncAttempt := 0; ; syncAttempt++ { if syncAttempt == 25 { \/\/ Release the lock and attempt a sync in 3 seconds. We might be blocking a RemoveTrack or AddTrack go func() { time.Sleep(time.Second * 3) room.Signal() }() return }  if !attemptSync() { fmt.Println(\"Signalling finished\") break } } } <\/code><\/pre>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u043b\u043e\u0433\u0438\u043a\u0438 Room. \u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c\u0441\u044f \u043f\u043e\u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435:<\/p>\n<ol>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043d\u0430\u0448\u0438 \u0442\u0435\u043a\u0443\u0449\u0438\u0435 \u043f\u0438\u0440\u044b. \u0415\u0441\u043b\u0438 \u043e\u043d \u0437\u0430\u043a\u0440\u044b\u0442, \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u0435\u0433\u043e.<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0432\u0441\u0435 Senders() \u043e\u0442 \u044d\u0442\u043e\u0433\u043e \u043f\u0438\u0440\u0430. Sender &#8212; \u043f\u043e\u0442\u043e\u043a, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043a \u043d\u0430\u043c \u043e\u0442 \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0438\u0440\u0430.<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0435\u0441\u0442\u044c \u043b\u0438 \u0443 \u043d\u0430\u0441 &#171;\u0437\u0430\u0432\u0438\u0441\u0448\u0438\u0435&#187; \u0442\u0440\u0435\u043a\u0438, \u0442\u043e \u0435\u0441\u0442\u044c \u0442\u0440\u0435\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0441\u0445\u043e\u0434\u0438\u043b\u0438 \u043e\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f,  \u0443\u0436\u0435 \u043f\u043e\u043a\u0438\u043d\u0443\u0432\u0448\u0435\u0433\u043e \u043a\u043e\u043c\u043d\u0430\u0442\u0443.<\/p>\n<\/li>\n<li>\n<p>\u0412 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e existingSenders \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0442\u0440\u0435\u043a\u0438, \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u043e\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0442\u0435\u043b\u044f. \u0414\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u044d\u0442\u043e \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043d\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c \u044d\u0442\u0430\u043f\u0435 \u043d\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0439 \u043e\u0442 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0442\u0440\u0435\u043a.<\/p>\n<\/li>\n<li>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 existingSenders \u0431\u0443\u0434\u0443\u0442 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432\u0441\u0435 \u0442\u0440\u0435\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0443\u0436\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0438\u043b\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442. \u041d\u0430\u043c \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432 \u044d\u0442\u043e\u043c \u043f\u0438\u0440\u0435 \u0435\u0449\u0435 \u043d\u0435 \u0447\u0438\u0441\u043b\u044f\u0442\u0441\u044f.<\/p>\n<\/li>\n<li>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c PendingLocalDescription. \u0417\u0434\u0435\u0441\u044c \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u043f\u043e\u044f\u0441\u043d\u0438\u0442\u044c. \u0412 \u043a\u0430\u0436\u0434\u043e\u043c PeerConnection \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0434\u0432\u0430 \u0441\u0442\u0430\u0442\u0443\u0441\u0430 LocalDescription: Current &amp; Pending. \u041f\u0435\u0440\u0432\u043e\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 Offer \u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f Answer. \u0412\u0442\u043e\u0440\u043e\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f. \u041d\u043e \u0432\u0441\u0442\u0443\u043f\u0438\u0442 \u043e\u043d\u043e \u0432 \u0441\u0438\u043b\u0443 (\u0442 \u0435 Current \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0432\u043d\u043e Pending )\u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u043c \u043e\u0431\u043c\u0435\u043d\u0435 Offer\/Answer. \u0417\u0434\u0435\u0441\u044c \u0438\u0434\u0435\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430 nil \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435. \u0415\u0441\u043b\u0438 PendingLocalDescription == nil, \u0437\u043d\u0430\u0447\u0438\u0442 \u043d\u0438\u043a\u0430\u043a\u0438\u0445 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u043d\u0435 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u043b\u043e \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c PeerConnection \u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f, \u0438\u043d\u0430\u0447\u0435 &#8212; \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c Offer \u0434\u0430\u043d\u043d\u043e\u043c\u0443 \u043f\u0438\u0440\u0443. \u0412 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435 Web RTC \u044d\u0442\u043e \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f renogotiation, \u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u043f\u0440\u043e \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/WebRTC_API\/Perfect_negotiation\" rel=\"noopener noreferrer nofollow\">\u0437\u0434\u0435\u0441\u044c<\/a>.<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043d\u0430\u0442\u043e\u043b\u043a\u043d\u0443\u0442\u044c\u0441\u044f \u043d\u0430 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u043e\u0448\u0438\u0431\u043a\u0438. \u041f\u0440\u0438\u043c\u0435\u0440: \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u043e\u0432\u044b\u0445 \u0442\u0440\u0435\u043a\u043e\u0432, \u044d\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043d\u0435 \u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u043e \u0438 \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u043a\u043e\u043d\u0444\u043b\u0438\u043a\u0442\u043d\u044b\u0435 \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u0438, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0430 room.Signal() \u0441 \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u043e\u0439 \u0432 3 \u0441\u0435\u043a\u0443\u043d\u0434\u044b <\/p>\n<\/li>\n<\/ol>\n<h2>Coordinator<\/h2>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u043f\u0435\u0440\u0435\u0448\u043b\u0438 \u043a \u0433\u043b\u0430\u0432\u043d\u043e\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0435, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u0443\u0435\u0442 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u043a\u043e\u043c\u043d\u0430\u0442. <\/p>\n<pre><code class=\"go\">type Coordinator struct { sessioins map[string]*Room } <\/code><\/pre>\n<p>\u041a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u043e, \u043e\u043f\u0438\u0448\u0435\u043c \u0435\u0435 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u043c <\/p>\n<pre><code class=\"go\">type CoordinatorInterface interface { CreateRoom(id string) RemoveRoom(id string) AddUserToRoom(self_id string, room_id string, socket *websocket.Conn) RemoveUserFromRoom(self_id string, room_id string, socket *websocket.Conn) ShowSessions() ObtainEvent(message WsMessage, socket *websocket.Conn) }<\/code><\/pre>\n<p>\u041f\u0440\u043e\u0439\u0434\u0435\u043c\u0441\u044f \u043f\u043e \u043c\u0435\u0442\u043e\u0434\u0430\u043c:<\/p>\n<ol>\n<li>\n<p>CreateRoom() &amp; RemoveRoom() &#8212; \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u0438 \u0443\u0434\u0430\u043b\u044f\u0435\u0442 \u043a\u043e\u043c\u043d\u0430\u0442\u0443 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e <\/p>\n<\/li>\n<li>\n<p>ShowSessions() &#8212; \u0432\u044b\u0434\u0430\u0435\u0442 \u0432\u0441\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u043d\u0430 \u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u043a\u043e\u043c\u043d\u0430\u0442\u044b <\/p>\n<\/li>\n<li>\n<p>RemoveUserFromRoom() &#8212; \u0443\u0434\u0430\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u0437 \u043a\u043e\u043c\u043d\u0430\u0442\u044b<\/p>\n<\/li>\n<li>\n<p>AddUserToRoom() &#8212; \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u0442 PeerConnection  <\/p>\n<\/li>\n<li>\n<p>ObtainEvent() &#8212; \u043c\u0435\u0442\u043e\u0434-\u0441\u0432\u0437\u044f\u043a\u0430 \u0441 \u043d\u0430\u0448\u0438\u043c Signal \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c. \u041f\u0440\u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 Coordinator \u0438 \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0431\u0443\u0434\u0435\u043c \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u044d\u0442\u0438\u043c \u043c\u0435\u0442\u043e\u0434\u043e\u043c <\/p>\n<\/li>\n<\/ol>\n<p>\u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043a\u043e\u0434 AddUserToRoom():<\/p>\n<pre><code class=\"go\">func (coordinator *Coordinator) AddUserToRoom(self_id string, room_id string, socket *websocket.Conn) {     \/\/ 1) if _, ok := coordinator.sessioins[room_id]; !ok { fmt.Println(\"New Room was created: \", room_id) coordinator.CreateRoom(room_id) } if room, ok := coordinator.sessioins[room_id]; ok { \/\/ 2) Add Peer to Room room.AddPeer(newPeer(self_id)) fmt.Println(\"Peer \", self_id, \"was added to room \", room_id) if peer, ok := room.peers[self_id]; ok { \/\/ 3) Set socket connection to Peer peer.SetSocket(socket)  \/\/ 4) Create Peer Connection conn, err := webrtc.NewPeerConnection(webrtc.Configuration{}) if err != nil { fmt.Println(\"Failed to establish peer connection\") }  peer.SetPeerConnection(conn) fmt.Println(\"Peer connection was established\") \/\/ 5) Accept one audio and one video track incoming for _, typ := range []webrtc.RTPCodecType{webrtc.RTPCodecTypeVideo, webrtc.RTPCodecTypeAudio} { if _, err := peer.connection.AddTransceiverFromKind(typ, webrtc.RTPTransceiverInit{ Direction: webrtc.RTPTransceiverDirectionRecvonly, }); err != nil { log.Print(err) return } }  \/\/ 6) If PeerConnection is closed remove it from global list peer.connection.OnConnectionStateChange(func(p webrtc.PeerConnectionState) { switch p { case webrtc.PeerConnectionStateFailed: if err := peer.connection.Close(); err != nil { log.Print(err) } case webrtc.PeerConnectionStateClosed: room.Signal() default: } })  \/\/ 7) When peer connection is getting the ICE -> send ICE to client peer.connection.OnICECandidate(func(i *webrtc.ICECandidate) { if i == nil { fmt.Println(\"ICEGatheringState: connected\") return } fmt.Println(\"Ice: \", i) room.SendICE(i, self_id) })             \/\/ 8)  peer.connection.OnTrack(func(t *webrtc.TrackRemote, _ *webrtc.RTPReceiver) { fmt.Println(\"Track added from peer: \", self_id) defer room.Signal() \/\/ Create a track to fan out our incoming video to all peers trackLocal := room.AddTrack(t) defer room.RemoveTrack(trackLocal) defer fmt.Println(\"Track\", trackLocal, \"was removed\") buf := make([]byte, 1500) for { i, _, err := t.Read(buf) if err != nil { return }  if _, err = trackLocal.Write(buf[:i]); err != nil { return } } }) }  } }<\/code><\/pre>\n<p>\u041f\u0440\u043e\u0439\u0434\u0435\u043c\u0441\u044f \u043f\u043e \u043f\u0443\u043d\u043a\u0442\u0430\u043c: <\/p>\n<ol>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u044e, \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u043b\u0438 \u043a\u043e\u043c\u043d\u0430\u0442\u0430, \u043a \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0445\u043e\u0447\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f. \u0415\u0441\u043b\u0438 \u0442\u0430\u043a\u043e\u0439 \u043d\u0435\u0442 &#8212; \u0441\u043e\u0437\u0434\u0430\u0435\u043c.<\/p>\n<\/li>\n<li>\n<p>\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c Peer \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u0443.<\/p>\n<\/li>\n<li>\n<p>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0441\u043e\u043a\u0435\u0442 \u0438 RTCPeerConnection \u0432 Peer <\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u043c \u0442\u0440\u0435\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0441\u0445\u043e\u0434\u044f\u0442 \u043e\u0442 \u043d\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.<\/p>\n<\/li>\n<li>\n<p>\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u0438\u0433\u043d\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043a\u043e\u043c\u043d\u0430\u0442\u0435, \u0435\u0441\u043b\u0438 \u0432 \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u043c\u043e\u043c\u0435\u043d\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043e\u0442\u043a\u043b\u044e\u0447\u0438\u043b\u0441\u044f <\/p>\n<\/li>\n<li>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b\u0438 \u043b\u043e\u0433\u0438\u043a\u0443 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0434\u0440\u0443\u0433 \u0434\u0440\u0443\u0433\u0443 ICE-\u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u043e\u0432. \u0415\u0441\u043b\u0438 \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 nil \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043d\u0430\u0448\u0435\u0433\u043e ICE-\u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430, \u0437\u043d\u0430\u0447\u0438\u0442 \u043f\u0435\u0440\u0435\u0441\u044b\u043b\u043a\u0430 ICE-\u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430\u043c\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430. \u0418\u043d\u0430\u0447\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u043d\u043e\u0432\u043e\u0438\u0441\u043f\u0435\u0447\u0435\u043d\u043d\u043e\u0433\u043e ICE-\u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430 \u0434\u0440\u0443\u0433\u043e\u043c\u0443 \u043f\u0438\u0440\u0443.<\/p>\n<\/li>\n<li>\n<p>\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0442\u0440\u0435\u043a \u0438 \u0441\u0438\u0433\u043d\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043a\u043e\u043c\u043d\u0430\u0442\u0435 \u043f\u0440\u0438 \u043f\u043e\u044f\u0432\u043b\u0435\u043d\u0438\u0438 \u043d\u043e\u0432\u043e\u0433\u043e \u0442\u0440\u0435\u043a\u0430.<\/p>\n<\/li>\n<\/ol>\n<p>Trickle ICE <\/p>\n<p>\u0412 <a href=\"https:\/\/github.com\/pion\" rel=\"noopener noreferrer nofollow\">Pion<\/a> \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0442\u0430\u043a \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u043c\u043e\u0433\u043e Trickle ICE. \u0421\u0443\u0442\u044c \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0430 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u043a\u043b\u0438\u0435\u043d\u0442 \u043d\u0435 \u0434\u043e\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044f \u043e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u044f Offer\/Answer. \u0410 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\/\u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 ICE-\u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u043e\u0432 \u0442\u0430\u043a \u0441\u043a\u043e\u0440\u043e, \u043a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u044d\u0442\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e. \u042d\u0442\u043e \u043c\u044b \u0438 \u0440\u0435\u0430\u0437\u0438\u043b\u043e\u0437\u0432\u0430\u043b\u0438 \u0432 \u043d\u0430\u0448\u0435\u043c Peer.OnICECandidate.<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0437\u0430 ObtainEvent:<\/p>\n<pre><code class=\"go\">func (coordinator *Coordinator) ObtainEvent(message WsMessage, socket *websocket.Conn) { wsMessage := message switch wsMessage.Event { case \"joinRoom\": go func() { m, ok := message.Data.(map[string]any) if ok { self_id := m[\"self_id\"].(string) room_id := m[\"room_id\"].(string) coordinator.AddUserToRoom(self_id, room_id, socket) } }() case \"leaveRoom\": go func() { m, ok := message.Data.(map[string]any) if ok { self_id := m[\"self_id\"].(string) room_id := m[\"room_id\"].(string) coordinator.RemoveUserFromRoom(self_id, room_id) } }() case \"offer\": go func() { m, ok := message.Data.(map[string]any) if ok { self_id, _ := m[\"self_id\"].(string) room_id, _ := m[\"room_id\"].(string) offer2 := m[\"offer\"].(map[string]any) if room, ok := coordinator.sessioins[room_id]; ok { if peer, ok := room.peers[self_id]; ok { answer, err2 := peer.ReactOnOffer(offer2[\"sdp\"].(string)) if err2 != nil { fmt.Println(err2) return } room.SendAnswer(answer, self_id) } } } }() case \"answer\": go func() { m, ok := message.Data.(map[string]any) if ok { self_id, _ := m[\"self_id\"].(string) room_id, _ := m[\"room_id\"].(string) offer2 := m[\"answer\"].(map[string]any) if room, ok := coordinator.sessioins[room_id]; ok { if peer, ok := room.peers[self_id]; ok { err := peer.ReactOnAnswer(offer2[\"sdp\"].(string)) if err != nil { fmt.Println(err) return } }  } } }() case \"ice-candidate\": go func() { m, ok := message.Data.(map[string]any) if ok { self_id, _ := m[\"self_id\"].(string) room_id, _ := m[\"room_id\"].(string) candidate := m[\"candidate\"].(map[string]any) i_candidate := candidate[\"candidate\"].(string) sdp_mid := candidate[\"sdpMid\"].(string) sdp_m_line_index := uint16(candidate[\"sdpMLineIndex\"].(float64)) var username_fragment string if candidate[\"usernameFragment\"] != nil { username_fragment = candidate[\"usernameFragment\"].(string) } else { username_fragment = \"\" } init := webrtc.ICECandidateInit{ Candidate:        i_candidate, SDPMid:           &amp;sdp_mid, SDPMLineIndex:    &amp;sdp_m_line_index, UsernameFragment: &amp;username_fragment, } if room, ok := coordinator.sessioins[room_id]; ok { if peer, ok := room.peers[self_id]; ok { if err := peer.connection.AddICECandidate(init); err != nil { log.Println(err) return } fmt.Println(\"ICE-CANDIDATE added for peer\", peer.id) fmt.Println(peer.connection.ICEConnectionState()) fmt.Println(peer.connection.ICEGatheringState()) } } } else { fmt.Println(m) fmt.Println(\"nach\") } }() default: fmt.Println(\"DEFAULT\") fmt.Println(wsMessage)  }  return } <\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u044f Event. \u0418 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u043c\u0435\u0442\u043e\u0434. \u041d\u0430 \u0432\u0445\u043e\u0434 \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 WsMessage, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043e\u0431\u0449\u0438\u043c \u043c\u0435\u0442\u043e\u0434\u043e\u043c \u0434\u043b\u044f \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439.<\/p>\n<h2>Signal<\/h2>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u044e\u0449\u0438\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u043d\u0430\u0448\u0435\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430, \u0441\u043e\u043a\u0435\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0438\u0445 \u0432 Coordinator.ObtainEvent()<\/p>\n<pre><code class=\"go\">\/\/ websockets listener func (ws *WsServer) wsInit(w http.ResponseWriter, r *http.Request) {  conn, err := upgrader.Upgrade(w, r, nil)  defer conn.Close()  fmt.Printf(\"Client connected\")  if err != nil { fmt.Printf(\" with error %s\", err) return }  fmt.Println(\" successfully\")  message := types.WsMessage{}  for { messageType, bmessage, err := conn.ReadMessage() fmt.Println(bmessage) if err != nil { fmt.Println(err) return } if messageType == websocket.CloseMessage { break }  err = json.Unmarshal(bmessage, &amp;message) if err != nil { fmt.Println(\"DROP\") fmt.Println(message.Data) fmt.Println(err) return } ws.coordinator.ObtainEvent(message, conn) } }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u043f\u0440\u0438\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u043c  \u043a WsMessage \u0438 \u043e\u0442\u0434\u0430\u0435\u043c \u043d\u0430\u0448\u0435\u043c\u0443 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u043e\u0440\u0443 <\/p>\n<h2>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 <\/h2>\n<p>\u041f\u043e \u0438\u0442\u043e\u0433\u0443 \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0441\u0442\u0430\u0440\u0442\u043e\u0432\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f SFU. \u041a\u0430\u043a \u0433\u043e\u0432\u043e\u0440\u0438\u043b\u043e\u0441\u044c \u0432 Web RTC for Curious:<\/p>\n<blockquote>\n<p>Building a simple SFU can be done in a weekend. Building a good SFU that can handle all types of clients is never ending. Tuning the Congestion Control, Error Correction and Performance is a never ending task.<\/p>\n<\/blockquote>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u044d\u0442\u043e \u043b\u0438\u0448\u044c \u043f\u0435\u0440\u0432\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e SFU \u0441\u0435\u0440\u0432\u0435\u0440\u0430.  \u0412\u043f\u0435\u0440\u0435\u0434\u0438 \u0435\u0449\u0435 \u043c\u043d\u043e\u0433\u043e \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043c\u043e\u0433\u0443\u0442 \u0443\u043b\u0443\u0447\u0448\u0438\u0442\u044c \u044d\u0442\u043e\u0442 \u0441\u0435\u0440\u0432\u0435\u0440 \u0438 \u043f\u043e\u0432\u044b\u0441\u0438\u0442\u044c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u043e \u0432\u0438\u0434\u0435\u043e\/\u0430\u0443\u0434\u0438\u043e \u043a\u043e\u043d\u0444\u0435\u0440\u0435\u043d\u0446\u0438\u0439. \u0415\u0441\u043b\u0438 \u044d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f \u0432\u0430\u043c \u043f\u043e\u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f, \u044f \u043d\u0435 \u0437\u0430\u0434\u0435\u0440\u0436\u0443 \u0432\u044b\u043f\u0443\u0441\u043a \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445.<\/p>\n<p>\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u0432 \u044d\u0442\u043e\u043c <a href=\"https:\/\/github.com\/WebWorkflow\/pion_sfu\" rel=\"noopener noreferrer nofollow\">\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438<\/a>.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0431\u044b \u0443\u0441\u043b\u044b\u0448\u0430\u0442\u044c \u043e\u0442 \u0432\u0430\u0441 \u043f\u043e\u0436\u0435\u043b\u0430\u043d\u0438\u044f \u0438 \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0438 \u043f\u043e \u0440\u0430\u0437\u0432\u0438\u0442\u0438\u044e SFU. \u0422\u0430\u043a \u043a\u0430\u043a \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b\u043e\u0441\u044c \u044d\u0442\u043e \u0441 \u043d\u0443\u043b\u044f \u043a\u0430\u043a \u0432 \u0437\u043d\u0430\u043d\u0438\u0438 WebRTC, \u0442\u0430\u043a \u0438 Golang.<\/p>\n<h2>\u0418\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0438 <\/h2>\n<p><a href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/WebRTC_API\/Protocols\" rel=\"noopener noreferrer nofollow\">https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/WebRTC_API\/Protocols<\/a><\/p>\n<p><a href=\"https:\/\/webrtcforthecurious.com\" rel=\"noopener noreferrer nofollow\">https:\/\/webrtcforthecurious.com<\/a><\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/790348\/\"> https:\/\/habr.com\/ru\/articles\/790348\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><\/figure>\n<blockquote>\n<p><a href=\"https:\/\/webrtc.org\" rel=\"noopener noreferrer nofollow\">WebRTC\u00a0<\/a>\u2014 \u044d\u0442\u043e \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043d\u0430\u044f \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f, \u043f\u0440\u0435\u0434\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u0430\u044f \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u043f\u043e\u0442\u043e\u043a\u043e\u0432\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u043c\u0435\u0436\u0434\u0443 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430\u043c\u0438 \u0438\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u043c\u0438 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0438 \u0434\u0432\u0443\u0445\u0442\u043e\u0447\u0435\u0447\u043d\u043e\u0439 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438\u00a0<em>(point-to-point transmission)<\/em>.<\/p>\n<\/blockquote>\n<p>Web RTC \u0443\u0436\u0435 \u0434\u0430\u0432\u043d\u043e \u0438\u043c\u0435\u0435\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u0432 \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043e\u0432, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043e\u0431\u0445\u043e\u0434\u0438\u0442\u044c \u0441\u0442\u043e\u0440\u043e\u043d\u043e\u0439 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044e \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0433\u043b\u0443\u043f\u043e. \u0412\u043e\u0442 \u0438 \u044f \u0442\u0430\u043a \u043f\u043e\u0434\u0443\u043c\u0430\u043b, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0435\u0442-\u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0440\u0435\u0448\u0438\u043b \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c sfu-\u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0430 golang. <\/p>\n<h2>\u041e \u0441\u0430\u043c\u043e\u043c Web RTC <\/h2>\n<p>\u0417\u0434\u0435\u0441\u044c \u044f \u043a\u0440\u0430\u0442\u043a\u043e \u043f\u0440\u043e\u0431\u0435\u0433\u0443\u0441\u044c \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0440\u0430\u0431\u043e\u0442\u044b Web RTC, \u043a\u043e\u043c\u0443 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043b\u0435\u0437\u0442\u044c \u0447\u0443\u0442\u044c \u043f\u043e\u0433\u043b\u0443\u0431\u0436\u0435 \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e \u0441\u0441\u044b\u043b\u043a\u0443 <a href=\"https:\/\/webrtcforthecurious.com\" rel=\"noopener noreferrer nofollow\">\u0437\u0434\u0435\u0441\u044c<\/a>.  <\/p>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0434\u0432\u0430 \u043f\u0438\u0440\u0430 \u0441\u043c\u043e\u0433\u043b\u0438 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0441\u0435\u0431\u0435 RTCPeerConnection, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b SDP (Session Description Protocol). \u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0438\u043c\u0435\u0435\u0442 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 &#171;\u043a\u043b\u044e\u0447-\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435&#187; \u0438 \u043f\u043e \u0441\u0443\u0442\u0438 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0432\u0437\u044f\u0442\u043e\u0433\u043e \u043f\u0438\u0440\u0430 (\u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0441\u0430\u043c\u043e \u0437\u0430 \u0441\u0435\u0431\u044f \u0433\u043e\u0432\u043e\u0440\u0438\u0442). <\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 SessionDescription <\/p>\n<pre><code>v=0 o=- 0 0 IN IP4 127.0.0.1 s=- c=IN IP4 127.0.0.1 t=0 0 m=audio 4000 RTP\/AVP 111 a=rtpmap:111 OPUS\/48000\/2 m=video 4002 RTP\/AVP 96 a=rtpmap:96 VP8\/90000<\/code><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e \u043a\u0430\u043a \u043f\u0438\u0440 \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043b \u043d\u0430\u043c SessionDescription (\u0434\u0430\u043b\u0435\u0435 \u043f\u0440\u043e\u0441\u0442\u043e SD), \u043f\u0438\u0440 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0435\u0433\u043e \u0434\u0440\u0443\u0433\u043e\u043c\u0443 \u043f\u0438\u0440\u0443 \u0432 \u0432\u0438\u0434\u0435 \u043e\u0444\u0444\u0435\u0440\u0430. <\/p>\n<p>\u0417\u0434\u0435\u0441\u044c \u0441\u0440\u0430\u0437\u0443 \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u0435\u0442 \u0432\u043e\u043f\u0440\u043e\u0441: &#171;\u0410 \u043a\u0443\u0434\u0430, \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c?&#187; \u0418 \u0432\u043e\u043f\u0440\u043e\u0441 \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439, \u0441\u0430\u043c \u043f\u043e \u0441\u0435\u0431\u0435 \u043f\u0438\u0440 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0437\u043d\u0430\u0442\u044c ip-\u0430\u0434\u0440\u0435\u0441 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u043f\u0438\u0440\u0430. \u0422\u0443\u0442 \u043d\u0430 \u043f\u043e\u043c\u043e\u0449\u044c \u043d\u0430\u043c \u043f\u0440\u0438\u0445\u043e\u0434\u044f\u0442: turn &amp; signal \u0441\u0435\u0440\u0432\u0435\u0440\u0430. <\/p>\n<p>Signal &#8212; \u044d\u0442\u043e \u0441\u0435\u0440\u0432\u0435\u0440, \u043a \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u044e\u0442\u0441\u044f \u043f\u0438\u0440\u044b \u0434\u043b\u044f \u043e\u0431\u043c\u0435\u043d\u0430 SD. \u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c\u0441\u044f \u043f\u043e\u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043d\u0430 \u043d\u0435\u043c \u0447\u0443\u0442\u044c \u043f\u043e\u0437\u0436\u0435.  <\/p>\n<p>Turn \u0441\u0435\u0440\u0432\u0435\u0440 \u0440\u0435\u0448\u0430\u0435\u0442 \u0434\u0440\u0443\u0433\u0443\u044e \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443. \u041d\u0430\u0448\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0447\u0430\u0441\u0442\u043e \u0432\u044b\u0445\u043e\u0434\u044f\u0442 \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u0447\u0435\u0440\u0435\u0437 wifi, \u0430 \u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043e\u043d\u0438 \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0432\u043d\u0443\u0442\u0440\u0438 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e NAT \u0432\u0430\u0448\u0435\u0433\u043e \u0440\u043e\u0443\u0442\u0435\u0440\u0430 \u0438 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e ip-\u0430\u0434\u0440\u0435\u0441\u0430 \u0443 \u043d\u0438\u0445 \u043d\u0435\u0442. \u041d\u043e \u0434\u043b\u044f turn \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u044d\u0442\u043e \u043d\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430. \u0421 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0441\u0432\u043e\u0438\u0445 &#171;Magic Cookie&#187; \u043e\u043d \u0432\u0441\u0435 \u0440\u0430\u0432\u043d\u043e \u0434\u043e\u0431\u0435\u0440\u0435\u0442\u0441\u044f \u0434\u043e \u0432\u0430\u0448\u0435\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430. <\/p>\n<p>\u0422\u0430\u043a, SD \u043c\u044b \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043b\u0438, \u0443\u0437\u043d\u0430\u043b\u0438 \u043d\u0430\u0448 IP, \u0434\u0430\u0436\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043b\u0438 \u043d\u0430\u0448 SD \u0432 \u0432\u0438\u0434\u0435 offer \u043d\u0430 signal \u0441\u0435\u0440\u0432\u0435\u0440. \u0414\u0430\u043b\u0435\u0435 \u0432\u0442\u043e\u0440\u043e\u0439 \u043f\u0438\u0440 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u0441\u0435\u0431\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u043d\u0430\u043c\u0438 SD, \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u0441\u0432\u043e\u0439 SD \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0435\u0433\u043e signal \u0441\u0435\u0440\u0432\u0435\u0440\u0443 \u0432 \u0432\u0438\u0434\u0435 answer. \u041f\u0435\u0440\u0432\u044b\u0439 \u043f\u0438\u0440 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 SD \u0432 \u0432\u0438\u0434\u0435 answer \u043a \u0441\u0435\u0431\u0435.<\/p>\n<blockquote>\n<p>\u0412\u00a0\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043a\u00a0\u043e\u0431\u043c\u0435\u043d\u0443 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0435\u0439 \u043e\u00a0\u043c\u0435\u0434\u0438\u0430(\u043e\u0431\u0441\u0443\u0436\u0434\u0451\u043d\u043d\u043e\u0439 \u0432\u044b\u0448\u0435 \u0432\u00a0offer\/answer \u0438 SDP), \u0443\u0437\u043b\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043e\u0431\u043c\u0435\u043d\u044f\u0442\u044c\u0441\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0435\u0439 \u043e\u00a0\u0441\u0435\u0442\u0435\u0432\u043e\u043c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0438. \u041e\u0431\u00a0\u044d\u0442\u043e\u043c \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e \u043a\u0430\u043a\u00a0\u043e\u00a0ICE\u2011\u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0435 \u0438 \u0434\u0435\u0442\u0430\u043b\u044f\u0445 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043c\u0435\u0442\u043e\u0434\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u0443\u0437\u0435\u043b \u043c\u043e\u0436\u0435\u0442 \u043e\u0431\u0449\u0430\u0442\u044c\u0441\u044f (\u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0438\u043b\u0438\u00a0\u0447\u0435\u0440\u0435\u0437 TURN\u2011\u0441\u0435\u0440\u0432\u0435\u0440).ICE\u2011\u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430\u043c\u0438 \u043f\u0438\u0440\u044b \u0442\u0430\u043a\u0436\u0435 \u0434\u0435\u043b\u044f\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 signal \u0441\u0435\u0440\u0432\u0435\u0440. <\/p>\n<\/blockquote>\n<figure class=\"full-width\"><\/figure>\n<p>\u0418 \u0432\u043e\u0442, \u043f\u043e\u0441\u043b\u0435 \u0432\u0441\u0435\u0445 \u0442\u0430\u043d\u0446\u0435\u0432 \u0441 \u0431\u0443\u0431\u043d\u043e\u043c, \u043c\u044b \u043d\u0430\u043a\u043e\u043d\u0435\u0446 \u043c\u043e\u0436\u0435\u043c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043d\u0430\u0448 RTCPeerConnection. <\/p>\n<h2>\u0410 \u0437\u0430\u0447\u0435\u043c \u043d\u0430\u043c \u043d\u0443\u0436\u0435\u043d SFU ?<\/h2>\n<p>Select Forwarding Unit \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043d\u0430 \u043f\u043e\u043c\u043e\u0449\u044c \u0442\u043e\u0433\u0434\u0430, \u043a\u043e\u0433\u0434\u0430 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u0438\u0440\u043e\u0432 \u0432 \u043e\u0434\u043d\u043e\u0439 \u0441\u0435\u0441\u0441\u0438\u0438 \u0434\u043e\u0441\u0442\u0438\u0433\u0430\u0435\u0442 7+. \u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043f\u0440\u0438\u043c\u0435\u0440: <\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u041c\u044b \u0438\u043c\u0435\u0435\u043c 7 \u043f\u0438\u0440\u043e\u0432, \u043a\u0430\u0436\u0434\u044b\u0439 \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0432\u0438\u0434\u0435\u043e \u0438 \u0430\u0443\u0434\u0438\u043e \u0442\u0440\u0435\u043a\u0438. \u0412 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 p2p \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043c: 6+5+4+3+2+1=21 Peer \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435. \u0410 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0442\u0440\u0435\u043a\u043e\u0432 \u0431\u0443\u0434\u0435\u0442 \u0431\u043e\u043b\u044c\u0448\u0435 \u0432 4 \u0440\u0430\u0437\u0430(\u043e\u0442 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0442\u0430\u043a\u043e\u0433\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u0441 \u043e\u0431\u0435\u0438\u0445 \u0441\u0442\u043e\u0440\u043e\u043d \u0438\u0434\u0435\u0442 \u0432\u0438\u0434\u0435\u043e \u0438 \u0430\u0443\u0434\u0438\u043e \u0434\u043e\u0440\u043e\u0436\u043a\u0430).<\/p>\n<p>\u041d\u0430\u043b\u0438\u0446\u043e \u0430\u0431\u0441\u043e\u043b\u044e\u0442\u043d\u043e \u043d\u0435\u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u044b \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043e\u0442 \u043f\u043e\u043b\u043d\u043e\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u043e\u0439 \u0442\u043e\u043f\u043e\u043b\u043e\u0433\u0438\u0438 \u043a \u0437\u0432\u0435\u0437\u0434\u043e\u0447\u043d\u043e\u0439: <\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u0438\u043c\u0435\u0435\u043c 7 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439 PeerConnection, \u0433\u0434\u0435 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u0438\u0440\u0430 \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c 2 \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0438 12 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0440\u0435\u043a\u043e\u0432. \u0425\u043e\u0442\u044c Web RTC \u0438 \u0431\u044b\u043b \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u043f\u0440\u0435\u0434\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d \u0434\u043b\u044f \u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043e\u0432 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e, \u0438\u043c\u0435\u043d\u043d\u043e \u0432 \u0441\u0432\u044f\u0437\u043a\u0435 \u0441 SFU \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u043e \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u043c\u0443 \u043f\u0440\u043e\u0447\u0443\u0432\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u043c\u043e\u0449\u044c \u044d\u0442\u043e\u0439 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0438. <\/p>\n<p>\u0421\u043b\u0435\u0434\u0443\u0435\u0442 \u0442\u0430\u043a\u0436\u0435 \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u0438\u043c\u0435\u0435\u0442\u0441\u044f \u0435\u0449\u0435 \u043e\u0434\u0438\u043d \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f &#8212; MCU (Multipoint Control Unit). \u041d\u043e \u0442\u043e\u0433\u0434\u0430 \u0432 \u043e\u0431\u044f\u0437\u0430\u043d\u043d\u043e\u0441\u0442\u0438 \u043d\u0430\u0448\u0435\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0431\u0443\u0434\u0435\u0442 \u0435\u0449\u0435 \u0432\u0445\u043e\u0434\u0438\u0442\u044c \u0443\u043f\u0430\u043a\u043e\u0432\u043a\u0430 \u0432\u0441\u0435\u0445 \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0440\u0435\u043a\u043e\u0432 \u043a \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u043f\u0438\u0440\u0443 \u0432 \u0435\u0434\u0438\u043d\u044b\u0439 MediaStream. \u041e\u0434\u043d\u0430\u043a\u043e \u0441\u043e \u0441\u0442\u043e\u0440\u043e\u043d\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0431\u0443\u0434\u0435\u0442 \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u043c \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0441 \u044d\u0442\u0438\u043c\u0438 \u043f\u043e\u0442\u043e\u043a\u0430\u043c\u0438. <\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u041f\u0440\u0438\u0432\u0435\u0434\u0443 \u043f\u0440\u0438\u043c\u0435\u0440: Zoom \u0438 \u0435\u0433\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0437\u0430\u0433\u043b\u0443\u0448\u0430\u0442\u044c, \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u0442\u044c \u043f\u043b\u0438\u0442\u043a\u0438 \u0441 \u0432\u0438\u0434\u0435\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439. \u0418\u043c\u0435\u043d\u043d\u043e \u0431\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u0442\u043e\u043c\u0443, \u0447\u0442\u043e \u043a\u0430\u0436\u0434\u0430\u044f \u043f\u043b\u0438\u0442\u043a\u0430 &#8212; \u044d\u0442\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 MediaStream, \u0441 \u043d\u0435\u0439 \u043c\u043e\u0436\u043d\u043e \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c. \u0415\u0441\u043b\u0438 \u0431\u044b \u043c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b\u0438 MCU, \u0442\u043e \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 \u0431\u044b \u043d\u0435 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u043f\u043b\u0438\u0442\u043e\u043a \u0441 \u0432\u0438\u0434\u0435\u043e, \u0430 \u0432 \u0435\u0434\u0438\u043d\u044b\u0439 \u0431\u043b\u043e\u043a \u0432\u0438\u0434\u0435\u043e \u043f\u043e\u0442\u043e\u043a\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 MCU \u0437\u0430\u0431\u043e\u0442\u043b\u0438\u0432\u043e \u043d\u0430\u043c \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u043b. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u043f\u043e\u0432\u044b\u0448\u0430\u044f \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u0432 \u0440\u0430\u0437\u044b, \u043c\u044b \u0443\u0440\u0435\u0437\u0430\u0435\u043c \u043c\u043d\u043e\u0433\u0438\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u0414\u0430, \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0442\u043e\u0433\u0434\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0441\u043e\u0439\u0434\u0435\u0442 \u043d\u0430 \u043d\u0435\u0442, \u043d\u043e \u043f\u043e\u043a\u0440\u044b\u0432\u0430\u044e\u0442 \u043b\u0438 \u044d\u0442\u0438 \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432\u0430 \u043f\u043e\u044f\u0432\u0438\u0432\u0448\u0438\u0435\u0441\u044f \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043a\u0438. \u042f \u0434\u0443\u043c\u0430\u044e, \u0447\u0442\u043e \u043d\u0435\u0442. <\/p>\n<h2>\u041f\u0440\u0438\u0441\u0442\u0443\u043f\u0430\u0435\u043c<\/h2>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u043c \u0432\u043e\u043f\u043b\u043e\u0449\u0430\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442 \u0432 \u0436\u0438\u0437\u043d\u044c. \u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c\u0441\u044f \u0441\u043e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u043e\u0439 \u043d\u0430\u0448\u0435\u0433\u043e sfu.<\/p>\n<figure class=\"float full-width\"><\/figure>\n<p>\u041d\u0430\u0448 \u0441\u0435\u0440\u0432\u0435\u0440 \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u0442\u044c \u0438\u0437 \u0434\u0432\u0443\u0445 \u0447\u0430\u0441\u0442\u0435\u0439: Signal &amp; Coordinator. \u041f\u0435\u0440\u0432\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0442\u044c \u043e\u0431\u043c\u0435\u043d SD &amp; ICE-\u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430\u043c\u0438, \u0432\u0442\u043e\u0440\u043e\u0439 \u0431\u0443\u0434\u0435\u0442 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0438 \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u043f\u043e\u0442\u043e\u043a\u0438.<\/p>\n<h2>Peer<\/h2>\n<p>Peer \u0431\u0443\u0434\u0435\u0442 \u044f\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0440\u043d\u043e\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u043e\u0439 \u0438 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f <\/p>\n<pre><code class=\"go\">type Peer struct { id         string connection *webrtc.PeerConnection streams    map[string]*webrtc.TrackRemote mutex      sync.RWMutex socket     *websocket.Conn }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043f\u043e\u043a\u0430 \u0432\u0441\u0435 \u043f\u0440\u043e\u0441\u0442\u043e, \u043a\u043b\u0430\u0441\u0441 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0432 \u0441\u0435\u0431\u0435 \u0441\u043e\u043a\u0435\u0442, \u0441\u0430\u043c\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0438  \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u043e\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0442\u0440\u0435\u043a\u0438 <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u043f\u0438\u0448\u0435\u043c \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0438\u0440\u0430 <\/p>\n<pre><code class=\"go\">type PeerInterface interface { SetSocket(ws_conn *websocket.Conn) AddRemoteTrack(track *webrtc.TrackRemote) RemoveRemoteTrack(track *webrtc.TrackRemote) SetPeerConnection(conn *webrtc.PeerConnection) ReactOnOffer(offer webrtc.SessionDescription) ReactOnAnswer(answer_str string) }<\/code><\/pre>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043f\u043e\u043a\u0430 \u0432\u0441\u0435 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e, \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0435\u0442\u043e\u0434\u043e\u0432 \u0434\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u0435\u0439 Peer. \u0415\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435, \u043d\u0430 \u0447\u0435\u043c \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0441\u044f: ReactOnAnswer&amp; ReactOnOffer:<\/p>\n<pre><code class=\"go\">func (peer *Peer) ReactOnOffer(offer_str string) (webrtc.SessionDescription, error) { peer.mutex.Lock() defer peer.mutex.Unlock()  offer := webrtc.SessionDescription{ Type: webrtc.SDPTypeOffer, SDP:  offer_str, } err := peer.connection.SetRemoteDescription(offer) if err != nil { fmt.Println(err) return offer, err } fmt.Println(\"Remote Description was set for peer \", peer.id) answer, err := peer.connection.CreateAnswer(nil) _ = peer.connection.SetLocalDescription(answer) fmt.Println(\"Local Description was set for peer \", peer.id) if err != nil { return offer, err } fmt.Println(\"Answer was created in peer \", peer.id) return answer, nil  }<\/code><\/pre>\n<p>\u041a\u043e\u0433\u0434\u0430 \u043e\u0442 signal-\u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c Offer \u043e\u0442 \u0434\u0440\u0443\u0433\u043e \u043f\u0438\u0440\u0430, \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u0435\u0431\u0435 \u043f\u0440\u0438\u0448\u0435\u0434\u0448\u0438\u0439 SD, \u0434\u043b\u044f \u043d\u0430\u0441 \u044d\u0442\u043e \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0439 SD, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 SetRemoteDescription() \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u043d\u0430\u043c \u0432 \u043d\u0430\u0448\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0435. \u0414\u0430\u043b\u0435\u0435, \u043a\u0430\u043a \u0443\u0436\u0435 \u0431\u044b\u043b\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u043e \u0432 \u0442\u0435\u043e\u0440\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0447\u0430\u0441\u0442\u0438, \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c Answer, \u0447\u0442\u043e\u0431\u044b \u0434\u0440\u0443\u0433\u043e\u0439 \u043f\u0438\u0440 \u0442\u043e\u0436\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b RemoteDescription, \u043d\u043e \u043f\u0435\u0440\u0435\u0434 \u044d\u0442\u0438\u043c \u043c\u044b \u0442\u0430\u043a\u0436\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0441\u0435\u0431\u0435 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 LocalDescription<\/p>\n<pre><code class=\"go\">func (peer *Peer) ReactOnAnswer(answer_str string) error { peer.mutex.Lock() defer peer.mutex.Unlock() answer := webrtc.SessionDescription{ Type: webrtc.SDPTypeAnswer, SDP:  answer_str, } err := peer.connection.SetRemoteDescription(answer) if err != nil { fmt.Println(err) return err } return nil } <\/code><\/pre>\n<p>\u0421\u0438\u0442\u0443\u0430\u0446\u0438\u044f \u0438\u0434\u0435\u043d\u0442\u0438\u0447\u043d\u0430\u044f, \u043f\u0440\u043e\u0441\u0442\u043e \u0442\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u043d\u0430 \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u0431\u0430\u0440\u0440\u0438\u043a\u0430\u0434. \u041c\u044b \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043b\u0438 Offer, \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 Answer, \u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0438 RemoteDescription.<\/p>\n<h2>Room <\/h2>\n<p>\u041f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u043c\u0441\u044f \u0447\u0443\u0442\u044c \u0432\u044b\u0448\u0435. \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 Room \u0431\u0443\u0434\u0435\u0442 \u044f\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u043e\u0434\u043d\u043e\u0439 \u0441\u0435\u0441\u0441\u0438\u0435\u0439 \u0432\u0438\u0434\u0435\u043e \u0438\/\u0438\u043b\u0438 \u0430\u0443\u0434\u0438\u043e \u043a\u043e\u043d\u0444\u0435\u0440\u0435\u043d\u0446\u0438\u0438. <\/p>\n<pre><code class=\"go\">type Room struct { id     string mutex  sync.RWMutex peers  map[string]*Peer tracks map[string]*webrtc.TrackLocalStaticRTP }<\/code><\/pre>\n<p>\u041e\u043d\u0430 \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0432 \u0441\u0435\u0431\u0435 \u0432\u0441\u0435 \u043f\u0438\u0440\u044b \u0432 \u0434\u0430\u043d\u043d\u043e\u0439 \u043a\u043e\u043c\u043d\u0430\u0442\u0435, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0442\u0440\u0435\u043a\u0438 \u043a \u0441\u0435\u0431\u0435 \u0432 \u043f\u043e\u043b\u0435 tracks  \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0438\u0445 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u043c \u043f\u0438\u0440\u0430\u043c. <\/p>\n<p>\u041e\u043f\u0438\u0448\u0435\u043c \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435:<\/p>\n<pre><code class=\"go\">type RoomInterface interface { JoinRoom(id string) AddPeer(peer *Peer) RemovePeer(peer_id string) AddTrack(track *webrtc.TrackRemote) RemoveTrack(track *webrtc.TrackRemote) SendAnswer(message webrtc.SessionDescription, peer_id string) SendOffer(message webrtc.SessionDescription, peer_id string) SendICE(message *webrtc.ICECandidate, peer_id string) Signal() }<\/code><\/pre>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u0439, \u043f\u0440\u043e\u0431\u0435\u0436\u0438\u043c\u0441\u044f \u043f\u043e \u043d\u0438\u043c: <\/p>\n<ol>\n<li>\n<p>AddPeer(peer *Peer) &#8212; \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432 \u043f\u043e\u043b\u0435 room.peers<\/p>\n<\/li>\n<li>\n<p>RemovePeer(peer_id string) &#8212; \u0443\u0434\u0430\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u0437 \u043a\u043e\u043c\u043d\u0430\u0442\u044b, \u0435\u0441\u043b\u0438 \u0442\u0430\u043a\u043e\u0439 \u0438\u043c\u0435\u0435\u0442\u0441\u044f <\/p>\n<\/li>\n<li>\n<p>AddTrack(track *webrtc.TrackRemote) &#8212; \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u043d\u043e\u0432\u044b\u0439 \u0442\u0440\u0435\u043a \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u0442 \u0435\u0433\u043e \u043a \u0441\u0435\u0431\u0435 \u0432 room.tracks<\/p>\n<\/li>\n<li>\n<p>RemoveTrack(track *webrtc.TrackRemote) &#8212; \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0443\u0434\u0430\u043b\u044f\u0435\u0442 \u0442\u0440\u0435\u043a<\/p>\n<\/li>\n<li>\n<p>SendAnswer, SendOffer, SendICE &#8212; \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0442 Offer, Answer &amp; Ice-\u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0432\u0441\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u0435, \u043a\u0440\u043e\u043c\u0435 \u043f\u0438\u0440\u0430 \u0441\u043e id=peer_id.<\/p>\n<\/li>\n<\/ol>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0435\u0440\u0435\u0439\u0434\u0435\u043c \u043a \u0441\u0430\u043c\u043e\u043c\u0443 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u043c\u0443: room.Signal()<\/p>\n<pre><code class=\"go\">func (room *Room) Signal() { room.mutex.Lock() defer room.mutex.Unlock() attemptSync := func() (again bool) { for _, peer := range room.peers {            \/\/ 1) Check if peer is already closed if peer.connection.ConnectionState() == webrtc.PeerConnectionStateClosed { fmt.Println(\"Peer with peer_id\", peer.id, \"was disconnected\") room.RemovePeer(peer.id) return true }             \/\/ 2)  existingSenders := map[string]bool{} for _, sender := range peer.connection.GetSenders() { if sender.Track() == nil { continue }                 \/\/ 3) existingSenders[sender.Track().ID()] = true \/\/ If we have a RTPSender that doesn't map to a existing track remove and signal if _, ok := room.tracks[sender.Track().ID()]; !ok { if err := peer.connection.RemoveTrack(sender); err != nil { fmt.Println(\"Track\", sender.Track().ID(), \"was removed\") return true } } }  \/\/ 4) Don't receive videos we are sending, make sure we don't have loopback for _, receiver := range peer.connection.GetReceivers() { if receiver.Track() == nil { continue }  existingSenders[receiver.Track().ID()] = true } \/\/ 5) Add all track we aren't sending yet to the PeerConnection for trackID := range room.tracks { if _, ok := existingSenders[trackID]; !ok { if _, err := peer.connection.AddTrack(room.tracks[trackID]); err == nil { fmt.Println(\"New track are sending for peer\", peer.id) return true } else { fmt.Println(err) } } }             \/\/ 6) if peer.connection.PendingLocalDescription() != nil { fmt.Println(peer.connection.PendingLocalDescription()) offer, err := peer.connection.CreateOffer(&amp;webrtc.OfferOptions{ OfferAnswerOptions: webrtc.OfferAnswerOptions{}, ICERestart:         true, }) if err != nil { fmt.Println(\"Error in CreateOffer: \", err) return true } if err = peer.connection.SetLocalDescription(offer); err != nil { fmt.Println(\"Offer: \", offer) fmt.Println(\"Cannot set LocalDescription: \", err) return false }  offerString, err := json.Marshal(offer) if err != nil { fmt.Println(\"Marshalling failed: \", err) return true }  if err = peer.socket.WriteJSON(&amp;WsMessage{ Event: \"offer\", <\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-366871","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/366871","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=366871"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/366871\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=366871"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=366871"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=366871"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}