(Kważi) webcam inutli streaming minn browser. Media Stream u Websockets

F'dan l-artikolu nixtieq naqsam it-tentattivi tiegħi biex nistrimja vidjo permezz ta 'websockets mingħajr ma nuża plugins tal-brawżer ta' partijiet terzi bħal Adobe Flash Player. Kompli aqra biex issir taf x'ġara minnha.

Adobe Flash, li qabel kienet Macromedia Flash, hija pjattaforma għall-ħolqien ta' applikazzjonijiet li jaħdmu f'web browser. Qabel l-introduzzjoni tal-Media Stream API, kienet prattikament l-unika pjattaforma għall-istrimjar ta 'vidjow u vuċi minn webcam, kif ukoll għall-ħolqien ta' diversi tipi ta 'konferenzi u chats fil-browser. Il-protokoll għat-trażmissjoni tal-informazzjoni tal-midja RTMP (Real Time Messaging Protocol) kien fil-fatt magħluq għal żmien twil, li fisser: jekk trid issaħħaħ is-servizz tal-istrimjar tiegħek, kun ġentili biżżejjed biex tuża softwer minn Adobe stess - Adobe Media Server (AMS).

Wara xi żmien fl-2012, Adobe "ċediet u beżgħet" lill-pubbliku. speċifikazzjoni Protokoll RTMP, li kien fih żbalji u kien essenzjalment mhux komplut. Sa dak iż-żmien, l-iżviluppaturi bdew jagħmlu l-implimentazzjonijiet tagħhom stess ta 'dan il-protokoll, u s-server Wowza deher. Fl-2011, Adobe ressqet kawża kontra Wowza għal użu illegali ta 'privattivi relatati mal-RTMP wara 4 snin, il-kunflitt ġie solvut b'mod amikevoli;

Il-pjattaforma Adobe Flash għandha aktar minn 20 sena, li matulu ġew skoperti ħafna vulnerabbiltajiet kritiċi, appoġġ imwiegħed biex jintemm sal-2020, u jħalli ftit alternattivi għas-servizz ta' streaming.

Għall-proġett tiegħi, immedjatament iddeċidejt li nabbanduna kompletament l-użu ta 'Flash fil-browser. Jien indikajt ir-raġuni ewlenija hawn fuq; Flash lanqas mhu appoġġjat xejn fuq pjattaformi mobbli, u verament ma ridtx niskjera Adobe Flash għall-iżvilupp fuq Windows (emulatur tal-inbid). Allura bdejt nikteb klijent f'JavaScript. Dan se jkun biss prototip, peress li aktar tard tgħallimt li l-istreaming jista 'jsir b'mod ħafna aktar effiċjenti bbażat fuq p2p, għalija biss se jkun peer - server - peers, iżda aktar dwar dan darba oħra, għax għadu mhux lest.

Biex tibda, għandna bżonn is-server tal-websockets attwali. Għamilt l-aktar waħda sempliċi bbażata fuq il-pakkett tal-melody go:

Kodiċi tas-server

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)
}

Fuq il-klijent (in-naħa tal-istreaming), l-ewwel trid taċċessa l-kamera. Dan isir permezz MediaStream API.

Aħna niksbu aċċess (permess) għall-kamera/mikrofonu permezz Media Devices API. Din l-API tipprovdi metodu MediaDevices.getUserMedia(), li turi popup. tieqa li titlob lill-utent il-permess biex jaċċessa l-kamera u/jew il-mikrofonu. Nixtieq ninnota li wettaqt l-esperimenti kollha fil-Google Chrome, iżda naħseb li kollox se jaħdem dwar l-istess fil-Firefox.

Sussegwentement, getUserMedia() jirritorna Wegħda, li lilha tgħaddi oġġett MediaStream - fluss ta 'dejta tal-vidjo-awdjo. Aħna jassenjaw dan l-oġġett għall-proprjetà src tal-element tal-vidjo. Kodiċi:

In-naħa tax-xandir

<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>

Biex tixxandar nixxiegħa tal-vidjow fuq sokits, għandek bżonn tikkodifikah x'imkien, taffah, u tittrasmettih f'partijiet. Il-fluss tal-vidjo mhux ipproċessat ma jistax jiġi trażmess permezz ta 'websockets. Dan huwa fejn niġu għall-għajnuna tagħna MediaRecorder API. Din l-API tippermettilek tikkodifika u tkisser il-fluss f'biċċiet. Nagħmel kodifikazzjoni biex tikkompressa l-fluss tal-vidjo sabiex nibgħat inqas bytes fuq in-netwerk. Wara li qatgħetha f'biċċiet, tista 'tibgħat kull biċċa lil websocket. Kodiċi:

Aħna nikkodifikaw il-fluss tal-vidjo, nkissruh f'biċċiet

<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>

Issa ejja nżidu t-trasmissjoni permezz ta 'websockets. B'mod sorprendenti, kulma għandek bżonn għal dan huwa oġġett WebSocket. Għandha biss żewġ metodi tibgħat u tagħlaq. L-ismijiet jitkellmu waħedhom. Kodiċi miżjud:

Aħna jittrasmettu l-fluss tal-vidjo lis-server

<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>

In-naħa tax-xandir hija lesta! Issa ejja nippruvaw nirċievu nixxiegħa tal-vidjo u juruha fuq il-klijent. X'għandna bżonn għal dan? L-ewwelnett, ovvjament, il-konnessjoni tas-sokit. Aħna nehmeż "semmiegħ" mal-oġġett WebSocket u tabbona għall-avveniment 'messaġġ'. Wara li rċieva biċċa dejta binarja, is-server tagħna jxandarha lill-abbonati, jiġifieri, lill-klijenti. F'dan il-każ, il-funzjoni ta 'callback assoċjata mas-"semmiegħ" tal-avveniment 'messaġġ' tiġi attivata fuq il-klijent l-oġġett innifsu jiġi mgħoddi fl-argument tal-funzjoni - biċċa tal-fluss tal-vidjo kodifikat minn vp8;

Aħna naċċettaw nixxiegħa tal-vidjo

<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>

Għal żmien twil ippruvajt nifhem għaliex huwa impossibbli li tibgħat immedjatament il-biċċiet riċevuti lill-element tal-vidjo għall-plejbek, iżda rriżulta li dan ma jistax isir, ovvjament, l-ewwel trid tpoġġi l-biċċa f'buffer speċjali marbut ma ' l-element tal-vidjo, u mbagħad biss jibda jilgħab il-fluss tal-vidjo. Għal dan ser ikollok bżonn MediaSource API и FileReader API.

MediaSource jaġixxi bħala tip ta 'intermedjarju bejn l-oġġett tal-qari tal-midja u s-sors ta' dan il-fluss tal-midja. L-oġġett MediaSource fih buffer pluggable għas-sors tal-fluss tal-vidjo/awdjo. Karatteristika waħda hija li l-buffer jista 'jżomm biss data Uint8, għalhekk ser ikollok bżonn FileReader biex toħloq tali buffer. Ħares lejn il-kodiċi u jsir aktar ċar:

Jilgħab il-fluss tal-vidjo

<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>

Il-prototip tas-servizz ta' streaming huwa lest. L-iżvantaġġ ewlieni huwa li d-daqq tal-vidjo se jibqa 'wara n-naħa li tittrasmetti b'100 ms aħna nissettjaw dan aħna stess meta naqsmu l-fluss tal-vidjo qabel ma jittrasmettuh lis-server; Barra minn hekk, meta ċċekkjajt fuq il-laptop tiegħi, id-dewmien bejn in-naħat li jittrasmetti u li jirċievi gradwalment akkumula, dan kien viżibbli b'mod ċar. Bdejt infittex modi kif negħleb dan l-iżvantaġġ, u... sab RTCPeerConnection API, li jippermettilek tittrasmetti nixxiegħa tal-vidjo mingħajr tricks bħall-qsim tal-fluss f'biċċiet. Id-dewmien li jakkumula, naħseb, huwa dovut għall-fatt li l-browser jerġa 'kodifika kull biċċa fil-format webm qabel it-trażmissjoni. Ma ħafferx aktar, iżda bdejt nistudja WebRTC naħseb li nikteb artiklu separat dwar ir-riżultati tar-riċerka tiegħi jekk insibha interessanti għall-komunità.

Sors: www.habr.com