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

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

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 के मुताबिक 6) से बचने में मदद मिलेगी. इससे संसाधनों को लोड होने में लगने वाला समय कम हो सकता है. ध्यान दें कि अगर वीडियो आपके मुख्य उपयोगकर्ता अनुभव का हिस्सा नहीं हैं, तो इससे पेज लोड होने की स्पीड भी बेहतर हो सकती है.

जैसा कि अन्य लेखों में बताया गया है, लिंक को पहले से लोड करना एक एलान वाला फ़ेच है. इससे, पेज डाउनलोड होने के दौरान और load इवेंट को ब्लॉक किए बिना, ब्राउज़र को किसी रिसॉर्स का अनुरोध करने के लिए मजबूर किया जा सकता है. <link rel="preload"> के ज़रिए लोड किए गए रिसॉर्स, ब्राउज़र में स्थानीय तौर पर सेव किए जाते हैं. ये तब तक काम नहीं करते, जब तक कि डीओएम, 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');
}

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

Cache API और सेवा वर्कर के बारे में जानने से पहले, आइए देखें कि एमएसई की मदद से, वीडियो को मैन्युअल तरीके से कैसे बफ़र किया जा सकता है. नीचे दिए गए उदाहरण में यह माना गया है कि आपका वेब सर्वर, एचटीटीपी 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 ऑब्जेक्ट फिर से बनाना होगा. ऐसा इसलिए, क्योंकि Cache API अभी 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;
    });

वीडियो चलाएं

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

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

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 अनुरोधों को मैनेज करने का पूरा तरीका जानने के लिए, आधिकारिक सैंपल मीडिया ऐप्लिकेशन और खास तौर पर उसकी ranged-response.js फ़ाइल देखें.