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

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