1/** 2 * BgmManager - Background Music Manager for Space Type 3 * Handles preview playback (HTML5 Audio) and export audio stream (Web Audio API). 4 */ 5(function() { 6 'use strict'; 7 8 function BgmManager(config) { 9 this.config = config || (window.__BGM_CONFIG__ && window.__BGM_CONFIG__.categories) || []; 10 this.audio = new Audio(); 11 this.audio.loop = true; 12 this.selectedTrack = null; 13 this.previewingTrack = null; 14 this.volume = 0.8; 15 this._onTrackChange = null; 16 this._onPlayStateChange = null; 17 18 var self = this; 19 this.audio.addEventListener('ended', function() { 20 if (!self.audio.loop) { 21 self.previewingTrack = null; 22 self._firePlayState(); 23 } 24 }); 25 } 26 27 BgmManager.prototype.getCategories = function() { 28 return this.config; 29 }; 30 31 BgmManager.prototype.getTracksByCategory = function(categoryId) { 32 for (var i = 0; i < this.config.length; i++) { 33 if (this.config[i].id === categoryId) return this.config[i].tracks; 34 } 35 return []; 36 }; 37 38 BgmManager.prototype.findTrack = function(trackId) { 39 for (var i = 0; i < this.config.length; i++) { 40 var tracks = this.config[i].tracks; 41 for (var j = 0; j < tracks.length; j++) { 42 if (tracks[j].id === trackId) return tracks[j]; 43 } 44 } 45 return null; 46 }; 47 48 // ---- Preview (HTML5 Audio) ---- 49 50 BgmManager.prototype.preview = function(trackId) { 51 var track = this.findTrack(trackId); 52 if (!track) return; 53 54 if (this.previewingTrack && this.previewingTrack.id === trackId) { 55 this.stopPreview(); 56 return; 57 } 58 59 this.audio.src = track.file; 60 this.audio.volume = this.volume; 61 this.audio.loop = false; 62 this.audio.currentTime = 0; 63 this.audio.play().catch(function() {}); 64 this.previewingTrack = track; 65 this._firePlayState(); 66 }; 67 68 BgmManager.prototype.stopPreview = function() { 69 if (!this.audio.paused) { 70 this.audio.pause(); 71 } 72 this.audio.currentTime = 0; 73 this.previewingTrack = null; 74 this._firePlayState(); 75 }; 76 77 BgmManager.prototype.isPlaying = function(trackId) { 78 return this.previewingTrack && this.previewingTrack.id === trackId && !this.audio.paused; 79 }; 80 81 // ---- Selection ---- 82 83 BgmManager.prototype.select = function(trackId) { 84 var track = this.findTrack(trackId); 85 if (!track) return; 86 this.selectedTrack = track; 87 88 this.audio.src = track.file; 89 this.audio.volume = this.volume; 90 this.audio.loop = true; 91 this.audio.currentTime = 0; 92 this.audio.play().catch(function() {}); 93 this.previewingTrack = track; 94 95 this._fireTrackChange(); 96 this._firePlayState(); 97 }; 98 99 BgmManager.prototype.deselect = function() { 100 this.stopPreview(); 101 this.selectedTrack = null; 102 this._fireTrackChange(); 103 }; 104 105 BgmManager.prototype.getSelectedTrack = function() { 106 return this.selectedTrack; 107 }; 108 109 /** 导åºç¨ï¼å·²å¾éæ²ç®ä¼å ï¼å¦å使ç¨å½åæ£å¨é¢è§çæ²ç®ï¼é¿å åªç¹ææ¾æªå¾éå¯¼è´æ é³è½¨ï¼ */ 110 BgmManager.prototype.getTrackForExport = function() { 111 return this.selectedTrack || this.previewingTrack; 112 }; 113 114 // ---- Volume ---- 115 116 BgmManager.prototype.setVolume = function(v) { 117 this.volume = Math.max(0, Math.min(1, v)); 118 this.audio.volume = this.volume; 119 }; 120 121 BgmManager.prototype.getVolume = function() { 122 return this.volume; 123 }; 124 125 // ---- Export: produce a MediaStream with audio ---- 126 127 BgmManager.prototype.createAudioStreamForExport = function(durationSec, callback) { 128 var track = this.selectedTrack; 129 if (!track) { 130 callback(null); 131 return; 132 } 133 134 var audioCtx = new (window.AudioContext || window.webkitAudioContext)(); 135 var vol = this.volume; 136 137 fetch(track.file) 138 .then(function(res) { return res.arrayBuffer(); }) 139 .then(function(buf) { return audioCtx.decodeAudioData(buf); }) 140 .then(function(audioBuffer) { 141 var dest = audioCtx.createMediaStreamDestination(); 142 var gainNode = audioCtx.createGain(); 143 gainNode.gain.value = vol; 144 gainNode.connect(dest); 145 146 var source = audioCtx.createBufferSource(); 147 source.buffer = audioBuffer; 148 source.loop = true; 149 source.connect(gainNode); 150 source.start(0); 151 152 callback({ 153 stream: dest.stream, 154 audioCtx: audioCtx, 155 source: source, 156 stop: function() { 157 try { source.stop(); } catch(e) {} 158 try { audioCtx.close(); } catch(e) {} 159 } 160 }); 161 }) 162 .catch(function(err) { 163 console.error('BgmManager: failed to decode audio', err); 164 try { audioCtx.close(); } catch(e) {} 165 callback(null); 166 }); 167 }; 168 169 // ---- Events ---- 170 171 BgmManager.prototype.onTrackChange = function(fn) { this._onTrackChange = fn; }; 172 BgmManager.prototype.onPlayStateChange = function(fn) { this._onPlayStateChange = fn; }; 173 174 BgmManager.prototype._fireTrackChange = function() { 175 if (typeof this._onTrackChange === 'function') this._onTrackChange(this.selectedTrack); 176 }; 177 BgmManager.prototype._firePlayState = function() { 178 if (typeof this._onPlayStateChange === 'function') this._onPlayStateChange(this.previewingTrack, !this.audio.paused); 179 }; 180 181 // ---- Helpers ---- 182 183 BgmManager.prototype.formatDuration = function(sec) { 184 if (sec == null || !isFinite(sec) || sec <= 0) return '--:--'; 185 var m = Math.floor(sec / 60); 186 var s = Math.floor(sec % 60); 187 return m + ':' + (s < 10 ? '0' : '') + s; 188 }; 189 190 BgmManager.prototype.destroy = function() { 191 this.stopPreview(); 192 this.selectedTrack = null; 193 this._onTrackChange = null; 194 this._onPlayStateChange = null; 195 }; 196 197 window.BgmManager = BgmManager; 198})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.