กรณีศึกษา - เรื่องเล่าของเกม HTML5 พร้อม Web Audio

ฟิลด์รันเนอร์

ภาพหน้าจอ Fieldrunners
ภาพหน้าจอของ Fieldrunners

Fieldrunners เป็นเกมสไตล์เกมป้องกันเมืองที่ได้รับรางวัล เปิดตัวครั้งแรกใน iPhone เมื่อปี 2008 นับจากนั้นมาก็มีการย้ายแพลตฟอร์มไปยังแพลตฟอร์มอื่นๆ อีกหลายแห่ง หนึ่งในแพลตฟอร์มล่าสุดคือเบราว์เซอร์ Chrome ในเดือนตุลาคม 2011 ความท้าทายอย่างหนึ่งของการย้าย Fieldrunners ไปยังแพลตฟอร์ม HTML5 คือวิธีการเล่นเสียง

Fieldrunners ไม่ได้ใช้เอฟเฟ็กต์เสียงที่ซับซ้อน แต่ก็มาพร้อมความคาดหวังบางอย่างเกี่ยวกับวิธีที่สามารถโต้ตอบกับเอฟเฟกต์เสียงได้ เกมมีเอฟเฟกต์เสียง 88 แบบ ซึ่งคาดว่าจะเล่นได้ในครั้งเดียวเป็นจำนวนมาก เสียงเหล่านี้ส่วนใหญ่สั้นมากและต้องเล่นอย่างทันท่วงทีเท่าที่จะเป็นไปได้ เพื่อหลีกเลี่ยงความไม่ต่อเนื่องในการนำเสนอแบบกราฟิก

ระบบแสดงการท้าบางอย่าง

ในขณะที่ย้าย Fieldrunners ไปใช้ HTML5 เราพบปัญหาเกี่ยวกับการเล่นเสียงด้วยแท็กเสียง แต่ก่อนอื่นตัดสินใจมุ่งเน้นที่ Web Audio API แทน การใช้ WebAudio ช่วยเราแก้ปัญหา เช่น ให้เอฟเฟกต์ที่ทำงานพร้อมกันในปริมาณมากที่ Fieldrunners ต้องการ ถึงกระนั้น ระหว่างการพัฒนาระบบเสียงสำหรับ Fieldrunners HTML5 นั้น เราเจอปัญหาเล็กๆ น้อยๆ ที่นักพัฒนาซอฟต์แวร์รายอื่นๆ อาจจะต้องทราบอยู่บ้าง

ลักษณะของ AudioBufferSourceNodes

AudioBufferSourceNodes เป็นวิธีหลักของคุณในการเล่นเสียงด้วย WebAudio ทั้งนี้คุณต้องเข้าใจว่าสิ่งเหล่านี้เป็นออบเจ็กต์ที่ใช้ครั้งเดียว คุณสร้าง AudioBufferSourceNode กำหนดบัฟเฟอร์ เชื่อมต่อกับกราฟ และเล่นด้วยnoteOn หรือnoteGrainOn หลังจากนั้นคุณสามารถเรียก notesOff เพื่อหยุดเล่นได้ แต่คุณจะไม่สามารถเล่นแหล่งที่มาอีกครั้งโดยการเรียกใช้ notesOn หรือ notesGrainOn ได้ คุณต้องสร้าง AudioBufferSourceNode อื่น แต่คุณสามารถใช้อ็อบเจ็กต์ AudioBuffer เบื้องหลังเดิมได้ อย่างไรก็ตาม คุณสามารถใช้ AudioBufferSourceNodes ที่ใช้งานอยู่หลายรายการที่ชี้ไปยังอินสแตนซ์ AudioBuffer เดียวกันได้ด้วย) คุณสามารถค้นหาตัวอย่างการเล่นจาก Fieldrunners ได้ใน Tell Me a Beat

เนื้อหาที่ไม่มีการแคช

ในช่วงเปิดตัว เซิร์ฟเวอร์ HTML5 ของ Fieldrunners แสดงคําขอไฟล์เพลงจํานวนมาก ผลลัพธ์นี้เกิดขึ้นจากการที่ Chrome 15 ดำเนินการดาวน์โหลดไฟล์เป็นส่วนๆ แล้วไม่นำไปแคช เพื่อเป็นการตอบสนองในตอนนั้น เราจึงตัดสินใจโหลดไฟล์เพลงเหมือนไฟล์เสียงอื่นๆ ของเรา การทำเช่นนี้มีประสิทธิภาพต่ำ แต่เบราว์เซอร์อื่นๆ บางเวอร์ชันก็ยังคงเป็นเช่นนั้น

ปิดเสียงเมื่อหลุดโฟกัส

ก่อนหน้านี้การตรวจหาเมื่อแท็บเกมหลุดโฟกัสเป็นเรื่องยาก Fieldrunners เริ่มย้ายก่อน Chrome 13 ซึ่ง Page visibility API แทนที่ความจำเป็นในการใช้โค้ดที่ซับซ้อนเพื่อตรวจหาการเบลอแท็บ ทุกเกมควรใช้ visibility API ในการเขียนตัวอย่างสั้นๆ เพื่อปิดเสียงหรือหยุดชั่วคราว หากไม่ได้หยุดเล่นทั้งเกม เนื่องจาก Fieldrunners ใช้ requestAnimationFrame API จึงมีการจัดการการหยุดเกมชั่วคราวโดยปริยาย แต่ไม่ใช่การหยุดเสียงชั่วคราว

การหยุดเสียงชั่วคราว

แต่แปลกประหลาดในขณะที่ได้รับความคิดเห็นในบทความนี้ เราได้รับแจ้งว่าเทคนิคที่เราใช้สำหรับการหยุดเสียงชั่วคราวนั้นไม่เหมาะสม โดยเราใช้ข้อบกพร่องในการใช้งานปัจจุบันของ Web Audio เพื่อหยุดเล่นเสียงชั่วคราว เนื่องจากปัญหานี้จะได้รับการแก้ไขในอนาคต คุณจึงไม่สามารถหยุดเสียงชั่วคราวโดยการยกเลิกการเชื่อมต่อโหนดหรือกราฟย่อยเพื่อหยุดการเล่น

สถาปัตยกรรมโหนดเสียงบนเว็บแบบง่าย

Fieldrunners มีโมเดลเสียงที่เรียบง่ายมาก โมเดลดังกล่าวรองรับชุดฟีเจอร์ต่อไปนี้ได้

  • ควบคุมระดับเสียงของเอฟเฟกต์เสียง
  • ควบคุมระดับเสียงของแทร็กเพลงพื้นหลัง
  • ปิดเสียงทั้งหมด
  • ปิดเสียงการเล่นเสียงเมื่อเกมหยุดชั่วคราว
  • เปิดเสียงเดียวกันนี้อีกครั้งเมื่อเกมกลับมาเล่นต่อ
  • ปิดเสียงทั้งหมดเมื่อแท็บเกมขาดโฟกัส
  • เริ่มเล่นอีกครั้งหลังจากเล่นเสียงตามที่ต้องการ

ในการใช้ฟีเจอร์ข้างต้นด้วย Web Audio ระบบจึงใช้โหนดที่เป็นไปได้ 3 โหนด ได้แก่ ปลายทาง Node, GETNode, AudioBufferSourceNode AudioBufferSourceNodes จะเล่นเสียง GETNodes จะเชื่อมต่อ AudioBufferSourceNodes เข้าด้วยกัน ปลายทางโหนดที่สร้างขึ้นจากบริบท Web Audio ซึ่งเรียกว่าปลายทางจะเล่นเสียงสำหรับโปรแกรมเล่น Web Audio มีโหนดหลายประเภท แต่มีเพียงโหนดเหล่านี้เท่านั้น เราจึงสามารถสร้างกราฟที่ง่ายมากสำหรับเสียงในเกม

แผนภูมิโหนดกราฟ

กราฟโหนด Web Audio จะนําจากโหนด Leaf ไปยังโหนดปลายทาง Fieldrunners ใช้โหนดถาวร 6 โหนด แต่จำนวน 3 โหนดก็เพียงพอแล้วที่จะช่วยให้ควบคุมระดับเสียงได้ง่ายและเชื่อมต่อโหนดชั่วคราวจำนวนมากขึ้นซึ่งจะบัฟเฟอร์การเล่น ขั้นแรก โหนดหลักจะได้โหนดที่แนบโหนดย่อยทุกโหนดไปยังปลายทาง โหนดเกนหลักที่แนบกับโหนดหลักจะมีโหนดเกน 2 โหนด โดยโหนดหนึ่งใช้กับช่องเพลงและอีกโหนดหนึ่งเพื่อลิงก์เอฟเฟกต์เสียงทั้งหมด

Fieldrunners มีโหนดที่ได้รับเพิ่ม 3 โหนดเนื่องจากการใช้ข้อบกพร่องเป็นฟีเจอร์อย่างไม่ถูกต้อง เราใช้โหนดเหล่านั้นเพื่อตัดกลุ่มเสียงที่เล่นเสียงจากกราฟซึ่งหยุดยั้งการพัฒนาของเสียงเหล่านั้น เราดำเนินการนี้เพื่อหยุดเสียงชั่วคราว เนื่องจากไม่ถูกต้อง ตอนนี้เราจะใช้โหนดที่ได้รับทั้งหมดเพียง 3 โหนดตามที่อธิบายไว้ข้างต้น ข้อมูลโค้ดจำนวนมากต่อไปนี้จะมีโหนดที่ไม่ถูกต้อง ซึ่งแสดงสิ่งที่เราทำ และวิธีแก้ไขในระยะสั้น แต่ในระยะยาว คุณไม่ต้องการใช้โหนดหลังจากโหนด CoreEffectsGET ของเรา

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

เกมส่วนใหญ่อนุญาตให้ควบคุมเอฟเฟกต์เสียงและเพลงแยกกันได้ ซึ่งสามารถทำได้ง่ายๆ ด้วยกราฟด้านบนของเรา โหนดเกนแต่ละโหนดมีแอตทริบิวต์ "gain" ที่สามารถตั้งค่าเป็นค่าทศนิยมระหว่าง 0 ถึง 1 ซึ่งสามารถใช้ควบคุมระดับเสียงเป็นหลัก เนื่องจากเราต้องการควบคุมระดับเสียงของช่องเพลงและเอฟเฟกต์เสียงแยกกัน เราจึงมีโหนดเกนสำหรับแต่ละช่องที่เราควบคุมระดับเสียงได้

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

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

เราสามารถใช้ความสามารถเดียวกันนี้ในการควบคุมระดับเสียงของทุกอย่าง เอฟเฟกต์เสียง และเพลง การตั้งค่าอัตราขยายของโหนดหลักจะส่งผลต่อเสียงทั้งหมดจากเกม หากคุณกำหนดค่าเกนเป็น 0 จะเป็นการปิดเสียงและเพลง AudioBufferSourceNodes มีพารามิเตอร์เกนด้วย คุณสามารถติดตามรายการเสียงทั้งหมดที่กำลังเล่น แล้วปรับค่าเกนสำหรับระดับเสียงโดยรวมทีละรายการ หากคุณสร้างเอฟเฟ็กต์เสียงด้วยแท็กเสียง สิ่งที่คุณจะต้องทำก็คือ กราฟโหนดของ Web Audio จะช่วยให้คุณแก้ไขระดับเสียงนับไม่ถ้วนของเสียงได้ง่ายขึ้นมาก การควบคุมระดับเสียงด้วยวิธีนี้ยังช่วยเพิ่มพลังให้คุณโดยไม่ยุ่งยากอีกด้วย เราเพียงแนบ AudioBufferSourceNode กับโหนดหลักโดยตรงสำหรับการเล่นเพลงและควบคุมอัตราขยายของโหนดเอง แต่คุณจะต้องตั้งค่านี้ทุกครั้งที่สร้าง AudioBufferSourceNode เพื่อจุดประสงค์ในการเล่นเพลง โดยคุณจะเปลี่ยนโหนดเพียงโหนดเดียวต่อเมื่อโปรแกรมเล่นเปลี่ยนระดับเสียงเพลงและเมื่อเปิดแอปเท่านั้น ตอนนี้เราก็ได้รับมูลค่าเพิ่มในแหล่งที่มาของบัฟเฟอร์เพื่อไปทำอย่างอื่นแล้ว สำหรับเพลง การใช้งานโดยทั่วไปอาจใช้สำหรับการสร้างแทร็กเสียงแบบครอสเฟดจากแทร็กเสียงหนึ่งไปอีกแทร็กหนึ่งเมื่อเสียงอื่นเข้ามาแทนที่ Web Audio เป็นวิธีที่ดีเพื่อให้ดำเนินการนี้ได้อย่างง่ายดาย

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

Fieldrunners ไม่ได้ใช้ครอสเฟดโดยเฉพาะ เราน่าจะรู้จักฟังก์ชันการตั้งค่าค่าของ WebAudio ระหว่างการส่งระบบเสียงต้นฉบับของเรา

การหยุดเสียงชั่วคราว

เมื่อเกมหยุดเล่นชั่วคราว อาจมีเสียงบางอย่างที่ยังเล่นอยู่ เสียงเป็นส่วนประกอบสำคัญในเสียงตอบรับที่ดีจากการกดองค์ประกอบอินเทอร์เฟซผู้ใช้ทั่วไปในเมนูเกม เนื่องจาก Fieldrunners มีอินเทอร์เฟซให้ผู้ใช้โต้ตอบกับขณะหยุดเกมชั่วคราว เราก็ยังต้องการให้ผู้เล่นเหล่านั้นเล่นอยู่ อย่างไรก็ตาม เราไม่ต้องการให้มีเสียงยาวๆ หรือวนซ้ำดังเดิม การหยุดเสียงเหล่านั้นด้วย Web Audio นั้นทำได้ไม่ยากเลย

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 and Tell Me a Beat คุณต้องเปลี่ยนแปลงทั้งกราฟโหนดของ Fieldrunners (เนื่องจากเราได้สร้างโหนดสำหรับการสั้นลง) และโค้ดเพิ่มเติมที่จะบันทึกและระบุสถานะที่หยุดชั่วคราวซึ่ง Web Audio ไม่ได้ทำด้วยตัวเอง

สูญเสียโฟกัส

โดยโหนดหลักของฟีเจอร์นี้จะเข้ามามีบทบาท เมื่อผู้ใช้เบราว์เซอร์เปลี่ยนไปใช้แท็บอื่น เกมจะมองไม่เห็นอีกต่อไป อยู่นอกสายตา ไม่สนใจ เสียงนั้นควรหายไป เรามีกลเม็ดหลายอย่างที่สามารถทำได้เพื่อระบุสถานะการเปิดเผยที่เจาะจงสำหรับหน้าเกม แต่วิธีนี้จะง่ายขึ้นมากเมื่อใช้ API การเปิดเผย

Fieldrunners จะเล่นเป็นแท็บที่ใช้งานอยู่เท่านั้น อันเนื่องมาจากการใช้ requestAnimationFrame ในการเรียกลูปการอัปเดต แต่บริบทของ Web Audio จะยังคงเล่นเอฟเฟกต์แบบวนซ้ำและแทร็กพื้นหลังต่อไปในขณะที่ผู้ใช้อยู่ในแท็บอื่น แต่เราหยุดได้ด้วยข้อมูลโค้ด 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();
    }
  });
}

ก่อนเขียนบทความนี้ เราคิดว่าการยกเลิกการเชื่อมต่อต้นแบบก็เพียงพอที่จะหยุดเสียงทั้งหมดชั่วคราวแทนที่จะปิดเสียง เมื่อยกเลิกการเชื่อมต่อโหนด ณ เวลานั้น เราได้หยุดโหนดและโหนดย่อยไม่ให้ประมวลผลและเล่น เมื่อเชื่อมต่ออีกครั้ง เสียงและเพลงจะเริ่มเล่นจากจุดที่ค้างไว้ขณะที่เกมจะดำเนินต่อไปจากจุดที่ค้างไว้ แต่นั่นเป็นการทำงานที่คาดไม่ถึง การยกเลิกการเชื่อมต่อเพื่อหยุดการเล่นเพียงอย่างเดียวนั้นไม่เพียงพอ

Page visibility API ช่วยให้ทราบได้ง่ายขึ้นเมื่อแท็บของคุณไม่อยู่ในโฟกัสอีกต่อไป หากคุณมีโค้ดที่มีประสิทธิภาพในการหยุดเสียงชั่วคราวอยู่แล้ว เมื่อซ่อนแท็บเกมอยู่ คุณจะเขียนโค้ดหยุดเสียงชั่วคราวได้เพียง 2-3 บรรทัด

จังหวะดนตรี

เราได้ตั้งค่าบางอย่างไว้ในขณะนี้ เรามีกราฟของโหนด เราสามารถหยุดเสียงชั่วคราวเมื่อผู้เล่นหยุดเกมชั่วคราว และเล่นเสียงใหม่สำหรับองค์ประกอบต่างๆ เช่น เมนูของเกม ได้ เราสามารถหยุดเสียงและเพลงทั้งหมดชั่วคราวเมื่อผู้ใช้สลับไปที่แท็บใหม่ ตอนนี้เราต้องเล่นเสียงจริงๆ

โดย Fieldrunners จะเล่นเสียงเพียง 1 ครั้งในระยะเวลา 1 ช่วงแทนการเล่นเสียงหลายๆ สำเนาต่อเอนทิตีในเกมหลายๆ อินสแตนซ์ เช่น ตัวละครที่ตาย หากต้องมีเสียงหลังจากเล่นจบแล้ว ฟีเจอร์จะรีสตาร์ทได้แต่จะไม่ส่งเสียงขณะที่เล่นอยู่ การตัดสินใจนี้ขึ้นอยู่กับการออกแบบเสียงของ Fieldrunners เนื่องจากมีเสียงที่ถูกขอให้เล่นอย่างรวดเร็วหากได้รับอนุญาตให้เล่นซ้ำหรือส่งเสียงซ้ำๆ หากได้รับอนุญาตให้เล่นหลายอินสแตนซ์ คาดว่าการใช้ AudioBufferSourceNodes เป็นแบบช็อตเดียว สร้างโหนด, แนบบัฟเฟอร์, กำหนดค่าบูลีนวนซ้ำหากจำเป็น, เชื่อมต่อกับโหนดบนกราฟที่จะนำไปยังปลายทาง, call notesOn หรือ notesGrainOn และอาจเรียกเสริม (ไม่บังคับ)

สำหรับ 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 ไม่ได้แคชไฟล์เพลงต่างๆ ที่สตรีมไว้ ซึ่งส่งผลให้เบราว์เซอร์ขอแทร็กที่เล่นอยู่ทุกๆ 2-3 นาทีเมื่อเล่นเสร็จแล้ว ในการทดสอบครั้งล่าสุด 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 และ HTML5 นอกเหนือขอบเขตการทำงานที่นำบรรทัด C++ จำนวนหลายพันบรรทัดมาไว้ใน JavaScript ยังมีอุปสรรคที่น่าสนใจและการตัดสินใจที่เกี่ยวข้องกับการกระตุ้นอารมณ์ HTML5 โดยเฉพาะ หากต้องการทวนตัวเลือกหนึ่งอีกครั้ง หากไม่มีตัวเลือกอื่น AudioBufferSourceNodes เป็นออบเจ็กต์แบบใช้ครั้งเดียว สร้างไฟล์ แนบบัฟเฟอร์เสียง เชื่อมต่อเข้ากับกราฟ Web Audio แล้วเล่นกับnoteOn หรือnoteGrainOn ต้องการเล่นเสียงนั้นอีกครั้งใช่ไหม จากนั้นสร้าง AudioBufferSourceNode อื่น