Transmetimi (Pothuajse) i padobishëm i kamerës së internetit nga një shfletues. Rrjedha e medias dhe bazat e internetit

Në këtë artikull dua të ndaj përpjekjet e mia për të transmetuar video përmes prizave të internetit pa përdorur shtojca të shfletuesit të palëve të treta si Adobe Flash Player. Lexoni më tej për të zbuluar se çfarë erdhi prej saj.

Adobe Flash, më parë Macromedia Flash, është një platformë për krijimin e aplikacioneve që funksionojnë në një shfletues ueb. Para prezantimit të Media Stream API, ajo ishte praktikisht e vetmja platformë për transmetimin e videos dhe zërit nga një webcam, si dhe për krijimin e llojeve të ndryshme të konferencave dhe bisedave në shfletues. Protokolli për transmetimin e informacionit të medias RTMP (Protokolli i mesazheve në kohë reale) në fakt ishte i mbyllur për një kohë të gjatë, që do të thoshte: nëse doni të rritni shërbimin tuaj të transmetimit, jini mjaft të sjellshëm për të përdorur softuerin nga vetë Adobe - Adobe Media Server (AMS).

Pas ca kohësh në vitin 2012, Adobe "u dorëzua dhe ia pështyu" publikut. Specifikim Protokolli RTMP, i cili përmbante gabime dhe në thelb ishte i paplotë. Në atë kohë, zhvilluesit filluan të bëjnë implementimet e tyre të këtij protokolli dhe u shfaq serveri Wowza. Në vitin 2011, Adobe ngriti një padi kundër Wowza për përdorim të paligjshëm të patentave të lidhura me RTMP; pas 4 vjetësh, konflikti u zgjidh në mënyrë miqësore.

Platforma Adobe Flash është më shumë se 20 vjeç, gjatë së cilës kohë janë zbuluar shumë dobësi kritike, mbështetje premtuar të përfundojë deri në vitin 2020, duke lënë pak alternativa për shërbimin e transmetimit.

Për projektin tim, menjëherë vendosa të braktis plotësisht përdorimin e Flash në shfletues. Unë tregova arsyen kryesore më lart; Flash gjithashtu nuk mbështetet fare në platformat celulare dhe me të vërtetë nuk doja të vendosja Adobe Flash për zhvillim në Windows (emulator i verës). Kështu që vendosa të shkruaj një klient në JavaScript. Ky do të jetë vetëm një prototip, pasi më vonë mësova se transmetimi mund të bëhet shumë më me efikasitet bazuar në p2p, vetëm për mua do të jetë peer - server - peers, por më shumë për këtë një herë tjetër, sepse nuk është ende gati.

Për të filluar, na duhet serveri aktual i websockets. Unë bëra më të thjeshtën bazuar në paketën melody go:

Kodi i serverit

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

Në klientin (ana e transmetimit), së pari duhet të aksesoni kamerën. Kjo bëhet përmes MediaStream API.

Ne fitojmë akses (leje) në kamera/mikrofon përmes API-ja e pajisjeve mediatike. Ky API ofron një metodë MediaDevices.getUserMedia(), e cila tregon popup. një dritare që i kërkon përdoruesit leje për të hyrë në kamerën dhe/ose mikrofonin. Dua të theksoj se i kam kryer të gjitha eksperimentet në Google Chrome, por mendoj se gjithçka do të funksionojë njësoj në Firefox.

Më pas, getUserMedia() kthen një Premtim, të cilit i kalon një objekt MediaStream - një rrjedhë e të dhënave video-audio. Këtë objekt ia caktojmë vetisë src të elementit video. Kodi:

Ana transmetuese

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

Për të transmetuar një transmetim video përmes prizave, duhet ta kodoni atë diku, ta ruani dhe ta transmetoni në pjesë. Transmetimi i videos së papërpunuar nuk mund të transmetohet nëpërmjet prizave në internet. Këtu na vjen në ndihmë MediaRecorder API. Ky API ju lejon të kodoni dhe copëtoni transmetimin në copa. Bëj kodim për të kompresuar transmetimin e videos në mënyrë që të dërgoj më pak bajt në rrjet. Pasi ta keni copëtuar atë në copa, mund ta dërgoni secilën pjesë në një prizë ueb. Kodi:

Ne kodojmë transmetimin e videos, e ndajmë atë në copa

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

Tani le të shtojmë transmetimin nëpërmjet prizave në internet. Çuditërisht, gjithçka që ju nevojitet për këtë është një objekt Fole në Web. Ka vetëm dy mënyra për të dërguar dhe mbyllur. Emrat flasin vetë. Kodi i shtuar:

Ne e transmetojmë transmetimin e videos në 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>

Ana e transmetimit është gati! Tani le të përpiqemi të marrim një transmetim video dhe ta shfaqim atë te klienti. Çfarë na duhet për këtë? Së pari, natyrisht, lidhja e prizës. Ne bashkojmë një "dëgjues" në objektin WebSocket dhe abonohemi në ngjarjen "mesazh". Pasi të ketë marrë një pjesë të të dhënave binare, serveri ynë ua transmeton atë abonentëve, domethënë klientëve. Në këtë rast, funksioni i kthimit të thirrjes i lidhur me "dëgjuesin" e ngjarjes "mesazh" aktivizohet tek klienti; vetë objekti kalohet në argumentin e funksionit - një pjesë e transmetimit video të koduar nga vp8.

Ne pranojmë transmetimin e videos

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

Për një kohë të gjatë u përpoqa të kuptoj pse është e pamundur të dërgosh menjëherë pjesët e marra në elementin e videos për riprodhim, por doli që kjo nuk mund të bëhet, natyrisht, së pari duhet ta vendosni pjesën në një tampon të veçantë të lidhur me elementin e videos dhe vetëm atëherë do të fillojë të luajë transmetimin e videos. Për këtë do t'ju duhet MediaSource API и FileReader API.

MediaSource vepron si një lloj ndërmjetësi midis objektit të riprodhimit të medias dhe burimit të këtij transmetimi mediatik. Objekti MediaSource përmban një buffer që mund të lidhet për burimin e transmetimit video/audio. Një veçori është se buferi mund të mbajë vetëm të dhëna Uint8, kështu që do t'ju duhet një FileReader për të krijuar një buffer të tillë. Shikoni kodin dhe do të bëhet më e qartë:

Po luhet transmetimi i videos

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

Prototipi i shërbimit të transmetimit është gati. Disavantazhi kryesor është se riprodhimi i videos do të mbetet prapa anës së transmetimit me 100 ms; ne e vendosim këtë vetë kur ndajmë transmetimin e videos përpara se ta transmetojmë atë në server. Për më tepër, kur kontrollova laptopin tim, vonesa midis anëve transmetuese dhe marrëse u grumbullua gradualisht, kjo ishte qartë e dukshme. Fillova të kërkoja mënyra për ta kapërcyer këtë disavantazh dhe... hasa RTCPeerConnection API, i cili ju lejon të transmetoni një transmetim video pa truke të tilla si ndarja e transmetimit në copa. Vonesa e akumuluar, mendoj, është për shkak të faktit se shfletuesi ri-kodon çdo pjesë në formatin webm përpara transmetimit. Nuk gërmova më tej, por fillova të studioja WebRTC. Mendoj se do të shkruaj një artikull të veçantë për rezultatet e kërkimit tim nëse më duket interesant për komunitetin.

Burimi: www.habr.com

Shto një koment