a munch adventure
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

296 行
5.5 KiB

  1. let playing = [];
  2. let looping = {};
  3. let loopGains = {};
  4. let waiting = {};
  5. let audioContext;
  6. let gainControl;
  7. let audioBaseUrl;
  8. let storyName;
  9. let audioDict = {};
  10. function setVolume(vol) {
  11. gainControl.gain.value = vol;
  12. }
  13. // play some sound
  14. function playSfx(name) {
  15. if (audioDict[name] == undefined) {
  16. if (waiting[name]) {
  17. waiting[name].push({
  18. type: "sfx",
  19. name: name
  20. });
  21. console.warn(name + " isn't ready yet");
  22. return;
  23. }
  24. console.error(name + " is not loaded yet, dingus");
  25. return;
  26. }
  27. let src = audioContext.createBufferSource();
  28. src.buffer = audioDict[name];
  29. src.connect(gainControl);
  30. playing.push(src);
  31. src.name = name;
  32. src.onended = (event) => src.done = true;
  33. src.start(0);
  34. }
  35. function playLoop(name, volume=1) {
  36. if (audioDict[name] === undefined) {
  37. if (waiting[name]) {
  38. waiting[name].push({
  39. type: "loop",
  40. name: name
  41. });
  42. console.warn(name + " isn't ready yet");
  43. return;
  44. }
  45. console.error(name + " is not loaded yet, dingus");
  46. return;
  47. }
  48. loopGains[name].gain.value = volume;
  49. // if already playing, just keep going
  50. if (looping[name] && !looping[name].done) {
  51. return;
  52. }
  53. let src = audioContext.createBufferSource();
  54. src.buffer = audioDict[name];
  55. src.connect(loopGains[name]);
  56. loopGains[name].connect(gainControl);
  57. looping[name] = src;
  58. src.name = name;
  59. src.onended = (event) => src.done = true;
  60. src.loop = true;
  61. src.start(0);
  62. }
  63. function stopSfx(name) {
  64. playing.map(item => {
  65. if (item.name == name)
  66. item.stop();
  67. } );
  68. cleanPlaying();
  69. }
  70. function stopAllSfx() {
  71. playing.map(item => item.stop());
  72. cleanPlaying();
  73. }
  74. function stopLoop(name) {
  75. if (looping[name]) {
  76. looping[name].stop();
  77. delete looping[name];
  78. }
  79. }
  80. function stopAllLoops() {
  81. Object.entries(looping).forEach(([key, val]) => {
  82. val.stop();
  83. delete looping[key];
  84. });
  85. }
  86. function stopAllSound() {
  87. stopAllSfx();
  88. stopAllLoops();
  89. }
  90. function cleanPlaying() {
  91. playing = playing.filter(item => !item.done);
  92. }
  93. // asynchronously load an audio file
  94. function loadAudio(name, flush=false) {
  95. // are we already trying to get the audio?
  96. if (waiting[name]) {
  97. return;
  98. }
  99. // do we already have the audio?
  100. if (audioDict[name] && !flush) {
  101. return;
  102. }
  103. loopGains[name] = audioContext.createGain();
  104. waiting[name] = [];
  105. // is the audio already stored locally?
  106. if (!flush) {
  107. checkCache(
  108. "audio",
  109. name,
  110. (data) => parseAudioData(name, data),
  111. () => loadRemoteAudio(name)
  112. );
  113. } else {
  114. loadRemoteAudio(name);
  115. }
  116. }
  117. function cacheAndParse(name, data) {
  118. storeCache("audio", name, data.slice(0));
  119. parseAudioData(name, data);
  120. }
  121. function parseAudioData(name, data) {
  122. audioContext.decodeAudioData(data, function(buffer) {
  123. audioDict[name] = buffer;
  124. waiting[name].forEach(queued => {
  125. if (queued.type == "sfx") {
  126. playSfx(name);
  127. }
  128. if (queued.type == "loop") {
  129. playLoop(name);
  130. }
  131. });
  132. delete waiting[name];
  133. }, function(e){
  134. console.error("Error with decoding audio data" + e.err);
  135. delete waiting[name];
  136. });
  137. }
  138. function loadRemoteAudio(name) {
  139. let xhr = new XMLHttpRequest();
  140. xhr.open("GET", audioBaseUrl + name, true);
  141. xhr.responseType = "arraybuffer";
  142. xhr.onload = (res) => {
  143. if (xhr.status == 200)
  144. cacheAndParse(name, xhr.response);
  145. else {
  146. console.error("Couldn't load " + name);
  147. delete waiting[name];
  148. }
  149. }
  150. xhr.onerror = (xhr) => {
  151. console.error("Couldn't load " + name);
  152. }
  153. xhr.send();
  154. }
  155. // check if the content is cached
  156. function checkCache(type, name, hit, miss) {
  157. const req = window.indexedDB.open("cache", 3);
  158. req.onsuccess = () => {
  159. const db = req.result;
  160. const tx = db.transaction([type], "readonly");
  161. const audio = tx.objectStore(type);
  162. const read = audio.get([storyName, name]);
  163. read.onsuccess = (event) => {
  164. const res = event.target.result;
  165. if (res) {
  166. console.log("cache hit on " + name);
  167. hit(res.content);
  168. } else {
  169. console.log("cache miss on " + name);
  170. miss();
  171. }
  172. }
  173. tx.oncomplete = () => {
  174. db.close();
  175. }
  176. }
  177. }
  178. function initAudio(story) {
  179. if (!audioContext)
  180. audioContext = new (window.AudioContext || window.webkitAudioContext)();
  181. if (!gainControl) {
  182. gainControl = audioContext.createGain();
  183. gainControl.gain.value = 1;
  184. gainControl.connect(audioContext.destination);
  185. }
  186. createCache();
  187. audioBaseUrl = "./media/" + story.id + "/audio/";
  188. storyName = story.id;
  189. story.sounds.forEach(sound => {
  190. loadAudio(sound);
  191. });
  192. }
  193. // caching stuff here
  194. function storeCache(type, name, blob) {
  195. const req = window.indexedDB.open("cache", 3);
  196. req.onsuccess = () => {
  197. const db = req.result;
  198. const tx = db.transaction([type], "readwrite");
  199. const audio = tx.objectStore(type);
  200. const update = audio.put({
  201. story: storyName,
  202. name: name,
  203. content: blob
  204. });
  205. tx.oncomplete = () => {
  206. db.close();
  207. }
  208. }
  209. }
  210. // if the indexedDB table doesn't exist at all, make it
  211. function createCache() {
  212. let idb = window.indexedDB;
  213. let req = idb.open("cache", 3);
  214. req.onupgradeneeded = event => {
  215. const db = event.target.result;
  216. if (event.oldVersion > 0 && event.oldVersion < 3) {
  217. db.deleteObjectStore("audio");
  218. }
  219. const audio = db.createObjectStore("audio", { keyPath: ["story", "name"] });
  220. }
  221. req.onerror = event => {
  222. alert("Couldn't open the database?");
  223. }
  224. }