{"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\/es\/blog\/administrirovanie\/pochti-bespoleznyj-striming-vebkamery-iz-brauzera-media-stream-i-websockets","title":{"rendered":"(Casi) in\u00fatil transmisi\u00f3n de la c\u00e1mara web desde el navegador. Media Stream y Websockets","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>En este art\u00edculo, quiero compartir mis intentos de transmitir video a trav\u00e9s de websockets sin utilizar complementos de navegador de terceros como Adobe Flash Player. Lo que logr\u00e9 se detalla a continuaci\u00f3n.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><br \/>\nAdobe Flash, anteriormente Macromedia Flash, es una plataforma para crear aplicaciones que operan en el navegador web. Antes de la implementaci\u00f3n de Media Stream API, era pr\u00e1cticamente la \u00fanica plataforma para transmitir video y audio desde una c\u00e1mara web, as\u00ed como para crear diversos tipos de conferencias y chats en el navegador. El protocolo para la transmisi\u00f3n de informaci\u00f3n multimedia, RTMP (Real Time Messaging Protocol), estuvo cerrado durante mucho tiempo, lo que significaba que, si quer\u00edas lanzar tu servicio de streaming, deb\u00edas utilizar el software de Adobe, como Adobe Media Server (AMS). <\/p>\n<p>Despu\u00e9s de un tiempo, en 2012, Adobe \"se rindi\u00f3 y escupi\u00f3\" ante el 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\">especificaci\u00f3n<\/a><\/noindex> un protocolo RTMP que conten\u00eda errores y, de hecho, estaba incompleto. Para ese momento, los desarrolladores comenzaron a crear sus propias implementaciones de este protocolo, lo que llev\u00f3 a la creaci\u00f3n del servidor Wowza. En 2011, Adobe demand\u00f3 a Wowza por el uso ilegal de patentes relacionadas con RTMP, y despu\u00e9s de 4 a\u00f1os, el conflicto se resolvi\u00f3 en paz. <\/p>\n<p>La plataforma Adobe Flash lleva m\u00e1s de 20 a\u00f1os, y durante este tiempo se han descubierto numerosas vulnerabilidades cr\u00edticas, y el soporte <noindex><a rel=\"nofollow\" href=\"https:\/\/webkit.org\/blog\/7839\/adobe-announces-flash-distribution-and-updates-to-end\/\">prometieron<\/a><\/noindex> se detendr\u00e1 en 2020, por lo que no hay muchas alternativas para el servicio de streaming. <\/p>\n<p>Para mi proyecto, decid\u00ed desde el principio abandonar completamente el uso de Flash en el navegador. La raz\u00f3n principal la mencion\u00e9 arriba, adem\u00e1s, Flash no es compatible en plataformas m\u00f3viles, y no quer\u00eda lidiar con la instalaci\u00f3n de Adobe Flash para desarrollar en Windows (emulador wine). As\u00ed que comenc\u00e9 a escribir un cliente en JavaScript. Esto ser\u00e1 solo un prototipo, ya que m\u00e1s adelante descubr\u00ed que el streaming se puede hacer de manera mucho m\u00e1s eficiente basado en p2p, solo que en mi caso ser\u00e1 peer - servidor - peers, pero eso es un tema para otra ocasi\u00f3n, ya que a\u00fan no est\u00e1 listo.<\/p>\n<p>Para comenzar a trabajar, necesitamos un servidor de websockets. Hice uno muy simple basado en el paquete Go melody:<\/p>\n<p>                        <b class=\"spoiler_title\">C\u00f3digo de la parte del 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         \/\/ Emitimos el flujo de video \n\tm.HandleMessageBinary(func(s *melody.Session, msg []byte) {\n\t\tm.BroadcastBinary(msg)\n\t})\n\n\tlog.Println(\"Iniciando el servidor...\")\n\n\thttp.ListenAndServe(\":3000\", r)\n}\n<\/code><\/pre>\n<p>En el cliente (lado emisor), primero es necesario acceder a la c\u00e1mara. Esto se hace a trav\u00e9s de <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Media_Streams_API\">API MediaStream<\/a><\/noindex>.<\/p>\n<p>Obtenemos acceso (permiso) a la c\u00e1mara\/micr\u00f3fono a trav\u00e9s de <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/MediaDevices\">API de Dispositivos Multimedia<\/a><\/noindex>. Esta API proporciona el m\u00e9todo <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/MediaDevices\/getUserMedia\">MediaDevices.getUserMedia()<\/a><\/noindex>, que muestra una ventana emergente preguntando al usuario por permiso para acceder a la c\u00e1mara y\/o micr\u00f3fono. Cabe destacar que realic\u00e9 todos los experimentos en Google Chrome, pero creo que en Firefox deber\u00eda funcionar de manera similar.<\/p>\n<p>Luego, getUserMedia() devuelve una Promesa, que transmite un objeto MediaStream: un flujo de datos de video y audio. Este objeto lo asignamos a la propiedad src del elemento video. C\u00f3digo:<\/p>\n<p>                        <b class=\"spoiler_title\">Lado emisor<\/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 el flujo de video a trav\u00e9s de sockets, es necesario codificarlo, bufferizarlo y enviarlo en partes. No se puede transmitir un flujo de video crudo a trav\u00e9s de websockets. Aqu\u00ed nos ayuda <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/MediaStream_Recording_API\">API MediaRecorder<\/a><\/noindex>. Esta API permite codificar y dividir el flujo en fragmentos. Realizo la codificaci\u00f3n para comprimir el flujo de video, de modo que se transmitan menos bytes por la red. Una vez dividido en partes, cada fragmento se puede enviar a trav\u00e9s de websocket. C\u00f3digo:<\/p>\n<p>                        <b class=\"spoiler_title\">Codificamos el flujo de video, lo dividimos en 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>Ahora agreguemos la transmisi\u00f3n a trav\u00e9s de websockets. Sorprendentemente, solo necesitamos un objeto <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/WebSocket\">WebSocket<\/a><\/noindex>. Tiene solo dos m\u00e9todos: send y close. Los nombres hablan por s\u00ed mismos. C\u00f3digo complementado:<\/p>\n<p>                        <b class=\"spoiler_title\">Transmitimos el flujo de video al 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>\u00a1La parte de transmisi\u00f3n est\u00e1 lista! Ahora intentemos recibir el flujo de video y mostrarlo en el cliente. \u00bfQu\u00e9 necesitamos para esto? Primero, por supuesto, una conexi\u00f3n de socket. Adjuntamos un \"listener\" al objeto WebSocket, suscribi\u00e9ndonos al evento 'message'. Cuando recibimos un fragmento de datos binarios, nuestro servidor lo emite a suscriptores, es decir, a los clientes. En el cliente, se activa la funci\u00f3n callback asociada al \"listener\" del evento 'message', y el argumento de la funci\u00f3n es el propio objeto: un fragmento del flujo de video codificado en vp8. <\/p>\n<p>                        <b class=\"spoiler_title\">Recibimos el flujo de video<\/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>Durante mucho tiempo intent\u00e9 entender por qu\u00e9 no se pueden enviar los fragmentos obtenidos directamente para reproducir en el elemento video, pero resulta que no se puede hacer as\u00ed, primero hay que colocar el fragmento en un b\u00fafer especial vinculado al elemento video, y solo entonces comenzar\u00e1 a reproducir la secuencia de video. Para esto se necesitar\u00e1 <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/MediaSource\">MediaSource API<\/a><\/noindex> y <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/FileReader\">FileReader API<\/a><\/noindex>.<\/p>\n<p>MediaSource act\u00faa como un intermediario entre el objeto de reproducci\u00f3n de media y la fuente de esta secuencia de media. El objeto MediaSource contiene un b\u00fafer conectable para la fuente de la secuencia de video\/audio. Una caracter\u00edstica es que el b\u00fafer solo puede contener datos del tipo Uint8, por lo que para crear dicho b\u00fafer se necesitar\u00e1 FileReader. Mira el c\u00f3digo y se volver\u00e1 m\u00e1s claro:<\/p>\n<p>                        <b class=\"spoiler_title\">Reproduciendo la secuencia de video<\/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>El prototipo del servicio de streaming est\u00e1 listo. La principal desventaja es que la reproducci\u00f3n de video estar\u00e1 atrasada con respecto a la parte transmissora en 100 ms, esto lo decidimos nosotros mismos al dividir la secuencia de video antes de enviarla al servidor. Adem\u00e1s, cuando lo comprob\u00e9 en mi port\u00e1til, not\u00e9 que se acumulaba un retraso entre la parte transmissora y la receptora, lo cual era bastante evidente. Comenc\u00e9 a buscar formas de superar este inconveniente y... me encontr\u00e9 con <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCPeerConnection\">RTCPeerConnection API<\/a><\/noindex>, que permite transmitir la secuencia de video sin artima\u00f1as como dividir la secuencia en fragmentos. Creo que el retraso acumulado se debe a que en el navegador, antes de enviar, se recodifica cada fragmento al formato webm. No segu\u00ed indagando m\u00e1s, y empec\u00e9 a estudiar WebRTC, sobre los resultados de mis investigaciones creo que escribir\u00e9 un art\u00edculo aparte si lo considero interesante para la comunidad.<br \/>\n<br \/>Fuente: <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.2 - 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\/es\/blog\/administrirovanie\/pochti-bespoleznyj-striming-vebkamery-iz-brauzera-media-stream-i-websockets\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"es_ES\" \/>\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\/es\/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(Casi) in\u00fatil streaming de la c\u00e1mara web desde el navegador. Media Stream y Websockets | ProHoster","description":"En el art\u00edculo quiero compartir mis intentos de hacer streaming de video a trav\u00e9s de websockets sin usar plugins de navegador de terceros como Adobe Flash Player.","canonical_url":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/pochti-bespoleznyj-striming-vebkamery-iz-brauzera-media-stream-i-websockets","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"es_ES","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\/es\/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\/es\/wp-json\/wp\/v2\/posts\/94138","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/comments?post=94138"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts\/94138\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/media?parent=94138"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/categories?post=94138"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/tags?post=94138"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}