(लगभग) ब्राउजरबाट बेकार वेबक्याम स्ट्रिमिङ। मिडिया स्ट्रिम र वेबसकेटहरू

यस लेखमा म तेस्रो-पक्ष ब्राउजर प्लगइनहरू जस्तै Adobe Flash Player प्रयोग नगरी वेबसकेटहरू मार्फत भिडियो स्ट्रिम गर्ने मेरो प्रयासहरू साझा गर्न चाहन्छु। यो के आयो पत्ता लगाउन पढ्नुहोस्।

Adobe Flash, पहिले Macromedia Flash, वेब ब्राउजरमा चल्ने एपहरू सिर्जना गर्ने प्लेटफर्म हो। मिडिया स्ट्रिम API को परिचय अघि, यो व्यावहारिक रूपमा वेबक्यामबाट भिडियो र आवाज स्ट्रिमिङको लागि एकमात्र प्लेटफर्म थियो, साथै ब्राउजरमा विभिन्न प्रकारका सम्मेलनहरू र च्याटहरू सिर्जना गर्न। मिडिया जानकारी RTMP (रियल टाइम मेसेजिङ प्रोटोकल) लाई प्रसारण गर्ने प्रोटोकल वास्तवमा लामो समयको लागि बन्द थियो, जसको अर्थ: यदि तपाइँ आफ्नो स्ट्रिमिङ सेवालाई बढावा दिन चाहनुहुन्छ भने, Adobe आफैंबाट सफ्टवेयर प्रयोग गर्न पर्याप्त दयालु हुनुहोस् - Adobe Media Server (AMS)।

2012 मा केहि समय पछि, Adobe "छाड्यो र यसलाई थुक्यो" जनतामा। विनिर्देशन RTMP प्रोटोकल, जसमा त्रुटिहरू थिए र अनिवार्य रूपमा अपूर्ण थियो। त्यस समयमा, विकासकर्ताहरूले यस प्रोटोकलको आफ्नै कार्यान्वयन गर्न थाले, र Wowza सर्भर देखा पर्‍यो। 2011 मा, Adobe ले RTMP-सम्बन्धित पेटेन्टहरूको अवैध प्रयोगको लागि Wowza विरुद्ध मुद्दा दायर गर्‍यो; 4 वर्ष पछि, विवाद सौहार्दपूर्ण रूपमा समाधान भयो।

Adobe Flash प्लेटफर्म २० वर्ष भन्दा पुरानो हो, जुन समयमा धेरै महत्वपूर्ण कमजोरीहरू पत्ता लगाइएको छ, समर्थन प्रतिज्ञा गरेको स्ट्रिमिङ सेवाका लागि केही विकल्पहरू छोडेर, २०२० सम्ममा अन्त्य हुनेछ।

मेरो परियोजनाको लागि, मैले तुरुन्तै ब्राउजरमा फ्ल्यासको प्रयोगलाई पूर्ण रूपमा त्याग्न निर्णय गरें। मैले माथिको मुख्य कारण संकेत गरें; फ्ल्यास पनि मोबाइल प्लेटफर्महरूमा समर्थित छैन, र म वास्तवमै Windows (वाइन इमुलेटर) मा विकासको लागि Adobe Flash प्रयोग गर्न चाहन्न। त्यसैले मैले जाभास्क्रिप्टमा क्लाइन्ट लेख्न सेट गरें। यो केवल एक प्रोटोटाइप हुनेछ, किनकि पछि मैले स्ट्रिमिङ 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)
}

ग्राहक (स्ट्रिमिङ साइड) मा, तपाईंले पहिले क्यामेरा पहुँच गर्न आवश्यक छ। यो मार्फत गरिन्छ MediaStream API.

हामी क्यामेरा/माइक्रोफोन मार्फत पहुँच (अनुमति) प्राप्त गर्छौं मिडिया उपकरण API। यो एपीआई एक विधि प्रदान गर्दछ MediaDevices.getUserMedia(), जसले पपअप देखाउँछ। प्रयोगकर्तालाई क्यामेरा र/वा माइक्रोफोन पहुँच गर्न अनुमति माग्ने विन्डो। म नोट गर्न चाहन्छु कि मैले गुगल क्रोममा सबै प्रयोगहरू गरें, तर मलाई लाग्छ कि सबै कुरा फायरफक्समा उस्तै काम गर्नेछ।

अर्को, getUserMedia() ले एउटा वाचा फर्काउँछ, जसमा यसले मिडियास्ट्रिम वस्तु पास गर्छ - भिडियो-अडियो डेटाको स्ट्रिम। हामी यो वस्तुलाई भिडियो तत्वको 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>

लामो समयको लागि मैले बुझ्ने प्रयास गरें किन तुरुन्तै प्राप्त गरिएका टुक्राहरूलाई प्लेब्याकको लागि भिडियो तत्वमा पठाउन असम्भव छ, तर यो पत्ता लाग्यो कि यो गर्न सकिँदैन, निस्सन्देह, तपाईंले पहिले टुक्रालाई विशेष बफरमा राख्नुपर्छ। भिडियो तत्व, र त्यसपछि मात्र यो भिडियो स्ट्रिम प्ले सुरु हुनेछ। यसका लागि तपाईलाई आवश्यक पर्नेछ 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, जसले तपाईंलाई स्ट्रिमलाई टुक्रामा विभाजन गर्ने जस्ता चालहरू बिना भिडियो स्ट्रिम प्रसारण गर्न अनुमति दिन्छ। जम्मा हुने ढिलाइ, मलाई लाग्छ, ब्राउजरले प्रसारण गर्नु अघि प्रत्येक टुक्रालाई वेबएम ढाँचामा पुन: एन्कोड गर्ने तथ्यको कारणले हो। मैले थप खन्ने काम गरिन, तर WebRTC अध्ययन गर्न थालें। मलाई लाग्छ कि यदि मैले यो समुदायलाई रोचक लागेमा मेरो अनुसन्धानको नतिजाहरूको बारेमा छुट्टै लेख लेख्नेछु।

स्रोत: www.habr.com

एक टिप्पणी थप्न