{"id":94138,"date":"2020-09-13T07:42:42","date_gmt":"2020-09-13T05:42:42","guid":{"rendered":"https:\/\/prohoster.info\/blog\/administrirovanie\/pochti-bespoleznyj-striming-vebkamery-iz-brauzera-media-stream-i-websockets"},"modified":"2020-09-13T07:42:42","modified_gmt":"2020-09-13T05:42:42","slug":"pochti-bespoleznyj-striming-vebkamery-iz-brauzera-media-stream-i-websockets","status":"publish","type":"post","link":"https:\/\/prohoster.info\/cs\/blog\/administrirovanie\/pochti-bespoleznyj-striming-vebkamery-iz-brauzera-media-stream-i-websockets","title":{"rendered":"(T\u00e9m\u011b\u0159) bezcenn\u00e9 streamov\u00e1n\u00ed webkamery z prohl\u00ed\u017ee\u010de. Media Stream a Websockets","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>V tomto \u010dl\u00e1nku se chci pod\u011blit o sv\u00e9 pokusy o streamov\u00e1n\u00ed videa p\u0159es websockety bez pou\u017eit\u00ed t\u0159et\u00edch stran jako je Adobe Flash Player. Co se mi poda\u0159ilo, si p\u0159e\u010dt\u011bte d\u00e1le.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><br \/>\nAdobe Flash \u2013 d\u0159\u00edve Macromedia Flash, je platforma pro vytv\u00e1\u0159en\u00ed aplikac\u00ed, kter\u00e9 b\u011b\u017e\u00ed ve webov\u00e9m prohl\u00ed\u017ee\u010di. P\u0159ed zaveden\u00edm Media Stream API to byla t\u00e9m\u011b\u0159 jedin\u00e1 platforma pro streamov\u00e1n\u00ed videa a zvuku z webov\u00e9 kamery, stejn\u011b jako pro po\u0159\u00e1d\u00e1n\u00ed r\u016fzn\u00fdch konferenc\u00ed a chat\u016f v prohl\u00ed\u017ee\u010di. Protokol pro p\u0159enos medi\u00e1ln\u00edch informac\u00ed RTMP (Real Time Messaging Protocol) byl dlouho fakticky uzav\u0159en\u00fd, co\u017e znamenalo: pokud chcete spustit svou streamingovou slu\u017ebu, budete muset pou\u017e\u00edvat software od Adobe \u2013 Adobe Media Server (AMS). <\/p>\n<p>Po n\u011bjak\u00e9 dob\u011b, v roce 2012, se Adobe \"vzdalo a p\u0159edlo\u017eilo\" ve\u0159ejnosti <noindex><a rel=\"nofollow\" href=\"http:\/\/wwwimages.adobe.com\/www.adobe.com\/content\/dam\/acom\/en\/devnet\/rtmp\/pdf\/rtmp_specification_1.0.pdf\">specifikaci<\/a><\/noindex> protokolu RTMP, kter\u00e1 obsahovala chyby a byla vlastn\u011b ne\u00fapln\u00e1. V t\u00e9 dob\u011b za\u010dali v\u00fdvoj\u00e1\u0159i vytv\u00e1\u0159et vlastn\u00ed implementace tohoto protokolu, co\u017e vedlo k vzniku serveru Wowza. V roce 2011 \u017ealovala Adobe spole\u010dnost Wowza za neleg\u00e1ln\u00ed vyu\u017e\u00edv\u00e1n\u00ed patent\u016f souvisej\u00edc\u00edch s RTMP, do \u010dty\u0159 let byl konflikt vy\u0159e\u0161en m\u00edrovou cestou. <\/p>\n<p>Platforma Adobe Flash existuje ji\u017e v\u00edce ne\u017e 20 let, b\u011bhem t\u00e9to doby se objevilo mnoho kritick\u00fdch zranitelnost\u00ed, podpora <noindex><a rel=\"nofollow\" href=\"https:\/\/webkit.org\/blog\/7839\/adobe-announces-flash-distribution-and-updates-to-end\/\">sl\u00edbili<\/a><\/noindex> m\u00e1 b\u00fdt ukon\u010dena v roce 2020, tak\u017ee alternativ pro streamovac\u00ed slu\u017eby u\u017e nen\u00ed mnoho. <\/p>\n<p>Pro sv\u016fj projekt jsem se rozhodl zcela vzd\u00e1t pou\u017e\u00edv\u00e1n\u00ed Flashe v prohl\u00ed\u017ee\u010di. Hlavn\u00ed d\u016fvod jsem uvedl v\u00fd\u0161e, tak\u00e9 Flash nen\u00ed v\u016fbec podporov\u00e1n na mobiln\u00edch platform\u00e1ch, a rozbalovat Adobe Flash pro v\u00fdvoj na Windows (v emul\u00e1toru wine) se mi opravdu necht\u011blo. Proto jsem se pustil do psan\u00ed klienta na JavaScriptu. To bude jen prototyp, proto\u017ee jsem pozd\u011bji zjistil, \u017ee streamov\u00e1n\u00ed m\u016f\u017ee b\u00fdt mnohem efektivn\u011bj\u0161\u00ed na z\u00e1klad\u011b p2p, pouze u m\u011b to bude peer \u2014 server \u2014 peers, ale o tom a\u017e jindy, proto\u017ee to je\u0161t\u011b nen\u00ed hotov\u00e9.<\/p>\n<p>Pro za\u010d\u00e1tek n\u00e1m vlastn\u011b pot\u0159ebujeme websocketov\u00fd server. Ud\u011blal jsem nejjednodu\u0161\u0161\u00ed na z\u00e1klad\u011b go bal\u00edku melody:<\/p>\n<p>                        <b class=\"spoiler_title\">K\u00f3d serverov\u00e9 \u010d\u00e1sti<\/b><\/p>\n<pre><code class=\"go\">package main\n\nimport (\n\t\"errors\"\n\t\"github.com\/go-chi\/chi\"\n\t\"gopkg.in\/olahol\/melody.v1\"\n\t\"log\"\n\t\"net\/http\"\n\t\"time\"\n)\n\nfunc main() {\n\tr := chi.NewRouter()\n\tm := melody.New()\n\n\tm.Config.MaxMessageSize = 204800\n\n\tr.Get(\"\/\", func(w http.ResponseWriter, r *http.Request) {\n\t\thttp.ServeFile(w, r, \"public\/index.html\")\n\t})\n\tr.Get(\"\/ws\", func(w http.ResponseWriter, r *http.Request) {\n\t\tm.HandleRequest(w, r)\n\t})\n\n         \/\/ Broadcast video stream \n\tm.HandleMessageBinary(func(s *melody.Session, msg []byte) {\n\t\tm.BroadcastBinary(msg)\n\t})\n\n\tlog.Println(\"Starting server...\")\n\n\thttp.ListenAndServe(\":3000\", r)\n}\n<\/code><\/pre>\n<p>Na klientovi (vys\u00edlaj\u00edc\u00ed stran\u011b) je nejprve nutn\u00e9 z\u00edskat p\u0159\u00edstup k kame\u0159e. To se prov\u00e1d\u00ed p\u0159es <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Media_Streams_API\">MediaStream API<\/a><\/noindex>.<\/p>\n<p>Z\u00edsk\u00e1n\u00ed p\u0159\u00edstupu (povolen\u00ed) k kame\u0159e\/mikrofonu pomoc\u00ed <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/MediaDevices\">Media Devices API<\/a><\/noindex>. Toto API poskytuje metodu <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/MediaDevices\/getUserMedia\">MediaDevices.getUserMedia()<\/a><\/noindex>, kter\u00e1 zobrazuje vyskakovac\u00ed okno, ptaj\u00edc\u00ed se u\u017eivatele na povolen\u00ed p\u0159\u00edstupu k kame\u0159e nebo\/i mikrofonu. R\u00e1d bych poznamenal, \u017ee v\u0161echny experimenty jsem prov\u00e1d\u011bl v Google Chrome, ale mysl\u00edm, \u017ee ve Firefoxu to bude fungovat podobn\u011b.<\/p>\n<p>N\u00e1sledn\u011b getUserMedia() vrac\u00ed Promise, do kter\u00e9ho p\u0159ed\u00e1v\u00e1 objekt MediaStream \u2014 tok video-audio dat. Tento objekt p\u0159i\u0159azujeme do src vlastnosti prvku video. K\u00f3d:<\/p>\n<p>                        <b class=\"spoiler_title\">Vys\u00edlaj\u00edc\u00ed strana<\/b><\/p>\n<pre><code class=\"javascript\">&lt;style&gt;\n  #videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }\n&lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n&lt;!-- \u0417\u0434\u0435\u0441\u044c \u0432 \u044d\u0442\u043e\u043c &quot;\u043e\u043a\u043e\u0448\u0435\u0447\u043a\u0435&quot; \u043a\u043b\u0438\u0435\u043d\u0442 \u0431\u0443\u0434\u0435\u0442 \u0432\u0438\u0434\u0435\u0442\u044c \u0441\u0435\u0431\u044f --&gt;\n&lt;video autoplay id=&quot;videoObjectHtml5ApiServer&quot;&gt;&lt;\/video&gt;\n\n&lt;script type=&quot;application\/javascript&quot;&gt;\n  var\n        video = document.getElementById(&#039;videoObjectHtml5ApiServer&#039;);\n\n\/\/ \u0435\u0441\u043b\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d MediaDevices API, \u043f\u044b\u0442\u0430\u0435\u043c\u0441\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043a\u0430\u043c\u0435\u0440\u0435 (\u043c\u043e\u0436\u043d\u043e \u0435\u0449\u0435 \u0438 \u043a \u043c\u0438\u043a\u0440\u043e\u0444\u043e\u043d\u0443)\n\/\/ getUserMedia \u0432\u0435\u0440\u043d\u0435\u0442 \u043e\u0431\u0435\u0449\u0430\u043d\u0438\u0435, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c\u0441\u044f \u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u0432\u0438\u0434\u0435\u043e\u043f\u043e\u0442\u043e\u043a \u0432 \u043a\u043e\u043b\u0431\u0435\u043a\u0435 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0432 video \u043e\u0431\u044a\u0435\u043a\u0442 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435\n\nif (navigator.mediaDevices.getUserMedia) {\n        navigator.mediaDevices.getUserMedia({video: true}).then(function (stream) {\n          \/\/ \u0432\u0438\u0434\u0435\u043e \u043f\u043e\u0442\u043e\u043a \u043f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u043c \u043a video \u0442\u0435\u0433\u0443, \u0447\u0442\u043e\u0431\u044b \u043a\u043b\u0438\u0435\u043d\u0442 \u043c\u043e\u0433 \u0432\u0438\u0434\u0435\u0442\u044c \u0441\u0435\u0431\u044f \u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \n          video.srcObject = stream;\n        });\n}\n&lt;\/script&gt;\n<\/code><\/pre>\n<p>Abychom mohli vys\u00edlat video stream p\u0159es sockety, je nutn\u00e9 jej n\u011bjak k\u00f3dovat, bufferovat a pos\u00edlat po \u010d\u00e1stech. Syrov\u00fd video stream se ned\u00e1 poslat p\u0159es websockets. Zde p\u0159ich\u00e1z\u00ed na pomoc <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/MediaStream_Recording_API\">MediaRecorder API<\/a><\/noindex>. Tento API umo\u017e\u0148uje k\u00f3dovat a rozd\u011blovat tok na kousky. K\u00f3dov\u00e1n\u00ed prov\u00e1d\u00edm pro kompresi video streamu, abych pos\u00edlal m\u00e9n\u011b bajt\u016f po s\u00edti. Rozd\u011blen\u00edm na kousky m\u016f\u017eeme ka\u017ed\u00fd kus poslat do websocketu. K\u00f3d:<\/p>\n<p>                        <b class=\"spoiler_title\">K\u00f3dujeme video stream, d\u011bl\u00edme jej na \u010d\u00e1sti<\/b><\/p>\n<pre><code class=\"javascript\">&lt;style&gt;\n  #videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }\n&lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n&lt;!-- \u0417\u0434\u0435\u0441\u044c \u0432 \u044d\u0442\u043e\u043c &quot;\u043e\u043a\u043e\u0448\u0435\u0447\u043a\u0435&quot; \u043a\u043b\u0438\u0435\u043d\u0442 \u0431\u0443\u0434\u0435\u0442 \u0432\u0438\u0434\u0435\u0442\u044c \u0441\u0435\u0431\u044f --&gt;\n&lt;video autoplay id=&quot;videoObjectHtml5ApiServer&quot;&gt;&lt;\/video&gt;\n\n&lt;script type=&quot;application\/javascript&quot;&gt;\n  var\n        video = document.getElementById(&#039;videoObjectHtml5ApiServer&#039;);\n\n\/\/ \u0435\u0441\u043b\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d MediaDevices API, \u043f\u044b\u0442\u0430\u0435\u043c\u0441\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043a\u0430\u043c\u0435\u0440\u0435 (\u043c\u043e\u0436\u043d\u043e \u0435\u0449\u0435 \u0438 \u043a \u043c\u0438\u043a\u0440\u043e\u0444\u043e\u043d\u0443)\n\/\/ getUserMedia \u0432\u0435\u0440\u043d\u0435\u0442 \u043e\u0431\u0435\u0449\u0430\u043d\u0438\u0435, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c\u0441\u044f \u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u0432\u0438\u0434\u0435\u043e\u043f\u043e\u0442\u043e\u043a \u0432 \u043a\u043e\u043b\u0431\u0435\u043a\u0435 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0432 video \u043e\u0431\u044a\u0435\u043a\u0442 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435\n\nif (navigator.mediaDevices.getUserMedia) {\n        navigator.mediaDevices.getUserMedia({video: true}).then(function (stream) {\n          \/\/ \u0432\u0438\u0434\u0435\u043e \u043f\u043e\u0442\u043e\u043a \u043f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u043c \u043a video \u0442\u0435\u0433\u0443, \u0447\u0442\u043e\u0431\u044b \u043a\u043b\u0438\u0435\u043d\u0442 \u043c\u043e\u0433 \u0432\u0438\u0434\u0435\u0442\u044c \u0441\u0435\u0431\u044f \u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \n          video.srcObject = s;\n          var\n            recorderOptions = {\n                mimeType: &#039;video\/webm; codecs=vp8&#039; \/\/ \u0431\u0443\u0434\u0435\u043c \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0438\u0434\u0435\u043e\u043f\u043e\u0442\u043e\u043a \u0432 \u0444\u043e\u0440\u043c\u0430\u0442 webm \u043a\u043e\u0434\u0435\u043a\u043e\u043c vp8\n              },\n              mediaRecorder = new MediaRecorder(s, recorderOptions ); \/\/ \u043e\u0431\u044a\u0435\u043a\u0442 MediaRecorder\n\n               mediaRecorder.ondataavailable = function(e) {\n                if (e.data &amp;&amp; e.data.size &gt; 0) {\n                  \/\/ \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043a\u0443\u0441\u043e\u0447\u0435\u043a \u0432\u0438\u0434\u0435\u043e\u043f\u043e\u0442\u043e\u043a\u0430 \u0432 e.data\n                }\n            }\n\n            mediaRecorder.start(100); \/\/ \u0434\u0435\u043b\u0438\u0442 \u043f\u043e\u0442\u043e\u043a \u043d\u0430 \u043a\u0443\u0441\u043e\u0447\u043a\u0438 \u043f\u043e 100 \u043c\u0441 \u043a\u0430\u0436\u0434\u044b\u0439\n\n        });\n}\n&lt;\/script&gt;\n<\/code><\/pre>\n<p>Nyn\u00ed p\u0159id\u00e1me p\u0159enos p\u0159es websockets. Jak p\u0159ekvapiv\u00e9, k tomu pot\u0159ebujeme pouze objekt <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/WebSocket\">WebSocket<\/a><\/noindex>. M\u00e1 pouze dv\u011b metody send a close. N\u00e1zvy mluv\u00ed samy za sebe. Dopln\u011bn\u00fd k\u00f3d:<\/p>\n<p>                        <b class=\"spoiler_title\">P\u0159en\u00e1\u0161\u00edme video stream na server<\/b><\/p>\n<pre><code class=\"javascript\">&lt;style&gt;\n  #videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }\n&lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n&lt;!-- \u0417\u0434\u0435\u0441\u044c \u0432 \u044d\u0442\u043e\u043c &quot;\u043e\u043a\u043e\u0448\u0435\u0447\u043a\u0435&quot; \u043a\u043b\u0438\u0435\u043d\u0442 \u0431\u0443\u0434\u0435\u0442 \u0432\u0438\u0434\u0435\u0442\u044c \u0441\u0435\u0431\u044f --&gt;\n&lt;video autoplay id=&quot;videoObjectHtml5ApiServer&quot;&gt;&lt;\/video&gt;\n\n&lt;script type=&quot;application\/javascript&quot;&gt;\n  var\n        video = document.getElementById(&#039;videoObjectHtml5ApiServer&#039;);\n\n\/\/ \u0435\u0441\u043b\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d MediaDevices API, \u043f\u044b\u0442\u0430\u0435\u043c\u0441\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043a\u0430\u043c\u0435\u0440\u0435 (\u043c\u043e\u0436\u043d\u043e \u0435\u0449\u0435 \u0438 \u043a \u043c\u0438\u043a\u0440\u043e\u0444\u043e\u043d\u0443)\n\/\/ getUserMedia \u0432\u0435\u0440\u043d\u0435\u0442 \u043e\u0431\u0435\u0449\u0430\u043d\u0438\u0435, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c\u0441\u044f \u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u0432\u0438\u0434\u0435\u043e\u043f\u043e\u0442\u043e\u043a \u0432 \u043a\u043e\u043b\u0431\u0435\u043a\u0435 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0432 video \u043e\u0431\u044a\u0435\u043a\u0442 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435\n\nif (navigator.mediaDevices.getUserMedia) {\n        navigator.mediaDevices.getUserMedia({video: true}).then(function (stream) {\n          \/\/ \u0432\u0438\u0434\u0435\u043e \u043f\u043e\u0442\u043e\u043a \u043f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u043c \u043a video \u0442\u0435\u0433\u0443, \u0447\u0442\u043e\u0431\u044b \u043a\u043b\u0438\u0435\u043d\u0442 \u043c\u043e\u0433 \u0432\u0438\u0434\u0435\u0442\u044c \u0441\u0435\u0431\u044f \u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \n          video.srcObject = s;\n          var\n            recorderOptions = {\n                mimeType: &#039;video\/webm; codecs=vp8&#039; \/\/ \u0431\u0443\u0434\u0435\u043c \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0438\u0434\u0435\u043e\u043f\u043e\u0442\u043e\u043a \u0432 \u0444\u043e\u0440\u043c\u0430\u0442 webm \u043a\u043e\u0434\u0435\u043a\u043e\u043c vp8\n              },\n              mediaRecorder = new MediaRecorder(s, recorderOptions ), \/\/ \u043e\u0431\u044a\u0435\u043a\u0442 MediaRecorder\n              socket = new WebSocket(&#039;ws:\/\/127.0.0.1:3000\/ws&#039;);\n\n               mediaRecorder.ondataavailable = function(e) {\n                if (e.data &amp;&amp; e.data.size &gt; 0) {\n                  \/\/ \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043a\u0443\u0441\u043e\u0447\u0435\u043a \u0432\u0438\u0434\u0435\u043e\u043f\u043e\u0442\u043e\u043a\u0430 \u0432 e.data\n                 socket.send(e.data);\n                }\n            }\n\n            mediaRecorder.start(100); \/\/ \u0434\u0435\u043b\u0438\u0442 \u043f\u043e\u0442\u043e\u043a \u043d\u0430 \u043a\u0443\u0441\u043e\u0447\u043a\u0438 \u043f\u043e 100 \u043c\u0441 \u043a\u0430\u0436\u0434\u044b\u0439\n\n        }).catch(function (err) { console.log(err); });\n}\n&lt;\/script&gt;\n<\/code><\/pre>\n<p>Strana vys\u00edl\u00e1n\u00ed je p\u0159ipravena! Te\u010f vyzkou\u0161ejme p\u0159ij\u00edmat video stream a zobrazit ho na klientovi. Co k tomu budeme pot\u0159ebovat? Za prv\u00e9, ur\u010dit\u011b socketov\u00e9 spojen\u00ed. Na objekt WebSocket nastav\u00edme \"poslucha\u010d\" (listener), p\u0159ihl\u00e1s\u00edme se k ud\u00e1losti \u201amessage\u2018. Jakmile n\u00e1\u0161 server obdr\u017e\u00ed kousek bin\u00e1rn\u00edch dat, vys\u00edl\u00e1 ho sv\u00fdm odeb\u00edratel\u016fm, tedy klient\u016fm. Na klientovi se n\u00e1sledn\u011b vyvol\u00e1 callback-funkce spojen\u00e1 s \"poslucha\u010dem\" ud\u00e1losti \u201amessage\u2018, p\u0159i\u010dem\u017e do argumentu funkce je p\u0159ed\u00e1v\u00e1n samotn\u00fd objekt \u2014 kousek video streamu zak\u00f3dovan\u00fd vp8. <\/p>\n<p>                        <b class=\"spoiler_title\">P\u0159ij\u00edm\u00e1me video stream<\/b><\/p>\n<pre><code class=\"javascript\">&lt;style&gt;\n  #videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }\n&lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n&lt;!-- \u0417\u0434\u0435\u0441\u044c \u0432 \u044d\u0442\u043e\u043c &quot;\u043e\u043a\u043e\u0448\u0435\u0447\u043a\u0435&quot; \u043a\u043b\u0438\u0435\u043d\u0442 \u0431\u0443\u0434\u0435\u0442 \u0432\u0438\u0434\u0435\u0442\u044c \u0442\u0435\u0431\u044f --&gt;\n&lt;video autoplay id=&quot;videoObjectHtml5ApiServer&quot;&gt;&lt;\/video&gt;\n\n&lt;script type=&quot;application\/javascript&quot;&gt;\n  var\n        video = document.getElementById(&#039;videoObjectHtml5ApiServer&#039;),\n         socket = new WebSocket(&#039;ws:\/\/127.0.0.1:3000\/ws&#039;), \n         arrayOfBlobs = [];\n\n         socket.addEventListener(&#039;message&#039;, function (event) {\n                \/\/ &quot;\u043a\u043b\u0430\u0434\u0435\u043c&quot; \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u043a\u0443\u0441\u043e\u0447\u0435\u043a \u0432 \u043c\u0430\u0441\u0441\u0438\u0432 \n                arrayOfBlobs.push(event.data);\n                \/\/ \u0437\u0434\u0435\u0441\u044c \u0431\u0443\u0434\u0435\u043c \u0447\u0438\u0442\u0430\u0442\u044c \u043a\u0443\u0441\u043e\u0447\u043a\u0438\n                readChunk();\n            });\n&lt;\/script&gt;\n<\/code><\/pre>\n<p>Dlouho jsem se sna\u017eil pochopit, pro\u010d nen\u00ed mo\u017en\u00e9 ihned odeslat z\u00edskan\u00e9 \u00fatr\u017eky na p\u0159ehr\u00e1v\u00e1n\u00ed do prvku video, ale uk\u00e1zalo se, \u017ee to tak opravdu nelze ud\u011blat. Nejprve je t\u0159eba \u00fatr\u017eek ulo\u017eit do speci\u00e1ln\u00edho bufferu p\u0159ipojen\u00e9ho k prvku video, a teprve pot\u00e9 za\u010dne p\u0159ehr\u00e1vat video stream. K tomu bude pot\u0159eba <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/MediaSource\">MediaSource API<\/a><\/noindex> a <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/FileReader\">FileReader API<\/a><\/noindex>.<\/p>\n<p>MediaSource funguje jako prost\u0159edn\u00edk mezi objektem p\u0159ehr\u00e1v\u00e1n\u00ed m\u00e9di\u00ed a zdrojem dan\u00e9ho streamu. Objekt MediaSource obsahuje p\u0159ipojiteln\u00fd buffer pro zdroj video\/audio streamu. Jednou z vlastnost\u00ed je, \u017ee buffer m\u016f\u017ee obsahovat pouze data typu Uint8, a proto k vytvo\u0159en\u00ed takov\u00e9ho bufferu bude pot\u0159eba FileReader. Pod\u00edvejte se na k\u00f3d a bude to jasn\u011bj\u0161\u00ed:<\/p>\n<p>                        <b class=\"spoiler_title\">P\u0159ehr\u00e1v\u00e1me video stream<\/b><\/p>\n<pre><code class=\"javascript\">&lt;style&gt;\n  #videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }\n&lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n&lt;!-- \u0417\u0434\u0435\u0441\u044c \u0432 \u044d\u0442\u043e\u043c &quot;\u043e\u043a\u043e\u0448\u0435\u0447\u043a\u0435&quot; \u043a\u043b\u0438\u0435\u043d\u0442 \u0431\u0443\u0434\u0435\u0442 \u0432\u0438\u0434\u0435\u0442\u044c \u0442\u0435\u0431\u044f --&gt;\n&lt;video autoplay id=&quot;videoObjectHtml5ApiServer&quot;&gt;&lt;\/video&gt;\n\n&lt;script type=&quot;application\/javascript&quot;&gt;\n  var\n        video = document.getElementById(&#039;videoObjectHtml5ApiServer&#039;),\n         socket = new WebSocket(&#039;ws:\/\/127.0.0.1:3000\/ws&#039;),\n        mediaSource = new MediaSource(), \/\/ \u043e\u0431\u044a\u0435\u043a\u0442 MediaSource\n        vid2url = URL.createObjectURL(mediaSource), \/\/ \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 URL \u0434\u043b\u044f \u0441\u0432\u044f\u0437\u044b\u0432\u0430\u043d\u0438\u044f \u0432\u0438\u0434\u0435\u043e\u043f\u043e\u0442\u043e\u043a\u0430 \u0441 \u043f\u0440\u043e\u0438\u0433\u0440\u044b\u0432\u0430\u0442\u0435\u043b\u0435\u043c\n        arrayOfBlobs = [],\n        sourceBuffer = null; \/\/ \u0431\u0443\u0444\u0435\u0440, \u043f\u043e\u043a\u0430 \u043d\u0443\u043b\u044c-\u043e\u0431\u044a\u0435\u043a\u0442\n\n         socket.addEventListener(&#039;message&#039;, function (event) {\n                \/\/ &quot;\u043a\u043b\u0430\u0434\u0435\u043c&quot; \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u043a\u0443\u0441\u043e\u0447\u0435\u043a \u0432 \u043c\u0430\u0441\u0441\u0438\u0432 \n                arrayOfBlobs.push(event.data);\n                \/\/ \u0437\u0434\u0435\u0441\u044c \u0431\u0443\u0434\u0435\u043c \u0447\u0438\u0442\u0430\u0442\u044c \u043a\u0443\u0441\u043e\u0447\u043a\u0438\n                readChunk();\n            });\n\n         \/\/ \u043a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e MediaSource \u0431\u0443\u0434\u0435\u0442 \u043e\u043f\u043e\u0432\u0435\u0449\u0435\u043d , \u0447\u0442\u043e \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0433\u043e\u0442\u043e\u0432 \u043e\u0442\u0434\u0430\u0432\u0430\u0442\u044c \u043a\u0443\u0441\u043e\u0447\u043a\u0438 \n        \/\/ \u0432\u0438\u0434\u0435\u043e\/\u0430\u0443\u0434\u0438\u043e \u043f\u043e\u0442\u043e\u043a\u0430\n        \/\/ \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0431\u0443\u0444\u0435\u0440 , \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u0431\u0443\u0444\u0435\u0440 \u0434\u043e\u043b\u0436\u0435\u043d \u0437\u043d\u0430\u0442\u044c \u0432 \u043a\u0430\u043a\u043e\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u0435 \n        \/\/ \u043a\u0430\u043a\u0438\u043c \u043a\u043e\u0434\u0435\u043a\u043e\u043c \u0431\u044b\u043b \u0437\u0430\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d \u043f\u043e\u0442\u043e\u043a, \u0447\u0442\u043e\u0431\u044b \u0442\u0435\u043c \u0436\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0432\u0438\u0434\u0435\u043e\u043f\u043e\u0442\u043e\u043a\n         mediaSource.addEventListener(&#039;sourceopen&#039;, function() {\n            var mediaSource = this;\n            sourceBuffer = mediaSource.addSourceBuffer(&quot;video\/webm; codecs=&quot;vp8&quot;&quot;);\n        });\n\n      function readChunk() {\n        var reader = new FileReader();\n        reader.onload = function(e) { \n          \/\/ \u043a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e FileReader \u0431\u0443\u0434\u0435\u0442 \u0433\u043e\u0442\u043e\u0432, \u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442 \u0441\u0435\u0431\u0435 \u043a\u0443\u0441\u043e\u0447\u0435\u043a \u0432\u0438\u0434\u0435\u043e\u043f\u043e\u0442\u043e\u043a\u0430\n          \/\/ \u043c\u044b &quot;\u043f\u0440\u0438\u0446\u0435\u043f\u043b\u044f\u0435\u043c&quot; \u043f\u0435\u0440\u0435\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0432 Uint8Array (\u0431\u044b\u043b Blob) \u043a\u0443\u0441\u043e\u0447\u0435\u043a \u0432 \u0431\u0443\u0444\u0435\u0440, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0439\n          \/\/ \u0441 \u043f\u0440\u043e\u0438\u0433\u0440\u044b\u0432\u0430\u0442\u0435\u043b\u0435\u043c, \u0438 \u043f\u0440\u043e\u0438\u0433\u0440\u044b\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442 \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u044c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u043a\u0443\u0441\u043e\u0447\u0435\u043a \u0432\u0438\u0434\u0435\u043e\/\u0430\u0443\u0434\u0438\u043e\n          sourceBuffer.appendBuffer(new Uint8Array(e.target.result));\n\n          reader.onload = null;\n        }\n        reader.readAsArrayBuffer(arrayOfBlobs.shift());\n      }\n&lt;\/script&gt;\n<\/code><\/pre>\n<p>Prototyp streamovac\u00ed slu\u017eby je hotov. Hlavn\u00ed nev\u00fdhodou je, \u017ee video p\u0159ehr\u00e1v\u00e1n\u00ed bude zaost\u00e1vat za vys\u00edlac\u00ed stranou o 100 ms, co\u017e jsme nastavili sami p\u0159i d\u011blen\u00ed video streamu p\u0159ed odesl\u00e1n\u00edm na server. Nav\u00edc, kdy\u017e jsem to zkou\u0161el na sv\u00e9m notebooku, postupn\u011b se mi hromadil lag mezi vys\u00edlac\u00ed a p\u0159ij\u00edmaj\u00edc\u00ed stranou, co\u017e bylo dob\u0159e viditeln\u00e9. Za\u010dal jsem hledat zp\u016fsoby, jak tento nedostatek p\u0159ekonat, a\u2026 narazil jsem na <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCPeerConnection\">RTCPeerConnection API<\/a><\/noindex>, kter\u00e9 umo\u017e\u0148uje p\u0159en\u00e1\u0161et video stream bez jak\u00fdchkoliv trik\u016f, jako je rozbit\u00ed streamu na kousky. Hromad\u00edc\u00ed se lag si mysl\u00edm, \u017ee vznik\u00e1 proto, \u017ee v prohl\u00ed\u017ee\u010di doch\u00e1z\u00ed p\u0159ed odesl\u00e1n\u00edm k p\u0159eveden\u00ed ka\u017ed\u00e9ho kousku do form\u00e1tu webm. U\u017e jsem to d\u00e1le nezkoumal a za\u010dal studovat WebRTC; o v\u00fdsledc\u00edch m\u00fdch zkoum\u00e1n\u00ed si mysl\u00edm, \u017ee nap\u00ed\u0161u samostatn\u00fd \u010dl\u00e1nek, pokud to pova\u017euji za zaj\u00edmav\u00e9 pro komunitu.<br \/>\n<br \/>Zdroj: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/461769\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0445\u043e\u0447\u0443 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0441\u0432\u043e\u0438\u043c\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0430\u043c\u0438 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0442\u0440\u0438\u043c\u043c\u0438\u043d\u0433 \u0432\u0438\u0434\u0435\u043e \u0447\u0435\u0440\u0435\u0437 websockets \u0431\u0435\u0437 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 \u0442\u0438\u043f\u0430 Adobe Flash Player. \u0427\u0442\u043e \u0438\u0437 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c \u0447\u0438\u0442\u0430\u0439\u0442\u0435 \u0434\u0430\u043b\u0435\u0435. Adobe Flash \u2014 \u0440\u0430\u043d\u0435\u0435 Macromedia Flash, \u044d\u0442\u043e \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439, \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u0445 \u0432 \u0432\u0435\u0431-\u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435. \u0414\u043e \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u0438\u044f Media Stream API \u044d\u0442\u043e \u0431\u044b\u043b\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u0430\u044f \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 \u0434\u043b\u044f \u0441\u0442\u0440\u0438\u043c\u043c\u0438\u043d\u0433\u0430 \u0441 \u0432\u0435\u0431-\u043a\u0430\u043c\u0435\u0440\u044b [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-94138","post","type-post","status-publish","format-standard","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.3 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0445\u043e\u0447\u0443 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0441\u0432\u043e\u0438\u043c\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0430\u043c\u0438 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0442\u0440\u0438\u043c\u043c\u0438\u043d\u0433 \u0432\u0438\u0434\u0435\u043e \u0447\u0435\u0440\u0435\u0437 websockets \u0431\u0435\u0437 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 \u0442\u0438\u043f\u0430 Adobe Flash Player.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/cs\/blog\/administrirovanie\/pochti-bespoleznyj-striming-vebkamery-iz-brauzera-media-stream-i-websockets\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.3\" \/>\n\t\t<meta property=\"og:locale\" content=\"cs_CZ\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47(\u041f\u043e\u0447\u0442\u0438) \u0431\u0435\u0441\u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0439 \u0441\u0442\u0440\u0438\u043c\u0438\u043d\u0433 \u0432\u0435\u0431\u043a\u0430\u043c\u0435\u0440\u044b \u0438\u0437 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430. Media Stream \u0438 Websockets | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0445\u043e\u0447\u0443 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0441\u0432\u043e\u0438\u043c\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0430\u043c\u0438 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0442\u0440\u0438\u043c\u043c\u0438\u043d\u0433 \u0432\u0438\u0434\u0435\u043e \u0447\u0435\u0440\u0435\u0437 websockets \u0431\u0435\u0437 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 \u0442\u0438\u043f\u0430 Adobe Flash Player.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/cs\/blog\/administrirovanie\/pochti-bespoleznyj-striming-vebkamery-iz-brauzera-media-stream-i-websockets\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-09-13T05:42:42+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-09-13T05:42:42+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47(T\u00e9m\u011b\u0159) bezcenn\u00e9 streamov\u00e1n\u00ed webkamery z prohl\u00ed\u017ee\u010de. Media Stream a Websockets | ProHoster","description":"V tomto \u010dl\u00e1nku se chci pod\u011blit o sv\u00e9 pokusy o streamov\u00e1n\u00ed videa p\u0159es websockets bez pou\u017eit\u00ed extern\u00edch plugin\u016f prohl\u00ed\u017ee\u010de, jako je Adobe Flash Player.","canonical_url":"https:\/\/prohoster.info\/cs\/blog\/administrirovanie\/pochti-bespoleznyj-striming-vebkamery-iz-brauzera-media-stream-i-websockets","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"cs_CZ","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47(\u041f\u043e\u0447\u0442\u0438) \u0431\u0435\u0441\u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0439 \u0441\u0442\u0440\u0438\u043c\u0438\u043d\u0433 \u0432\u0435\u0431\u043a\u0430\u043c\u0435\u0440\u044b \u0438\u0437 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430. Media Stream \u0438 Websockets | ProHoster","og:description":"\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0445\u043e\u0447\u0443 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0441\u0432\u043e\u0438\u043c\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0430\u043c\u0438 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0442\u0440\u0438\u043c\u043c\u0438\u043d\u0433 \u0432\u0438\u0434\u0435\u043e \u0447\u0435\u0440\u0435\u0437 websockets \u0431\u0435\u0437 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 \u0442\u0438\u043f\u0430 Adobe Flash Player.","og:url":"https:\/\/prohoster.info\/cs\/blog\/administrirovanie\/pochti-bespoleznyj-striming-vebkamery-iz-brauzera-media-stream-i-websockets","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-09-13T05:42:42+00:00","article:modified_time":"2020-09-13T05:42:42+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"94138","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 11:32:23","updated":"2022-10-01 11:18:30","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/posts\/94138","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/comments?post=94138"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/posts\/94138\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/media?parent=94138"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/categories?post=94138"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/tags?post=94138"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}