Node.js के साथ एक html5 वीडियो प्लेयर में वीडियो फ़ाइल को स्ट्रीम करना ताकि वीडियो नियंत्रण काम करना जारी रखे?


96

टीएल; डॉ - प्रश्न:

Node.js के साथ एक वीडियो फ़ाइल को html5 वीडियो प्लेयर में स्ट्रीमिंग करने का सही तरीका क्या है ताकि वीडियो नियंत्रण काम करना जारी रखे?

मुझे लगता है कि हेडर को जिस तरह से हैंडल किया जाता है, उसके साथ ऐसा करना पड़ता है। वैसे भी, यहाँ पृष्ठभूमि की जानकारी है। कोड थोड़ा लंबा है, हालांकि, यह बहुत सीधा है।

नोड के साथ एचटीएमएल 5 वीडियो के लिए छोटी वीडियो फ़ाइलों को स्ट्रीम करना आसान है

मैंने सीख लिया कि छोटी वीडियो फ़ाइलों को एचटीएमएल 5 वीडियो प्लेयर में कैसे आसानी से स्ट्रीम किया जा सकता है। इस सेटअप के साथ, नियंत्रण मेरी ओर से बिना किसी काम के काम करता है, और वीडियो निर्दोष रूप से प्रवाहित होता है। Google डॉक्स पर डाउनलोड के लिए, नमूना वीडियो के साथ पूरी तरह से काम करने वाले कोड की एक कार्यशील प्रति यहां है

ग्राहक:

<html>
  <title>Welcome</title>
    <body>
      <video controls>
        <source src="movie.mp4" type="video/mp4"/>
        <source src="movie.webm" type="video/webm"/>
        <source src="movie.ogg" type="video/ogg"/>
        <!-- fallback -->
        Your browser does not support the <code>video</code> element.
    </video>
  </body>
</html>

सर्वर:

// Declare Vars & Read Files

var fs = require('fs'),
    http = require('http'),
    url = require('url'),
    path = require('path');
var movie_webm, movie_mp4, movie_ogg;
// ... [snip] ... (Read index page)
fs.readFile(path.resolve(__dirname,"movie.mp4"), function (err, data) {
    if (err) {
        throw err;
    }
    movie_mp4 = data;
});
// ... [snip] ... (Read two other formats for the video)

// Serve & Stream Video

http.createServer(function (req, res) {
    // ... [snip] ... (Serve client files)
    var total;
    if (reqResource == "/movie.mp4") {
        total = movie_mp4.length;
    }
    // ... [snip] ... handle two other formats for the video
    var range = req.headers.range;
    var positions = range.replace(/bytes=/, "").split("-");
    var start = parseInt(positions[0], 10);
    var end = positions[1] ? parseInt(positions[1], 10) : total - 1;
    var chunksize = (end - start) + 1;
    if (reqResource == "/movie.mp4") {
        res.writeHead(206, {
            "Content-Range": "bytes " + start + "-" + end + "/" + total,
                "Accept-Ranges": "bytes",
                "Content-Length": chunksize,
                "Content-Type": "video/mp4"
        });
        res.end(movie_mp4.slice(start, end + 1), "binary");
    }
    // ... [snip] ... handle two other formats for the video
}).listen(8888);

लेकिन यह विधि फाइलों में सीमित है <1GB आकार में।

स्ट्रीमिंग (किसी भी आकार) वीडियो फ़ाइलों के साथ fs.createReadStream

उपयोग करके fs.createReadStream(), सर्वर फ़ाइल को एक बार में सभी मेमोरी में पढ़ने के बजाय एक स्ट्रीम में पढ़ सकता है। यह चीजों को करने का सही तरीका लगता है, और वाक्य रचना बेहद सरल है:

सर्वर स्निपेट:

movieStream = fs.createReadStream(pathToFile);
movieStream.on('open', function () {
    res.writeHead(206, {
        "Content-Range": "bytes " + start + "-" + end + "/" + total,
            "Accept-Ranges": "bytes",
            "Content-Length": chunksize,
            "Content-Type": "video/mp4"
    });
    // This just pipes the read stream to the response object (which goes 
    //to the client)
    movieStream.pipe(res);
});

movieStream.on('error', function (err) {
    res.end(err);
});

यह वीडियो को ठीक ठीक स्ट्रीम करता है! लेकिन वीडियो अब काम नहीं करता है।


1
मैंने उस writeHead()कोड पर टिप्पणी की, लेकिन इसमें मदद मिलती है। क्या मुझे उस कोड स्निपेट को अधिक पठनीय बनाने के लिए निकालना चाहिए?
WebDeveloper404

3
req.headers.range कहाँ से आता है? जब मैं प्रतिस्थापित विधि करने की कोशिश करता हूं तो मैं अपरिभाषित रहता हूं। धन्यवाद।
चाड वाटकिंस

जवाबों:


118

Accept Rangesहैडर (में बिट writeHead()) काम करने के लिए HTML5 वीडियो नियंत्रण के लिए आवश्यक है।

मुझे लगता है कि केवल आँख बंद करके पूरी फ़ाइल भेजने के बजाय, आपको सबसे पहले Accept RangesREQUEST में हैडर की जांच करनी चाहिए , फिर उस बिट में पढ़ना और भेजना चाहिए। fs.createReadStreamसमर्थन start, और endउस के लिए विकल्प।

इसलिए मैंने एक उदाहरण की कोशिश की और यह काम करता है। कोड सुंदर नहीं है, लेकिन इसे समझना आसान है। पहले हम स्टार्ट / एंड पोजिशन पाने के लिए रेंज हेडर को प्रोसेस करते हैं। फिर हम fs.statपूरी फ़ाइल को मेमोरी में पढ़े बिना फ़ाइल का आकार प्राप्त करने के लिए उपयोग करते हैं। अंत में, fs.createReadStreamक्लाइंट को अनुरोधित भाग भेजने के लिए उपयोग करें।

var fs = require("fs"),
    http = require("http"),
    url = require("url"),
    path = require("path");

http.createServer(function (req, res) {
  if (req.url != "/movie.mp4") {
    res.writeHead(200, { "Content-Type": "text/html" });
    res.end('<video src="http://localhost:8888/movie.mp4" controls></video>');
  } else {
    var file = path.resolve(__dirname,"movie.mp4");
    fs.stat(file, function(err, stats) {
      if (err) {
        if (err.code === 'ENOENT') {
          // 404 Error if file not found
          return res.sendStatus(404);
        }
      res.end(err);
      }
      var range = req.headers.range;
      if (!range) {
       // 416 Wrong range
       return res.sendStatus(416);
      }
      var positions = range.replace(/bytes=/, "").split("-");
      var start = parseInt(positions[0], 10);
      var total = stats.size;
      var end = positions[1] ? parseInt(positions[1], 10) : total - 1;
      var chunksize = (end - start) + 1;

      res.writeHead(206, {
        "Content-Range": "bytes " + start + "-" + end + "/" + total,
        "Accept-Ranges": "bytes",
        "Content-Length": chunksize,
        "Content-Type": "video/mp4"
      });

      var stream = fs.createReadStream(file, { start: start, end: end })
        .on("open", function() {
          stream.pipe(res);
        }).on("error", function(err) {
          res.end(err);
        });
    });
  }
}).listen(8888);

3
क्या हम इस रणनीति का उपयोग फिल्म के केवल कुछ हिस्से को भेजने के लिए कर सकते हैं, अर्थात ५ वें सेकंड और to वें सेकंड के बीच? वहाँ एक तरीका है कि अंतराल क्या पुस्तकालयों की तरह ffmpeg द्वारा बाइट्स अंतराल से मेल खाती है? धन्यवाद।
पेम्बी

8
मेरे सवाल का कभी बुरा मत मानना। मैंने जो कुछ पूछा, उसे प्राप्त करने के लिए मुझे जादुई शब्द मिले: छद्म-स्ट्रीमिंग
पेम्बी

यह कैसे काम किया जा सकता है यदि किसी कारण से फिल्म .mp4 एक एन्क्रिप्टेड प्रारूप में है और हमें ब्राउज़र को स्ट्रीमिंग करने से पहले इसे डिक्रिप्ट करना होगा?
सरफ

@ सरफ: यह इस बात पर निर्भर करता है कि एन्क्रिप्शन के लिए कौन सा एल्गोरिदम इस्तेमाल किया जाता है। क्या यह स्ट्रीम के साथ काम करता है या यह केवल संपूर्ण फ़ाइल एन्क्रिप्शन के रूप में काम करता है? क्या यह संभव है कि आप वीडियो को अस्थायी स्थान पर डिक्रिप्ट करें और हमेशा की तरह सेवा करें? सामान्य बोल मैं यह संभव है, लेकिन यह मुश्किल हो सकता है। यहां कोई सामान्य समाधान नहीं है।
13

हाय, टंगड, प्रतिक्रिया के लिए धन्यवाद! उपयोग मामला एक रास्पबेरी पाई आधारित उपकरण है जो शैक्षिक सामग्री डेवलपर्स के लिए मीडिया वितरण मंच के रूप में कार्य करेगा। हम एन्क्रिप्शन एल्गोरिदम को चुनने के लिए स्वतंत्र हैं, कुंजी फर्मवेयर में होगी - लेकिन मेमोरी 1GB रैम तक सीमित है और सामग्री का आकार लगभग 200GB (जो हटाने योग्य मीडिया पर होगा - USB संलग्न है।) यहां तक ​​कि स्पष्ट कुंजी एल्गोरिथम जैसा कुछ। ईएमई ठीक होगा - इस समस्या को छोड़कर कि क्रोमियम को एआरएम पर ईएमई नहीं बनाया गया है। बस इतना है कि हटाने योग्य मीडिया प्लेबैक / प्रतिलिपि को सक्षम करने के लिए पर्याप्त नहीं होना चाहिए।
सरफ

24

इस प्रश्न का स्वीकृत उत्तर भयानक है और स्वीकृत उत्तर ही रहना चाहिए। हालाँकि मैं कोड के साथ एक समस्या में भाग गया था जहां रीड स्ट्रीम हमेशा समाप्त / बंद नहीं हो रही थी। समाधान का हिस्सा दूसरे arg में autoClose: trueसाथ भेजना था ।start:start, end:endcreateReadStream

समाधान का दूसरा हिस्सा chunksizeप्रतिक्रिया में भेजे जा रहे अधिकतम को सीमित करना था । अन्य उत्तर की endतरह सेट :

var end = positions[1] ? parseInt(positions[1], 10) : total - 1;

... जिसका शेष फ़ाइल को अपने अंतिम बाइट के माध्यम से अनुरोधित स्थिति से भेजने का प्रभाव है, चाहे कितनी भी बाइट्स हों। हालाँकि क्लाइंट ब्राउज़र के पास केवल उस स्ट्रीम के एक हिस्से को पढ़ने का विकल्प होता है, और यदि उसे अभी तक सभी बाइट्स की आवश्यकता नहीं है। यह तब तक पढ़ी जाने वाली धारा को अवरुद्ध कर देगा, जब तक कि ब्राउज़र को अधिक डेटा प्राप्त करने का समय नहीं मिल जाता (उदाहरण के लिए उपयोगकर्ता की कार्रवाई जैसे कि तलाश / स्क्रब, या सिर्फ स्ट्रीम को चलाकर)।

मुझे इस स्ट्रीम को बंद करने की आवश्यकता थी क्योंकि मैं <video>उस पृष्ठ पर तत्व प्रदर्शित कर रहा था जिसने उपयोगकर्ता को वीडियो फ़ाइल को हटाने की अनुमति दी थी। हालाँकि फ़ाइल को फ़ाइल सिस्टम से तब तक नहीं हटाया जा रहा था जब तक क्लाइंट (या सर्वर) ने कनेक्शन बंद नहीं कर दिया था, क्योंकि यह एकमात्र तरीका है जिससे स्ट्रीम समाप्त / बंद हो रही थी।

मेरा समाधान सिर्फ एक maxChunkकॉन्फ़िगरेशन चर सेट करने के लिए था , इसे 1MB पर सेट करें, और प्रतिक्रिया के लिए एक समय में 1MB से अधिक की धारा को कभी भी न पढ़ें।

// same code as accepted answer
var end = positions[1] ? parseInt(positions[1], 10) : total - 1;
var chunksize = (end - start) + 1;

// poor hack to send smaller chunks to the browser
var maxChunk = 1024 * 1024; // 1MB at a time
if (chunksize > maxChunk) {
  end = start + maxChunk - 1;
  chunksize = (end - start) + 1;
}

यह सुनिश्चित करने का प्रभाव है कि रीड स्ट्रीम प्रत्येक अनुरोध के बाद समाप्त / बंद हो गई है, और ब्राउज़र द्वारा जीवित नहीं रखी गई है।

मैंने इस मुद्दे को कवर करते हुए एक अलग StackOverflow प्रश्न और उत्तर भी लिखा ।


यह क्रोम के लिए बहुत अच्छा काम करता है, लेकिन सफारी में काम नहीं करता है। सफारी में यह तभी काम करता है जब यह पूरी रेंज का अनुरोध कर सकता है। क्या आप सफारी के लिए कुछ अलग कर रहे हैं?
f1lt3r 21

2
आगे की खुदाई करने पर: सफारी "/ $ {कुल}" को 2-बाइट प्रतिक्रिया में देखता है, और फिर कहता है ... "अरे, कैसे तुम्हारे बारे में मुझे पूरी फाइल भेजना है?"। फिर जब यह बताया जाता है, "नहीं, आप केवल पहला 1Mb प्राप्त कर रहे हैं!", सफारी परेशान हो गई "संसाधन का नेतृत्व करने की कोशिश में एक त्रुटि हुई"।
f1lt3r

0

सबसे पहले app.jsउस डायरेक्टरी में फाइल बनाएं जिसे आप प्रकाशित करना चाहते हैं।

var http = require('http');
var fs = require('fs');
var mime = require('mime');
http.createServer(function(req,res){
    if (req.url != '/app.js') {
    var url = __dirname + req.url;
        fs.stat(url,function(err,stat){
            if (err) {
            res.writeHead(404,{'Content-Type':'text/html'});
            res.end('Your requested URI('+req.url+') wasn\'t found on our server');
            } else {
            var type = mime.getType(url);
            var fileSize = stat.size;
            var range = req.headers.range;
                if (range) {
                    var parts = range.replace(/bytes=/, "").split("-");
                var start = parseInt(parts[0], 10);
                    var end = parts[1] ? parseInt(parts[1], 10) : fileSize-1;
                    var chunksize = (end-start)+1;
                    var file = fs.createReadStream(url, {start, end});
                    var head = {
                'Content-Range': `bytes ${start}-${end}/${fileSize}`,
                'Accept-Ranges': 'bytes',
                'Content-Length': chunksize,
                'Content-Type': type
                }
                    res.writeHead(206, head);
                    file.pipe(res);
                    } else {    
                    var head = {
                'Content-Length': fileSize,
                'Content-Type': type
                    }
                res.writeHead(200, head);
                fs.createReadStream(url).pipe(res);
                    }
            }
        });
    } else {
    res.writeHead(403,{'Content-Type':'text/html'});
    res.end('Sorry, access to that file is Forbidden');
    }
}).listen(8080);

बस चलाएं node app.jsऔर आपका सर्वर 8080 पोर्ट पर चल रहा होगा। वीडियो के अलावा यह सभी प्रकार की फाइलों को स्ट्रीम कर सकता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.