पहले से लोड किए गए ऑडियो और वीडियो के साथ तेज़ी से वीडियो चलाना

संसाधनों को पहले से लोड करके, मीडिया कॉन्टेंट को तेज़ी से चलाने का तरीका.

François Beaufort
François Beaufort

वीडियो तुरंत चलने का मतलब है कि ज़्यादा लोग आपका वीडियो देख रहे हैं या आपका ऑडियो सुन रहे हैं. यह एक जाना-पहचाना तथ्य है. इस लेख में, हम उन तकनीकों के बारे में बताएंगे जिनका इस्तेमाल करके, ऑडियो और वीडियो को तेज़ी से चलाया जा सकता है. इसके लिए, आपको अपने इस्तेमाल के उदाहरण के हिसाब से, संसाधनों को पहले से लोड करना होगा.

क्रेडिट: कॉपीराइट Blender Foundation | www.blender.org .

हम मीडिया फ़ाइलों को पहले से लोड करने के तीन तरीकों के बारे में बताएंगे. सबसे पहले, इनके फ़ायदों और नुकसानों के बारे में बताएंगे.

यह बहुत बढ़िया है... लेकिन...
वीडियो पहले से लोड करने का एट्रिब्यूट वेब सर्वर पर होस्ट की गई किसी यूनीक फ़ाइल के लिए, इसे इस्तेमाल करना आसान होता है. ब्राउज़र इस एट्रिब्यूट को पूरी तरह अनदेखा कर सकते हैं.
एचटीएमएल दस्तावेज़ के पूरी तरह लोड हो जाने और पार्स हो जाने पर, रिसॉर्स फ़ेच करने की प्रोसेस शुरू हो जाती है.
मीडिया सोर्स एक्सटेंशन (एमएसई), मीडिया एलिमेंट पर मौजूद preload एट्रिब्यूट को अनदेखा करते हैं. ऐसा इसलिए होता है, क्योंकि एमएसई को मीडिया उपलब्ध कराने की ज़िम्मेदारी ऐप्लिकेशन की होती है.
लिंक प्रीलोड ब्राउज़र को दस्तावेज़ के onload इवेंट को ब्लॉक किए बिना, वीडियो रिसॉर्स का अनुरोध करने के लिए मजबूर करता है. एचटीटीपी रेंज के अनुरोध काम नहीं करते.
MSE और फ़ाइल सेगमेंट के साथ काम करता है. पूरे संसाधनों को फ़ेच करते समय, इसका इस्तेमाल सिर्फ़ छोटी मीडिया फ़ाइलों (<5 एमबी) के लिए किया जाना चाहिए.
मैन्युअल तरीके से बफ़र करना पूरा कंट्रोल जटिल गड़बड़ियों को ठीक करना वेबसाइट की ज़िम्मेदारी है.

वीडियो को पहले से लोड करने की सुविधा देने वाला एट्रिब्यूट

अगर वीडियो का सोर्स, वेब सर्वर पर होस्ट की गई कोई यूनीक फ़ाइल है, तो ब्राउज़र को यह बताने के लिए वीडियो preload एट्रिब्यूट का इस्तेमाल किया जा सकता है कि कितनी जानकारी या कॉन्टेंट को पहले से लोड करना है. इसका मतलब है कि मीडिया सोर्स एक्सटेंशन (एमएसई), preload के साथ काम नहीं करता.

रिसॉर्स फ़ेच करने की प्रोसेस सिर्फ़ तब शुरू होगी, जब शुरुआती एचटीएमएल दस्तावेज़ पूरी तरह लोड और पार्स हो गया हो (उदाहरण के लिए, DOMContentLoaded इवेंट ट्रिगर हो गया हो). जब रिसॉर्स को असल में फ़ेच किया जाएगा, तो सबसे अलग load इवेंट ट्रिगर होगा.

preload एट्रिब्यूट को metadata पर सेट करने का मतलब है कि उपयोगकर्ता को वीडियो की ज़रूरत नहीं है, लेकिन उसका मेटाडेटा (डाइमेंशन, ट्रैक सूची, अवधि वगैरह) फ़ेच करना सही होता है. ध्यान दें कि Chrome 64 में, preload की डिफ़ॉल्ट वैल्यू metadata है. (पहले यह auto था).

<video id="video" preload="metadata" src="file.mp4" controls></video>

<script>
  video.addEventListener('loadedmetadata', function() {
    if (video.buffered.length === 0) return;

    const bufferedSeconds = video.buffered.end(0) - video.buffered.start(0);
    console.log(`${bufferedSeconds} seconds of video are ready to play.`);
  });
</script>

preload एट्रिब्यूट को auto पर सेट करने का मतलब है कि ब्राउज़र इतना डेटा कैश मेमोरी में सेव कर सकता है कि वीडियो को बिना रुकावट के चलाया जा सके. इसके लिए, वीडियो को फिर से बफ़र करने की ज़रूरत नहीं पड़ेगी.

<video id="video" preload="auto" src="file.mp4" controls></video>

<script>
  video.addEventListener('loadedmetadata', function() {
    if (video.buffered.length === 0) return;

    const bufferedSeconds = video.buffered.end(0) - video.buffered.start(0);
    console.log(`${bufferedSeconds} seconds of video are ready to play.`);
  });
</script>

हालांकि, इसमें कुछ बातों का ध्यान रखना ज़रूरी है. यह सिर्फ़ एक संकेत है. ब्राउज़र preload एट्रिब्यूट को पूरी तरह से अनदेखा कर सकता है. इस लेख को लिखने के समय, Chrome में ये नियम लागू थे:

  • डेटा बचाने की सेटिंग चालू होने पर, Chrome preload वैल्यू को none पर सेट कर देता है.
  • Android 4.3 में, Android के एक बग की वजह से Chrome, preload वैल्यू को none पर सेट कर देता है.
  • मोबाइल कनेक्शन (2G, 3G, और 4G) पर, Chrome preload वैल्यू को metadata पर भेजता है.

सलाह

अगर आपकी वेबसाइट पर एक ही डोमेन पर कई वीडियो रिसॉर्स हैं, तो हमारा सुझाव है कि आप preload वैल्यू को metadata पर सेट करें या poster एट्रिब्यूट को तय करें और preload को none पर सेट करें. इस तरह, आप एक ही डोमेन पर एचटीटीपी कनेक्शन की ज़्यादा से ज़्यादा संख्या तक पहुंचने से बच जाएंगे (एचटीटीपी 1.1 स्पेसिफ़िकेशन के मुताबिक छह), इससे रिसॉर्स के लोड होने का समय रुक सकता है. ध्यान दें कि अगर वीडियो आपके मुख्य उपयोगकर्ता अनुभव का हिस्सा नहीं हैं, तो इससे पेज लोड होने में लगने वाला समय भी कम हो सकता है.

जैसा कि अन्य लेखों में शामिल है, लिंक को पहले से लोड करने की सुविधा, जानकारी देने के मकसद से फ़ेच किया जाता है. इसकी मदद से, ब्राउज़र को load इवेंट को ब्लॉक किए बिना और पेज डाउनलोड होने के दौरान, किसी संसाधन के लिए अनुरोध करने के लिए कहा जा सकता है. <link rel="preload"> से लोड किए गए रिसॉर्स, ब्राउज़र में लोकल तौर पर सेव किए जाते हैं. साथ ही, ये तब तक काम नहीं करते हैं, जब तक कि उन्हें DOM, JavaScript या सीएसएस में साफ़ तौर पर रेफ़र नहीं किया जाता.

प्रीलोड, प्रीफ़ेच से अलग होता है. यह मौजूदा नेविगेशन पर फ़ोकस करता है और संसाधनों को उनके टाइप (स्क्रिप्ट, स्टाइल, फ़ॉन्ट, वीडियो, ऑडियो वगैरह) के आधार पर प्राथमिकता के साथ फ़ेच करता है. इसका इस्तेमाल मौजूदा सेशन के लिए, ब्राउज़र की कैश मेमोरी को गर्म करने के लिए किया जाना चाहिए.

पूरा वीडियो पहले से लोड करना

यहां आपकी वेबसाइट पर पूरे वीडियो को पहले से लोड करने का तरीका बताया गया है. इससे, जब JavaScript आपसे वीडियो कॉन्टेंट फ़ेच करने के लिए कहता है, तो उसे कैश मेमोरी से पढ़ा जा सकता है. ऐसा इसलिए, क्योंकि हो सकता है कि ब्राउज़र ने संसाधन को पहले ही कैश मेमोरी में सेव कर लिया हो. अगर प्रीलोड करने का अनुरोध अब तक पूरा नहीं हुआ है, तो नेटवर्क से फ़ेच करने की सामान्य प्रोसेस शुरू हो जाएगी.

<link rel="preload" as="video" href="https://cdn.com/small-file.mp4">

<video id="video" controls></video>

<script>
  // Later on, after some condition has been met, set video source to the
  // preloaded video URL.
  video.src = 'https://cdn.com/small-file.mp4';
  video.play().then(() => {
    // If preloaded video URL was already cached, playback started immediately.
  });
</script>

इस उदाहरण में दिए गए वीडियो एलिमेंट के ज़रिए, पहले से लोड किए गए रिसॉर्स का इस्तेमाल किया जाएगा. इसलिए, पहले से लोड किए गए as के लिंक की वैल्यू video है. अगर यह ऑडियो एलिमेंट होता, तो यह as="audio" होता.

पहला सेगमेंट पहले से लोड करना

यहां दिए गए उदाहरण में, <link rel="preload"> का इस्तेमाल करके वीडियो के पहले सेगमेंट को प्रीलोड करने और उसे मीडिया सोर्स एक्सटेंशन के साथ इस्तेमाल करने का तरीका बताया गया है. अगर आपको MSE JavaScript API के बारे में नहीं पता है, तो MSE की बुनियादी बातें देखें.

इसे आसानी से समझने के लिए, मान लें कि पूरे वीडियो को छोटी फ़ाइलों में बांटा गया है, जैसे कि file_1.webm, file_2.webm, file_3.webm वगैरह.

<link rel="preload" as="fetch" href="https://cdn.com/file_1.webm">

<video id="video" controls></video>

<script>
  const mediaSource = new MediaSource();
  video.src = URL.createObjectURL(mediaSource);
  mediaSource.addEventListener('sourceopen', sourceOpen, { once: true });

  function sourceOpen() {
    URL.revokeObjectURL(video.src);
    const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp09.00.10.08"');

    // If video is preloaded already, fetch will return immediately a response
    // from the browser cache (memory cache). Otherwise, it will perform a
    // regular network fetch.
    fetch('https://cdn.com/file_1.webm')
    .then(response => response.arrayBuffer())
    .then(data => {
      // Append the data into the new sourceBuffer.
      sourceBuffer.appendBuffer(data);
      // TODO: Fetch file_2.webm when user starts playing video.
    })
    .catch(error => {
      // TODO: Show "Video is not available" message to user.
    });
  }
</script>

सहायता

नीचे दिए गए स्निपेट की मदद से, <link rel=preload> के लिए कई तरह के as के साथ काम करने की सुविधा का पता लगाया जा सकता है:

function preloadFullVideoSupported() {
  const link = document.createElement('link');
  link.as = 'video';
  return (link.as === 'video');
}

function preloadFirstSegmentSupported() {
  const link = document.createElement('link');
  link.as = 'fetch';
  return (link.as === 'fetch');
}

मैन्युअल तरीके से बफ़र करना

कैश एपीआई और सर्विस वर्कर के बारे में बात करने से पहले, आइए जानते हैं कि एमएसई का इस्तेमाल करके वीडियो को मैन्युअल तरीके से कैसे बफ़र किया जाता है. नीचे दिए गए उदाहरण में यह माना गया है कि आपका वेब सर्वर, एचटीटीपी Range अनुरोधों के साथ काम करता है. हालांकि, यह फ़ाइल के सेगमेंट के साथ काफ़ी मिलता-जुलता होगा. ध्यान दें कि कुछ मिडलवेयर लाइब्रेरी, जैसे कि Google का Shaka Player, JW Player, और Video.js को आपके लिए मैनेज किया जाता है.

<video id="video" controls></video>

<script>
  const mediaSource = new MediaSource();
  video.src = URL.createObjectURL(mediaSource);
  mediaSource.addEventListener('sourceopen', sourceOpen, { once: true });

  function sourceOpen() {
    URL.revokeObjectURL(video.src);
    const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp09.00.10.08"');

    // Fetch beginning of the video by setting the Range HTTP request header.
    fetch('file.webm', { headers: { range: 'bytes=0-567139' } })
    .then(response => response.arrayBuffer())
    .then(data => {
      sourceBuffer.appendBuffer(data);
      sourceBuffer.addEventListener('updateend', updateEnd, { once: true });
    });
  }

  function updateEnd() {
    // Video is now ready to play!
    const bufferedSeconds = video.buffered.end(0) - video.buffered.start(0);
    console.log(`${bufferedSeconds} seconds of video are ready to play.`);

    // Fetch the next segment of video when user starts playing the video.
    video.addEventListener('playing', fetchNextSegment, { once: true });
  }

  function fetchNextSegment() {
    fetch('file.webm', { headers: { range: 'bytes=567140-1196488' } })
    .then(response => response.arrayBuffer())
    .then(data => {
      const sourceBuffer = mediaSource.sourceBuffers[0];
      sourceBuffer.appendBuffer(data);
      // TODO: Fetch further segment and append it.
    });
  }
</script>

ज़रूरी बातें

अब आपके पास मीडिया को बफ़र करने के पूरे अनुभव को कंट्रोल करने का विकल्प है. इसलिए, हमारा सुझाव है कि आप वीडियो को पहले से लोड करने के बारे में सोचते समय, डिवाइस की बैटरी लेवल, "डेटा सेवर मोड" की उपयोगकर्ता की प्राथमिकता, और नेटवर्क की जानकारी को ध्यान में रखें.

बैटरी की जानकारी

वीडियो को पहले से लोड करने के बारे में सोचने से पहले, लोगों के डिवाइस के बैटरी लेवल का ध्यान रखें. इससे पावर लेवल कम होने पर भी बैटरी लाइफ़ बची रहेगी.

डिवाइस की बैटरी खत्म होने पर, कम रिज़ॉल्यूशन वाले वीडियो को पहले से लोड करने की सुविधा को बंद करें या कम से कम उसे पहले से लोड करें.

if ('getBattery' in navigator) {
  navigator.getBattery()
  .then(battery => {
    // If battery is charging or battery level is high enough
    if (battery.charging || battery.level > 0.15) {
      // TODO: Preload the first segment of a video.
    }
  });
}

"डेटा-सेवर" का पता लगाना

जिन उपयोगकर्ताओं ने अपने ब्राउज़र में "डेटा की बचत" मोड के लिए ऑप्ट-इन किया है उन्हें तेज़ और हल्के रंग के ऐप्लिकेशन डिलीवर करने के लिए, Save-Data क्लाइंट संकेत अनुरोध हेडर का इस्तेमाल करें. इस अनुरोध हेडर की पहचान करके, आपका ऐप्लिकेशन ज़रूरत के मुताबिक और परफ़ॉर्मेंस सीमित करने वाले उपयोगकर्ताओं को ऑप्टिमाइज़ किया गया उपयोगकर्ता अनुभव दे सकता है.

ज़्यादा जानने के लिए, सेव-डेटा की मदद से, तेज़ी से काम करने वाले और कम डेटा वाले ऐप्लिकेशन डिलीवर करना देखें.

नेटवर्क की जानकारी के आधार पर स्मार्ट लोडिंग

पेजों को पहले से लोड करने से पहले, navigator.connection.type की जांच कर लें. अगर इसे cellular पर सेट किया जाता है, तो पेजों को पहले से लोड होने से रोका जा सकता है. साथ ही, उपयोगकर्ताओं को बताया जा सकता है कि उनका मोबाइल नेटवर्क ऑपरेटर, बैंडविथ के हिसाब से चार्ज कर रहा है. साथ ही, कैश मेमोरी में सेव किए गए, पहले से मौजूद कॉन्टेंट को अपने-आप चलाने की सुविधा चालू करें.

if ('connection' in navigator) {
  if (navigator.connection.type == 'cellular') {
    // TODO: Prompt user before preloading video
  } else {
    // TODO: Preload the first segment of a video.
  }
}

नेटवर्क में होने वाले बदलावों पर भी कार्रवाई करने का तरीका जानने के लिए, नेटवर्क की जानकारी का सैंपल देखें.

एक से ज़्यादा शुरुआती सेगमेंट को पहले से कैश मेमोरी में सेव करना

अब अगर मैं यह जाने बिना कि उपयोगकर्ता कौनसा मीडिया चुनेगा, तो मुझे अनुमान के हिसाब से मीडिया कॉन्टेंट को पहले से लोड करने के लिए क्या करना होगा? अगर उपयोगकर्ता किसी ऐसे वेब पेज पर है जिस पर 10 वीडियो मौजूद हैं, तो हो सकता है कि हमारे पास हर वीडियो से एक सेगमेंट फ़ाइल फ़ेच करने के लिए ज़रूरत के मुताबिक मेमोरी हो. हालांकि, हमें 10 छिपे हुए <video> एलिमेंट और 10 MediaSource ऑब्जेक्ट नहीं बनाने चाहिए और उस डेटा को फ़ीड नहीं करना चाहिए.

यहां दिए गए दो हिस्सों वाले उदाहरण में, Cache API का इस्तेमाल करके, वीडियो के शुरुआती कई सेगमेंट को पहले से कैश मेमोरी में सेव करने का तरीका बताया गया है. यह एपीआई, आसानी से इस्तेमाल किया जा सकता है और इसमें कई बेहतरीन सुविधाएं हैं. ध्यान दें कि IndexedDB से भी ऐसा ही कुछ हासिल किया जा सकता है. फ़िलहाल, हम सेवा वर्कर का इस्तेमाल नहीं कर रहे हैं, क्योंकि window ऑब्जेक्ट से भी Cache API को ऐक्सेस किया जा सकता है.

फ़ेच और कैश मेमोरी में सेव करना

const videoFileUrls = [
  'bat_video_file_1.webm',
  'cow_video_file_1.webm',
  'dog_video_file_1.webm',
  'fox_video_file_1.webm',
];

// Let's create a video pre-cache and store all first segments of videos inside.
window.caches.open('video-pre-cache')
.then(cache => Promise.all(videoFileUrls.map(videoFileUrl => fetchAndCache(videoFileUrl, cache))));

function fetchAndCache(videoFileUrl, cache) {
  // Check first if video is in the cache.
  return cache.match(videoFileUrl)
  .then(cacheResponse => {
    // Let's return cached response if video is already in the cache.
    if (cacheResponse) {
      return cacheResponse;
    }
    // Otherwise, fetch the video from the network.
    return fetch(videoFileUrl)
    .then(networkResponse => {
      // Add the response to the cache and return network response in parallel.
      cache.put(videoFileUrl, networkResponse.clone());
      return networkResponse;
    });
  });
}

ध्यान दें कि अगर मुझे एचटीटीपी Range अनुरोध का इस्तेमाल करना है, तो मुझे मैन्युअल तरीके से Response ऑब्जेक्ट बनाना होगा. इसकी वजह यह है कि कैश एपीआई, अब तक Range रिस्पॉन्स के साथ काम नहीं करता. ध्यान रखें कि networkResponse.arrayBuffer() को कॉल करने से, रिस्पॉन्स का पूरा कॉन्टेंट एक ही बार में रेंडरर मेमोरी में आ जाता है. इसलिए, हो सकता है कि आप छोटी रेंज का इस्तेमाल करना चाहें.

रेफ़रंस के लिए, मैंने ऊपर दिए गए उदाहरण के कुछ हिस्से में बदलाव किया है, ताकि वीडियो के प्रीकैश में एचटीटीपी रेंज के अनुरोध सेव किए जा सकें.

    ...
    return fetch(videoFileUrl, { headers: { range: 'bytes=0-567139' } })
    .then(networkResponse => networkResponse.arrayBuffer())
    .then(data => {
      const response = new Response(data);
      // Add the response to the cache and return network response in parallel.
      cache.put(videoFileUrl, response.clone());
      return response;
    });

वीडियो चलाएं

जब कोई उपयोगकर्ता 'चलाएं' बटन पर क्लिक करेगा, तो हम कैश एपीआई में उपलब्ध वीडियो का पहला सेगमेंट फ़ेच करेंगे, ताकि उपलब्ध होने पर वीडियो तुरंत शुरू हो जाए. अगर ऐसा नहीं किया जाता है, तो हम इसे नेटवर्क से फ़ेच कर लेंगे. ध्यान रखें कि ब्राउज़र और उपयोगकर्ता कैश मेमोरी को हटाने का फ़ैसला ले सकते हैं.

जैसा कि हमने पहले देखा है, हम वीडियो के पहले सेगमेंट को वीडियो एलिमेंट में फ़ीड करने के लिए, MSE का इस्तेमाल करते हैं.

function onPlayButtonClick(videoFileUrl) {
  video.load(); // Used to be able to play video later.

  window.caches.open('video-pre-cache')
  .then(cache => fetchAndCache(videoFileUrl, cache)) // Defined above.
  .then(response => response.arrayBuffer())
  .then(data => {
    const mediaSource = new MediaSource();
    video.src = URL.createObjectURL(mediaSource);
    mediaSource.addEventListener('sourceopen', sourceOpen, { once: true });

    function sourceOpen() {
      URL.revokeObjectURL(video.src);

      const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp09.00.10.08"');
      sourceBuffer.appendBuffer(data);

      video.play().then(() => {
        // TODO: Fetch the rest of the video when user starts playing video.
      });
    }
  });
}

सर्विस वर्कर की मदद से रेंज रिस्पॉन्स बनाना

अब अगर आपने पूरी वीडियो फ़ाइल फ़ेच करके, उसे कैश मेमोरी वाले एपीआई में सेव कर लिया है, तो क्या होगा? जब ब्राउज़र कोई एचटीटीपी Range अनुरोध भेजता है, तो आपको पूरे वीडियो को रेंडरर की मेमोरी में नहीं ले जाना चाहिए. इसकी वजह यह है कि अब तक कैश एपीआई, Range रिस्पॉन्स के साथ काम नहीं करता.

चलिए, दिखाते हैं कि इन अनुरोधों को रोकने और सर्विस वर्कर से मिले, Range रिस्पॉन्स को कैसे वापस करते हैं.

addEventListener('fetch', event => {
  event.respondWith(loadFromCacheOrFetch(event.request));
});

function loadFromCacheOrFetch(request) {
  // Search through all available caches for this request.
  return caches.match(request)
  .then(response => {

    // Fetch from network if it's not already in the cache.
    if (!response) {
      return fetch(request);
      // Note that we may want to add the response to the cache and return
      // network response in parallel as well.
    }

    // Browser sends a HTTP Range request. Let's provide one reconstructed
    // manually from the cache.
    if (request.headers.has('range')) {
      return response.blob()
      .then(data => {

        // Get start position from Range request header.
        const pos = Number(/^bytes\=(\d+)\-/g.exec(request.headers.get('range'))[1]);
        const options = {
          status: 206,
          statusText: 'Partial Content',
          headers: response.headers
        }
        const slicedResponse = new Response(data.slice(pos), options);
        slicedResponse.setHeaders('Content-Range': 'bytes ' + pos + '-' +
            (data.size - 1) + '/' + data.size);
        slicedResponse.setHeaders('X-From-Cache': 'true');

        return slicedResponse;
      });
    }

    return response;
  }
}

ध्यान दें कि मैंने इस स्लाइस किए गए रिस्पॉन्स को फिर से बनाने के लिए response.blob() का इस्तेमाल किया है, क्योंकि इससे मुझे फ़ाइल का हैंडल मिलता है. वहीं, response.arrayBuffer() से पूरी फ़ाइल रेंडरर मेमोरी में आ जाती है.

मैंने अपनी पसंद के मुताबिक X-From-Cache एचटीटीपी हेडर का इस्तेमाल करके, यह पता लगाया है कि यह अनुरोध कैश मेमोरी से आया है या नेटवर्क से. ShakaPlayer जैसे प्लेयर इसका इस्तेमाल, नेटवर्क स्पीड के इंडिकेटर के तौर पर रिस्पॉन्स टाइम को अनदेखा करने के लिए कर सकते हैं.

Range अनुरोधों को मैनेज करने का तरीका जानने के लिए, आधिकारिक Sample Media App और खास तौर पर इसकी ranged-response.js फ़ाइल पर एक नज़र डालें.