(Қариб) веб-камера ҷараёнҳои бефоида аз браузер. Media Stream ва Websockets

Дар ин мақола ман мехоҳам кӯшишҳои худро барои ҷараён додани видео тавассути вебсокетҳо бидуни истифодаи плагинҳои браузери тарафи сеюм ба монанди Adobe Flash Player мубодила кунам. Бихонед, то бифаҳмед, ки аз он чӣ рӯй дод.

Adobe Flash, қаблан Macromedia Flash, платформаест барои эҷоди барномаҳое, ки дар браузери веб кор мекунанд. Пеш аз ҷорӣ намудани Media Stream API, он амалан ягона платформаи ҷараён додани видео ва овоз аз веб-камера, инчунин барои эҷоди намудҳои гуногуни конфронсҳо ва чатҳо дар браузер буд. Протоколи интиқоли иттилооти медиавӣ RTMP (Protocol Messaging Real Time) барои муддати тӯлонӣ баста буд, ки ин маънои онро дошт: агар шумо хоҳед, ки хидмати ҷараёнро афзоиш диҳед, барои истифодаи нармафзори худи Adobe - Adobe Media Server (AMS) меҳрубон бошед.

Пас аз чанде дар соли 2012, Adobe "таслим шуд ва онро ба мардум туф кард". мушаххасот Протоколи RTMP, ки дорои хатогиҳо буд ва аслан нопурра буд. То он вақт, таҳиягарон ба амалисозии худ аз ин протокол шурӯъ карданд ва сервери Wowza пайдо шуд. Дар соли 2011, Adobe бар зидди Wowza барои истифодаи ғайриқонунии патентҳои марбут ба RTMP парванда боз кард; пас аз 4 сол, низоъ бо роҳи мусолиҳа ҳал карда шуд.

Платформаи Adobe Flash зиёда аз 20 сол дорад, ки дар ин муддат бисёр осебпазириҳои муҳим ошкор карда шуданд, дастгирӣ ваъда дод то соли 2020 ба итмом мерасад ва барои хидмати ҷараён алтернативаҳои каме боқӣ мемонад.

Барои лоиҳаи худ ман фавран тасмим гирифтам, ки истифодаи Flash-ро дар браузер комилан тарк кунам. Ман сабаби асосиро дар боло нишон додам; Flash инчунин дар платформаҳои мобилӣ тамоман дастгирӣ намешавад ва ман аслан намехостам Adobe Flash-ро барои рушд дар Windows (эмулятори шароб) ҷойгир кунам. Ҳамин тавр, ман тасмим гирифтам, ки дар 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)
}

Дар муштарӣ (тарафи ҷараён), шумо аввал бояд ба камера дастрасӣ пайдо кунед. Ин ба воситаи API MediaStream.

Мо тавассути он ба камера/микрофон дастрасӣ (иҷоза) мегирем API-и дастгоҳҳои медиавӣ. Ин API усулеро пешниҳод мекунад MediaDevices.getUserMedia(), ки поп-апро нишон медиҳад. тирезае, ки аз корбар барои дастрасӣ ба камера ва/ё микрофон иҷозат мепурсад. Ман мехоҳам қайд намоям, ки ман ҳама таҷрибаҳоро дар Google Chrome гузаронидаам, аммо ман фикр мекунам, ки ҳама чиз дар Firefox тақрибан якхела мешавад.

Баъдан, getUserMedia() ваъдаеро бармегардонад, ки ба он объекти 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>

Барои пахши ҷараёни видео тавассути розеткаҳо, шумо бояд онро дар ҷое рамзгузорӣ кунед, буфер кунед ва онро қисм-қисм интиқол диҳед. Ҷараёни видеои хом тавассути вебсокетҳо интиқол дода намешавад. Дар ин чо ба ёрии мо меояд API MediaRecorder. Ин 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>

Дар муддати тӯлонӣ ман кӯшиш кардам, ки бифаҳмам, ки чаро пораҳои гирифташударо фавран ба унсури видео барои бозӣ фиристодан ғайриимкон аст, аммо маълум шуд, ки ин корро кардан ғайриимкон аст, албатта, шумо бояд аввал порчаро дар буфери махсус ҷойгир кунед. унсури видео, ва танҳо он гоҳ он ба навозиш ҷараёни видео оғоз мекунад. Барои ин ба шумо лозим меояд API MediaSource и API FileReader.

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 мс ақиб мемонад; мо инро худамон ҳангоми тақсим кардани ҷараёни видео пеш аз интиқол ба сервер муқаррар мекунем. Гузашта аз ин, вақте ки ман ноутбуки худро тафтиш кардам, оҳиста-оҳиста байни паҳлӯҳои интиқол ва қабулкунанда ҷамъ мешуд, ин ба таври равшан намоён буд. Ман чустучуи роххои рафъи ин камбудй шудам ва... дучор омадам API RTCPeerConnection, ки ба шумо имкон медиҳад, ки ҷараёни видеоро бидуни ҳилаҳо ба монанди тақсим кардани ҷараён ба қисмҳо интиқол диҳед. Ман фикр мекунам, ки таъхири ҷамъшуда аз он иборат аст, ки браузер ҳар як порчаро пеш аз интиқол ба формати webm дубора рамзгузорӣ мекунад. Ман дигар кофта нашудаам, балки ба омӯзиши WebRTC шурӯъ кардам.Фикр мекунам, ки агар барои ҷомеа ҷолиб бошад, дар бораи натиҷаҳои тадқиқоти худ мақолаи алоҳида менависам.

Манбаъ: will.com

Илова Эзоҳ