{"id":329014,"date":"2022-02-03T09:01:46","date_gmt":"2022-02-03T09:01:46","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=329014"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=329014","title":{"rendered":"<span>React: WebRTC Media Call<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" data-src=\"https:\/\/habrastorage.org\/webt\/jc\/hw\/st\/jchwst6a3nwlxlscnmuw5tdhmry.png\"\/>  <\/p>\n<p>  \u041f\u0440\u0438\u0432\u0435\u0442, \u0434\u0440\u0443\u0437\u044c\u044f!<\/p>\n<p>  <\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043f\u043e\u043a\u0430\u0436\u0443 \u0432\u0430\u043c, \u043a\u0430\u043a \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0430\u0443\u0434\u0438\u043e\/\u0432\u0438\u0434\u0435\u043e \u0437\u0432\u043e\u043d\u043a\u043e\u0432 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/WebRTC_API\"><code>WebRTC<\/code><\/a>.<\/p>\n<p>  <\/p>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0410 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440;<\/li>\n<li>\u043e\u043d \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442 \u044d\u0442\u043e\u0442 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u0411;<\/li>\n<li>\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0411 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0410 \u0434\u043b\u044f \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0430\u0443\u0434\u0438\u043e \u0438\u043b\u0438 \u0432\u0438\u0434\u0435\u043e \u0437\u0432\u043e\u043d\u043a\u0430;<\/li>\n<li>\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0410 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u043e \u0437\u0432\u043e\u043d\u043a\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0411 \u0438 \u043c\u043e\u0436\u0435\u0442 \u043e\u0442\u0432\u0435\u0442\u0438\u0442\u044c \u043d\u0430 \u043d\u0435\u0433\u043e \u0441 \u0432\u0438\u0434\u0435\u043e \u0438\u043b\u0438 \u0431\u0435\u0437 \u043b\u0438\u0431\u043e \u043e\u0442\u043a\u043b\u043e\u043d\u0438\u0442\u044c \u0437\u0432\u043e\u043d\u043e\u043a;<\/li>\n<li>\u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u0438\u043c\u0435\u044e\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0432\u043a\u043b\u044e\u0447\u0430\u0442\u044c\/\u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0430\u0443\u0434\u0438\u043e \u0438 \u0432\u0438\u0434\u0435\u043e;<\/li>\n<li>\u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0437\u0432\u043e\u043d\u043a\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 <code>WebRTC<\/code> \u0434\u043b\u044f \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u043d\u043e\u0432\u043e\u0433\u043e \u0437\u0432\u043e\u043d\u043a\u0430.<\/li>\n<\/ul>\n<p>  <\/p>\n<p><a href=\"https:\/\/react-webrtc-call.herokuapp.com\/\">\u0414\u0435\u043c\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/a>.<\/p>\n<p>  <\/p>\n<p><a href=\"https:\/\/github.com\/harryheman\/react-webrtc\">\u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0441 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u043c \u043a\u043e\u0434\u043e\u043c<\/a>.<\/p>\n<p>  <\/p>\n<p><a href=\"https:\/\/github.com\/nguymin4\/react-videocall\">\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0432\u0434\u043e\u0445\u043d\u043e\u0432\u0435\u043d\u0438\u044f<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u0430\u043c \u044d\u0442\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e, \u043f\u0440\u043e\u0448\u0443 \u043f\u043e\u0434 \u043a\u0430\u0442.<\/p>\n<p><a name=\"habracut\"><\/a>  <\/p>\n<p>\u0414\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <a href=\"https:\/\/yarnpkg.com\/\">Yarn<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0448\u0430\u0431\u043b\u043e\u043d\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u2014 <a href=\"https:\/\/vitejs.dev\/\">Vite<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0441\u0442\u0438\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u2014 <a href=\"https:\/\/sass-lang.com\/\">Sass<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043c\u0435\u0434\u0438\u0430\u0441\u0435\u0441\u0441\u0438\u0438 (signalling \u2014 \u0441\u0438\u0433\u043d\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438) \u043c\u0435\u0436\u0434\u0443 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430\u043c\u0438 \u2014 <a href=\"https:\/\/ru.wikipedia.org\/wiki\/WebSocket\">\u0432\u0435\u0431-\u0441\u043e\u043a\u0435\u0442\u044b<\/a> \u0432 \u043b\u0438\u0446\u0435 <a href=\"https:\/\/socket.io\/\">Socket.io<\/a>.<\/p>\n<p>  <\/p>\n<p>\u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043e\u043f\u0438\u0440\u0430\u0442\u044c\u0441\u044f \u043f\u0440\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f:<\/p>\n<p>  <\/p>\n<ul>\n<li><a href=\"https:\/\/w3c.github.io\/webrtc-pc\">WebRTC 1.0: Real-Time Communication Between Browsers<\/a> \u2014 \u043d\u0430\u0431\u043e\u0440 <code>API<\/code> \u0434\u043b\u044f \u043e\u0431\u043c\u0435\u043d\u0430 \u043c\u0435\u0434\u0438\u0430 \u0438 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u043c\u0435\u0436\u0434\u0443 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430\u043c\u0438, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u043d\u0430\u0431\u043e\u0440 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u043e\u0432, \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438;<\/li>\n<li><a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc3264\">An Offer\/Answer Model with the Session Description Protocol (SDP)<\/a> \u2014 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c, \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0449\u0438\u0439 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430\u043c \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0430 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0441\u0435\u0441\u0441\u0438\u0438 (Session Description Protocol, SDP);<\/li>\n<li><a href=\"https:\/\/w3c.github.io\/mediacapture-main\">Media Capture and Streams<\/a> \u2014 \u043d\u0430\u0431\u043e\u0440 <code>API<\/code>, \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0449\u0438\u0445 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0443 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043c\u0435\u0434\u0438\u0430\u043f\u043e\u0442\u043e\u043a\u0443 \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0421\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u044b <a href=\"https:\/\/developer.mozilla.org\/\">MDN<\/a> \u0431\u0443\u0434\u0443\u0442 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u043f\u043e \u043c\u0435\u0440\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438.<\/p>\n<p>  <\/p>\n<p>\u0424\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u0442\u043e\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432, \u043e\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0445 <a href=\"https:\/\/w3c.github.io\/webrtc-pc\/#simple-peer-to-peer-example\">\u0437\u0434\u0435\u0441\u044c<\/a> \u0438 <a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc8829#section-7.1\">\u0437\u0434\u0435\u0441\u044c<\/a>.<\/p>\n<p>  <\/p>\n<h2 id=\"podgotovka-i-nastroyka-proekta\">\u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/h2>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e, \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u0432 \u043d\u0435\u0435 \u0438 \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0448\u0430\u0431\u043b\u043e\u043d <code>React-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">mkdir react-webrtc cd react-webrtc  yarn create vite client --template react<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u043e\u043a\u0430 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0448\u0430\u0431\u043b\u043e\u043d, \u043f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u043c \u043e \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 <a href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%9E%D0%B4%D0%BD%D0%BE%D1%80%D0%B0%D0%BD%D0%B3%D0%BE%D0%B2%D0%B0%D1%8F_%D1%81%D0%B5%D1%82%D1%8C\">P2P-\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f<\/a> (peer-to-peer \u2014 \u0440\u0430\u0432\u043d\u044b\u0439 \u043a \u0440\u0430\u0432\u043d\u043e\u043c\u0443, \u043e\u0434\u043d\u043e\u0440\u0430\u043d\u0433\u043e\u0432\u0430\u044f \u0441\u0435\u0442\u044c), \u043e\u0431 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430\u0445 \u0438 \u043c\u0435\u0442\u043e\u0434\u0430\u0445, \u0437\u0430\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0432 \u044d\u0442\u043e\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435.<\/p>\n<p>  <\/p>\n<p>\u0412\u043e\u0442 \u0447\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043d\u0430 \u0441\u0430\u043c\u043e\u043c \u0432\u044b\u0441\u043e\u043a\u043e\u043c \u0443\u0440\u043e\u0432\u043d\u0435:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0410 (\u0434\u0430\u043b\u0435\u0435 \u2014 <code>\u0410<\/code>) \u0437\u0430\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u0442 (capture) \u043c\u0435\u0434\u0438\u0430\u043f\u043e\u0442\u043e\u043a \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f (\u0432\u0438\u0434\u0435\u043e\u043a\u0430\u043c\u0435\u0440\u0430 \u0438 \u043c\u0438\u043a\u0440\u043e\u0444\u043e\u043d). \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434 <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/MediaDevices\/getUserMedia\"><code>getUserMedia<\/code><\/a>:<\/li>\n<\/ul>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f \u0438\u043b\u0438 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f \u043a \u043f\u043e\u0442\u043e\u043a\u0443 \/\/ https:\/\/w3c.github.io\/mediacapture-main\/#constrainable-interface const config = {  audio: true,  video: true }  \/\/ https:\/\/w3c.github.io\/mediacapture-main\/#dom-mediadevices-getusermedia const localStream = await navigator.mediaDevices.getUserMedia(config)  \/\/ \u043c\u0435\u0434\u0438\u0430\u043f\u043e\u0442\u043e\u043a \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 1 \u0438\u043b\u0438 \u0431\u043e\u043b\u0435\u0435 \u043c\u0435\u0434\u0438\u0430\u0442\u0440\u0435\u043a\u043e\u0432 \/\/ https:\/\/w3c.github.io\/mediacapture-main\/#dom-mediastream-gettracks const localTracks = localStream.getTracks()<\/code><\/pre>\n<p>  <\/p>\n<ul>\n<li><code>\u0410<\/code> \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCPeerConnection\"><code>RTCPeerConnection<\/code><\/a> \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCPeerConnection\/RTCPeerConnection\">\u043e\u0434\u043d\u043e\u0438\u043c\u0435\u043d\u043d\u043e\u0433\u043e \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0430<\/a>:<\/li>\n<\/ul>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ https:\/\/w3c.github.io\/webrtc-pc\/#dom-rtcconfiguration const config = { iceServers: [{ urls: ['stun:stun.l.google.com:19302'] }] }  \/\/ https:\/\/w3c.github.io\/webrtc-pc\/#interface-definition const pc = new RTCPeerConnection(config)<\/code><\/pre>\n<p>  <\/p>\n<ul>\n<li><code>\u0410<\/code> \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u0437\u0430\u0445\u0432\u0430\u0447\u0435\u043d\u043d\u044b\u0435 \u0442\u0440\u0435\u043a\u0438 \u0438 \u043f\u043e\u0442\u043e\u043a \u0432 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 <code>RTCPeerConnection<\/code> \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0435\u0442\u043e\u0434\u0430 <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCPeerConnection\/addTrack\"><code>addTrack<\/code><\/a>:<\/li>\n<\/ul>\n<p>  <\/p>\n<pre><code class=\"javascript\">stream.getTracks().forEach((track) => {  \/\/ https:\/\/w3c.github.io\/webrtc-pc\/#dom-rtcpeerconnection-addtrack  pc.addTrack(track, stream) })<\/code><\/pre>\n<p>  <\/p>\n<ul>\n<li><code>\u0410<\/code> \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u0435 (offer) \u043e\u0431 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0435\u0442\u043e\u0434\u0430 <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCPeerConnection\/createOffer\"><code>createOffer<\/code><\/a>. \u0414\u0430\u043d\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 <a href=\"https:\/\/w3c.github.io\/webrtc-pc\/#dom-rtclocalsessiondescriptioninit\"><code>RTCLocalSessionDescriptionInit<\/code><\/a>:<\/li>\n<\/ul>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ https:\/\/w3c.github.io\/webrtc-pc\/#dom-rtcpeerconnection-createoffer const offer = await pc.createOffer()<\/code><\/pre>\n<p>  <\/p>\n<ul>\n<li><code>\u0410<\/code> \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u043c\u0435\u0442\u043e\u0434 <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCPeerConnection\/setLocalDescription\"><code>setLocalDescription<\/code><\/a>, \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u044f \u0435\u043c\u0443 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u0435:<\/li>\n<\/ul>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ https:\/\/w3c.github.io\/webrtc-pc\/#dom-peerconnection-setlocaldescription pc.setLocalDescription(offer)<\/code><\/pre>\n<p>  <\/p>\n<ul>\n<li>\u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044f \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0411 (\u0434\u0430\u043b\u0435\u0435 \u2014 <code>\u0411<\/code>) \u043b\u044e\u0431\u044b\u043c \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u043c \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0447\u0435\u0440\u0435\u0437 \u0432\u0435\u0431-\u0441\u043e\u043a\u0435\u0442\u044b (\u0441\u0438\u0433\u043d\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f):<\/li>\n<\/ul>\n<p>  <\/p>\n<pre><code class=\"javascript\">socket.emit('call', {  \/\/ \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0411  to: remoteId,  \/\/ \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u0435  sdp: offer })<\/code><\/pre>\n<p>  <\/p>\n<ul>\n<li><code>\u0411<\/code> \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 <code>RTCPeerConnection<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u0432 \u043d\u0435\u0433\u043e \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432 \u0432\u0438\u0434\u0435 <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCSessionDescription\"><code>RTCSessionDescription<\/code><\/a>, \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0435\u0442\u043e\u0434\u0430 <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCPeerConnection\/setRemoteDescription\"><code>setRemoteDescription<\/code><\/a>. <code>RTCSessionDescription<\/code> (\u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0441\u0435\u0441\u0441\u0438\u0438) \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCSessionDescription\/RTCSessionDescription\">\u043e\u0434\u043d\u043e\u0438\u043c\u0435\u043d\u043d\u043e\u0433\u043e \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0430<\/a>:<\/li>\n<\/ul>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ https:\/\/w3c.github.io\/webrtc-pc\/#rtcsessiondescription-class const sdp = new RTCSessionDescription(desc) \/\/ https:\/\/w3c.github.io\/webrtc-pc\/#dom-peerconnection-setremotedescription pc.setRemoteDescription(sdp)<\/code><\/pre>\n<p>  <\/p>\n<ul>\n<li>\u043f\u0440\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0438 <code>\u0410<\/code> \u0442\u0440\u0435\u043a\u043e\u0432 \u0438 \u043f\u043e\u0442\u043e\u043a\u0430 \u0432 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 <code>RTCPeerConnection<\/code> \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 <code>\u0411<\/code> \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u0435\u0442 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCPeerConnection\/track_event\"><code>track<\/code><\/a>, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCPeerConnection\/ontrack\"><code>ontrack<\/code><\/a>:<\/li>\n<\/ul>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043c\u0435\u0434\u0438\u0430\u043f\u043e\u0442\u043e\u043a\u0438, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0435 \u043e\u0442 \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u044b, \u0432 \u0432\u0438\u0434\u0435 \u043c\u0430\u0441\u0441\u0438\u0432\u0430 \/\/ \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0432 \u043c\u0430\u0441\u0441\u0438\u0432\u0435 \u0431\u0443\u0434\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u0442\u0430\u043a\u043e\u0439 \u043f\u043e\u0442\u043e\u043a \/\/ https:\/\/w3c.github.io\/webrtc-pc\/#rtctrackevent \/\/ https:\/\/w3c.github.io\/webrtc-pc\/#dom-rtcpeerconnection-ontrack pc.ontrack = ({ streams }) => {  remoteStream = streams[0] }<\/code><\/pre>\n<p>  <\/p>\n<ul>\n<li><code>\u0411<\/code> \u0437\u0430\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u043c\u0435\u0434\u0438\u0430\u043f\u043e\u0442\u043e\u043a \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u0442\u0440\u0435\u043a\u0438 \u0438 \u043f\u043e\u0442\u043e\u043a \u0432 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 <code>RTCPeerConnection<\/code>, \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u043e\u0442\u0432\u0435\u0442 \u043d\u0430 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043e\u0431 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f (answer) \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0435\u0442\u043e\u0434\u0430 <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCPeerConnection\/createAnswer\"><code>createAnswer<\/code><\/a>, \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 <code>setLocalDescription<\/code> \u0441 \u043e\u0442\u0432\u0435\u0442\u043e\u043c \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442 \u043e\u0442\u0432\u0435\u0442 <code>\u0410<\/code>:<\/li>\n<\/ul>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ https:\/\/w3c.github.io\/webrtc-pc\/#dom-rtcpeerconnection-createanswer const answer = await pc.createAnswer()  pc.setLocalDescription(answer)  \/\/ \u0441\u0438\u0433\u043d\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f socket.emit('call', {  \/\/ \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0410  to: remoteId,  sdp: answer })<\/code><\/pre>\n<p>  <\/p>\n<ul>\n<li>\n<p><code>\u0410<\/code>, \u0432 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u0442\u0430\u043a\u0436\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 <code>setRemoteDescription<\/code> \u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442 <code>ontrack<\/code>;<\/p>\n<p>  <\/li>\n<li>\n<p>\u0432 \u044d\u0442\u043e \u0436\u0435 \u0432\u0440\u0435\u043c\u044f (\u043f\u043e\u0441\u043b\u0435 \u0432\u044b\u0437\u043e\u0432\u0430 <code>setLocalDescription<\/code>) \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043f\u043e\u0434\u0431\u043e\u0440 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u043e\u0432 \u0434\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f (ICE gathering). \u0412\u043e\u0437\u043d\u0438\u043a\u0430\u0435\u0442 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCPeerConnection\/icecandidate_event\"><code>icecandidate<\/code><\/a>, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCPeerConnection\/onicecandidate\"><code>onicecandidate<\/code><\/a>:<\/p>\n<p>  <\/li>\n<\/ul>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ https:\/\/w3c.github.io\/webrtc-pc\/#dom-rtcpeerconnection-onicecandidate \/\/ https:\/\/w3c.github.io\/webrtc-pc\/#dom-rtcpeerconnectioniceevent pc.onicecandidate = ({ candidate }) => {  \/\/ \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 `RTCIceCandidateInit`  \/\/ https:\/\/w3c.github.io\/webrtc-pc\/#dom-rtcicecandidateinit  \/\/ \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \"\u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430\" \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u0435  socket.emit('call', {    to: remoteId,    candidate  }) }<\/code><\/pre>\n<p>  <\/p>\n<ul>\n<li>\u043f\u0440\u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0438 &#171;\u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430&#187; \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u043e\u0439, \u043e\u043d\u0430 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCIceCandidate\"><code>RTCIceCandidate<\/code><\/a> \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCIceCandidate\/RTCIceCandidate\">\u043e\u0434\u043d\u043e\u0438\u043c\u0435\u043d\u043d\u043e\u0433\u043e \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0430<\/a> \u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u043c\u0435\u0442\u043e\u0434 <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCPeerConnection\/addIceCandidate\"><code>addIceCandidate<\/code><\/a>:<\/li>\n<\/ul>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ https:\/\/w3c.github.io\/webrtc-pc\/#rtcicecandidate-interface const _candidate = new RTCIceCandidate(candidate)  \/\/ https:\/\/w3c.github.io\/webrtc-pc\/#dom-peerconnection-addicecandidate pc.addIceCandidate(_candidate)<\/code><\/pre>\n<p>  <\/p>\n<ul>\n<li>\u043f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u0441\u0442\u043e\u0440\u043e\u043d\u044b \u043c\u043e\u0433\u0443\u0442 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u043e\u0431\u043c\u0435\u043d\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u043c\u0435\u0434\u0438\u0430\u0434\u0430\u043d\u043d\u044b\u043c\u0438.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0411\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u0432\u0441\u0435 \u044d\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043d\u0430 \u0431\u043e\u043b\u0435\u0435 \u043d\u0438\u0437\u043a\u043e\u043c \u0443\u0440\u043e\u0432\u043d\u0435, \u0432 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0439 \u0444\u043e\u0440\u043c\u0435 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c <a href=\"https:\/\/webrtcforthecurious.com\/\">\u0437\u0434\u0435\u0441\u044c<\/a>.<\/p>\n<p>  <\/p>\n<h2 id=\"server\">\u0421\u0435\u0440\u0432\u0435\u0440<\/h2>\n<p>  <\/p>\n<p>\u041f\u043e\u0436\u0430\u043b\u0443\u0439, \u0438\u043c\u0435\u0435\u0442 \u0441\u043c\u044b\u0441\u043b \u043d\u0430\u0447\u0430\u0442\u044c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0441 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043c\u0435\u0434\u0438\u0430\u0441\u0435\u0441\u0441\u0438\u0438 \u043c\u0435\u0436\u0434\u0443 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430\u043c\u0438 (\u0441\u0438\u0433\u043d\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438).<\/p>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e, \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u0432 \u043d\u0435\u0435, \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c <code>Node.js-\u043f\u0440\u043e\u0435\u043a\u0442<\/code> \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">mkdir server cd server  yarn init -yp  yarn add express socket.io dotenv nanoid  yarn add -D nodemon<\/code><\/pre>\n<p>  <\/p>\n<p>\u0417\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438:<\/p>\n<p>  <\/p>\n<ul>\n<li><a href=\"https:\/\/expressjs.com\/ru\/\"><code>express<\/code><\/a> \u2014 <code>Node.js-\u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a<\/code> \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0432\u0435\u0431-\u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432;<\/li>\n<li><a href=\"https:\/\/www.npmjs.com\/package\/socket.io\"><code>socket.io<\/code><\/a> \u2014 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430, \u043e\u0431\u043b\u0435\u0433\u0447\u0430\u044e\u0449\u0430\u044f \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 \u0432\u0435\u0431-\u0441\u043e\u043a\u0435\u0442\u0430\u043c\u0438;<\/li>\n<li><a href=\"https:\/\/www.npmjs.com\/package\/dotenv\"><code>dotenv<\/code><\/a> \u2014 \u0443\u0442\u0438\u043b\u0438\u0442\u0430 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c\u0438 \u0441\u0440\u0435\u0434\u044b \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f;<\/li>\n<li><a href=\"https:\/\/www.npmjs.com\/package\/nanoid\"><code>nanoid<\/code><\/a> \u2014 \u0443\u0442\u0438\u043b\u0438\u0442\u0430 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u0432;<\/li>\n<li><a href=\"https:\/\/www.npmjs.com\/package\/nodemon\"><code>nodemon<\/code><\/a> \u2014 \u0443\u0442\u0438\u043b\u0438\u0442\u0430 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0442\u0438\u043f \u043a\u043e\u0434\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0438 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432 \u0432 <code>package.json<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\"type\": \"module\", \"scripts\": {  \"dev\": \"nodemon\",  \"start\": \"node index.js\" }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0444\u0430\u0439\u043b <code>index.js<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">touch index.js<\/code><\/pre>\n<p>  <\/p>\n<p>\u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0438 \u0443\u0442\u0438\u043b\u0438\u0442\u044b:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import express from 'express' import { createServer } from 'http' import { join, dirname } from 'path' import { fileURLToPath } from 'url' import { Server } from 'socket.io' import { config } from 'dotenv' import initSocket from '.\/utils\/initSocket.js'<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c \u0441\u0440\u0435\u0434\u044b \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0438 \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u043f\u0443\u0442\u044c \u043a \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">config()  const __dirname = dirname(fileURLToPath(import.meta.url))<\/code><\/pre>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u044b <code>express<\/code> \u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">const app = express() const server = createServer(app)<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043e\u0431\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u043d\u0438\u0435 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 \u0438\u0437 \u0441\u0431\u043e\u0440\u043a\u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">app.use(express.static(join(__dirname, '..\/client\/dist')))<\/code><\/pre>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 <code>socket.io<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">const io = new Server(server, {  cors: process.env.ALLOWED_ORIGIN,  serveClient: false }) io.on('connection', initSocket)<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043f\u043e\u0440\u0442 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0441\u0435\u0440\u0432\u0435\u0440:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">const port = process.env.PORT || 4000 server.listen(port, () => {  console.log(`Server ready on port ${port} ?`) })<\/code><\/pre>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0444\u0430\u0439\u043b <code>.env<\/code> \u0438 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0432 \u043d\u0435\u0433\u043e \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u043d\u044b\u0439 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">ALLOWED_ORIGIN=http:\/\/localhost:3000<\/code><\/pre>\n<p>  <\/p>\n<p>\u0420\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f (<code>utils\/initSocket.js<\/code>).<\/p>\n<p>  <\/p>\n<p>\u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c <code>nanoid<\/code> \u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u0434\u043b\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { nanoid } from 'nanoid'  const users = {}  \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0441\u043e\u043a\u0435\u0442 export default function initSocket(socket) {  \/\/ TODO }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0421\u043e\u043a\u0435\u0442 \u0431\u0443\u0434\u0435\u0442 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c 5 \u0442\u0438\u043f\u043e\u0432 \u0441\u043e\u0431\u044b\u0442\u0438\u0439:<\/p>\n<p>  <\/p>\n<ul>\n<li><code>init<\/code>: \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u2014 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f <code>id<\/code> \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f (\u0435\u0433\u043e \u0441\u043e\u043a\u0435\u0442\u0430) \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0430 <code>id<\/code> \u043a\u043b\u0438\u0435\u043d\u0442\u0443;<\/li>\n<li><code>request<\/code>: \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0430 <code>id<\/code> \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0434\u0440\u0443\u0433\u043e\u043c\u0443 \u043a\u043b\u0438\u0435\u043d\u0442\u0443;<\/li>\n<li><code>call<\/code>: \u043d\u0430\u0447\u0430\u043b\u043e \u0437\u0432\u043e\u043d\u043a\u0430;<\/li>\n<li><code>end<\/code>: \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u0437\u0432\u043e\u043d\u043a\u0430;<\/li>\n<li><code>disconnect<\/code>: \u0440\u0430\u0437\u0440\u044b\u0432 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f (\u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0441\u043e\u043a\u0435\u0442\u0430).<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u0434\u043b\u044f <code>id<\/code> \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0430\u0434\u0440\u0435\u0441\u0430\u0442\u0443:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">let id  \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 `id` \u0430\u0434\u0440\u0435\u0441\u0430\u0442\u0430, \u0442\u0438\u043f \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0438 \u043f\u043e\u043b\u0435\u0437\u043d\u0443\u044e \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0443 - \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 const emit = (userId, event, data) => {  \/\/ \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u044f  const receiver = users[userId]  if (receiver) {    \/\/ \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u0441\u043e\u0431\u044b\u0442\u0438\u0435    receiver.emit(event, data)  } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u043d\u0430\u0437\u0432\u0430\u043d\u043d\u044b\u0435 \u0432\u044b\u0448\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">socket  .on('init', () => {    id = nanoid(5)    users[id] = socket    console.log(id, 'connected')    socket.emit('init', { id })  })  .on('request', (data) => {    emit(data.to, 'request', { from: id })  })  .on('call', (data) => {    emit(data.to, 'call', { ...data, from: id })  })  .on('end', (data) => {    emit(data.to, 'end')  })  .on('disconnect', () => {    delete users[id]    console.log(id, 'disconnected')  })<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u0443\u043c\u0430\u044e, \u0437\u0434\u0435\u0441\u044c \u0432\u0441\u0435 \u043f\u043e\u043d\u044f\u0442\u043d\u043e.<\/p>\n<p>  <\/p>\n<p>\u0411\u043e\u043b\u044c\u0448\u0435 \u043e\u0442 \u043d\u0430\u0448\u0435\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f.<\/p>\n<p>  <\/p>\n<h2 id=\"klient\">\u041a\u043b\u0438\u0435\u043d\u0442<\/h2>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u0432 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e \u0441 \u043a\u043e\u0434\u043e\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c 3 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">cd client  yarn add socket.io-client sass react-icons<\/code><\/pre>\n<p>  <\/p>\n<ul>\n<li><a href=\"https:\/\/www.npmjs.com\/package\/socket.io-client\"><code>socket.io-client<\/code><\/a> \u2014 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0430\u044f \u0447\u0430\u0441\u0442\u044c <code>socket.io<\/code>;<\/li>\n<li><a href=\"https:\/\/www.npmjs.com\/package\/sass\"><code>sass<\/code><\/a> \u2014 <code>CSS-\u043f\u0440\u0435\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440<\/code>.<\/li>\n<li><a href=\"https:\/\/www.npmjs.com\/package\/react-icons\"><code>react-icons<\/code><\/a> \u2014 \u0438\u043a\u043e\u043d\u043a\u0438.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 <code>src<\/code> \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439:<\/p>\n<p>  <\/p>\n<ul>\n<li>components<br \/> \n<ul>\n<li>MainWindow.jsx \u2014 \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u044b\u0439 \u044d\u043a\u0440\u0430\u043d<\/li>\n<li>CallModal.jsx \u2014 \u043c\u043e\u0434\u0430\u043b\u044c\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u0441 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435\u043c \u043e \u0432\u0445\u043e\u0434\u044f\u0449\u0435\u043c \u0437\u0432\u043e\u043d\u043a\u0435<\/li>\n<li>CallWindow.jsx \u2014 \u044d\u043a\u0440\u0430\u043d \u0434\u043b\u044f \u043a\u043e\u043c\u043c\u0443\u043d\u0438\u043a\u0430\u0446\u0438\u0438<\/li>\n<li>index.js \u2014 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0439 \u044d\u043a\u0441\u043f\u043e\u0440\u0442 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432<\/li>\n<\/ul>\n<\/li>\n<li>styles \u2014 \u0441\u0442\u0438\u043b\u0438 (\u044f \u043d\u0435 \u0431\u0443\u0434\u0443 \u043e \u043d\u0438\u0445 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c, \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043a\u043e\u043f\u0438\u0440\u0443\u0439\u0442\u0435 \u0438\u0445 \u0438\u0437 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f \u0441 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u043c \u043a\u043e\u0434\u043e\u043c)<\/li>\n<li>utils<br \/> \n<ul>\n<li>socket.js \u2014 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f <code>socket.io<\/code><\/li>\n<li>Emitter.js \u2014 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u2014 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u0430 <a href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%98%D0%B7%D0%B4%D0%B0%D1%82%D0%B5%D0%BB%D1%8C-%D0%BF%D0%BE%D0%B4%D0%BF%D0%B8%D1%81%D1%87%D0%B8%D0%BA_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)\"><code>Pub\/Sub<\/code><\/a><\/li>\n<li>MediaDevices.js \u2014 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043c\u0435\u0434\u0438\u0430\u043f\u043e\u0442\u043e\u043a\u043e\u043c<\/li>\n<li>PeerConnection.js \u2014 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 <code>RTCPeerConnection<\/code><\/li>\n<\/ul>\n<\/li>\n<li>App.jsx<\/li>\n<li>main.jsx<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u0443\u0442\u0438\u043b\u0438\u0442\u044b \u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u0432.<\/p>\n<p>  <\/p>\n<h3 id=\"interfeysy-i-utilita\">\u0418\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u044b \u0438 \u0443\u0442\u0438\u043b\u0438\u0442\u0430<\/h3>\n<p>  <\/p>\n<p>\u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u0441\u043e\u043a\u0435\u0442 (<code>utils\/socket.js<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { io } from 'socket.io-client'  \/\/ \u0432 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435 \u0441\u0435\u0440\u0432\u0435\u0440 \u0438 \u043a\u043b\u0438\u0435\u043d\u0442 \u0431\u0443\u0434\u0443\u0442 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u043e\u0434\u043d\u043e\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0435 (origin), \/\/ \u0430 \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 - \u0432 \u0440\u0430\u0437\u043d\u044b\u0445 const SERVER_URI = import.meta.env.DEV ? 'http:\/\/localhost:4000' : ''  const socket = io(SERVER_URI)  export default socket<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 <code>pub\/sub<\/code> (<code>utils\/Emitter.js<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">class Emitter {  constructor() {    this.events = {}  }   emit(e, ...args) {    if (this.events[e]) {      this.events[e].forEach((fn) => fn(...args))    }    return this  }   on(e, fn) {    this.events[e] ? this.events[e].push(fn) : (this.events[e] = [fn])    return this  }   off(e, fn) {    if (e &amp;&amp; typeof fn === 'function') {      const listeners = this.events[e]      listeners.splice(        listeners.findIndex((_fn) => _fn === fn),        1      )    } else {      this.events[e] = []    }    return this  } }  export default Emitter<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u0430\u043d\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 3 \u043c\u0435\u0442\u043e\u0434\u0430:<\/p>\n<p>  <\/p>\n<ul>\n<li><code>emit<\/code>: \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u044f;<\/li>\n<li><code>on<\/code>: \u0434\u043b\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u2014 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u044f;<\/li>\n<li><code>off<\/code>: \u0434\u043b\u044f \u043e\u0442\u043f\u0438\u0441\u043a\u0438 \u2014 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u0438\u043b\u0438 \u0432\u0441\u0435\u0445 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u044f.<\/li>\n<\/ul>\n<p>  <\/p>\n<p><em>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435<\/em>: \u043a\u0430\u0436\u0434\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 <code>this<\/code>. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434\u044b \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0432 \u0446\u0435\u043f\u043e\u0447\u043a\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, <code>emitter.on(event1, callback1).on(event2, callback2)<\/code>.<\/p>\n<p>  <\/p>\n<p>\u0418\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u044b <code>MediaDevices<\/code> \u0438 <code>PeerConnection<\/code> \u0431\u0443\u0434\u0443\u0442 \u0440\u0430\u0441\u0448\u0438\u0440\u044f\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 <code>Emitter<\/code>, \u0442\u0435\u043c \u0441\u0430\u043c\u044b\u043c \u043d\u0430\u0441\u043b\u0435\u0434\u0443\u044f \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b.<\/p>\n<p>  <\/p>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043c\u0435\u0434\u0438\u0430\u043f\u043e\u0442\u043e\u043a\u043e\u043c (<code>utils\/MediaDevices.js<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import Emitter from '.\/Emitter'  class MediaDevice extends Emitter {  start() {    navigator.mediaDevices      .getUserMedia({        audio: true,        video: true      })      .then((stream) => {        this.stream = stream        this.emit('stream', stream)      })      .catch(console.error)     return this  }   toggle(type, on) {    if (this.stream) {      this.stream[`get${type}Tracks`]().forEach((t) => {        t.enabled = on ? on : !t.enabled      })    }     return this  }   stop() {    if (this.stream) {      this.stream.getTracks().forEach((t) => { t.stop() })    }    \/\/ \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u0432\u0441\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0432\u0441\u0435\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u0439    this.off()     return this  } }  export default MediaDevice<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u0430\u043d\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0440\u0430\u0441\u0448\u0438\u0440\u044f\u0435\u0442 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 <code>Emitter<\/code> \u0438 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 3 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u043c\u0435\u0442\u043e\u0434\u0430:<\/p>\n<p>  <\/p>\n<ul>\n<li><code>start<\/code>: \u0434\u043b\u044f \u0437\u0430\u0445\u0432\u0430\u0442\u0430 \u043c\u0435\u0434\u0438\u0430\u043f\u043e\u0442\u043e\u043a\u0430 \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u0435\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0438 \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u0438 \u0432\u044b\u0437\u043e\u0432\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u044f <code>stream<\/code>;<\/li>\n<li><code>toggle<\/code>: \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0430\u0443\u0434\u0438\u043e \u0438 \u0432\u0438\u0434\u0435\u043e \u0442\u0440\u0435\u043a\u043e\u0432. \u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0430\u0443\u0434\u0438\u043e \u0442\u0440\u0435\u043a\u043e\u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434 <code>getAudioTracks<\/code>, \u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0432\u0438\u0434\u0435\u043e\u0442\u0440\u0435\u043a\u043e\u0432 \u2014 <code>getVideoTracks<\/code>;<\/li>\n<li><code>stop<\/code>: \u0434\u043b\u044f \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0437\u0430\u0445\u0432\u0430\u0442\u0430 \u043c\u0435\u0434\u0438\u0430\u043f\u043e\u0442\u043e\u043a\u0430.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u041d\u0430 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 <code>PeerConnection<\/code> \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c\u0441\u044f \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435.<\/p>\n<p>  <\/p>\n<p>\u0414\u0430\u043d\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0440\u0430\u0441\u0448\u0438\u0440\u044f\u0435\u0442 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 <code>Emitter<\/code>, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 <code>MediaDevices<\/code> \u0438 \u0443\u0442\u0438\u043b\u0438\u0442\u0443 <code>socket<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import Emitter from '.\/Emitter' import MediaDevice from '.\/MediaDevice' import socket from '.\/socket'  \/\/ \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 const CONFIG = { iceServers: [{ urls: ['stun:stun.l.google.com:19302'] }] }  class PeerConnection extends Emitter {  \/\/ TODO }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 <code>PeerConnection<\/code> \u0432 \u0435\u0433\u043e \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044f <code>id<\/code> \u0430\u0434\u0440\u0435\u0441\u0430\u0442\u0430 \u2014 \u0442\u043e\u0433\u043e, \u043a\u043e\u043c\u0443 \u043c\u044b \u0437\u0432\u043e\u043d\u0438\u043c. \u0412 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0435 <code>id<\/code> \u0430\u0434\u0440\u0435\u0441\u0430\u0442\u0430 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e, \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 <code>RTCPeerConnection<\/code>, \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 <code>icecandidate<\/code> \u0438 <code>track<\/code>, \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 <code>MediaDevices<\/code> \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0430 (\u0444\u0438\u043a\u0441\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0434\u043b\u044f) \u043c\u0435\u0442\u043e\u0434\u0430 <code>getDescription<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">constructor(remoteId) {  super()  this.remoteId = remoteId   this.pc = new RTCPeerConnection(CONFIG)  this.pc.onicecandidate = ({ candidate }) => {    socket.emit('call', {      to: this.remoteId,      candidate    })  }  this.pc.ontrack = ({ streams }) => {    \/\/ \u0443\u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434    this.emit('remoteStream', streams[0])  }   this.mediaDevice = new MediaDevice()  this.getDescription = this.getDescription.bind(this) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0437\u0432\u043e\u043d\u043a\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u043c\u0435\u0442\u043e\u0434 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u0442\u043e\u0433\u043e, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0438\u043d\u0438\u0446\u0438\u0430\u0442\u043e\u0440\u043e\u043c \u0437\u0432\u043e\u043d\u043a\u0430 \/\/ \u0438 \u043e\u0431\u044a\u0435\u043a\u0442 \u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c\u0438 \u0434\u043b\u044f `getUserMedia` start(isCaller, config) {  this.mediaDevice    \/\/ \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u0441\u043e\u0431\u044b\u0442\u0438\u0435 `stream`    .on('stream', (stream) => {      \/\/ \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0437\u0430\u0445\u0432\u0430\u0447\u0435\u043d\u043d\u044b\u0435 \u0442\u0440\u0435\u043a\u0438 \u0438 \u043f\u043e\u0442\u043e\u043a \u0432 `PeerConnection`      stream.getTracks().forEach((t) => {        this.pc.addTrack(t, stream)      })       \/\/ \u0434\u0430\u043d\u043d\u044b\u0439 \u0437\u0430\u0445\u0432\u0430\u0447\u0435\u043d\u043d\u044b\u0439 \u043f\u043e\u0442\u043e\u043a \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u043c      this.emit('localStream', stream)       \/\/ \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u0442\u043e\u0440\u043e\u043c \u0437\u0432\u043e\u043d\u043a\u0430,      \/\/ \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0430 \u0437\u0432\u043e\u043d\u043e\u043a \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u0435,      \/\/ \u0438\u043d\u0430\u0447\u0435 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043e\u0431 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f      isCaller        ? socket.emit('request', { to: this.remoteId })        : this.createOffer()    })    \/\/ \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043c\u0435\u0442\u043e\u0434 `start` \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 `MediaDevices`    .start(config)   return this }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0437\u0432\u043e\u043d\u043a\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">stop(isCaller) {  \/\/ \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u0442\u043e\u0440\u043e\u043c \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0437\u0432\u043e\u043d\u043a\u0430,  \/\/ \u0441\u043e\u043e\u0431\u0449\u0430\u0435\u043c \u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0438 \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u0435  if (isCaller) {    socket.emit('end', { to: this.remoteId })  }  \/\/ \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0437\u0430\u0445\u0432\u0430\u0442 \u043c\u0435\u0434\u0438\u0430\u043f\u043e\u0442\u043e\u043a\u0430  this.mediaDevice.stop()  \/\/ \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0434\u043b\u044f \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u043d\u043e\u0432\u043e\u0433\u043e \u0437\u0432\u043e\u043d\u043a\u0430  this.pc.restartIce()  this.off()   return this }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u0439, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0445 \u0434\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043c\u0435\u0434\u0438\u0430\u0441\u0435\u0441\u0441\u0438\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u044f createOffer() {  this.pc.createOffer().then(this.getDescription).catch(console.error)   return this }  \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u043e\u0442\u0432\u0435\u0442\u0430 createAnswer() {  this.pc.createAnswer().then(this.getDescription).catch(console.error)   return this }  \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0432 `PeerConnection` \/\/ \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 getDescription(desc) {  this.pc.setLocalDescription(desc)   socket.emit('call', { to: this.remoteId, sdp: desc })   return this }  \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0433\u043e (\u0432 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0438 \"\u043d\u0430\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e\u0441\u044f \u0434\u0430\u043b\u0435\u043a\u043e\") \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0432 `PeerConnection` setRemoteDescription(desc) {  this.pc.setRemoteDescription(new RTCSessionDescription(desc))   return this }  \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430 \u0432 `PeerConnection` addIceCandidate(candidate) {  \/\/ \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0443\u0441\u0442\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u043e\u0439  if (candidate) {    this.pc.addIceCandidate(new RTCIceCandidate(candidate))  }   return this }<\/code><\/pre>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0435\u043c\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430:<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import Emitter from '.\/Emitter' import MediaDevice from '.\/MediaDevice' import socket from '.\/socket'  const CONFIG = { iceServers: [{ urls: ['stun:stun.l.google.com:19302'] }] }  class PeerConnection extends Emitter {  constructor(remoteId) {    super()    this.remoteId = remoteId     this.pc = new RTCPeerConnection(CONFIG)    this.pc.onicecandidate = ({ candidate }) => {      socket.emit('call', {        to: this.remoteId,        candidate      })    }    this.pc.ontrack = ({ streams }) => {      this.emit('remoteStream', streams[0])    }     this.mediaDevice = new MediaDevice()     this.getDescription = this.getDescription.bind(this)  }   start(isCaller, config) {    this.mediaDevice      .on('stream', (stream) => {        stream.getTracks().forEach((t) => {          this.pc.addTrack(t, stream)        })         this.emit('localStream', stream)         isCaller          ? socket.emit('request', { to: this.remoteId })          : this.createOffer()      })      .start(config)     return this  }   stop(isCaller) {    if (isCaller) {      socket.emit('end', { to: this.remoteId })    }    this.mediaDevice.stop()    this.pc.restartIce()    this.off()     return this  }   createOffer() {    this.pc.createOffer().then(this.getDescription).catch(console.error)     return this  }   createAnswer() {    this.pc.createAnswer().then(this.getDescription).catch(console.error)     return this  }   getDescription(desc) {    this.pc.setLocalDescription(desc)     socket.emit('call', { to: this.remoteId, sdp: desc })     return this  }   setRemoteDescription(desc) {    this.pc.setRemoteDescription(new RTCSessionDescription(desc))     return this  }   addIceCandidate(candidate) {    if (candidate) {      this.pc.addIceCandidate(new RTCIceCandidate(candidate))    }     return this  } }  export default PeerConnection<\/code><\/pre>\n<\/div><\/div>\n<p>  <\/p>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u044f \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u044b <code>MediaDevice<\/code> \u0438 <code>PeerConnection<\/code> \u0432 \u0432\u0438\u0434\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0445 \u0445\u0443\u043a\u043e\u0432, \u043d\u043e \u043c\u043d\u0435 \u043d\u0435 \u043f\u043e\u043d\u0440\u0430\u0432\u0438\u043b\u0441\u044f \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u043f\u0440\u0435\u0434\u043f\u043e\u0447\u0435\u043b \u0432\u0435\u0440\u043d\u0443\u0442\u044c\u0441\u044f \u043a \u043a\u043b\u0430\u0441\u0441\u0430\u043c.<\/p>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u043c.<\/p>\n<p>  <\/p>\n<h3 id=\"komponenty\">\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b<\/h3>\n<p>  <\/p>\n<p>\u041d\u0430\u0447\u0430\u043b\u044c\u043d\u044b\u0439 \u044d\u043a\u0440\u0430\u043d (<code>components\/MainWindow.jsx<\/code>).<\/p>\n<p>  <\/p>\n<p>\u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0445\u0443\u043a\u0438, \u0438\u043a\u043e\u043d\u043a\u0438 \u0438 \u0441\u043e\u043a\u0435\u0442:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { useEffect, useState } from 'react' import { BsCameraVideo, BsPhone } from 'react-icons\/bs'  import socket from '..\/utils\/socket'  \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0437\u0432\u043e\u043d\u043a\u0430 export const MainWindow = ({ startCall }) => {  \/\/ TODO }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0434\u043b\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e (\u043d\u0430\u0448\u0435\u0433\u043e) \u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0433\u043e (\u0432 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0438 &#171;\u043d\u0430\u0445\u043e\u0434\u044f\u0449\u0438\u0439\u0441\u044f \u0434\u0430\u043b\u0435\u043a\u043e&#187;) <code>id<\/code>, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0434\u043b\u044f \u043e\u0448\u0438\u0431\u043a\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">const [localId, setLocalId] = useState('') const [remoteId, setRemoteId] = useState('') const [error, setError] = useState('')<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0438 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u0441\u043e\u0431\u044b\u0442\u0438\u0435 <code>init<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">useEffect(() => {  socket    .on('init', ({ id }) => {      \/\/ \u043d\u0430\u0448 `id`, \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435      setLocalId(id)    })    .emit('init') }, [])<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0437\u0432\u043e\u043d\u043a\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u0437\u0432\u043e\u043d\u043e\u043a \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c\u0441\u044f \u043a\u0430\u043a \u0441 \u0432\u0438\u0434\u0435\u043e, \u0442\u0430\u043a \u0438 \u0431\u0435\u0437 \u043d\u0435\u0433\u043e const callWithVideo = (video) => {  \/\/ `id` \u043d\u0430\u0448\u0435\u0433\u043e \u0434\u0440\u0443\u0433\u0430 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u043d \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u043c \u043f\u043e\u043b\u0435  if (!remoteId.trim()) {    return setError('Your friend ID must be specified!')  }  \/\/ \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0434\u043b\u044f \u0437\u0430\u0445\u0432\u0430\u0442\u0430 \u043c\u0435\u0434\u0438\u0430\u043f\u043e\u0442\u043e\u043a\u0430  const config = { audio: true, video }  \/\/ \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f `PeerConnection`  startCall(true, remoteId, config) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0443:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">return (  &lt;div className='container main-window'>    &lt;div className='local-id'>      &lt;h2>Your ID is&lt;\/h2>      &lt;p>{localId}&lt;\/p>    &lt;\/div>    &lt;div className='remote-id'>      &lt;label htmlFor='remoteId'>Your friend ID&lt;\/label>      &lt;p className='error'>{error}&lt;\/p>      &lt;input        type='text'        spellCheck={false}        placeholder='Enter friend ID'        onChange={({ target: { value } }) => {          setError('')          setRemoteId(value)        }}      \/>      &lt;div className='control'>        {\/* \u0432\u0438\u0434\u0435\u043e \u0437\u0432\u043e\u043d\u043e\u043a *\/}        &lt;button onClick={() => callWithVideo(true)}>          &lt;BsCameraVideo \/>        &lt;\/button>        {\/* \u0430\u0443\u0434\u0438\u043e \u0437\u0432\u043e\u043d\u043e\u043a *\/}        &lt;button onClick={() => callWithVideo(false)}>          &lt;BsPhone \/>        &lt;\/button>      &lt;\/div>    &lt;\/div>  &lt;\/div> )<\/code><\/pre>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0435\u043c\u043e\u0433\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430:<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import { useEffect, useState } from 'react' import { BsCameraVideo, BsPhone } from 'react-icons\/bs'  import socket from '..\/utils\/socket'  export const MainWindow = ({ startCall }) => {  const [localId, setLocalId] = useState('')  const [remoteId, setRemoteId] = useState('')  const [error, setError] = useState('')   useEffect(() => {    socket      .on('init', ({ id }) => {        setLocalId(id)      })      .emit('init')  }, [])   const callWithVideo = (video) => {    if (!remoteId.trim()) {      return setError('Your friend ID must be specified!')    }    const config = { audio: true, video }    startCall(true, remoteId, config)  }   return (    &lt;div className='container main-window'>      &lt;div className='local-id'>        &lt;h2>Your ID is&lt;\/h2>        &lt;p>{localId}&lt;\/p>      &lt;\/div>      &lt;div className='remote-id'>        &lt;label htmlFor='remoteId'>Your friend ID&lt;\/label>        &lt;p className='error'>{error}&lt;\/p>        &lt;input          type='text'          spellCheck={false}          placeholder='Enter friend ID'          onChange={({ target: { value } }) => {            setError('')            setRemoteId(value)          }}        \/>        &lt;div className='control'>          &lt;button onClick={() => callWithVideo(true)}>            &lt;BsCameraVideo \/>          &lt;\/button>          &lt;button onClick={() => callWithVideo(false)}>            &lt;BsPhone \/>          &lt;\/button>        &lt;\/div>      &lt;\/div>    &lt;\/div>  ) }<\/code><\/pre>\n<\/div><\/div>\n<p>  <\/p>\n<hr\/>\n<p>  <\/p>\n<p>\u041c\u043e\u0434\u0430\u043b\u044c\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u2014 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u043e \u0432\u0445\u043e\u0434\u044f\u0449\u0435\u043c \u0437\u0432\u043e\u043d\u043a\u0435 (<code>components\/CallModal.jsx<\/code>).<\/p>\n<p>  <\/p>\n<p>\u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0445\u0443\u043a\u0438 \u0438 \u0438\u043a\u043e\u043d\u043a\u0443:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { BsCameraVideo, BsPhone } from 'react-icons\/bs' import { FiPhoneOff } from 'react-icons\/fi'  \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 `id` \u0437\u0432\u043e\u043d\u044f\u0449\u0435\u0433\u043e \u0438 \u043c\u0435\u0442\u043e\u0434\u044b \u0434\u043b\u044f \u043f\u0440\u0438\u043d\u044f\u0442\u0438\u044f \u0437\u0432\u043e\u043d\u043a\u0430 \u0438 \u0435\u0433\u043e \u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u0438\u044f export const CallModal = ({ callFrom, startCall, rejectCall }) => {  \/\/ TODO }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043f\u0440\u0438\u043d\u044f\u0442\u0438\u044f \u0437\u0432\u043e\u043d\u043a\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u0437\u0432\u043e\u043d\u043e\u043a \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c\u0441\u044f \u0441 \u0432\u0438\u0434\u0435\u043e \u0438 \u0431\u0435\u0437 const acceptWithVideo = (video) => {  const config = { audio: true, video }  \/\/ \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f `PeerConnection`  startCall(false, callFrom, config) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0418 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0443:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">return (  &lt;div className='call-modal'>    &lt;div className='inner'>      &lt;p>{`${callFrom} is calling`}&lt;\/p>      &lt;div className='control'>        {\/* \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u043c \u0437\u0432\u043e\u043d\u043e\u043a \u0441 \u0432\u0438\u0434\u0435\u043e *\/}        &lt;button onClick={() => acceptWithVideo(true)}>          &lt;BsCameraVideo \/>        &lt;\/button>        {\/* \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u043c \u0437\u0432\u043e\u043d\u043e\u043a \u0431\u0435\u0437 \u0432\u0438\u0434\u0435\u043e *\/}        &lt;button onClick={() => acceptWithVideo(false)}>          &lt;BsPhone \/>        &lt;\/button>        {\/* \u043e\u0442\u043a\u043b\u043e\u043d\u044f\u0435\u043c \u0437\u0432\u043e\u043d\u043e\u043a *\/}        &lt;button onClick={rejectCall} className='reject'>          &lt;FiPhoneOff \/>        &lt;\/button>      &lt;\/div>    &lt;\/div>  &lt;\/div> )<\/code><\/pre>\n<p>  <\/p>\n<hr\/>\n<p>  <\/p>\n<p>\u042d\u043a\u0440\u0430\u043d \u0434\u043b\u044f \u043a\u043e\u043c\u043c\u0443\u043d\u0438\u043a\u0430\u0446\u0438\u0438 (<code>components\/CallWindow.jsx<\/code>).<\/p>\n<p>  <\/p>\n<p>\u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0445\u0443\u043a\u0438 \u0438 \u0438\u043a\u043e\u043d\u043a\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { useState, useEffect, useRef } from 'react' import { BsCameraVideo, BsPhone } from 'react-icons\/bs' import { FiPhoneOff } from 'react-icons\/fi'  \/*  \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:  - \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0439 \u043c\u0435\u0434\u0438\u0430 \u043f\u043e\u0442\u043e\u043a  - \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u043c\u0435\u0434\u0438\u0430 \u043f\u043e\u0442\u043e\u043a  - \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0434\u043b\u044f \u0437\u0430\u0445\u0432\u0430\u0442\u0430 \u043c\u0435\u0434\u0438\u0430\u043f\u043e\u0442\u043e\u043a\u0430  - \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043f\u043e\u0442\u043e\u043a\u043e\u043c  - \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0437\u0432\u043e\u043d\u043a\u0430 *\/ export const CallWindow = ({  remoteSrc,  localSrc,  config,  mediaDevice,  finishCall }) => {  \/\/ TODO }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0438\u043c\u043c\u0443\u0442\u0430\u0431\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u0441\u0441\u044b\u043b\u043e\u043a \u043d\u0430 <code>DOM-\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b<\/code>, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0434\u043b\u044f \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u0432 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0441 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u043c \u0432\u0438\u0434\u0435\u043e \u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0434\u043b\u044f \u0430\u0443\u0434\u0438\u043e \u0438 \u0432\u0438\u0434\u0435\u043e:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">const remoteVideo = useRef() const localVideo = useRef() const localVideoSize = useRef() \/\/ \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043c\u043e\u0433\u0443\u0442 \u0438\u043c\u0435\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 `null`, \/\/ \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0437\u0434\u0435\u0441\u044c \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0439 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 `?.` const [video, setVideo] = useState(config?.video) const [audio, setAudio] = useState(config?.audio)<\/code><\/pre>\n<p>  <\/p>\n<p>\u0421 \u0442\u043e\u0447\u043a\u0438 \u0437\u0440\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u043c\u043d\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043b\u043e\u0433\u0438\u0447\u043d\u044b\u043c \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0441 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u043c \u0432\u0438\u0434\u0435\u043e \u043e\u0447\u0435\u043d\u044c \u0431\u043e\u043b\u044c\u0448\u0438\u043c (\u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f \u0432\u044b\u0441\u043e\u0442\u0430 \u2014 90vh) \u0438 \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0435\u0433\u043e \u043f\u043e \u0446\u0435\u043d\u0442\u0440\u0443. \u0421 \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u043c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0441 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u043c \u0432\u0438\u0434\u0435\u043e \u044f \u0442\u043e\u0436\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043b\u0441\u044f \u0438 \u0441\u0434\u0435\u043b\u0430\u043b \u0435\u0433\u043e \u0440\u0430\u0432\u043d\u044b\u043c 25vw \u0432 \u0448\u0438\u0440\u0438\u043d\u0443. \u041d\u043e \u044f \u043d\u0435 \u043c\u043e\u0433 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0441 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043c \u044d\u0442\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0440\u0435\u0448\u0438\u043b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0435\u0433\u043e \u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u043d\u0438\u044f \u0438\u043b\u0438, \u0441\u043a\u043e\u0440\u0435\u0435, \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0430 (\u043a\u043b\u0438\u043a -> \u043f\u0435\u0440\u0435\u043d\u043e\u0441 -> \u043a\u043b\u0438\u043a). \u041f\u0440\u0438 \u044d\u0442\u043e\u043c, \u044f \u0445\u043e\u0442\u0435\u043b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u043e\u0441\u0442\u044b\u043c \u0438 \u043f\u043e\u043d\u044f\u0442\u043d\u044b\u043c \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c.<\/p>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u043d\u0438\u044f (\u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440) \u0438 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">const [dragging, setDragging] = useState(false) const [coords, setCoords] = useState({  x: 0,  y: 0 })<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c \u0440\u0430\u0437\u043c\u0435\u0440\u044b \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0441 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u043c \u0432\u0438\u0434\u0435\u043e \u0438 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0438\u0445 \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">useEffect(() => {  const { width, height } = localVideo.current.getBoundingClientRect()  localVideoSize.current = { width, height } }, [])<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u043d\u0438\u044f \u0437\u0430 \u0441\u0447\u0435\u0442 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f <code>CSS-\u043a\u043b\u0430\u0441\u0441\u043e\u0432<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">useEffect(() => {  dragging    ? localVideo.current.classList.add('dragging')    : localVideo.current.classList.remove('dragging') }, [dragging])<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u043d\u0438\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0441 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u043c \u0432\u0438\u0434\u0435\u043e:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">const onMouseMove = (e) => {  \/\/ \u0435\u0441\u043b\u0438 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438 \u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u043d\u0438\u044f  if (dragging) {    \/\/ \u044d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f \u0442\u043e\u0433\u043e,    \/\/ \u0447\u0442\u043e \u0446\u0435\u043d\u0442\u0440 \u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u0435\u043c\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0432\u0441\u0435\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c \u0437\u0430 \u043a\u0443\u0440\u0441\u043e\u0440\u043e\u043c    setCoords({      x: e.clientX - localVideoSize.current.width \/ 2,      y: e.clientY - localVideoSize.current.height \/ 2    })  } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043d\u0430 \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e\u043c \u043e\u0431\u044a\u0435\u043a\u0442\u0435 <code>window<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">useEffect(() => {  window.addEventListener('mousemove', onMouseMove)   return () => {    window.removeEventListener('mousemove', onMouseMove)  } })<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u0430\u043b\u0435\u0435, \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u043c\u0435\u0434\u0438\u0430\u043f\u043e\u0442\u043e\u043a\u0438 \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">useEffect(() => {  \/\/ \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0439 \u043f\u043e\u0442\u043e\u043a  if (remoteVideo.current &amp;&amp; remoteSrc) {    remoteVideo.current.srcObject = remoteSrc  }  \/\/ \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0442\u043e\u043a  if (localVideo.current &amp;&amp; localSrc) {    localVideo.current.srcObject = localSrc  } }, [remoteSrc, localSrc])<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0442\u0440\u0435\u043a\u0438 \u0432 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c \u0434\u043b\u044f \u0437\u0430\u0445\u0432\u0430\u0442\u0430 \u043c\u0435\u0434\u0438\u0430\u043f\u043e\u0442\u043e\u043a\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">useEffect(() => {  if (mediaDevice) {    \/\/ \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0432\u0438\u0434\u0435\u043e\u0442\u0440\u0435\u043a\u0438    mediaDevice.toggle('Video', video)    \/\/ \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0430\u0443\u0434\u0438\u043e\u0442\u0440\u0435\u043a\u0438    mediaDevice.toggle('Audio', audio)  } }, [mediaDevice])<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439 \u0438 \u0442\u0440\u0435\u043a\u043e\u0432:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">const toggleMediaDevice = (deviceType) => {  \/\/ \u0432\u0438\u0434\u0435\u043e  if (deviceType === 'video') {    setVideo(!video)    mediaDevice.toggle('Video')  }  \/\/ \u0430\u0443\u0434\u0438\u043e  if (deviceType === 'audio') {    setAudio(!audio)    mediaDevice.toggle('Audio')  } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430\u043a\u043e\u043d\u0435\u0446, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0443:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">return (  &lt;div className='call-window'>    &lt;div className='inner'>      &lt;div className='video'>        {\/* \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0432\u0438\u0434\u0435\u043e\u043f\u043e\u0442\u043e\u043a\u0430 *\/}        &lt;video className='remote' ref={remoteVideo} autoPlay \/>        {\/*          \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0434\u043b\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0432\u0438\u0434\u0435\u043e\u043f\u043e\u0442\u043e\u043a\u0430          \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u0430\u0442\u0440\u0438\u0431\u0443\u0442 `muted`,          \u0431\u0435\u0437 \u043d\u0435\u0433\u043e \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0441\u043b\u044b\u0448\u0430\u0442\u044c \u0441\u0430\u043c\u0438 \u0441\u0435\u0431\u044f,          \u0447\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0435\u0442 \u043a\u043e\u043c\u043c\u0443\u043d\u0438\u043a\u0430\u0446\u0438\u044e \u0437\u0430\u0442\u0440\u0443\u0434\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439        *\/}        &lt;video          className='local'          ref={localVideo}          autoPlay          muted          {\/* \u043f\u0435\u0440\u0435\u043d\u043e\u0441 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 *\/}          onClick={() => setDragging(!dragging)}          style={{            top: `${coords.y}px`,            left: `${coords.x}px`          }}        \/>      &lt;\/div>      &lt;div className='control'>        {\/* \u043a\u043d\u043e\u043f\u043a\u0430 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0432\u0438\u0434\u0435\u043e *\/}        &lt;button          className={video ? '' : 'reject'}          onClick={() => toggleMediaDevice('video')}        >          &lt;BsCameraVideo \/>        &lt;\/button>        {\/* \u043a\u043d\u043e\u043f\u043a\u0430 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0430\u0443\u0434\u0438\u043e *\/}        &lt;button          className={audio ? '' : 'reject'}          onClick={() => toggleMediaDevice('audio')}        >          &lt;BsPhone \/>        &lt;\/button>        {\/* \u043a\u043d\u043e\u043f\u043a\u0430 \u0434\u043b\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0437\u0432\u043e\u043d\u043a\u0430 *\/}        &lt;button className='reject' onClick={() => finishCall(true)}>          &lt;FiPhoneOff \/>        &lt;\/button>      &lt;\/div>    &lt;\/div>  &lt;\/div> )<\/code><\/pre>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0435\u043c\u043e\u0433\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430:<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import { useState, useEffect, useRef } from 'react' import { BsCameraVideo, BsPhone } from 'react-icons\/bs' import { FiPhoneOff } from 'react-icons\/fi'  export const CallWindow = ({  remoteSrc,  localSrc,  config,  mediaDevice,  finishCall }) => {  const remoteVideo = useRef()  const localVideo = useRef()  const localVideoSize = useRef()  const [video, setVideo] = useState(config?.video)  const [audio, setAudio] = useState(config?.audio)   const [dragging, setDragging] = useState(false)  const [coords, setCoords] = useState({    x: 0,    y: 0  })   useEffect(() => {    const { width, height } = localVideo.current.getBoundingClientRect()    localVideoSize.current = { width, height }  }, [])   useEffect(() => {    dragging      ? localVideo.current.classList.add('dragging')      : localVideo.current.classList.remove('dragging')  }, [dragging])   useEffect(() => {    window.addEventListener('mousemove', onMouseMove)     return () => {      window.removeEventListener('mousemove', onMouseMove)    }  })   useEffect(() => {    if (remoteVideo.current &amp;&amp; remoteSrc) {      remoteVideo.current.srcObject = remoteSrc    }    if (localVideo.current &amp;&amp; localSrc) {      localVideo.current.srcObject = localSrc    }  }, [remoteSrc, localSrc])   useEffect(() => {    if (mediaDevice) {      mediaDevice.toggle('Video', video)      mediaDevice.toggle('Audio', audio)    }  }, [mediaDevice])   const onMouseMove = (e) => {    if (dragging) {      setCoords({        x: e.clientX - localVideoSize.current.width \/ 2,        y: e.clientY - localVideoSize.current.height \/ 2      })    }  }   const toggleMediaDevice = (deviceType) => {    if (deviceType === 'video') {      setVideo(!video)      mediaDevice.toggle('Video')    }    if (deviceType === 'audio') {      setAudio(!audio)      mediaDevice.toggle('Audio')    }  }   return (    &lt;div className='call-window'>      &lt;div className='inner'>        &lt;div className='video'>          &lt;video className='remote' ref={remoteVideo} autoPlay \/>          &lt;video            className='local'            ref={localVideo}            autoPlay            muted            onClick={() => setDragging(!dragging)}            style={{              top: `${coords.y}px`,              left: `${coords.x}px`            }}          \/>        &lt;\/div>        &lt;div className='control'>          &lt;button            className={video ? '' : 'reject'}            onClick={() => toggleMediaDevice('video')}          >            &lt;BsCameraVideo \/>          &lt;\/button>          &lt;button            className={audio ? '' : 'reject'}            onClick={() => toggleMediaDevice('audio')}          >            &lt;BsPhone \/>          &lt;\/button>          &lt;button className='reject' onClick={() => finishCall(true)}>            &lt;FiPhoneOff \/>          &lt;\/button>        &lt;\/div>      &lt;\/div>    &lt;\/div>  ) }<\/code><\/pre>\n<\/div><\/div>\n<p>  <\/p>\n<hr\/>\n<p>  <\/p>\n<p>\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f (<code>App.jsx<\/code>).<\/p>\n<p>  <\/p>\n<p>\u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0441\u0442\u0438\u043b\u0438, \u0445\u0443\u043a\u0438, \u0438\u043a\u043e\u043d\u043a\u0443, \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 <code>PeerConnection<\/code> \u0438 \u0441\u043e\u043a\u0435\u0442:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import '.\/styles\/app.scss'  import { useState, useEffect } from 'react' import { BsPhoneVibrate } from 'react-icons\/bs'  import PeerConnection from '.\/utils\/PeerConnection' import socket from '.\/utils\/socket'  import { MainWindow, CallWindow, CallModal } from '.\/components'  export default function App() {  \/\/ TODO }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0434\u043b\u044f:<\/p>\n<p>  <\/p>\n<ul>\n<li><code>id<\/code> \u0437\u0432\u043e\u043d\u044f\u0449\u0435\u0433\u043e;<\/li>\n<li>\u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440\u0430 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f;<\/li>\n<li>\u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440\u0430 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f;<\/li>\n<li>\u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043e\u0442\u043e\u043a\u0430;<\/li>\n<li>\u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043e\u0442\u043e\u043a\u0430;<\/li>\n<li>\u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 <code>PeerConnetion<\/code>;<\/li>\n<li>\u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u0434\u043b\u044f \u043c\u0435\u0434\u0438\u0430.<\/li>\n<\/ul>\n<p>  <\/p>\n<pre><code class=\"javascript\">const [callFrom, setCallFrom] = useState('') const [calling, setCalling] = useState(false)  const [showModal, setShowModal] = useState(false)  const [localSrc, setLocalSrc] = useState(null) const [remoteSrc, setRemoteSrc] = useState(null)  const [pc, setPc] = useState(null) const [config, setConfig] = useState(null)<\/code><\/pre>\n<p>  <\/p>\n<p>\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043d\u0430 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">useEffect(() => {  socket.on('request', ({ from }) => {    \/\/ \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c `id` \u0437\u0432\u043e\u043d\u044f\u0449\u0435\u0433\u043e    setCallFrom(from)    \/\/ \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u043c\u043e\u0434\u0430\u043b\u044c\u043d\u043e\u0435 \u043e\u043a\u043d\u043e    setShowModal(true)  }) }, [])<\/code><\/pre>\n<p>  <\/p>\n<p>\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0438 \u043a \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044e \u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0437\u0432\u043e\u043d\u043a\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432 \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0441\u043b\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \/\/ \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 `PeerConnection` - \u044d\u0442\u043e \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043a\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0432\u0430\u0436\u043d\u044b\u043c useEffect(() => {  if (!pc) return   socket    \/\/ \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0438 \u043a \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044e    \/\/ \u0434\u0430\u043d\u043d\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043e\u0442\u0432\u0435\u0442 \u0438 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430 ICE (\u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435, \u0432 \u0432\u0438\u0434\u0435 \u043f\u0443\u0441\u0442\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0438 - \u043d\u0443\u043b\u0435\u0432\u043e\u0439 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442)    .on('call', (data) => {      \/\/ \u0435\u0441\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435      if (data.sdp) {        pc.setRemoteDescription(data.sdp)         \/\/ \u0435\u0441\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u0435        if (data.sdp.type === 'offer') {          \/\/ \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u043e\u0442\u0432\u0435\u0442          pc.createAnswer()        }      } else {        \/\/ \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430        pc.addIceCandidate(data.candidate)      }    })    \/\/ \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0437\u0432\u043e\u043d\u043a\u0430    .on('end', () => finishCall(false)) }, [pc])<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0437\u0432\u043e\u043d\u043a\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/*  \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 3 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430:  - \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0438\u043d\u0438\u0446\u0438\u0430\u0442\u043e\u0440\u043e\u043c \u0437\u0432\u043e\u043d\u043a\u0430  - `id` \u0430\u0434\u0440\u0435\u0441\u0430\u0442\u0430  - \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0434\u043b\u044f \u043c\u0435\u0434\u0438\u0430 *\/ const startCall = (isCaller, remoteId, config) => {  \/\/ \u0441\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u043c\u043e\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u043e\u043a\u043d\u043e - \u0434\u043b\u044f \u0441\u043b\u0443\u0447\u0430\u044f, \u043a\u043e\u0433\u0434\u0430 \u043c\u044b \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u043c \u0437\u0432\u043e\u043d\u043e\u043a  setShowModal(false)  \/\/ \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f  setCalling(true)  \/\/ \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438  setConfig(config)   \/\/ \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 `PeerConnection`,  \/\/ \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u044f \u0435\u043c\u0443 `id` \u0430\u0434\u0440\u0435\u0441\u0430\u0442\u0430  const _pc = new PeerConnection(remoteId)    \/\/ \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u043e\u0442\u043e\u043a\u0430    .on('localStream', (stream) => {      setLocalSrc(stream)    })    \/\/ \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u043f\u043e\u0442\u043e\u043a\u0430    .on('remoteStream', (stream) => {      setRemoteSrc(stream)      \/\/ \u0441\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f      setCalling(false)    })    \/\/ \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c `PeerConnection`    .start(isCaller, config)   \/\/ \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 `PeerConnection`  \/\/ \u044d\u0442\u043e \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442 \u043a \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432  \/\/ \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0438 \u043a \u0437\u0432\u043e\u043d\u043a\u0443 \u0438 \u0435\u0433\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f  setPc(_pc) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u0438\u044f \u0437\u0432\u043e\u043d\u043a\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">const rejectCall = () => {  socket.emit('end', { to: callFrom })   setShowModal(false) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0437\u0432\u043e\u043d\u043a\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">const finishCall = (isCaller) => {  \/\/ \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443 `WebRTC`  pc.stop(isCaller)   \/\/ \u043e\u0431\u043d\u0443\u043b\u044f\u0435\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f  setPc(null)  setConfig(null)   setCalling(false)  setShowModal(false)   setLocalSrc(null)  setRemoteSrc(null) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0418 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0443:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">return (  &lt;div className='app'>    &lt;h1>React WebRTC&lt;\/h1>    {\/* \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u044b\u0439 \u044d\u043a\u0440\u0430\u043d *\/}    &lt;MainWindow startCall={startCall} \/>    {\/* \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f *\/}    {calling &amp;&amp; (      &lt;div className='calling'>        &lt;button disabled>          &lt;BsPhoneVibrate \/>        &lt;\/button>      &lt;\/div>    )}    {\/* \u043c\u043e\u0434\u0430\u043b\u044c\u043d\u043e\u0435 \u043e\u043a\u043d\u043e *\/}    {showModal &amp;&amp; (      &lt;CallModal        callFrom={callFrom}        startCall={startCall}        rejectCall={rejectCall}      \/>    )}    {\/* \u044d\u043a\u0440\u0430\u043d \u043a\u043e\u043c\u043c\u0443\u043d\u0438\u043a\u0430\u0446\u0438\u0438 *\/}    {remoteSrc &amp;&amp; (      &lt;CallWindow        localSrc={localSrc}        remoteSrc={remoteSrc}        config={config}        mediaDevice={pc?.mediaDevice}        finishCall={finishCall}      \/>    )}  &lt;\/div> )<\/code><\/pre>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0435\u043c\u043e\u0433\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430:<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import '.\/styles\/app.scss'  import { useState, useEffect } from 'react' import { BsPhoneVibrate } from 'react-icons\/bs'  import PeerConnection from '.\/utils\/PeerConnection' import socket from '.\/utils\/socket'  import { MainWindow, CallWindow, CallModal } from '.\/components'  export default function App() {  const [callFrom, setCallFrom] = useState('')  const [calling, setCalling] = useState(false)   const [showModal, setShowModal] = useState(false)   const [localSrc, setLocalSrc] = useState(null)  const [remoteSrc, setRemoteSrc] = useState(null)   const [pc, setPc] = useState(null)  const [config, setConfig] = useState(null)   useEffect(() => {    socket.on('request', ({ from }) => {      setCallFrom(from)      setShowModal(true)    })  }, [])   useEffect(() => {    if (!pc) return     socket      .on('call', (data) => {        if (data.sdp) {          pc.setRemoteDescription(data.sdp)           if (data.sdp.type === 'offer') {            pc.createAnswer()          }        } else {          pc.addIceCandidate(data.candidate)        }      })      .on('end', () => finishCall(false))  }, [pc])   const startCall = (isCaller, remoteId, config) => {    setShowModal(false)    setCalling(true)    setConfig(config)     const _pc = new PeerConnection(remoteId)      .on('localStream', (stream) => {        setLocalSrc(stream)      })      .on('remoteStream', (stream) => {        setRemoteSrc(stream)        setCalling(false)      })      .start(isCaller, config)     setPc(_pc)  }   const rejectCall = () => {    socket.emit('end', { to: callFrom })     setShowModal(false)  }   const finishCall = (isCaller) => {    pc.stop(isCaller)     setPc(null)    setConfig(null)     setCalling(false)    setShowModal(false)     setLocalSrc(null)    setRemoteSrc(null)  }   return (    &lt;div className='app'>      &lt;h1>React WebRTC&lt;\/h1>      &lt;MainWindow startCall={startCall} \/>      {calling &amp;&amp; (        &lt;div className='calling'>          &lt;button disabled>            &lt;BsPhoneVibrate \/>          &lt;\/button>        &lt;\/div>      )}      {showModal &amp;&amp; (        &lt;CallModal          callFrom={callFrom}          startCall={startCall}          rejectCall={rejectCall}        \/>      )}      {remoteSrc &amp;&amp; (        &lt;CallWindow          localSrc={localSrc}          remoteSrc={remoteSrc}          config={config}          mediaDevice={pc?.mediaDevice}          finishCall={finishCall}        \/>      )}    &lt;\/div>  ) }<\/code><\/pre>\n<\/div><\/div>\n<p>  <\/p>\n<h2 id=\"proverka-rabotosposobnosti\">\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0440\u0430\u0431\u043e\u0442\u043e\u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u0438<\/h2>\n<p>  <\/p>\n<p>\u041f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u043c\u0441\u044f \u0432 \u043a\u043e\u0440\u043d\u0435\u0432\u0443\u044e \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e (<code>react-webrtc<\/code>), \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c <code>Node.js-\u043f\u0440\u043e\u0435\u043a\u0442<\/code> \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c <a href=\"https:\/\/www.npmjs.com\/package\/concurrently\"><code>concurrently<\/code><\/a> \u2014 \u0443\u0442\u0438\u043b\u0438\u0442\u0443 \u0434\u043b\u044f \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0433\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043a\u043e\u043c\u0430\u043d\u0434, \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0445 \u0432 <code>package.json<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">cd ..  yarn init -yp  yarn add concurrently<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0432 <code>package.json<\/code> \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"json\">\"scripts\": {  \"dev\": \"concurrently \\\"yarn --cwd server dev\\\" \\\"yarn --cwd client dev\\\"\" }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u0434\u0430\u043d\u043d\u0443\u044e \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <code>yarn dev<\/code>.<\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 <code>http:\/\/localhost:4000<\/code> \u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 <code>http:\/\/localhost:3000<\/code>.<\/p>\n<p>  <\/p>\n<p>\u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c 2 \u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 \u0441 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u043c:<\/p>\n<p>  <img decoding=\"async\" src=\"\/img\/image-loader.svg\" data-src=\"https:\/\/habrastorage.org\/webt\/og\/ce\/_v\/ogce_v2r7ekvtfh64t8s-f1x7ga.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041a\u043e\u043f\u0438\u0440\u0443\u0435\u043c <code>id<\/code> \u0441 \u043e\u0434\u043d\u043e\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0438, \u0432\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0435\u0433\u043e \u0432 \u043f\u043e\u043b\u0435 <code>Your friend ID<\/code> \u0432 \u0434\u0440\u0443\u0433\u043e\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0435 \u0438 \u043d\u0430\u0436\u0438\u043c\u0430\u0435\u043c \u043d\u0430 \u0438\u043a\u043e\u043d\u043a\u0443 \u0432\u0438\u0434\u0435\u043e\u043a\u0430\u043c\u0435\u0440\u044b.<\/p>\n<p>  <\/p>\n<p>\u0411\u0440\u0430\u0443\u0437\u0435\u0440 \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442 \u043d\u0430\u0448\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u043d\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0432\u0438\u0434\u0435\u043e\u043a\u0430\u043c\u0435\u0440\u044b \u0438 \u043c\u0438\u043a\u0440\u043e\u0444\u043e\u043d\u0430. \u041f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0435\u043c\u0443 \u0442\u0430\u043a\u043e\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0435. \u0412 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0435 \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f, \u0430 \u0432 \u0434\u0440\u0443\u0433\u043e\u0439 \u2014 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u043e \u0432\u0445\u043e\u0434\u044f\u0449\u0435\u043c \u0437\u0432\u043e\u043d\u043a\u0435:<\/p>\n<p>  <img decoding=\"async\" src=\"\/img\/image-loader.svg\" data-src=\"https:\/\/habrastorage.org\/webt\/ph\/r0\/z_\/phr0z_twkfjpq6lgu_1t-ukj7xa.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u043c \u0437\u0432\u043e\u043d\u043e\u043a \u0441 \u0432\u0438\u0434\u0435\u043e. \u041f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u044d\u043a\u0440\u0430\u043d \u043a\u043e\u043c\u043c\u0443\u043d\u0438\u043a\u0430\u0446\u0438\u0438:<\/p>\n<p>  <img decoding=\"async\" src=\"\/img\/image-loader.svg\" data-src=\"https:\/\/habrastorage.org\/webt\/nh\/zc\/ta\/nhzctavsre0xvefj5by9r8tq3d8.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0432\u0438\u0434\u0435\u043e \u0438 \u0430\u0443\u0434\u0438\u043e, \u043c\u0435\u043d\u044f\u0435\u043c \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0441 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u043c \u0432\u0438\u0434\u0435\u043e:<\/p>\n<p>  <img decoding=\"async\" src=\"\/img\/image-loader.svg\" data-src=\"https:\/\/habrastorage.org\/webt\/-5\/j5\/xo\/-5j5xoj9vknruqqye__ggcr51dm.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u0418 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u043c \u0437\u0432\u043e\u043d\u043e\u043a.<\/p>\n<p>  <\/p>\n<p>\u041a\u0440\u0443\u0442\u043e! \u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043a\u0430\u043a \u043e\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044f.<\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u0436\u0430\u043b\u0443\u0439, \u044d\u0442\u043e \u0432\u0441\u0435, \u0447\u0435\u043c \u044f \u0445\u043e\u0442\u0435\u043b \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0441 \u0432\u0430\u043c\u0438 \u0432 \u0434\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435.<\/p>\n<p>  <\/p>\n<p>\u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044e \u0437\u0430 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u0438 happy coding!<\/p>\n<p>  <\/p>\n<hr\/>\n<p>  <\/p>\n<p><a href=\"https:\/\/cloud.timeweb.com\/?utm_source=habr&amp;utm_medium=banner&amp;utm_campaign=cloud&amp;utm_content=direct&amp;utm_term=low\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" data-src=\"https:\/\/habrastorage.org\/webt\/bh\/0m\/fv\/bh0mfviu_rma7be5uylpewumuxi.png\"\/><\/a><\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"v-portal\" style=\"display:none;\"><\/div>\n<\/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\/company\/timeweb\/blog\/649369\/\"> https:\/\/habr.com\/ru\/company\/timeweb\/blog\/649369\/<\/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_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" data-src=\"https:\/\/habrastorage.org\/webt\/jc\/hw\/st\/jchwst6a3nwlxlscnmuw5tdhmry.png\"\/>  <\/p>\n<p>  \u041f\u0440\u0438\u0432\u0435\u0442, \u0434\u0440\u0443\u0437\u044c\u044f!<\/p>\n<p>  <\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043f\u043e\u043a\u0430\u0436\u0443 \u0432\u0430\u043c, \u043a\u0430\u043a \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0430\u0443\u0434\u0438\u043e\/\u0432\u0438\u0434\u0435\u043e \u0437\u0432\u043e\u043d\u043a\u043e\u0432 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/WebRTC_API\"><code>WebRTC<\/code><\/a>.<\/p>\n<p>  <\/p>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0410 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440;<\/li>\n<li>\u043e\u043d \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442 \u044d\u0442\u043e\u0442 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u0411;<\/li>\n<li>\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0411 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0410 \u0434\u043b\u044f \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0430\u0443\u0434\u0438\u043e \u0438\u043b\u0438 \u0432\u0438\u0434\u0435\u043e \u0437\u0432\u043e\u043d\u043a\u0430;<\/li>\n<li>\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0410 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u043e \u0437\u0432\u043e\u043d\u043a\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0411 \u0438 \u043c\u043e\u0436\u0435\u0442 \u043e\u0442\u0432\u0435\u0442\u0438\u0442\u044c \u043d\u0430 \u043d\u0435\u0433\u043e \u0441 \u0432\u0438\u0434\u0435\u043e \u0438\u043b\u0438 \u0431\u0435\u0437 \u043b\u0438\u0431\u043e \u043e\u0442\u043a\u043b\u043e\u043d\u0438\u0442\u044c \u0437\u0432\u043e\u043d\u043e\u043a;<\/li>\n<li>\u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u0438\u043c\u0435\u044e\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0432\u043a\u043b\u044e\u0447\u0430\u0442\u044c\/\u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0430\u0443\u0434\u0438\u043e \u0438 \u0432\u0438\u0434\u0435\u043e;<\/li>\n<li>\u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0437\u0432\u043e\u043d\u043a\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 <code>WebRTC<\/code> \u0434\u043b\u044f \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u043d\u043e\u0432\u043e\u0433\u043e \u0437\u0432\u043e\u043d\u043a\u0430.<\/li>\n<\/ul>\n<p>  <\/p>\n<p><a href=\"https:\/\/react-webrtc-call.herokuapp.com\/\">\u0414\u0435\u043c\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/a>.<\/p>\n<p>  <\/p>\n<p><a href=\"https:\/\/github.com\/harryheman\/react-webrtc\">\u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0441 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u043c \u043a\u043e\u0434\u043e\u043c<\/a>.<\/p>\n<p>  <\/p>\n<p><a href=\"https:\/\/github.com\/nguymin4\/react-videocall\">\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0432\u0434\u043e\u0445\u043d\u043e\u0432\u0435\u043d\u0438\u044f<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u0430\u043c \u044d\u0442\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e, \u043f\u0440\u043e\u0448\u0443 \u043f\u043e\u0434 \u043a\u0430\u0442.<\/p>\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-329014","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/329014","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=329014"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/329014\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=329014"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=329014"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=329014"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}