(පාහේ) බ්‍රවුසරයකින් නිෂ්ඵල වෙබ් කැමරාවක් ප්‍රවාහ කිරීම. මාධ්‍ය ප්‍රවාහය සහ වෙබ්සොකට්

මෙම ලිපියෙන් මට 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 වන විට අවසන් කිරීමට.

මගේ ව්‍යාපෘතිය සඳහා, බ්‍රවුසරයේ ෆ්ලෑෂ් භාවිතය සම්පූර්ණයෙන්ම අත්හැරීමට මම වහාම තීරණය කළෙමි. මම ඉහත ප්‍රධාන හේතුව සඳහන් කළෙමි; ජංගම වේදිකාවල ෆ්ලෑෂ් කිසිසේත්ම සහාය නොදක්වන අතර, වින්ඩෝස් (වයින් ඉමුලේටරය) මත සංවර්ධනය සඳහා ඇඩෝබි ෆ්ලෑෂ් යෙදවීමට මට අවශ්‍ය නොවීය. ඉතින් මම ජාවාස්ක්‍රිප්ට් වලින් ක්ලයන්ට් එකක් ලියන්න හිතුවා. මෙය මූලාකෘතියක් පමණක් වනු ඇත, පසුව මම p2p මත පදනම්ව ප්‍රවාහය වඩාත් කාර්යක්ෂමව කළ හැකි බව දැනගත් බැවින්, මට පමණක් එය peer - server - peers වනු ඇත, නමුත් එය තවම සූදානම් නැති නිසා තවත් අවස්ථාවක.

ආරම්භ කිරීමට, අපට සත්‍ය 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.

අපි කැමරාවට/මයික්‍රොෆෝනයට ප්‍රවේශය (අවසර) ලබා ගනිමු මාධ්‍ය උපාංග API. මෙම API ක්‍රමයක් සපයයි MediaDevices.getUserMedia(), popup පෙන්වන. කැමරාව සහ/හෝ මයික්‍රෆෝනය වෙත ප්‍රවේශ වීමට පරිශීලකයාගෙන් අවසර ඉල්ලා සිටින කවුළුවක්. මම ගූගල් ක්‍රෝම් හි සියලුම අත්හදා බැලීම් සිදු කළ බව සටහන් කිරීමට කැමැත්තෙමි, නමුත් ෆයර්ෆොක්ස් හි සෑම දෙයක්ම එලෙසම ක්‍රියාත්මක වනු ඇතැයි මම සිතමි.

මීළඟට, 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>

සොකට් හරහා වීඩියෝ ප්‍රවාහයක් විකාශනය කිරීම සඳහා, ඔබ එය කොතැනක හෝ කේතනය කිරීම, එය බෆරය කිරීම සහ කොටස් වශයෙන් සම්ප්‍රේෂණය කිරීම අවශ්‍ය වේ. අමු වීඩියෝ ප්‍රවාහය වෙබ් සොකට් හරහා සම්ප්‍රේෂණය කළ නොහැක. මේක තමයි අපේ පිහිටට එන්නේ 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 ms කින් පසුගාමී වීමයි; එය සේවාදායකයට සම්ප්‍රේෂණය කිරීමට පෙර වීඩියෝ ප්‍රවාහය බෙදීමේදී අපි මෙය සකසමු. එපමණක්ද නොව, මම මගේ ලැප්ටොප් පරිගණකය පරීක්ෂා කළ විට, සම්ප්රේෂණය සහ ලැබීමේ පැති අතර ප්රමාදය ක්රමයෙන් සමුච්චය විය, මෙය පැහැදිලිව දැකගත හැකි විය. මම මේ අවාසිය මඟහරවා ගැනීමට ක්‍රම සොයන්නට පටන් ගත් අතර ... හමු විය RTCPeerConnection API, ප්‍රවාහය කැබලිවලට බෙදීම වැනි උපක්‍රමවලින් තොරව වීඩියෝ ප්‍රවාහයක් සම්ප්‍රේෂණය කිරීමට ඔබට ඉඩ සලසයි. සමුච්චිත ප්‍රමාදය, මම හිතන්නේ, බ්‍රවුසරය සම්ප්‍රේෂණයට පෙර එක් එක් කොටස webm ආකෘතියට නැවත කේතනය කිරීමයි. මම තවදුරටත් හාරන්නේ නැත, නමුත් WebRTC අධ්‍යයනය කිරීමට පටන් ගතිමි, මම මගේ පර්යේෂණයේ ප්‍රතිඵල ප්‍රජාවට සිත්ගන්නාසුළු නම් ඒ ගැන වෙනම ලිපියක් ලියන්නට සිතමි.

මූලාශ්රය: www.habr.com

අදහස් එක් කරන්න