কেস স্টাডি - ওয়েব অডিও সহ HTML5 গেমের গল্প

Fieldrunners

Fieldrunners স্ক্রিনশট
Fieldrunners স্ক্রিনশট

Fieldrunners হল একটি পুরস্কারপ্রাপ্ত টাওয়ার-ডিফেন্স স্টাইলের গেম যা মূলত ২০০৮ সালে iPhone-এর জন্য রিলিজ করা হয়েছিল। তারপর থেকে এটি আরও অনেক প্ল্যাটফর্মে পোর্ট করা হয়েছে। সবচেয়ে সাম্প্রতিক প্ল্যাটফর্মগুলির মধ্যে একটি হল ২০১১ সালের অক্টোবর মাসে লঞ্চ হওয়া Chrome ব্রাউজার। Fieldrunners-কে HTML5 প্ল্যাটফর্মে পোর্ট করার সময় অন্যতম চ্যালেঞ্জ ছিল সাউন্ড কীভাবে প্লে করা হবে।

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

কিছু চ্যালেঞ্জ দেখা গেছে

Fieldrunners-কে HTML5-এ পোর্ট করার সময় আমরা অডিও ট্যাগের মাধ্যমে অডিও প্লেব্যাক সংক্রান্ত সমস্যার সম্মুখীন হয়েছিলাম এবং শুরুতেই Web Audio API-এর উপর ফোকাস করার সিদ্ধান্ত নিয়েছিলাম। WebAudio ব্যবহার করার ফলে আমরা অনেক সমস্যার সমাধান করতে পেরেছি, যেমন Fieldrunners-এর জন্য প্রয়োজনীয় অনেক সংখ্যক কনকারেন্ট এফেক্ট প্লেব্যাক করা। তবুও, Fieldrunners HTML5-এর জন্য অডিও সিস্টেম ডেভেলপ করার সময় আমরা কিছু সূক্ষ্ম সমস্যার সম্মুখীন হয়েছি যা অন্যান্য ডেভেলপাররা জানতে চাইতে পারেন।

AudioBufferSourceNodes-এর প্রকৃতি

WebAudio-এর মাধ্যমে সাউন্ড চালানোর প্রাথমিক পদ্ধতি হল AudioBufferSourceNodes। এটি বোঝা খুবই গুরুত্বপূর্ণ যে এগুলি একবারই ব্যবহার করা যায়। আপনি একটি AudioBufferSourceNode তৈরি করেন, এটিকে একটি বাফার অ্যাসাইন করেন, এটিকে গ্রাফের সাথে কানেক্ট করেন এবং noteOn বা noteGrainOn-এর মাধ্যমে এটি চালান। এর পরে, আপনি প্লেব্যাক বন্ধ করতে noteOff কল করতে পারেন, তবে আপনি noteOn বা noteGrainOn কল করে আবার সোর্স চালাতে পারবেন না - আপনাকে আরেকটি AudioBufferSourceNode তৈরি করতে হবে। আপনি - এবং এটিই হল মূল বিষয় - একই অন্তর্নিহিত AudioBuffer অবজেক্ট আবার ব্যবহার করতে পারবেন, তবে (আসলে, আপনার কাছে একাধিক অ্যাক্টিভ AudioBufferSourceNode থাকতে পারে যা একই AudioBuffer ইনস্ট্যান্সকে পয়েন্ট করে!)। আপনি Give Me a Beat-এ Fieldrunners থেকে প্লেব্যাক স্নিপেট খুঁজে পেতে পারেন।

ক্যাশে করা যায় না এমন কন্টেন্ট

রিলিজের সময় Fieldrunners HTML5 সার্ভার মিউজিক ফাইলের জন্য প্রচুর সংখ্যক অনুরোধ দেখিয়েছে। Chrome 15-এ ফাইলটি চাঙ্ক হিসেবে ডাউনলোড করার পরে ক্যাশে না করার কারণে এই ফলাফল দেখা গেছে। সেই সময় আমরা সিদ্ধান্ত নিয়েছিলাম যে আমাদের অন্যান্য অডিও ফাইলের মতো মিউজিক ফাইলও লোড করব। এটি করা সাবঅপ্টিমাল হলেও অন্যান্য ব্রাউজারের কিছু ভার্সন এখনও এটি করে।

ফোকাসের বাইরে থাকলে সাইলেন্ট করা হচ্ছে

আপনার গেমের ট্যাব কখন ফোকাসের বাইরে চলে গেছে তা আগে শনাক্ত করা কঠিন ছিল। Chrome 13-এর আগে Fieldrunners পোর্ট করা শুরু হয় যেখানে ট্যাব ব্লার শনাক্ত করার জন্য আমাদের জটিল কোডের প্রয়োজনীয়তা Page Visibility API দ্বারা প্রতিস্থাপিত হয়। প্রতিটি গেমের ক্ষেত্রে Visibility API ব্যবহার করে একটি ছোট স্নিপেট লিখতে হবে যাতে পুরো গেম পজ না করা হলেও সাউন্ড মিউট বা পজ করা যায়। Fieldrunners যেহেতু requestAnimationFrame API ব্যবহার করেছে, তাই গেম পজ করার বিষয়টি অন্তর্নিহিতভাবে ম্যানেজ করা হয়েছে, তবে সাউন্ড পজ করার বিষয়টি নয়।

সাউন্ড পজ করা হচ্ছে

মজার ব্যাপার হল, এই নিবন্ধে প্রতিক্রিয়া পাওয়ার সময় আমাদের জানানো হয়েছিল যে আমরা সাউন্ড পজ করার জন্য যে কৌশলটি ব্যবহার করছি তা উপযুক্ত নয় - আমরা সাউন্ডের প্লেব্যাক পজ করার জন্য ওয়েব অডিওর বর্তমান বাস্তবায়নে একটি বাগ ব্যবহার করছি। ভবিষ্যতে এই সমস্যার সমাধান করা হবে, তাই প্লেব্যাক বন্ধ করার জন্য আপনি শুধু নোড বা সাবগ্রাফ ডিসকানেক্ট করে সাউন্ড পজ করতে পারবেন না।

একটি সহজ Web Audio নোড আর্কিটেকচার

Fieldrunners-এর অডিও মডেল খুব সহজ। সেই মডেলে নিম্নলিখিত ফিচারের সেট কাজ করে:

  • সাউন্ড এফেক্টের ভলিউম নিয়ন্ত্রণ করা।
  • ব্যাকগ্রাউন্ড মিউজিক ট্র্যাকের ভলিউম কন্ট্রোল করুন।
  • সব অডিও মিউট করুন।
  • গেম পজ করা থাকলে সাউন্ড বাজানো বন্ধ করুন।
  • গেম আবার চালু হলে সেই একই সাউন্ড আবার চালু করুন।
  • গেমের ট্যাব থেকে ফোকাস সরে গেলে সব অডিও বন্ধ করে দিন।
  • প্রয়োজন অনুযায়ী সাউন্ড চালানোর পরে প্লেব্যাক রিস্টার্ট করুন।

Web Audio-এর মাধ্যমে উপরের ফিচারগুলি পেতে, এটি প্রদত্ত সম্ভাব্য নোডের মধ্যে ৩টি ব্যবহার করেছে: DestinationNode, GainNode, AudioBufferSourceNode. AudioBufferSourceNodes সাউন্ড চালায়। GainNodes, AudioBufferSourceNodes-কে একসাথে কানেক্ট করে। DestinationNode, যা Web Audio কনটেক্সট দ্বারা তৈরি করা হয়েছে, যাকে ডেস্টিনেশন বলা হয়, প্লেয়ারের জন্য সাউন্ড প্লে করে। ওয়েব অডিওতে আরও অনেক ধরনের নোড আছে, তবে শুধু এগুলি ব্যবহার করেই আমরা গেমে সাউন্ডের জন্য খুব সহজ গ্রাফ তৈরি করতে পারি।

নোড গ্রাফ চার্ট

একটি ওয়েব অডিও নোড গ্রাফ লিফ নোড থেকে ডেস্টিনেশন নোড পর্যন্ত যায়। 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 );
}

বেশিরভাগ গেমে সাউন্ড এফেক্ট ও মিউজিক আলাদাভাবে কন্ট্রোল করা যায়। উপরের গ্রাফের সাহায্যে এটি সহজেই করা যেতে পারে। প্রতিটি গেইন নোডে একটি "গেইন" অ্যাট্রিবিউট থাকে যা ০ থেকে ১-এর মধ্যে যেকোনও দশমিক মানে সেট করা যায়, যা মূলত ভলিউম কন্ট্রোল করতে ব্যবহার করা যায়। যেহেতু আমরা মিউজিক ও সাউন্ড এফেক্ট চ্যানেলের ভলিউম আলাদাভাবে কন্ট্রোল করতে চাই, তাই আমাদের কাছে প্রতিটির জন্য একটি করে গেইন নোড আছে যেখানে আমরা সেগুলির ভলিউম কন্ট্রোল করতে পারি।

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

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

আমরা এই একই ক্ষমতা ব্যবহার করে সাউন্ড এফেক্ট ও মিউজিক সহ সবকিছুর ভলিউম কন্ট্রোল করতে পারি। মাস্টার নোডের গেন সেট করলে তা গেমের সব সাউন্ডকে প্রভাবিত করবে। আপনি গেন ভ্যালু ০ হিসেবে সেট করলে, সাউন্ড ও মিউজিক মিউট হয়ে যাবে। AudioBufferSourceNodes-এ একটি গেইন প্যারামিটারও আছে। আপনি সব সাউন্ডের তালিকা ট্র্যাক করতে এবং সামগ্রিক ভলিউমের জন্য আলাদাভাবে তাদের গেইন ভ্যালু অ্যাডজাস্ট করতে পারবেন। আপনি যদি অডিও ট্যাগ ব্যবহার করে সাউন্ড এফেক্ট তৈরি করেন, তাহলে আপনাকে এটি করতে হবে। পরিবর্তে, Web Audio-এর নোড গ্রাফ অসংখ্য সাউন্ডের ভলিউম পরিবর্তন করা অনেক সহজ করে তোলে। এইভাবে ভলিউম কন্ট্রোল করলে, আপনি কোনও জটিলতা ছাড়াই অতিরিক্ত পাওয়ার পাবেন। মিউজিক চালানোর জন্য এবং এর নিজস্ব গেইন কন্ট্রোল করার জন্য আমরা সরাসরি মাস্টার নোডে একটি 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;
    }
  }
};

আমরা যদি আগে জানতাম যে আমরা একটি বাগের অপব্যবহার করছি, তাহলে আমাদের অডিও কোডের স্ট্রাকচার খুব আলাদা হতো। এই কারণে, এই নিবন্ধের বেশ কয়েকটি বিভাগ প্রভাবিত হয়েছে। এটি এখানে সরাসরি প্রভাব ফেলে, তবে আমাদের কোড স্নিপেট, লুজিং ফোকাস এবং গিভ মি আ বিট-এও প্রভাব ফেলে। এটি আসলে কীভাবে কাজ করে তা জানতে Fieldrunners নোড গ্রাফ (যেহেতু আমরা প্লেব্যাক শর্ট করার জন্য নোড তৈরি করেছি) এবং অতিরিক্ত কোড যা পজ করা স্ট্যাটাস রেকর্ড করবে এবং প্রদান করবে যা Web Audio নিজে থেকে করে না, এই দুটিতেই পরিবর্তন করতে হবে।

মনোযোগ হারিয়ে ফেলা

এই ফিচারের জন্য আমাদের মাস্টার নোড কাজ করে। ব্রাউজার ব্যবহারকারী অন্য ট্যাবে গেলে, গেমটি আর দেখা যায় না। চোখের আড়াল মানেই মনের আড়াল, তাই সাউন্ডও বন্ধ হয়ে যাওয়া উচিত। গেমের পৃষ্ঠার জন্য নির্দিষ্ট দৃশ্যমানতা স্ট্যাটাস নির্ধারণ করার জন্য কিছু কৌশল আছে, তবে Visibility API-এর মাধ্যমে এটি অনেক সহজ হয়ে গেছে।

Fieldrunners শুধুমাত্র অ্যাক্টিভ ট্যাব হিসেবে চলবে, কারণ এর আপডেট লুপ কল করার জন্য requestAnimationFrame ব্যবহার করা হয়। কিন্তু ব্যবহারকারী অন্য ট্যাবে থাকলেও, ওয়েব অডিও কনটেক্সট লুপ করা এফেক্ট ও ব্যাকগ্রাউন্ড ট্র্যাক চালাতে থাকবে। কিন্তু আমরা খুব ছোট একটি ভিজিবিলিটি 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();
    }
  });
}

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

আপনার ট্যাব কখন আর ফোকাসে নেই তা জানা Page Visibility API-এর মাধ্যমে খুব সহজ হয়ে যায়। সাউন্ড পজ করার জন্য আপনার কাছে আগে থেকেই কার্যকর কোড থাকলে, গেম ট্যাব লুকানো থাকলে সাউন্ড পজ করার জন্য মাত্র কয়েকটি লাইন লিখতে হবে।

আমাকে একটা বিট দাও

আমরা এখন কিছু জিনিস সেট-আপ করেছি। আমাদের কাছে নোডের একটি গ্রাফ আছে। প্লেয়ার গেম পজ করলে আমরা সাউন্ড পজ করতে পারি এবং গেম মেনুর মতো এলিমেন্টের জন্য নতুন সাউন্ড চালাতে পারি। ব্যবহারকারী নতুন ট্যাবে গেলে আমরা সব সাউন্ড ও মিউজিক পজ করতে পারি। এখন আমাদের সত্যিই একটি সাউন্ড চালাতে হবে।

কোনও চরিত্রের মৃত্যুর মতো গেমের কোনও ঘটনার একাধিক উদাহরণের জন্য সাউন্ডের একাধিক কপি চালানোর পরিবর্তে, Fieldrunners তার সময়সীমার জন্য একবারই একটি সাউন্ড চালায়। সাউন্ড শেষ হওয়ার পরে আবার প্রয়োজন হলে, এটি রিস্টার্ট করা যেতে পারে, তবে সাউন্ড চলাকালীন নয়। 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 স্ট্রিম করা ট্র্যাক ক্যাশে করেছে, তবে অন্যান্য ব্রাউজার এখনও এটি নাও করতে পারে। মিউজিক প্লেব্যাকের মতো কার্যকারিতার জন্য অডিও ট্যাগ সহ বড় অডিও ফাইল স্ট্রিম করা ভালো, তবে কিছু ব্রাউজার ভার্সনের জন্য আপনি সাউন্ড এফেক্ট লোড করার মতো একইভাবে আপনার মিউজিক লোড করতে চাইতে পারেন।

যেহেতু সব সাউন্ড এফেক্ট ওয়েব অডিওর মাধ্যমে চালানো হচ্ছিল, তাই আমরা ব্যাকগ্রাউন্ড মিউজিকও ওয়েব অডিওতে চালানোর ব্যবস্থা করেছি। এর অর্থ হল, 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 ও HTML5-এ নিয়ে আসাটা ছিল দারুণ ব্যাপার। হাজার হাজার C++ লাইনকে জাভাস্ক্রিপ্টে নিয়ে আসার মতো বিশাল কাজ ছাড়াও, HTML5-এর ক্ষেত্রে কিছু আকর্ষণীয় দ্বিধা ও সিদ্ধান্ত নেওয়ার মতো পরিস্থিতি তৈরি হয়। অন্য কোনওটি না থাকলে, AudioBufferSourceNodes হল একবার ব্যবহার করার অবজেক্ট। সেগুলি তৈরি করুন, একটি অডিও বাফার অ্যাটাচ করুন, এটিকে ওয়েব অডিও গ্রাফের সাথে কানেক্ট করুন এবং noteOn বা noteGrainOn-এর সাথে চালান। এই সাউন্ড আবার চালাতে চান? তারপর অন্য একটি AudioBufferSourceNode তৈরি করুন।