নোড.জেএস সহ এইচটিএমএল 5 ভিডিও প্লেয়ারে একটি ভিডিও ফাইল স্ট্রিমিং করা যাতে ভিডিও নিয়ন্ত্রণগুলি কাজ চালিয়ে যেতে পারে?


97

টিএল; ডাঃ - প্রশ্ন:

Node.js সহ এইচটিএমএল 5 ভিডিও প্লেয়ারে কোনও ভিডিও ফাইল স্ট্রিমিং হ্যান্ডেল করার সঠিক উপায়টি কী যাতে ভিডিও নিয়ন্ত্রণগুলি কাজ চালিয়ে যেতে পারে?

আমি মনে করি এটি শিরোনামগুলি যেভাবে পরিচালিত হয় তার সাথে এটি করতে হবে। যাইহোক, এখানে পটভূমি তথ্য। কোডটি কিছুটা লম্বা, তবে এটি বেশ সোজা।

নোড সহ HTML5 ভিডিওতে ছোট ভিডিও ফাইলগুলি স্ট্রিমিং করা সহজ

আমি কীভাবে খুব সহজেই এইচটিএমএল 5 ভিডিও প্লেয়ারে ছোট ভিডিও ফাইলগুলি স্ট্রিম করতে পারি তা শিখেছি। এই সেটআপটি দিয়ে, নিয়ন্ত্রণগুলি আমার পক্ষ থেকে কোনও কাজ ছাড়াই কাজ করে এবং ভিডিও নির্বিঘ্নে প্রবাহিত করে। গুগল ডক্সে ডাউনলোডের জন্য স্যাম্পল ভিডিও সহ পুরোপুরি কার্যকরী কোডের একটি ওয়ার্কিং কপি এখানে

ক্লায়েন্ট:

<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);
});

এই ভিডিওটি ঠিক আছে প্রবাহিত! তবে ভিডিও নিয়ন্ত্রণ করে আর কাজ করে না।


4
আমি writeHead()কোডটি মন্তব্য করে রেখেছি , তবে সেখানে যদি এটি সাহায্য করে। কোড স্নিপেটকে আরও পঠনযোগ্য করার জন্য আমি কি তা সরিয়ে ফেলতে পারি?
ডেভেলপার 404

4
req.headers.range কোথা থেকে আসে? আমি যখন প্রতিস্থাপন পদ্ধতিটি করার চেষ্টা করি তখন আমি অপরিবর্তিত থাকি। ধন্যবাদ
চাদ ওয়াটকিন্স

উত্তর:


118

Accept Rangesহেডার (ইন বিট writeHead()) কাজ HTML5 ভিডিও নিয়ন্ত্রণের জন্য প্রয়োজন হয়।

আমি মনে করি অন্ধভাবে সম্পূর্ণ ফাইলটি প্রেরণের পরিবর্তে আপনার প্রথমে Accept Rangesঅনুরোধের শিরোনামটি পরীক্ষা করা উচিত , তারপরে পড়ুন এবং ঠিক তেমনটি প্রেরণ করুন। 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);

4
আমরা এই কৌশলটি সিনেমার কিছু অংশ, অর্থাৎ 5 তম দ্বিতীয় থেকে 7 তম মধ্যে পাঠাতে পারি? লাইব্রেরির মতো ffmpeg দ্বারা অন্তর অন্তর্নির্মিত যা অন্তর এর সাথে মিলে যায় কি খুঁজে পাওয়ার কোনও উপায় আছে? ধন্যবাদ
পেম্বেসি

8
আমার প্রশ্ন মনে করবেন না। আমি যা জিজ্ঞাসা করেছি তা কীভাবে অর্জন করতে হয় তা জানতে আমি যাদুবিদ্যার সন্ধান পেয়েছি: সিউডো-স্ট্রিমিং
পেম্বেসি

যদি কোনও কারণে মুভি.এমপি 4 এনক্রিপ্ট করা ফর্ম্যাটে থাকে এবং ব্রাউজারে স্ট্রিমিংয়ের আগে আমাদের এটি ডিক্রিপ্ট করা দরকার তবে কীভাবে এটি কাজ করা যায়?
saraf

@ সরফ: এটি এনক্রিপশনের জন্য কী অ্যালগরিদম ব্যবহার করে তা নির্ভর করে। এটি স্ট্রিমের সাথে কাজ করে বা এটি কেবল পুরো ফাইল এনক্রিপশন হিসাবে কাজ করে? আপনি কি কেবলমাত্র অস্থায়ী স্থানে ভিডিওটি ডিক্রিপ্ট করে এটিকে যথারীতি পরিবেশন করতে পারবেন? সাধারণ বক্তৃতা আমি এটি সম্ভব, কিন্তু এটি জটিল হতে পারে। এখানে কোনও সাধারণ সমাধান নেই।
টুংড

হাই, টুংড, সাড়া দেওয়ার জন্য ধন্যবাদ! ব্যবহারের কেসটি একটি রাস্পবেরি পাই ভিত্তিক ডিভাইস যা শিক্ষামূলক সামগ্রী বিকাশকারীদের জন্য মিডিয়া বিতরণ প্ল্যাটফর্ম হিসাবে কাজ করবে। আমরা এনক্রিপশন অ্যালগরিদম চয়ন করতে নিখরচায়, কীটি ফার্মওয়্যারটিতে থাকবে - তবে মেমরিটি 1GB র্যামের মধ্যে সীমাবদ্ধ এবং সামগ্রীটির আকার 200 গিগাবাইটের কাছাকাছি (যা অপসারণযোগ্য মিডিয়াতে থাকবে - ইউএসবি সংযুক্ত attached) এমনকি ক্লিয়ার কী অ্যালগরিদমের মতো কিছু EME ঠিক থাকবে - ক্রোমিয়ামটি এআরএম এ ইএমই তৈরি করে না এমন সমস্যা ব্যতীত। অপসারণযোগ্য মিডিয়া কেবল প্লেব্যাক / অনুলিপি সক্ষম করার পক্ষে যথেষ্ট নয়।
saraf

25

এই প্রশ্নের স্বীকৃত উত্তরটি দুর্দান্ত and তবে আমি কোডটি নিয়ে একটি ইস্যুতে দৌড়েছি যেখানে পঠিত স্ট্রিমটি সর্বদা শেষ / বন্ধ ছিল না। সমাধানের অংশ পাঠাতে ছিল autoClose: trueবরাবর start:start, end:endদ্বিতীয় createReadStreamARG।

সমাধানের অন্য অংশটি ছিল chunksizeপ্রতিক্রিয়াতে পাঠানো সর্বাধিক সীমাবদ্ধ করা । অন্যান্য উত্তর সেট endমত সেট :

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

... যার ফলে বাকী ফাইলটি তার শেষ বাইটের মাধ্যমে অনুরোধ করা শুরু অবস্থান থেকে প্রেরণ করার প্রভাব রয়েছে, তা যতই বাইট হোক না কেন। তবে ক্লায়েন্ট ব্রাউজারের কাছে কেবলমাত্র সেই স্ট্রিমের কিছু অংশ পড়ার বিকল্প রয়েছে এবং যদি এটির এখনও সমস্ত বাইটের প্রয়োজন না হয় will ব্রাউজারটি আরও ডেটা পাওয়ার সময় না আসা পর্যন্ত এই স্ট্রিমটি পড়তে বাধা দেবে (উদাহরণস্বরূপ কোনও ব্যবহারকারী পদক্ষেপ যেমন সন্ধান / স্ক্রাব, বা কেবল স্ট্রিমটি খোলার মাধ্যমে)।

আমার এই স্ট্রিমটি বন্ধ হওয়ার দরকার ছিল কারণ আমি এমন <video>একটি পৃষ্ঠায় উপাদানটি প্রদর্শন করছিলাম যা ব্যবহারকারীকে ভিডিও ফাইল মুছতে দেয়। তবে ক্লায়েন্ট (বা সার্ভার) সংযোগ বন্ধ না হওয়া পর্যন্ত ফাইলটি ফাইল সিস্টেম থেকে সরানো হচ্ছে না, কারণ এটাই একমাত্র উপায় যে স্ট্রিমটি শেষ / বন্ধ হয়ে যাচ্ছিল।

আমার সমাধানটি ছিল একটি maxChunkকনফিগারেশন ভেরিয়েবল সেট করা, এটি 1 এমবিতে সেট করা এবং প্রতিক্রিয়ার জন্য একবারে 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;
}

প্রতিটি অনুরোধের পরে পঠিত স্ট্রিমটি শেষ / বন্ধ হয়ে গেছে এবং ব্রাউজার দ্বারা জীবিত রাখা হবে না তা নিশ্চিত করার প্রভাব এটিতে রয়েছে।

আমি পৃথক স্ট্যাকওভারফ্লো প্রশ্ন এবং এই সমস্যাটি জুড়ে উত্তর লিখেছি ।


এটি ক্রোমের পক্ষে দুর্দান্ত কাজ করে তবে সাফারি তে কাজ করে না। সাফারিতে এটি কেবলমাত্র কাজ করে মনে হয় যদি এটি সম্পূর্ণ পরিসীমাটির জন্য অনুরোধ করতে পারে। আপনি কি সাফারির জন্য আলাদা কিছু করছেন?
f1lt3r

4
আরও খনন করার পরে: সাফারি 2-বাইট প্রতিক্রিয়াতে "/ $ {টোটাল}" দেখে, এবং তারপরে বলে ... "আরে, আপনি আমাকে পুরো ফাইলটি কীভাবে প্রেরণ করবেন?" তারপরে যখন বলা হয়, "না, আপনি কেবল প্রথম 1 এমবি পাচ্ছেন!", সাফারি বিচলিত হয়ে যায় "সংস্থানটি পরিচালনার চেষ্টা করার সময় একটি ত্রুটি ঘটেছে"।
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 পোর্টে চলমান থাকবে video ভিডিওর পাশাপাশি এটি সমস্ত ধরণের ফাইল প্রবাহিত করতে পারে।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.