(Дээрлик) браузерден агылып жаткан пайдасыз веб-камера. Media Stream жана Websockets

Бул макалада мен Adobe Flash Player сыяктуу үчүнчү тараптын браузеринин плагиндерин колдонбостон, веб-розеткалар аркылуу видеону агылтуу аракети менен бөлүшкүм келет. Андан эмне болгонун билүү үчүн окуңуз.

Adobe Flash, мурда Macromedia Flash, веб-браузерде иштеген тиркемелерди түзүү үчүн платформа. Media Stream API ишке киргенге чейин, ал веб-камерадан видео жана үн агымы үчүн, ошондой эле браузерде ар кандай конференцияларды жана чаттарды түзүү үчүн иш жүзүндө жалгыз платформа болгон. Медиа маалыматын берүү протоколу RTMP (Real Time Messaging Protocol) чындыгында узак убакытка жабылган, бул: эгерде сиз агымдык кызматыңызды өркүндөткүңүз келсе, Adobe'дун программалык камсыздоосун колдонуңуз - Adobe Media Server (AMS).

2012-жылы бир нече убакыт өткөндөн кийин, Adobe коомчулукка "багынып, түкүрдү". спецификация RTMP протоколу, анда каталар камтылган жана негизинен толук эмес болгон. Ошол убакта, иштеп чыгуучулар бул протоколду өз алдынча ишке ашыра башташты жана Wowza сервери пайда болду. 2011-жылы Adobe RTMP менен байланышкан патенттерди мыйзамсыз колдонгондугу үчүн Wowzaга каршы доо арыз менен кайрылган; 4 жылдан кийин чыр-чатактар ​​тынчтык жолу менен чечилген.

Adobe Flash платформасынын түзүлгөнүнө 20 жылдан ашты, бул убакыттын ичинде көптөгөн олуттуу кемчиликтер табылды, колдоо убада кылган 2020-жылга чейин бүтүп, стриминг кызматына бир нече альтернатива калтырат.

Долбоорум үчүн мен дароо браузерде Flash колдонуудан толугу менен баш тартууну чечтим. Мен жогоруда негизги себепти айттым; Flash да мобилдик платформаларда такыр колдоого алынбайт жана мен чындыгында Windows'до (шарап эмулятору) иштеп чыгуу үчүн Adobe Flashти жайылтууну каалаган эмесмин. Ошентип, мен JavaScript-те кардар жазууну чечтим. Бул жөн гана прототиби болот, анткени кийинчерээк мен стримингди p2p негизинде алда канча эффективдүү жүргүзүүгө болорун билдим, мен үчүн гана ал тең - сервер - теңдештер болот, бирок дагы башка жолу, анткени ал азырынча даяр эмес.

Баштоо үчүн, бизге чыныгы websockets сервери керек. Мен melody go пакетинин негизинде эң жөнөкөйсүн жасадым:

Сервер коду

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

кардар (агым тарабында), сиз биринчи камерага кирүү керек. Бул аркылуу ишке ашырылат MediaStream API.

Биз аркылуу камерага/микрофонго кирүү (уруксат) алабыз Media Devices API. Бул API ыкмасын камсыз кылат MediaDevices.getUserMedia(), калкыма терезени көрсөтөт. колдонуучудан камерага жана/же микрофонго кирүүгө уруксат сураган терезе. Мен бардык эксперименттерди Google Chrome'до өткөргөнүмдү белгилегим келет, бирок Firefoxто баары бирдей иштейт деп ойлойм.

Андан кийин, getUserMedia() Promise кайтарат, ага MediaStream объектисин - видео-аудио маалыматтарынын агымын өткөрөт. Бул объектти видео элементтин src касиетине ыйгарабыз. Код:

Берүү жагы

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

Видео агымын розеткалар аркылуу берүү үчүн, аны кайсы бир жерде коддоп, буферлеп, бөлүктөргө бөлүп берүү керек. Чийки видео агымын веб-розеткалар аркылуу өткөрүү мүмкүн эмес. Бул жерде биздин жардамыбыз келет MediaRecorder API. Бул API агымды коддоо жана бөлүктөргө бөлүүгө мүмкүндүк берет. Тармак аркылуу азыраак байт жөнөтүү үчүн видео агымын кысуу үчүн коддоону жасайм. Аны бөлүктөргө бөлүп, ар бир бөлүгүн веб-розеткага жөнөтө аласыз. Код:

Биз видео агымын коддойбуз, аны бөлүктөргө бөлөбүз

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

Эми веб-розеткалар аркылуу берүүнү кошолу. Таң калыштуусу, бул үчүн сизге объект керек WebSockets. Анын жөнөтүү жана жабуу эки гана ыкмасы бар. ысымдар өздөрү үчүн сүйлөйт. Кошулган код:

Биз видео агымын серверге өткөрүп беребиз

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

Эфир тарап даяр! Эми видео агымын кабыл алып, аны кардарга көрсөтүүгө аракет кылалы. Бул үчүн бизге эмне керек? Биринчиден, албетте, розетка байланышы. Биз WebSocket объектисине "угуучуну" тиркеп, "билдирүү" окуясына жазылабыз. Бинардык маалыматтардын бир бөлүгүн алгандан кийин, биздин сервер аны абоненттерге, башкача айтканда, кардарларга таркатат. Бул учурда, "билдирүү" окуясынын "угуучусу" менен байланышкан кайра чалуу функциясы кардарда ишке киргизилет, объекттин өзү функциянын аргументине өтөт - vp8 менен коддолгон видео агымдын бөлүгү.

Биз видео агымды кабыл алабыз

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

Мен көп убакыт бою кабыл алынган бөлүктөрдү эмне үчүн видео элементине кайра ойнотуу үчүн дароо жөнөтүү мүмкүн эмес экенин түшүнүүгө аракет кылдым, бирок муну жасоо мүмкүн эмес экени белгилүү болду, албетте, алгач кесимди атайын буферге байлап коюу керек. видео элементин жана андан кийин гана ал видео агымын ойното баштайт. Бул үчүн сизге керек болот MediaSource API и FileReader API.

MediaSource медиа ойнотуу объекти менен бул медиа агымынын булагынын ортосунда кандайдыр бир ортомчу катары иштейт. MediaSource объекти видео/аудио агымынын булагы үчүн кошулуучу буферди камтыйт. Бир өзгөчөлүк - буфер Uint8 маалыматтарын гана кармай алат, андыктан мындай буферди түзүү үчүн сизге FileReader керек болот. Кодду караңыз жана ал айкыныраак болот:

Видео агым ойнотулууда

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

Стриминг кызматынын прототиби даяр. Негизги жетишпеген жагы - видеону ойнотуу өткөрүп жаткан тараптан 100 мс артта калат; биз муну серверге өткөрүүдөн мурун видео агымын бөлгөндө өзүбүз орнотобуз. Анын үстүнө, мен ноутбукту текшергенде, берүүчү жана кабыл алуучу тараптардын ортосундагы артта калуу акырындык менен топтолуп, бул ачык көрүнүп турду. Мен бул кемчиликтен чыгуунун жолдорун издей баштадым жана... туш келдим RTCPeerConnection API, бул агымды бөлүктөргө бөлүү сыяктуу амалдарсыз видео агымын өткөрүүгө мүмкүндүк берет. Топтолгон артта калуу, менимче, браузер берүү алдында ар бир бөлүгүн webm форматына кайра коддогондугу менен байланыштуу. Мен мындан ары казган жокмун, бирок WebRTCди изилдей баштадым.Изилдөөмдүн натыйжалары коомчулукка кызыктуу болсо, өзүнчө макала жазам деп ойлойм.

Source: www.habr.com

Комментарий кошуу