PageSourceSearch

https://www.wordcloud.art/js/bgm-manager.js?v=1790574168643

js wordcloud.art collected 2026-09-28 05:43:03 UTC 6,377 bytes, 198 lines download raw bytes

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.