Skip to content

Commit 968a684

Browse files
committed
Re-wrote CreateFromRW for file RWops
Re-wrote CreateFromFile for paths existing in MEMFS Consolidated music/blob creation and state: createBlob deleteBlob createMusic resetMusicState
1 parent e00017d commit 968a684

File tree

1 file changed

+118
-110
lines changed

1 file changed

+118
-110
lines changed

music_html5.c

Lines changed: 118 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ typedef struct {
3131
int id;
3232
SDL_RWops *src;
3333
SDL_bool freesrc;
34+
void *buf;
35+
SDL_bool freebuf;
3436
SDL_bool playing;
3537
} MusicHTML5;
3638

@@ -74,6 +76,36 @@ static int MusicHTML5_Open(const SDL_AudioSpec *spec)
7476
// randomId: new Audio(file);
7577
},
7678

79+
createBlob: function(buf) {
80+
const blob = new Blob([buf], { type: "octet/stream" });
81+
const url = URL.createObjectURL(blob);
82+
83+
// TODO: Match blob by ptr and size so we don't duplicate
84+
85+
if (!(url in Module["SDL2Mixer"].blob))
86+
Module["SDL2Mixer"].blob[url] = 0;
87+
Module["SDL2Mixer"].blob[url]++;
88+
89+
return url;
90+
},
91+
92+
deleteBlob: function(url) {
93+
if (url in this.blob && --this.blob[url] <= 0) {
94+
URL.revokeObjectURL(url);
95+
delete this.blob[url];
96+
}
97+
},
98+
99+
createMusic: function(url, context) {
100+
const id = Module["SDL2Mixer"].getNewId();
101+
Module["SDL2Mixer"].music[id] = new Audio(url);
102+
Module["SDL2Mixer"].music[id].addEventListener("ended", Module["SDL2Mixer"].musicFinished, false);
103+
Module["SDL2Mixer"].music[id].addEventListener("error", Module["SDL2Mixer"].musicError, false);
104+
if (context)
105+
Module["SDL2Mixer"].music[id].dataset.context = context;
106+
return id;
107+
},
108+
77109
deleteMusic: function(id) {
78110
if (!(id in this.music))
79111
return;
@@ -90,11 +122,18 @@ static int MusicHTML5_Open(const SDL_AudioSpec *spec)
90122
this.deleteBlob(url);
91123
},
92124

93-
deleteBlob: function(url) {
94-
if (url in this.blob && --this.blob[url] <= 0) {
95-
URL.revokeObjectURL(url);
96-
delete this.blob[url];
125+
resetMusicState: function(audio) {
126+
let context = 0;
127+
128+
if (audio instanceof HTMLMediaElement) {
129+
audio.pause();
130+
audio.dataset.playCount = 0;
131+
audio.currentTime = 0;
132+
audio.loop = false;
133+
context = parseInt(audio.dataset.context);
97134
}
135+
136+
dynCall("vi", wasmMusicStopped, [context]);
98137
},
99138

100139
getNewId: function() {
@@ -137,7 +176,7 @@ static int MusicHTML5_Open(const SDL_AudioSpec *spec)
137176
if(!audio)
138177
audio = this.music[0] = new Audio();
139178

140-
return audio.canPlayType(formats[type] || type);
179+
return !!audio.canPlayType(formats[type] || type);
141180
},
142181

143182
canPlayFile: function(file) {
@@ -160,12 +199,8 @@ static int MusicHTML5_Open(const SDL_AudioSpec *spec)
160199
if (--audio.dataset.playCount > 0) {
161200
audio.currentTime = 0;
162201
audio.play();
163-
} else {
164-
audio.dataset.playCount = 0;
165-
audio.currentTime = 0;
166-
audio.loop = false;
167-
dynCall("vi", wasmMusicStopped, [audio.dataset.context]);
168-
}
202+
} else
203+
this.resetMusicState(audio);
169204
},
170205

171206
musicError: function(e) {
@@ -177,11 +212,7 @@ static int MusicHTML5_Open(const SDL_AudioSpec *spec)
177212
console.error("Error " + audio.error.code + "; details: " + audio.error.message);
178213

179214
// Reset to defaults
180-
audio.pause();
181-
audio.dataset.playCount = 0;
182-
audio.currentTime = 0;
183-
audio.loop = false;
184-
dynCall("vi", wasmMusicStopped, [audio.dataset.context]);
215+
this.resetMusicState(audio);
185216
}
186217
};
187218
}), html5_handle_music_stopped);
@@ -192,6 +223,7 @@ static int MusicHTML5_Open(const SDL_AudioSpec *spec)
192223
static void *MusicHTML5_CreateFromRW(SDL_RWops *src, int freesrc)
193224
{
194225
void *buf;
226+
SDL_bool freebuf = SDL_FALSE;
195227
int id = -1;
196228
int size = src->size(src);
197229
MusicHTML5 *music = (MusicHTML5 *)SDL_calloc(1, sizeof *music);
@@ -201,15 +233,41 @@ static void *MusicHTML5_CreateFromRW(SDL_RWops *src, int freesrc)
201233
return NULL;
202234
}
203235

204-
if (src->type == SDL_RWOPS_STDFILE)
205-
buf = src->hidden.stdio.fp;
206-
else if (src->type == SDL_RWOPS_MEMORY || src->type == SDL_RWOPS_MEMORY_RO)
236+
if (src->type == SDL_RWOPS_STDFILE) {
237+
// We must make a copy of the whole file to a new buffer.
238+
//
239+
// Note that it's more memory-efficient for the user to call Mix_LoadMUS()
240+
// instead of Mix_LoadWAV_RW(). In the former, we query FS.readFile() in JS
241+
// and avoid copying memory.
242+
243+
Sint64 res_size = SDL_RWsize(src);
244+
void *res = malloc(res_size + 1);
245+
246+
Sint64 nb_read_total = 0, nb_read = 1;
247+
buf = res;
248+
while (nb_read_total < res_size && nb_read != 0) {
249+
nb_read = SDL_RWread(src, buf, 1, (res_size - nb_read_total));
250+
nb_read_total += nb_read;
251+
buf += nb_read;
252+
}
253+
if (nb_read_total != res_size) {
254+
free(res);
255+
return NULL;
256+
}
257+
((char *)res)[nb_read_total] = '\0';
258+
259+
// Reset buf pointer back to start of memory for re-use
260+
buf = res;
261+
freebuf = SDL_TRUE;
262+
} else if (src->type == SDL_RWOPS_MEMORY || src->type == SDL_RWOPS_MEMORY_RO)
263+
// This violates "private" membership, but it works because
264+
// the entire file is loaded in this pointer.
207265
buf = src->hidden.mem.base;
208266
else
209267
{
210268
Mix_SetError("Unsupported RWops type: %d", src->type);
211269
if (freesrc)
212-
src->close(src);
270+
SDL_RWclose(src);
213271
return NULL;
214272
}
215273

@@ -220,21 +278,10 @@ static void *MusicHTML5_CreateFromRW(SDL_RWops *src, int freesrc)
220278
const size = $1;
221279
const context = $2;
222280

223-
const arr = new Uint8Array(Module.HEAPU8.buffer, ptr, size);
224-
const blob = new Blob([arr], { type: "octet/stream" });
225-
const url = URL.createObjectURL(blob);
226-
227-
// TODO: Match blob by ptr and size so we don't duplicate
228-
229-
if (!(url in Module["SDL2Mixer"].blob))
230-
Module["SDL2Mixer"].blob[url] = 0;
231-
Module["SDL2Mixer"].blob[url]++;
281+
const buf = new Uint8Array(Module.HEAPU8.buffer, ptr, size);
282+
const url = Module["SDL2Mixer"].createBlob(buf);
283+
const id = Module["SDL2Mixer"].createAudio(url, context);
232284

233-
const id = Module["SDL2Mixer"].getNewId();
234-
Module["SDL2Mixer"].music[id] = new Audio(url);
235-
Module["SDL2Mixer"].music[id].addEventListener("ended", Module["SDL2Mixer"].musicFinished, false);
236-
Module["SDL2Mixer"].music[id].addEventListener("error", Module["SDL2Mixer"].musicError, false);
237-
Module["SDL2Mixer"].music[id].dataset.context = context;
238285
return id;
239286
}, buf, size, music);
240287
}
@@ -249,6 +296,8 @@ static void *MusicHTML5_CreateFromRW(SDL_RWops *src, int freesrc)
249296
music->id = id;
250297
music->src = src;
251298
music->freesrc = freesrc;
299+
music->buf = buf;
300+
music->freebuf = SDL_TRUE;
252301
music->playing = SDL_TRUE;
253302

254303
/* We're done */
@@ -266,53 +315,18 @@ static void *MusicHTML5_CreateFromFile(const char *file)
266315
return NULL;
267316
}
268317

269-
SDL_RWops *src = SDL_RWFromFile(file, "rb");
270-
if (src != NULL)
271-
{
272-
#ifdef HTML5_MIXER
273-
// Just pass the entire path; we don't bundle detect_music_type()
274-
const char *fileType = file;
275-
#else
276-
Mix_MusicType type = detect_music_type(src);
277-
278-
char *fileType;
279-
switch(type) {
280-
case MUS_OGG:
281-
fileType = "ogg";
282-
break;
283-
284-
case MUS_FLAC:
285-
fileType = "flac";
286-
break;
287-
288-
case MUS_MP3:
289-
fileType = "mp3";
290-
break;
291-
292-
default:
293-
fileType = file;
294-
break;
295-
}
296-
#endif
318+
id = EM_ASM_INT({
319+
const file = UTF8ToString($0);
320+
const context = $1;
321+
let url;
297322

298-
SDL_bool canPlay = EM_ASM_INT({
299-
const file = UTF8ToString($0);
300-
return Module["SDL2Mixer"].canPlayFile(file);
301-
}, file);
302-
303-
if (canPlay)
304-
return MusicHTML5_CreateFromRW(src, SDL_TRUE);
305-
else
306-
{
307-
Mix_SetError("Format is not supported by HTML5 <audio>");
308-
src->close(src);
309-
return NULL;
310-
}
311-
}
312-
else
313-
{
314-
// Assume it's a URL
315-
id = EM_ASM_INT({
323+
try {
324+
// Is path in FS?
325+
const buf = FS.readFile(file);
326+
url = Module["SDL2Mixer"].createBlob(buf);
327+
} catch(e) {
328+
// Fail silently, presume file not in FS.
329+
// Assume it's a URL
316330
function isValidUrl(string) {
317331
let url;
318332

@@ -325,22 +339,17 @@ static void *MusicHTML5_CreateFromFile(const char *file)
325339
return url.protocol === "http:" || url.protocol === "https:";
326340
}
327341

328-
const url = UTF8ToString($0);
329-
const context = $1;
330-
331-
if (!isValidUrl) {
342+
if (isValidUrl(file))
343+
url = file;
344+
else {
332345
console.error(`URL ${url} is invalid`);
333346
return -1;
334347
}
348+
}
335349

336-
const id = Module["SDL2Mixer"].getNewId();
337-
Module["SDL2Mixer"].music[id] = new Audio(url);
338-
Module["SDL2Mixer"].music[id].addEventListener("ended", Module["SDL2Mixer"].musicFinished, false);
339-
Module["SDL2Mixer"].music[id].addEventListener("error", Module["SDL2Mixer"].musicError, false);
340-
Module["SDL2Mixer"].music[id].dataset.context = context;
341-
return id;
342-
}, file, music);
343-
}
350+
const id = Module["SDL2Mixer"].createMusic(url, context);
351+
return id;
352+
}, file, music);
344353

345354
if (id == -1) {
346355
SDL_free(music);
@@ -350,6 +359,7 @@ static void *MusicHTML5_CreateFromFile(const char *file)
350359
/* Fill the music structure */
351360
music->id = id;
352361
music->freesrc = SDL_FALSE;
362+
music->freebuf = SDL_FALSE;
353363
music->playing = SDL_TRUE;
354364

355365
/* We're done */
@@ -435,19 +445,21 @@ static SDL_bool MusicHTML5_IsPlaying(void *context)
435445

436446
int safeStatus = EM_ASM_INT({
437447
const id = $0;
438-
return Module["SDL2Mixer"].music[id]
439-
&& !Module["SDL2Mixer"].music[id].ended
440-
// SDL Mixer considers "paused" music as "playing"
441-
//&& !Module["SDL2Mixer"].music[id].paused
442-
// These conditions interfere with the "playing" check
443-
//&& Module["SDL2Mixer"].music[id].readyState > 2;
444-
//&& Module["SDL2Mixer"].music[id].currentTime > 0
445-
;
448+
const safeStatus =
449+
Module["SDL2Mixer"].music[id]
450+
&& !Module["SDL2Mixer"].music[id].ended
451+
// SDL Mixer considers "paused" music as "playing"
452+
//&& !Module["SDL2Mixer"].music[id].paused
453+
// These conditions interfere with the "playing" check
454+
//&& Module["SDL2Mixer"].music[id].readyState > 2;
455+
//&& Module["SDL2Mixer"].music[id].currentTime > 0
456+
;
457+
458+
if (!safeStatus)
459+
// Reset JS state and falsify music->playing
460+
Module["SDL2Mixer"].resetMusicState(Module["SDL2Mixer"].music[id]);
446461
}, music->id);
447462

448-
if (!safeStatus)
449-
html5_handle_music_stopped(context);
450-
451463
return music->playing;
452464
}
453465

@@ -494,14 +506,8 @@ static void MusicHTML5_Stop(void *context)
494506

495507
EM_ASM({
496508
const id = $0;
497-
498-
Module["SDL2Mixer"].music[id].pause();
499-
Module["SDL2Mixer"].music[id].dataset.playCount = 0;
500-
Module["SDL2Mixer"].music[id].currentTime = 0;
501-
Module["SDL2Mixer"].music[id].loop = false;
509+
Module["SDL2Mixer"].resetMusicState(Module["SDL2Mixer"].music[id]);
502510
}, music->id);
503-
504-
html5_handle_music_stopped(context);
505511
}
506512

507513
/* Close the given music stream */
@@ -519,6 +525,8 @@ static void MusicHTML5_Delete(void *context)
519525

520526
if (music->freesrc && music->src)
521527
SDL_RWclose(music->src);
528+
if (music->freebuf && music->buf)
529+
SDL_free(music->buf);
522530

523531
SDL_free(music);
524532
}

0 commit comments

Comments
 (0)