براؤزر سے (تقریباً) بیکار ویب کیم سٹریمنگ۔ میڈیا اسٹریم اور ویب ساکٹ

اس آرٹیکل میں میں ویب ساکٹ کے ذریعے ویڈیو کو اسٹریم کرنے کی اپنی کوششوں کو شیئر کرنا چاہتا ہوں بغیر تھرڈ پارٹی براؤزر پلگ ان جیسے کہ ایڈوب فلیش پلیئر۔ اس سے کیا نکلا یہ جاننے کے لیے پڑھیں۔

ایڈوب فلیش، پہلے میکرومیڈیا فلیش، ویب براؤزر میں چلنے والی ایپلی کیشنز بنانے کا ایک پلیٹ فارم ہے۔ میڈیا اسٹریم API کے متعارف ہونے سے پہلے، یہ عملی طور پر ویب کیم سے ویڈیو اور آواز کو اسٹریم کرنے کے ساتھ ساتھ براؤزر میں مختلف قسم کی کانفرنسیں اور چیٹس بنانے کا واحد پلیٹ فارم تھا۔ میڈیا کی معلومات کی ترسیل کے لیے پروٹوکول RTMP (Real Time Messaging Protocol) دراصل ایک طویل عرصے کے لیے بند تھا، جس کا مطلب تھا: اگر آپ اپنی اسٹریمنگ سروس کو بڑھانا چاہتے ہیں، تو خود ایڈوب سے سافٹ ویئر استعمال کرنے کے لیے مہربان رہیں - Adobe Media Server (AMS)۔

2012 میں کچھ عرصے کے بعد، Adobe نے عوام کے سامنے "ہت ہار دی اور اسے تھوک دیا۔" تفصیلات RTMP پروٹوکول، جس میں غلطیاں تھیں اور بنیادی طور پر نامکمل تھا۔ اس وقت تک، ڈویلپرز نے اس پروٹوکول کو خود نافذ کرنا شروع کر دیا، اور Wowza سرور ظاہر ہوا۔ 2011 میں، Adobe نے Wowza کے خلاف RTMP سے متعلقہ پیٹنٹ کے غیر قانونی استعمال پر مقدمہ دائر کیا؛ 4 سال کے بعد، تنازعہ کو خوش اسلوبی سے حل کر لیا گیا۔

Adobe Flash پلیٹ فارم 20 سال سے زیادہ پرانا ہے، اس دوران کئی اہم کمزوریاں دریافت ہوئیں، سپورٹ وعدہ 2020 تک ختم ہونے کے لیے، سٹریمنگ سروس کے لیے کچھ متبادل چھوڑ کر۔

اپنے پروجیکٹ کے لیے، میں نے فوری طور پر براؤزر میں فلیش کے استعمال کو مکمل طور پر ترک کرنے کا فیصلہ کیا۔ میں نے اوپر کی بنیادی وجہ کی نشاندہی کی؛ موبائل پلیٹ فارمز پر فلیش بھی بالکل بھی تعاون یافتہ نہیں ہے، اور میں واقعی میں ونڈوز (وائن ایمولیٹر) پر ترقی کے لیے ایڈوب فلیش کو تعینات نہیں کرنا چاہتا تھا۔ تو میں جاوا اسکرپٹ میں کلائنٹ لکھنے کے لیے نکلا۔ یہ صرف ایک پروٹو ٹائپ ہو گا، کیونکہ بعد میں میں نے سیکھا کہ p2p کی بنیاد پر سٹریمنگ بہت زیادہ مؤثر طریقے سے کی جا سکتی ہے، صرف میرے لیے یہ ہم مرتبہ - سرور - ہم عمر ہوں گے، لیکن اس کے بارے میں کسی اور وقت، کیونکہ یہ ابھی تک تیار نہیں ہے۔

شروع کرنے کے لیے، ہمیں اصل ویب ساکٹس سرور کی ضرورت ہے۔ میں نے میلوڈی گو پیکج کی بنیاد پر سب سے آسان بنایا:

سرور کوڈ

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.

ہم کیمرے/مائیکروفون کے ذریعے رسائی (اجازت) حاصل کرتے ہیں۔ میڈیا ڈیوائسز API. یہ API ایک طریقہ فراہم کرتا ہے۔ MediaDevices.getUserMedia()، جو پاپ اپ دکھاتا ہے۔ ایک ونڈو جو صارف سے کیمرے اور/یا مائیکروفون تک رسائی کی اجازت مانگتی ہے۔ میں نوٹ کرنا چاہوں گا کہ میں نے گوگل کروم میں تمام تجربات کیے ہیں، لیکن مجھے لگتا ہے کہ فائر فاکس میں سب کچھ اسی طرح کام کرے گا۔

اگلا، 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>

اب ویب ساکٹس کے ذریعے ٹرانسمیشن شامل کریں۔ حیرت کی بات یہ ہے کہ اس کے لیے آپ کو صرف ایک چیز کی ضرورت ہے۔ ویب ساکٹ. اس کے بھیجنے اور بند کرنے کے صرف دو طریقے ہیں۔ نام خود بولتے ہیں۔ شامل کردہ کوڈ:

ہم ویڈیو اسٹریم کو سرور پر منتقل کرتے ہیں۔

<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 и فائل ریڈر API.

میڈیا سورس میڈیا پلے بیک آبجیکٹ اور اس میڈیا اسٹریم کے ماخذ کے درمیان ایک قسم کے بیچوان کے طور پر کام کرتا ہے۔ میڈیا سورس آبجیکٹ میں ویڈیو/آڈیو اسٹریم کے ماخذ کے لیے پلگ ایبل بفر ہوتا ہے۔ ایک خصوصیت یہ ہے کہ بفر صرف 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، جو آپ کو بغیر کسی چال کے ویڈیو اسٹریم منتقل کرنے کی اجازت دیتا ہے جیسے کہ اسٹریم کو ٹکڑوں میں تقسیم کرنا۔ میرے خیال میں جمع ہونے والا وقفہ اس حقیقت کی وجہ سے ہے کہ براؤزر ہر ٹکڑے کو ٹرانسمیشن سے پہلے ویب ایم فارمیٹ میں دوبارہ انکوڈ کرتا ہے۔ میں نے مزید کھدائی نہیں کی، لیکن WebRTC کا مطالعہ شروع کیا۔ مجھے لگتا ہے کہ اگر مجھے کمیونٹی کے لیے یہ دلچسپ معلوم ہوا تو میں اپنی تحقیق کے نتائج کے بارے میں ایک الگ مضمون لکھوں گا۔

ماخذ: www.habr.com

نیا تبصرہ شامل کریں