{"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\/pt\/blog\/administrirovanie\/pochti-bespoleznyj-striming-vebkamery-iz-brauzera-media-stream-i-websockets","title":{"rendered":"(Quase) streaming in\u00fatil de webcam pelo navegador. Media Stream e Websockets","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>No artigo, quero compartilhar minhas tentativas de fazer streaming de v\u00eddeo atrav\u00e9s de websockets sem usar plugins de navegador de terceiros como o Adobe Flash Player. O que consegui fazer, voc\u00ea pode ler a seguir.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><br \/>\nAdobe Flash \u2014 anteriormente Macromedia Flash, \u00e9 uma plataforma para criar aplicativos que funcionam em navegadores da web. Antes da implementa\u00e7\u00e3o do Media Stream API, essa era praticamente a \u00fanica plataforma para streaming de v\u00eddeo e \u00e1udio de webcam, al\u00e9m de criar diversos tipos de confer\u00eancias e chats no navegador. O protocolo para transmiss\u00e3o de informa\u00e7\u00f5es de m\u00eddia, RTMP (Real Time Messaging Protocol), foi essencialmente fechado por muito tempo, o que significava que, se voc\u00ea quisesse estabelecer seu servi\u00e7o de streaming, precisava utilizar o software da pr\u00f3pria Adobe \u2014 Adobe Media Server (AMS). <\/p>\n<p>Ap\u00f3s algum tempo, em 2012, a Adobe 'desistiu e lan\u00e7ou' ao p\u00fablico <noindex><a rel=\"nofollow\" href=\"http:\/\/wwwimages.adobe.com\/www.adobe.com\/content\/dam\/acom\/en\/devnet\/rtmp\/pdf\/rtmp_specification_1.0.pdf\">uma especifica\u00e7\u00e3o<\/a><\/noindex> o protocolo RTMP, que continha erros e, essencialmente, era incompleto. Nessa \u00e9poca, os desenvolvedores come\u00e7aram a criar suas pr\u00f3prias implementa\u00e7\u00f5es desse protocolo, surgindo assim o servidor Wowza. Em 2011, a Adobe processou a Wowza por uso n\u00e3o autorizado de patentes relacionadas ao RTMP, e ap\u00f3s 4 anos, o conflito foi resolvido pacificamente. <\/p>\n<p>A plataforma Adobe Flash existe h\u00e1 mais de 20 anos, e durante esse tempo foram descobertas in\u00fameras vulnerabilidades cr\u00edticas, o suporte <noindex><a rel=\"nofollow\" href=\"https:\/\/webkit.org\/blog\/7839\/adobe-announces-flash-distribution-and-updates-to-end\/\">prometeram<\/a><\/noindex> encerrar em 2020, ent\u00e3o as alternativas para servi\u00e7os de streaming n\u00e3o s\u00e3o t\u00e3o numerosas assim. <\/p>\n<p>Para meu projeto, decidi imediatamente cancelar completamente o uso do Flash no navegador. A principal raz\u00e3o, que mencionei acima, \u00e9 que o Flash n\u00e3o \u00e9 suportado em plataformas m\u00f3veis, e al\u00e9m disso, n\u00e3o queria instalar o Adobe Flash para desenvolvimento no Windows (com emulador Wine). Portanto, decidi escrever um cliente em JavaScript. Isso ser\u00e1 apenas um prot\u00f3tipo, pois descobri depois que o streaming pode ser feito de maneira muito mais eficiente com base em p2p, embora para mim isso ser\u00e1 peer \u2014 server \u2014 peers, mas falaremos sobre isso em outra ocasi\u00e3o, pois ainda n\u00e3o est\u00e1 pronto.<\/p>\n<p>Para come\u00e7ar a trabalhar, precisaremos de um servidor websockets. Eu fiz o mais simples baseado no pacote go melody:<\/p>\n<p>                        <b class=\"spoiler_title\">C\u00f3digo da parte do servidor<\/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\t\/\/ Transmitindo o fluxo de v\u00eddeo \n\tm.HandleMessageBinary(func(s *melody.Session, msg []byte) {\n\t\tm.BroadcastBinary(msg)\n\t})\n\n\tlog.Println(\"Iniciando servidor...\\n\")\n\n\thttp.ListenAndServe(\":3000\", r)\n}\n<\/code><\/pre>\n<p>Do lado do cliente (o lado de transmiss\u00e3o), \u00e9 necess\u00e1rio primeiro obter acesso \u00e0 c\u00e2mera. Isso \u00e9 feito atrav\u00e9s de <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Media_Streams_API\">MediaStream API<\/a><\/noindex>.<\/p>\n<p>Obtemos acesso (permiss\u00e3o) \u00e0 c\u00e2mera\/microfone atrav\u00e9s de <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/MediaDevices\">Media Devices API<\/a><\/noindex>. Esta API fornece o m\u00e9todo <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/MediaDevices\/getUserMedia\">MediaDevices.getUserMedia()<\/a><\/noindex>, que exibe uma janela pop-up solicitando permiss\u00e3o do usu\u00e1rio para acessar a c\u00e2mera e\/ou microfone. Vale ressaltar que todas as experi\u00eancias foram realizadas no Google Chrome, mas acredito que no Firefox funcionar\u00e1 de maneira semelhante.<\/p>\n<p>Em seguida, o getUserMedia() retorna uma Promise, que transmite um objeto MediaStream \u2014 um fluxo de dados de v\u00eddeo e \u00e1udio. Atribu\u00edmos este objeto \u00e0 propriedade src do elemento video. C\u00f3digo:<\/p>\n<p>                        <b class=\"spoiler_title\">O lado de transmiss\u00e3o<\/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>Para transmitir o fluxo de v\u00eddeo atrav\u00e9s de sockets, \u00e9 necess\u00e1rio codific\u00e1-lo, armazen\u00e1-lo em buffer e envi\u00e1-lo em partes. Um fluxo de v\u00eddeo bruto n\u00e3o pode ser enviado atrav\u00e9s de websockets. Aqui entra em cena <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/MediaStream_Recording_API\">MediaRecorder API<\/a><\/noindex>. Esta API permite codificar e dividir o fluxo em peda\u00e7os. A codifica\u00e7\u00e3o \u00e9 feita para comprimir o fluxo de v\u00eddeo, para que menos bytes sejam transmitidos pela rede. Ao dividi-lo em partes, cada peda\u00e7o pode ser enviado pelo websocket. C\u00f3digo:<\/p>\n<p>                        <b class=\"spoiler_title\">Codificamos o fluxo de v\u00eddeo, dividimos em partes<\/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>Agora vamos adicionar a transmiss\u00e3o via websockets. Para nossa surpresa, isso requer apenas um objeto <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/WebSocket\">WebSocket<\/a><\/noindex>. Ele tem apenas dois m\u00e9todos: send e close. Os nomes falam por si. C\u00f3digo atualizado:<\/p>\n<p>                        <b class=\"spoiler_title\">Transmitimos o fluxo de v\u00eddeo para o servidor<\/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>O lado transmissor est\u00e1 pronto! Agora vamos tentar receber o fluxo de v\u00eddeo e mostr\u00e1-lo ao cliente. O que precisamos para isso? Primeiro, claro, uma conex\u00e3o de socket. Anexamos um \"ouvinte\" (listener) ao objeto WebSocket, inscrevendo-nos no evento 'message'. Ao receber um peda\u00e7o de dados bin\u00e1rios, nosso servidor o transmite para seus assinantes, ou seja, clientes. No cliente, \u00e9 acionada uma fun\u00e7\u00e3o de retorno associada ao \"ouvinte\" do evento 'message', onde o argumento da fun\u00e7\u00e3o \u00e9 o pr\u00f3prio objeto \u2014 um peda\u00e7o do fluxo de v\u00eddeo, codificado em vp8. <\/p>\n<p>                        <b class=\"spoiler_title\">Recebemos o fluxo de v\u00eddeo<\/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>Eu passei muito tempo tentando entender por que n\u00e3o podemos enviar os peda\u00e7os obtidos imediatamente para reprodu\u00e7\u00e3o no elemento video, mas parece que isso realmente n\u00e3o pode ser feito. Primeiro, \u00e9 necess\u00e1rio colocar o peda\u00e7o em um buffer especial associado ao elemento video, e s\u00f3 ent\u00e3o a reprodu\u00e7\u00e3o do fluxo de v\u00eddeo come\u00e7ar\u00e1. Para isso ser\u00e1 necess\u00e1rio <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/MediaSource\">API MediaSource<\/a><\/noindex> e <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/FileReader\">API FileReader<\/a><\/noindex>.<\/p>\n<p>MediaSource \u00e9 um intermedi\u00e1rio entre o objeto de reprodu\u00e7\u00e3o de m\u00eddia e a fonte deste fluxo de m\u00eddia. O objeto MediaSource cont\u00e9m um buffer conect\u00e1vel para a fonte do fluxo de v\u00eddeo\/a\u00fadio. Uma caracter\u00edstica \u00e9 que o buffer s\u00f3 pode conter dados do tipo Uint8, portanto, ser\u00e1 necess\u00e1rio um FileReader para criar esse buffer. Veja o c\u00f3digo e ficar\u00e1 mais claro:<\/p>\n<p>                        <b class=\"spoiler_title\">Fluxo de v\u00eddeo em reprodu\u00e7\u00e3o<\/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>O prot\u00f3tipo do servi\u00e7o de streaming est\u00e1 pronto. A principal desvantagem \u00e9 que a reprodu\u00e7\u00e3o do v\u00eddeo ficar\u00e1 com um atraso de 100 ms em rela\u00e7\u00e3o ao lado transmissor, pois isso foi definido por n\u00f3s ao dividir o fluxo de v\u00eddeo antes de envi\u00e1-lo para o servidor. Al\u00e9m disso, quando eu verifiquei no meu laptop, o atraso entre o lado transmissor e o lado receptor foi aumentando gradualmente, o que era claramente vis\u00edvel. Comecei a buscar formas de superar essa desvantagem e\u2026 encontrei <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCPeerConnection\">API RTCPeerConnection<\/a><\/noindex>, que permite transmitir o fluxo de v\u00eddeo sem artif\u00edcios como a divis\u00e3o do fluxo em peda\u00e7os. Acredito que o atraso acumulado se deve ao fato de que, no navegador, ocorre a recodifica\u00e7\u00e3o de cada peda\u00e7o para o formato webm antes da transmiss\u00e3o. N\u00e3o investi mais nisso e comecei a estudar o WebRTC; sobre os resultados de minhas investiga\u00e7\u00f5es, talvez escreva um artigo separado, se achar que ser\u00e1 interessante para a comunidade.<br \/>\n<br \/>Fonte: <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\/pt\/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=\"pt_PT\" \/>\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\/pt\/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(Quase) streaming in\u00fatil de uma webcam do navegador. Media Stream e Websockets | ProHoster","description":"Neste artigo, quero compartilhar minhas tentativas de fazer streaming de v\u00eddeo atrav\u00e9s de websockets sem usar plugins de terceiros no navegador, como o Adobe Flash Player.","canonical_url":"https:\/\/prohoster.info\/pt\/blog\/administrirovanie\/pochti-bespoleznyj-striming-vebkamery-iz-brauzera-media-stream-i-websockets","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pt_PT","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\/pt\/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\/pt\/wp-json\/wp\/v2\/posts\/94138","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/comments?post=94138"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/posts\/94138\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/media?parent=94138"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/categories?post=94138"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/tags?post=94138"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}