(Ampir) gunana webcam streaming ti browser. Media Stream sarta Websockets

Dina artikel ieu abdi hoyong bagikeun usaha kuring ngalirkeun data video via websockets tanpa ngagunakeun plugins browser pihak katilu kayaning Adobe Flash Player. Baca on pikeun manggihan naon datang ti eta.

Adobe Flash, baheulana Macromedia Flash, nyaéta platform pikeun nyieun aplikasi anu dijalankeun dina browser wéb. Sateuacan ngenalkeun Media Stream API, éta mangrupikeun hiji-hijina platform pikeun ngalirkeun pidéo sareng sora tina webcam, ogé pikeun nyiptakeun rupa-rupa konperénsi sareng obrolan dina browser. Protokol pikeun ngirimkeun inpormasi média RTMP (Real Time Messaging Protocol) saleresna ditutup kanggo waktos anu lami, anu hartosna: upami anjeun hoyong ningkatkeun jasa streaming anjeun, janten bageur nganggo parangkat lunak ti Adobe sorangan - Adobe Media Server (AMS).

Saatos sababaraha waktos di 2012, Adobe "pasrah sareng nyiduh" ka masarakat. spésifikasi Protokol RTMP, anu ngandung kasalahan sareng dasarna henteu lengkep. Ku waktos éta, pamekar mimiti ngadamel palaksanaan sorangan tina protokol ieu, sareng server Wowza muncul. Dina 2011, Adobe ngajukeun gugatan ka Wowza pikeun panggunaan ilegal patén-patén anu aya hubunganana sareng RTMP; saatos 4 taun, konflik éta direngsekeun sacara damai.

Platform Adobe Flash umurna langkung ti 20 taun, salami waktos seueur kerentanan kritis parantos kapanggih, ngadukung jangji mungkas taun 2020, nyésakeun sababaraha alternatif pikeun layanan streaming.

Pikeun proyék kuring, kuring langsung mutuskeun pikeun ngantunkeun panggunaan Flash dina browser. Kuring nunjukkeun alesan utama di luhur; Flash ogé henteu didukung pisan dina platform mobile, sareng kuring leres-leres henteu hoyong nyebarkeun Adobe Flash pikeun pangwangunan dina Windows (émulator anggur). Ku kituna kuring diatur kaluar nulis klien dina JavaScript. Ieu ngan ukur prototipe, sabab engké kuring diajar yén streaming tiasa dilakukeun langkung éfisién dumasar kana p2p, ngan pikeun kuring éta bakal janten peer - server - peers, tapi langkung seueur ngeunaan waktos sanés, sabab éta henteu acan siap.

Pikeun ngamimitian, urang peryogi server websockets anu saleresna. Kuring ngadamel anu pangbasajanna dumasar kana pakét melodi go:

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

Dina klien (sisi streaming), Anjeun mimitina kudu ngakses kaméra. Hal ieu dilakukeun ngaliwatan API MediaStream.

Urang meunang aksés (idin) kana kaméra / mikropon ngaliwatan API Alat Média. API Ieu nyadiakeun metoda MediaDevices.getUserMedia(), nu nembongkeun popup. jandela nanya ka pamaké pikeun idin pikeun ngakses kaméra jeung/atawa mikropon. Abdi hoyong dicatet yén kuring dilumangsungkeun sagala percobaan dina Google Chrome, tapi kuring pikir sagalana bakal dianggo ngeunaan sarua dina Firefox.

Salajengna, getUserMedia () ngabalikeun Janji, anu ngalangkungan objek MediaStream - aliran data video-audio. Urang napelkeun obyék ieu sipat src sahiji elemen video. Kodeu:

Sisi siaran

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

Pikeun nyiarkeun aliran video ngaliwatan stop kontak, Anjeun kudu encode eta wae, buffer eta, sarta ngirimkeunana dina bagian. Stream video atah teu bisa dikirimkeun via websockets. Ieu dimana datang ka bantuan urang API MediaRecorder. API ieu ngidinan Anjeun pikeun encode sarta megatkeun stream kana lembar. Kuring ngalakukeun encoding pikeun niiskeun aliran pidéo pikeun ngirimkeun langkung seueur bait dina jaringan. Saanggeus pegat kana lembar, Anjeun bisa ngirim unggal sapotong kana websocket a. Kodeu:

Urang encode aliran video, megatkeun kana sababaraha bagian

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

Ayeuna hayu urang tambahkeun transmisi via websockets. Ahéng, sadaya anu anjeun peryogikeun pikeun ieu mangrupikeun obyék WébSocket. Cai mibanda ngan dua métode ngirim jeung nutup. Ngaran-ngaran nyarita sorangan. Kode ditambahkeun:

Urang ngirimkeun aliran video ka 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>

Sisi siaran parantos siap! Ayeuna hayu urang cobian nampi aliran pidéo sareng nampilkeunana dina klien. Naon anu urang peryogikeun pikeun ieu? Firstly, tangtosna, sambungan stop kontak. Urang ngagantelkeun "pangdéngé" kana objék WebSocket tur ngalanggan acara 'pesen'. Saatos nampi sapotong data binér, server kami nyiarkeun ka palanggan, nyaéta, klien. Dina hal ieu, fungsi callback pakait sareng "pangdéngé" tina acara 'pesen' dipicu dina klien; obyék sorangan dialirkeun kana argumen fungsi - sapotong aliran video disandikeun ku vp8.

Kami nampi aliran video

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

Pikeun lila kuring nyobian ngartos naha mustahil pikeun ngirim potongan anu ditampi kana unsur pidéo pikeun maén deui, tapi tétéla yén ieu teu tiasa dilakukeun, tangtosna, anjeun kedah mimiti nempatkeun potongan éta dina panyangga khusus anu kabeungkeut kana. unsur video, sarta ngan lajeng bakal mimiti muterkeun aliran video. Pikeun ieu anjeun peryogi MediaSource API и FileReader API.

MediaSource tindakan minangka jenis perantara antara objék playback média jeung sumber aliran média ieu. Obyék MediaSource ngandung panyangga pluggable pikeun sumber aliran video/audio. Hiji fitur nyaéta panyangga ngan ukur tiasa nahan data Uint8, janten anjeun peryogi FileReader pikeun nyiptakeun panyangga sapertos kitu. Tingali kodeu sareng éta bakal langkung jelas:

Muterkeun aliran video

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

Prototipe jasa streaming parantos siap. Karugian utama nyaéta playback pidéo bakal katinggaleun sisi pangiriman ku 100 mdet; urang nyetél ieu nyalira nalika ngabagi aliran pidéo sateuacan ngirimkeunana ka server. Sumawona, nalika kuring pariksa dina laptop kuring, lag antara sisi ngirimkeun sareng nampi sacara bertahap akumulasi, ieu jelas katingali. Kuring mimiti néangan cara pikeun nungkulan disadvantage ieu, sarta ... datang di sakuliah RTCPeerConnection API, nu ngidinan Anjeun pikeun ngirimkeun aliran video tanpa trik kayaning bengkahna stream kana potongan. The accumulating lag, Jigana, alatan kanyataan yén browser ulang encodes unggal sapotong kana format webm saméméh transmisi. Kuring henteu ngagali deui, tapi mimiti diajar WebRTC. Jigana bakal nulis artikel misah ngeunaan hasil panalungtikan kuring lamun kuring manggihan eta metot pikeun masarakat.

sumber: www.habr.com

Tambahkeun komentar