a munch adventure
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

289 line
5.2 KiB

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