(Aneane) pono ʻole ke kahe ʻana o ka webcam mai kahi polokalamu kele pūnaewele. ʻO ka Media Stream a me Websockets

Ma kēia ʻatikala makemake wau e kaʻana like i kaʻu mau hoʻāʻo e kahe wikiō ma o nā websockets me ka ʻole o ka hoʻohana ʻana i nā plugins polokalamu ʻaoʻao ʻekolu e like me Adobe Flash Player. E heluhelu e ʻike i ka mea i loaʻa mai.

ʻO Adobe Flash, ʻo Macromedia Flash ma mua, he kahua no ka hana ʻana i nā noi e holo ana ma kahi polokalamu kele pūnaewele. Ma mua o ka hoʻokomo ʻia ʻana o ka Media Stream API, ʻo ia wale nō ke kahua no ka hoʻoheheʻe ʻana i ka wikiō a me ka leo mai kahi webcam, a me ka hana ʻana i nā ʻano kūkā like ʻole a me nā kamaʻilio ʻana ma ka polokalamu kele pūnaewele. ʻO ka protocol no ka hoʻouna ʻana i ka ʻike media RTMP (Real Time Messaging Protocol) ua pani maoli ʻia no ka manawa lōʻihi, ʻo ia hoʻi: inā makemake ʻoe e hoʻoikaika i kāu lawelawe streaming, e ʻoluʻolu e hoʻohana i nā polokalamu mai Adobe iā lākou iho - Adobe Media Server (AMS).

Ma hope o kekahi manawa ma 2012, "haʻalele a kuha" ʻo Adobe i ka lehulehu. kikoʻī ʻO ka protocol RTMP, aia nā hewa a ʻaʻole i piha. I kēlā manawa, hoʻomaka nā mea hoʻomohala e hana i kā lākou hoʻokō ponoʻī o kēia protocol, a ʻike ʻia ka server Wowza. I ka makahiki 2011, ua hoʻopiʻi ʻo Adobe i ka hoʻopiʻi kūʻē iā Wowza no ka hoʻohana ʻole ʻana i nā patent e pili ana i ka RTMP; ma hope o 4 mau makahiki, ua hoʻoholo maikaʻi ʻia ka hakakā.

ʻOi aku ka Adobe Flash platform ma mua o 20 mau makahiki, i ia manawa ua ʻike ʻia nā mea koʻikoʻi koʻikoʻi, kākoʻo. hoohikiia e hoʻopau i ka 2020, e waiho ana i nā mea ʻē aʻe no ka lawelawe streaming.

No kaʻu papahana, ua hoʻoholo koke wau e haʻalele loa i ka hoʻohana ʻana o Flash i ka polokalamu kele pūnaewele. Ua hōʻike wau i ke kumu nui ma luna; ʻAʻole i kākoʻo ʻia ʻo Flash ma nā kahua paʻa, a ʻaʻole wau makemake e kau i ka Adobe Flash no ka hoʻomohala ʻana ma Windows (wine emulator). No laila ua hoʻomaka wau e kākau i kahi mea kūʻai aku ma JavaScript. He prototype wale nō kēia, no ka mea ma hope mai ua aʻo au e hiki ke hana ʻoi aku ka maikaʻi o ke kahe ʻana ma muli o ka p2p, noʻu wale nō e lilo ia i hoa - server - peers, akā ʻoi aku ka nui o kēlā me kēia manawa, no ka mea ʻaʻole i mākaukau.

No ka hoʻomaka ʻana, pono mākou i ka server websockets maoli. Ua hana au i ka mea maʻalahi loa ma muli o ka pūʻolo melody go:

Code kikowaena

package main

import (
	"errors"
	"github.com/go-chi/chi"
	"gopkg.in/olahol/melody.v1"
	"log"
	"net/http"
	"time"
)

func main() {
	r := chi.NewRouter()
	m := melody.New()

	m.Config.MaxMessageSize = 204800

	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, "public/index.html")
	})
	r.Get("/ws", func(w http.ResponseWriter, r *http.Request) {
		m.HandleRequest(w, r)
	})

         // Бродкастим видео поток 
	m.HandleMessageBinary(func(s *melody.Session, msg []byte) {
		m.BroadcastBinary(msg)
	})

	log.Println("Starting server...")

	http.ListenAndServe(":3000", r)
}

Ma ka mea kūʻai aku (ka ʻaoʻao kahawai), pono ʻoe e komo i ke kāmela. Hana ʻia kēia ma o API MediaStream.

Loaʻa iā mākou ke komo (ʻae) i ka pahupaʻikiʻi / microphone ma o API no nā polokalamu Media. Hāʻawi kēia API i kahi ala MediaDevices.getUserMedia(), e hōʻike ana i ka popup. he puka aniani e noi ana i ka mea hoʻohana no ka ʻae ʻana e komo i ka pahupaʻikiʻi a/a i ʻole microphone. Makemake wau e hoʻomaopopo ua hana wau i nā hoʻokolohua āpau ma Google Chrome, akā manaʻo wau e hana like nā mea āpau ma Firefox.

A laila, e hoʻihoʻi ka getUserMedia () i kahi ʻōlelo hoʻohiki, kahi e hāʻawi ai i kahi mea MediaStream - kahi kahawai o ka ʻikepili wikiō-leo. Hāʻawi mākou i kēia mea i ka waiwai src o ka mea wikiō. Code:

ʻaoʻao hoʻolaha

<style>
  #videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }
</style>
</head>
<body>
<!-- Здесь в этом "окошечке" клиент будет видеть себя -->
<video autoplay id="videoObjectHtml5ApiServer"></video>

<script type="application/javascript">
  var
        video = document.getElementById('videoObjectHtml5ApiServer');

// если доступен MediaDevices API, пытаемся получить доступ к камере (можно еще и к микрофону)
// getUserMedia вернет обещание, на которое подписываемся и полученный видеопоток в колбеке направляем в video объект на странице

if (navigator.mediaDevices.getUserMedia) {
        navigator.mediaDevices.getUserMedia({video: true}).then(function (stream) {
          // видео поток привязываем к video тегу, чтобы клиент мог видеть себя и контролировать 
          video.srcObject = stream;
        });
}
</script>

No ka hoʻolaha ʻana i ke kahawai wikiō ma luna o nā kumu, pono ʻoe e hoʻopili iā ia ma kahi, hoʻopaʻa iā ia, a hoʻouna i nā ʻāpana. ʻAʻole hiki ke hoʻouna ʻia ke kahawai wikiō maka ma o nā websockets. ʻO kēia kahi e hiki mai ai ke kōkua iā mākou API no ka MediaRecorder. Hāʻawi kēia API iā ʻoe e hoʻopili a wāwahi i ke kahawai i ʻāpana. Hana wau i ka hoʻopili ʻana i ke kahawai wikiō i mea e hoʻouna ai i nā byte liʻiliʻi ma luna o ka pūnaewele. I ka ʻoki ʻana i nā ʻāpana, hiki iā ʻoe ke hoʻouna i kēlā me kēia ʻāpana i kahi pūnaewele. Code:

Hoʻopili mākou i ke kahawai wikiō, e wāwahi i nā ʻāpana

<style>
  #videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }
</style>
</head>
<body>
<!-- Здесь в этом "окошечке" клиент будет видеть себя -->
<video autoplay id="videoObjectHtml5ApiServer"></video>

<script type="application/javascript">
  var
        video = document.getElementById('videoObjectHtml5ApiServer');

// если доступен MediaDevices API, пытаемся получить доступ к камере (можно еще и к микрофону)
// getUserMedia вернет обещание, на которое подписываемся и полученный видеопоток в колбеке направляем в video объект на странице

if (navigator.mediaDevices.getUserMedia) {
        navigator.mediaDevices.getUserMedia({video: true}).then(function (stream) {
          // видео поток привязываем к video тегу, чтобы клиент мог видеть себя и контролировать 
          video.srcObject = s;
          var
            recorderOptions = {
                mimeType: 'video/webm; codecs=vp8' // будем кодировать видеопоток в формат webm кодеком vp8
              },
              mediaRecorder = new MediaRecorder(s, recorderOptions ); // объект MediaRecorder

               mediaRecorder.ondataavailable = function(e) {
                if (e.data && e.data.size > 0) {
                  // получаем кусочек видеопотока в e.data
                }
            }

            mediaRecorder.start(100); // делит поток на кусочки по 100 мс каждый

        });
}
</script>

I kēia manawa e hoʻohui kākou i ka hoʻouna ʻana ma o nā websockets. ʻO ka mea kupanaha, ʻo ka mea āu e pono ai no kēia mea Pūnaewele Pūnaewele. ʻElua wale nō ala e hoʻouna a pani. 'Ōlelo nā inoa no lākou iho. code i hoʻohui ʻia:

Hoʻouna mākou i ke kahawai wikiō i ke kikowaena

<style>
  #videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }
</style>
</head>
<body>
<!-- Здесь в этом "окошечке" клиент будет видеть себя -->
<video autoplay id="videoObjectHtml5ApiServer"></video>

<script type="application/javascript">
  var
        video = document.getElementById('videoObjectHtml5ApiServer');

// если доступен MediaDevices API, пытаемся получить доступ к камере (можно еще и к микрофону)
// getUserMedia вернет обещание, на которое подписываемся и полученный видеопоток в колбеке направляем в video объект на странице

if (navigator.mediaDevices.getUserMedia) {
        navigator.mediaDevices.getUserMedia({video: true}).then(function (stream) {
          // видео поток привязываем к video тегу, чтобы клиент мог видеть себя и контролировать 
          video.srcObject = s;
          var
            recorderOptions = {
                mimeType: 'video/webm; codecs=vp8' // будем кодировать видеопоток в формат webm кодеком vp8
              },
              mediaRecorder = new MediaRecorder(s, recorderOptions ), // объект MediaRecorder
              socket = new WebSocket('ws://127.0.0.1:3000/ws');

               mediaRecorder.ondataavailable = function(e) {
                if (e.data && e.data.size > 0) {
                  // получаем кусочек видеопотока в e.data
                 socket.send(e.data);
                }
            }

            mediaRecorder.start(100); // делит поток на кусочки по 100 мс каждый

        }).catch(function (err) { console.log(err); });
}
</script>

Ua mākaukau ka ʻaoʻao hoʻolaha! I kēia manawa e hoʻāʻo mākou e loaʻa i kahi kahawai wikiō a hōʻike iā ia ma ka mea kūʻai aku. He aha kā mākou e pono ai no kēia? ʻO ka mea mua, ʻoiaʻiʻo, ka pilina socket. Hoʻopili mākou i kahi "hoʻolohe" i ka mea WebSocket a kau inoa i ka hanana 'memo'. I ka loaʻa ʻana o kahi ʻāpana ʻikepili binary, hoʻolaha kā mākou kikowaena i nā mea kākau inoa, ʻo ia hoʻi, nā mea kūʻai aku. I kēia hihia, hoʻomaka ka hana callback e pili ana me ka "hoʻolohe" o ka hanana 'message' i ka mea kūʻai aku; ua hoʻoili ʻia ka mea ponoʻī i loko o ka manaʻo hana - kahi ʻāpana o ke kahawai wikiō i hoʻopaʻa ʻia e vp8.

ʻAe mākou i ke kahawai wikiō

<style>
  #videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }
</style>
</head>
<body>
<!-- Здесь в этом "окошечке" клиент будет видеть тебя -->
<video autoplay id="videoObjectHtml5ApiServer"></video>

<script type="application/javascript">
  var
        video = document.getElementById('videoObjectHtml5ApiServer'),
         socket = new WebSocket('ws://127.0.0.1:3000/ws'), 
         arrayOfBlobs = [];

         socket.addEventListener('message', function (event) {
                // "кладем" полученный кусочек в массив 
                arrayOfBlobs.push(event.data);
                // здесь будем читать кусочки
                readChunk();
            });
</script>

No ka manawa lōʻihi ua ho'āʻo wau e hoʻomaopopo i ke kumu hiki ʻole ke hoʻouna koke i nā ʻāpana i loaʻa i ka mea wikiō no ka pāʻani ʻana, akā ua ʻike ʻia ʻaʻole hiki ke hana ʻia kēia, ʻoiaʻiʻo, pono ʻoe e kau mua i ka ʻāpana i kahi paʻa kūikawā i hoʻopaʻa ʻia i ka mea wikiō, a laila wale nō e hoʻomaka ai e pāʻani i ke kahawai wikiō. No kēia e pono ai ʻoe API no MediaSource и FileReader API.

Hana ʻia ʻo MediaSource ma ke ʻano he mea waena ma waena o ka mea pāʻani pāʻani a me ke kumu o kēia kahawai media. Aia i loko o ka mea MediaSource kahi pale hoʻopili no ke kumu o ke kahawai wikiō/leo. ʻO kahi hiʻohiʻona hiki i ka buffer ke hoʻopaʻa i ka ʻikepili Uint8, no laila pono ʻoe i kahi FileReader e hana i kahi pale. E nānā i ke code a e ʻike ʻia:

Ke pāʻani nei i ke kahawai wikiō

<style>
  #videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }
</style>
</head>
<body>
<!-- Здесь в этом "окошечке" клиент будет видеть тебя -->
<video autoplay id="videoObjectHtml5ApiServer"></video>

<script type="application/javascript">
  var
        video = document.getElementById('videoObjectHtml5ApiServer'),
         socket = new WebSocket('ws://127.0.0.1:3000/ws'),
        mediaSource = new MediaSource(), // объект MediaSource
        vid2url = URL.createObjectURL(mediaSource), // создаем объект URL для связывания видеопотока с проигрывателем
        arrayOfBlobs = [],
        sourceBuffer = null; // буфер, пока нуль-объект

         socket.addEventListener('message', function (event) {
                // "кладем" полученный кусочек в массив 
                arrayOfBlobs.push(event.data);
                // здесь будем читать кусочки
                readChunk();
            });

         // как только MediaSource будет оповещен , что источник готов отдавать кусочки 
        // видео/аудио потока
        // создаем буфер , следует обратить внимание, что буфер должен знать в каком формате 
        // каким кодеком был закодирован поток, чтобы тем же способом прочитать видеопоток
         mediaSource.addEventListener('sourceopen', function() {
            var mediaSource = this;
            sourceBuffer = mediaSource.addSourceBuffer("video/webm; codecs="vp8"");
        });

      function readChunk() {
        var reader = new FileReader();
        reader.onload = function(e) { 
          // как только FileReader будет готов, и загрузит себе кусочек видеопотока
          // мы "прицепляем" перекодированный в Uint8Array (был Blob) кусочек в буфер, связанный
          // с проигрывателем, и проигрыватель начинает воспроизводить полученный кусочек видео/аудио
          sourceBuffer.appendBuffer(new Uint8Array(e.target.result));

          reader.onload = null;
        }
        reader.readAsArrayBuffer(arrayOfBlobs.shift());
      }
</script>

Ua mākaukau ka prototype o ka lawelawe streaming. ʻO ka hemahema nui, ʻo ka pāʻani wikiō e waiho ma hope o ka ʻaoʻao hoʻouna e 100 ms; hoʻonoho mākou iā mākou iho i ka wā e hoʻokaʻawale ai i ke kahawai wikiō ma mua o ka hoʻouna ʻana i ke kikowaena. Eia kekahi, i koʻu nānā ʻana i kaʻu pona, ua hōʻuluʻulu ʻia ka lag ma waena o ka lawe ʻana a me ka loaʻa ʻana o nā ʻaoʻao, ʻike maopopo ʻia kēia. Ua hoʻomaka wau e ʻimi i nā ala e lanakila ai i kēia hemahema, a ... hele mai API RTCPeerConnection, hiki iā ʻoe ke hoʻouna i kahi kahawai wikiō me ka ʻole o nā hoʻopunipuni e like me ka hoʻokaʻawale ʻana i ke kahawai i mau ʻāpana. ʻO ka hōʻiliʻili ʻana, manaʻo wau, ma muli o ka hoʻopili hou ʻana o ka polokalamu kele i kēlā me kēia ʻāpana i ka format webm ma mua o ka hoʻouna ʻana. ʻAʻole au i ʻeli hou aʻe, akā ua hoʻomaka wau e aʻo i ka WebRTC. Manaʻo wau e kākau wau i kahi ʻatikala ʻokoʻa e pili ana i nā hopena o kaʻu noiʻi inā ʻike wau he hoihoi i ke kaiāulu.

Source: www.habr.com

Pākuʻi i ka manaʻo hoʻopuka