केस स्टडी - वेब ऑडियो के साथ HTML5 गेम की एक कहानी

Fieldrunners

Fieldrunners का स्क्रीनशॉट
Fieldrunners का स्क्रीनशॉट

Fieldrunners, टावर-डिफ़ेंस स्टाइल का एक ऐसा गेम है जिसे कई पुरस्कार मिले हैं. इसे साल 2008 में iPhone के लिए रिलीज़ किया गया था. इसके बाद, इसे कई अन्य प्लैटफ़ॉर्म पर पोर्ट किया गया. अक्टूबर 2011 में, Chrome ब्राउज़र सबसे नए प्लैटफ़ॉर्म में से एक था. Fieldrunners को HTML5 प्लैटफ़ॉर्म पर पोर्ट करने में, साउंड चलाने का तरीका ढूंढना एक चुनौती थी.

Fieldrunners में साउंड इफ़ेक्ट का इस्तेमाल मुश्किल नहीं है. हालांकि, इस बात की उम्मीद की जाती है कि यह अपने साउंड इफ़ेक्ट के साथ कैसे इंटरैक्ट कर सकता है. इस गेम में 88 साउंड इफ़ेक्ट हैं. इनमें से एक साथ कई साउंड इफ़ेक्ट चल सकते हैं. इनमें से ज़्यादातर साउंड बहुत छोटे होते हैं और इन्हें समय पर चलाना ज़रूरी होता है, ताकि ग्राफ़िक प्रज़ेंटेशन से कोई गड़बड़ी न हो.

कुछ चुनौतियां दिख रही हैं

Fieldrunners को HTML5 में पोर्ट करते समय, हमें ऑडियो टैग के साथ ऑडियो चलाने में समस्याएं आ रही थीं. इसलिए, हमने शुरुआत में Web Audio API पर फ़ोकस करने का फ़ैसला लिया. WebAudio का इस्तेमाल करने से, हमें कई समस्याओं को हल करने में मदद मिली. जैसे, Fieldrunners में एक साथ कई इफ़ेक्ट चलाने की ज़रूरत होती है. हालांकि, Fieldrunners HTML5 के लिए ऑडियो सिस्टम डेवलप करते समय, हमें कुछ ऐसी समस्याओं का सामना करना पड़ा जिनके बारे में अन्य डेवलपर को पता होना चाहिए.

AudioBufferSourceNodes का टाइप

WebAudio की मदद से आवाज़ें चलाने का मुख्य तरीका, AudioBufferSourceNodes है. यह समझना बहुत ज़रूरी है कि ये एक बार इस्तेमाल होने वाले आइटम हैं. इसके लिए, AudioBufferSourceNode बनाएं, उसे बफ़र असाइन करें, और ग्राफ़ से कनेक्ट करें. इसके बाद, noteOn या noteGrainOn का इस्तेमाल करके उसे चलाएं. इसके बाद, चलाने की प्रोसेस को रोकने के लिए noteOff को कॉल किया जा सकता है. हालांकि, noteOn या noteGrainOn को कॉल करके, सोर्स को फिर से नहीं चलाया जा सकेगा. इसके लिए, आपको एक और AudioBufferSourceNode बनाना होगा. हालांकि, आपके पास उसी AudioBuffer ऑब्जेक्ट का फिर से इस्तेमाल करने का विकल्प है. यह बहुत ज़रूरी है. असल में, आपके पास एक ही AudioBuffer इंस्टेंस पर पॉइंट करने वाले कई चालू AudioBufferSourceNodes भी हो सकते हैं! 'मुझे बीट दें' में, Fieldrunners का एक स्निपेट चलाया जा सकता है.

कैश मेमोरी में सेव न होने वाला कॉन्टेंट

रिलीज़ के समय, Fieldrunners के HTML5 सर्वर पर संगीत फ़ाइलों के लिए बहुत ज़्यादा अनुरोध मिले. यह समस्या तब हुई, जब Chrome 15 ने फ़ाइल को अलग-अलग हिस्सों में डाउनलोड किया और उसे कैश मेमोरी में सेव नहीं किया. इसलिए, हमने उस समय यह फ़ैसला लिया था कि हम अपनी अन्य ऑडियो फ़ाइलों की तरह ही म्यूज़िक फ़ाइलों को भी लोड करेंगे. ऐसा करना सही नहीं है, लेकिन दूसरे ब्राउज़र के कुछ वर्शन अब भी ऐसा करते हैं.

फ़ोकस से बाहर होने पर आवाज़ बंद होना

पहले यह पता लगाना मुश्किल था कि आपके गेम के टैब पर फ़ोकस कब नहीं है. Fieldrunners को Chrome 13 से पहले पोर्ट करना शुरू किया गया था. इसमें Page Visibility API ने टैब धुंधला होने का पता लगाने के लिए, हमारे जटिल कोड की जगह ले ली थी. हर गेम को Visibility API का इस्तेमाल करके, एक छोटा स्निपेट लिखना चाहिए. इससे, गेम को पूरी तरह से रोकने के बजाय, उसकी आवाज़ को म्यूट या रोका जा सकता है. Fieldrunners में requestAnimationFrame एपीआई का इस्तेमाल किया गया था. इसलिए, गेम को रोकने की सुविधा को चुपचाप मैनेज किया गया था, लेकिन साउंड को रोकने की सुविधा को नहीं.

आवाज़ें रोकना

इस लेख के बारे में सुझाव, राय या शिकायत मिलने के दौरान, हमें पता चला कि आवाज़ों को रोकने के लिए इस्तेमाल की जा रही तकनीक सही नहीं है. हम आवाज़ों को रोकने के लिए, वेब ऑडियो के मौजूदा वर्शन में मौजूद गड़बड़ी का इस्तेमाल कर रहे थे. आने वाले समय में इस समस्या को ठीक कर दिया जाएगा. इसलिए, वीडियो चलाने की प्रोसेस को रोकने के लिए, किसी नोड या सबग्राफ़ को डिसकनेक्ट करके, सिर्फ़ आवाज़ को रोका नहीं जा सकेगा.

वेब ऑडियो नोड का आसान आर्किटेक्चर

Fieldrunners का ऑडियो मॉडल बहुत आसान है. यह मॉडल, इन सुविधाओं के सेट के साथ काम कर सकता है:

  • साउंड इफ़ेक्ट की आवाज़ कंट्रोल करना.
  • बैकग्राउंड में चल रहे संगीत के ट्रैक की आवाज़ कंट्रोल करना.
  • सभी ऑडियो को म्यूट करें.
  • गेम रोके जाने पर, आवाज़ें चलने की सुविधा बंद करें.
  • गेम फिर से शुरू होने पर, उन आवाज़ों को फिर से चालू करें.
  • गेम के टैब पर फ़ोकस हटने पर, सभी ऑडियो बंद कर दें.
  • ज़रूरत के हिसाब से, आवाज़ चलने के बाद वीडियो चलाना फिर से शुरू करें.

वेब ऑडियो की मदद से ऊपर दी गई सुविधाएं पाने के लिए, इसमें दिए गए तीन नोड का इस्तेमाल किया गया: DestinationNode, GainNode, AudioBufferSourceNode. AudioBufferSourceNodes, आवाज़ें चलाते हैं. GainNodes, AudioBufferSourceNodes को एक-दूसरे से कनेक्ट करते हैं. वेब ऑडियो कॉन्टेक्स्ट से बनाया गया DestinationNode, जिसे डेस्टिनेशन कहा जाता है, प्लेयर के लिए आवाज़ें चलाता है. वेब ऑडियो में कई तरह के नोड होते हैं, लेकिन सिर्फ़ इनकी मदद से ही गेम में साउंड के लिए एक बहुत ही आसान ग्राफ़ बनाया जा सकता है.

नोड ग्राफ़ चार्ट

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

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

function AudioManager() {
  // map for loaded sounds
  this.sounds = {};

  // create our permanent nodes
  this.nodes = {
    destination: this.audioContext.destination,
    masterGain: this.audioContext.createGain(),

    backgroundMusicGain: this.audioContext.createGain(),

    coreEffectsGain: this.audioContext.createGain(),
    effectsGain: this.audioContext.createGain(),
    pausedEffectsGain: this.audioContext.createGain()
  };

  // and setup the graph
  this.nodes.masterGain.connect( this.nodes.destination );

  this.nodes.backgroundMusicGain.connect( this.nodes.masterGain );

  this.nodes.coreEffectsGain.connect( this.nodes.masterGain );
  this.nodes.effectsGain.connect( this.nodes.coreEffectsGain );
  this.nodes.pausedEffectsGain.connect( this.nodes.coreEffectsGain );
}

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

function setArbitraryVolume() {
  var musicGainNode = this.nodes.backgroundMusicGain;

  // set music volume to 50%
  musicGainNode.gain.value = 0.5;
}

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

function arbitraryCrossfade( track1, track2 ) {
  track1.gain.linearRampToValueAtTime( 0, 1 );
  track2.gain.linearRampToValueAtTime( 1, 1 );
}

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

आवाज़ें रोकना

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

AudioManager.prototype.pauseEffects = function() {
  this.nodes.effectsGain.disconnect();
}

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

AudioManager.prototype.resumeEffects = function() {
  this.nodes.effectsGain.connect( this.nodes.coreEffectsGain );
}

Fieldrunners को शिप करने के बाद, हमें पता चला कि किसी एक नोड या सबग्राफ़ को डिसकनेक्ट करने से, AudioBufferSourceNodes का वीडियो चलाना नहीं रुकेगा. हमने WebAudio में मौजूद एक गड़बड़ी का फ़ायदा उठाया है. इस गड़बड़ी की वजह से, फ़िलहाल ग्राफ़ में डेस्टिनेशन नोड से कनेक्ट नहीं किए गए नोड का वीडियो नहीं चलता. इसलिए, यह पक्का करने के लिए कि हम आने वाले समय में इस समस्या को ठीक करने के लिए तैयार हैं, हमें इस तरह का कोड चाहिए:

AudioManager.prototype.pauseEffects = function() {
  this.nodes.effectsGain.disconnect();

  var now = Date.now();
  for ( var name in this.sounds ) {
    var sound = this.sounds[ name ];

    if ( !sound.ignorePause && ( now - sound.source.noteOnAt < sound.buffer.duration * 1000 ) ) {
      sound.pausedAt = now - sound.source.noteOnAt;
      sound.source.noteOff();
    }
  }
}

AudioManager.prototype.resumeEffects = function() {
  this.nodes.effectsGain.connect( this.nodes.coreEffectsGain );

  var now = Date.now();
  for ( var name in this.sounds ) {
    if ( sound.pausedAt ) {
      this.play( sound.name );
      delete sound.pausedAt;
    }
  }
};

अगर हमें पहले पता होता कि हम किसी गड़बड़ी का गलत इस्तेमाल कर रहे हैं, तो हमारे ऑडियो कोड का स्ट्रक्चर बहुत अलग होता. इसलिए, इस लेख के कई सेक्शन पर इसका असर पड़ा है. इसका सीधा असर यहां पड़ता है. साथ ही, Losing Focus और Give Me a Beat में मौजूद हमारे कोड स्निपेट पर भी असर पड़ता है. यह जानने के लिए कि यह सुविधा कैसे काम करती है, आपको Fieldrunners के नोड ग्राफ़ (क्योंकि हमने वीडियो चलाने के समय को कम करने के लिए नोड बनाए हैं) और उस अतिरिक्त कोड, दोनों में बदलाव करने होंगे जो रोके गए स्टेटस को रिकॉर्ड और उपलब्ध कराएगा. वेब ऑडियो अपने-आप ऐसा नहीं करता.

Losing Focus

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

Fieldrunners सिर्फ़ ऐक्टिव टैब के तौर पर चलेगा. ऐसा इसलिए, क्योंकि इसके अपडेट लूप को कॉल करने के लिए requestAnimationFrame का इस्तेमाल किया जाता है. हालांकि, जब कोई उपयोगकर्ता किसी दूसरे टैब पर होगा, तब वेब ऑडियो कॉन्टेक्स्ट, लूप किए गए इफ़ेक्ट और बैकग्राउंड ट्रैक चलाता रहेगा. हालांकि, Visibility API के बारे में जानकारी देने वाले छोटे स्निपेट की मदद से, इस समस्या को रोका जा सकता है.

function AudioManager() {
  // map and node setup
  // ...

  // disable all sound when on other tabs
  var self = this;
  window.addEventListener( 'webkitvisibilitychange', function( e ) {
    if ( document.webkitHidden ) {
      self.nodes.masterGain.disconnect();

      // As noted in Pausing Sounds disconnecting isn't enough.
      // For Fieldrunners calling our new pauseEffects method would be
      // enough to accomplish that, though we may still need some logic
      // to not resume if already paused.
      self.pauseEffects();
    } else {
      self.nodes.masterGain.connect( this.nodes.destination );
      self.resumeEffects();
    }
  });
}

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

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

Give Me a Beat

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

Fieldrunners में, किसी गेम इकाई के कई इंस्टेंस के लिए साउंड की कई कॉपी चलाने के बजाय, गेम के दौरान सिर्फ़ एक बार साउंड चलाया जाता है. जैसे, किसी कैरेक्टर की मौत होने पर. अगर साउंड खत्म होने के बाद उसे फिर से चलाना है, तो ऐसा किया जा सकता है. हालांकि, साउंड चलने के दौरान ऐसा नहीं किया जा सकता. यह फ़ील्डरनर के ऑडियो डिज़ाइन के लिए एक फ़ैसला है, क्योंकि इसमें ऐसी आवाज़ें हैं जिन्हें तेज़ी से चलाने का अनुरोध किया जाता है. अगर उन्हें फिर से चलाने की अनुमति दी जाती है, तो वे रुक-रुककर चलती हैं या एक से ज़्यादा बार चलाने पर, वे अप्रिय शोर पैदा करती हैं. AudioBufferSourceNodes का इस्तेमाल, एक बार इस्तेमाल होने वाले ऑडियो के तौर पर किया जाना चाहिए. कोई नोड बनाएं, बफ़र अटैच करें, ज़रूरत पड़ने पर लूप बूलियन वैल्यू सेट करें, ग्राफ़ पर उस नोड से कनेक्ट करें जो डेस्टिनेशन पर ले जाएगा, noteOn या noteGrainOn को कॉल करें, और ज़रूरत पड़ने पर noteOff को कॉल करें.

Fieldrunners के लिए, यह कुछ ऐसा दिखता है:

AudioManager.prototype.play = function( options ) {
  var now = Date.now(),
    // pull from a map of loaded audio buffers
    sound = this.sounds[ options.name ],
    channel,
    source,
    resumeSource;

  if ( !sound ) {
    return;
  }

  if ( sound.source ) {
    var source = sound.source;
    if ( !options.loop && now - source.noteOnAt > sound.buffer.duration * 1000 ) {
      // discard the previous source node
      source.stop( 0 );
      source.disconnect();
    } else {
      return;
    }
  }

  source = this.audioContext.createBufferSource();
  sound.source = source;
  // track when the source is started to know if it should still be playing
  source.noteOnAt = now;

  // help with pausing
  sound.ignorePause = !!options.ignorePause;

  if ( options.ignorePause ) {
    channel = this.nodes.pausedEffectsGain;
  } else {
    channel = this.nodes.effectsGain;
  }

  source.buffer = sound.buffer;
  source.connect( channel );
  source.loop = options.loop || false;

  // Fieldrunners' current code doesn't consider sound.pausedAt.
  // This is an added section to assist the new pausing code.
  if ( sound.pausedAt ) {
    source.start( ( sound.buffer.duration * 1000 - sound.pausedAt ) / 1000 );
    source.noteOnAt = now + sound.buffer.duration * 1000 - sound.pausedAt;

    // if you needed to precisely stop sounds, you'd want to store this
    resumeSource = this.audioContext.createBufferSource();
    resumeSource.buffer = sound.buffer;
    resumeSource.connect( channel );
    resumeSource.start(
      0,
      sound.pausedAt,
      sound.buffer.duration - sound.pausedAt / 1000
    );
  } else {
    // start play immediately with a value of 0 or less
    source.start( 0 );
  }
}

बहुत ज़्यादा स्ट्रीमिंग

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

सभी साउंड इफ़ेक्ट, Web Audio की मदद से चलाए जा रहे थे. इसलिए, हमने बैकग्राउंड संगीत को भी Web Audio पर चलाने का विकल्प जोड़ा है. इसका मतलब है कि हम ट्रैक को उसी तरह लोड करेंगे जिस तरह हमने XMLHttpRequests और arraybuffer रिस्पॉन्स टाइप के साथ सभी इफ़ेक्ट लोड किए थे.

AudioManager.prototype.load = function( options ) {
  var xhr,
      // pull from a map of name, object pairs
      sound = this.sounds[ options.name ];

  if ( sound ) {
    // this is a great spot to add success methods to a list or use promises
    // for handling the load event or call success if already loaded
    if ( sound.buffer && options.success ) {
      options.success( options.name );
    } else if ( options.success ) {
      sound.success.push( options.success );
    }

    // one buffer is enough so shortcut here
    return;
  }

  sound = {
    name: options.name,
    buffer: null,
    source: null,
    success: ( options.success ? [ options.success ] : [] )
  };
  this.sounds[ options.name ] = sound;

  xhr = new XMLHttpRequest();
  xhr.open( 'GET', options.path, true );
  xhr.responseType = 'arraybuffer';
  xhr.onload = function( e ) {
    sound.buffer = self._context.createBuffer( xhr.response, false );

    // call all waiting handlers
    sound.success.forEach( function( success ) {
      success( sound.name );
    });
    delete sound.success;
  };
  xhr.onerror = function( e ) {

    // failures are uncommon but you want to do deal with them

  };
  xhr.send();
}

खास जानकारी

Fieldrunners को Chrome और एचटीएमएल5 पर उपलब्ध कराना बहुत मज़ेदार रहा. C++ की हज़ारों लाइनों को JavaScript में बदलने के अलावा, HTML5 के लिए कुछ दिलचस्प समस्याएं और फ़ैसले भी सामने आते हैं. AudioBufferSourceNodes, एक बार इस्तेमाल होने वाले ऑब्जेक्ट होते हैं. उन्हें बनाएं, ऑडियो बफ़र अटैच करें, उसे वेब ऑडियो ग्राफ़ से कनेक्ट करें, और noteOn या noteGrainOn के साथ चलाएं. क्या आपको उस आवाज़ को फिर से चलाना है? इसके बाद, एक और AudioBufferSourceNode बनाएं.