Skip to content

Commit e00017d

Browse files
committed
HTML5: IsPlaying() fixes
1 parent d2afd3d commit e00017d

File tree

1 file changed

+79
-41
lines changed

1 file changed

+79
-41
lines changed

music_html5.c

Lines changed: 79 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ typedef struct {
3131
int id;
3232
SDL_RWops *src;
3333
SDL_bool freesrc;
34+
SDL_bool playing;
3435
} MusicHTML5;
3536

3637
static SDL_bool html5_opened(void)
@@ -40,6 +41,20 @@ static SDL_bool html5_opened(void)
4041
});
4142
}
4243

44+
static void html5_handle_music_stopped(void *context)
45+
{
46+
// Sets music->playing to FALSE. Call "finished" handler explicitly
47+
// in devappd/html5_mixer which does not run its own sound loop.
48+
49+
MusicHTML5 *music = (MusicHTML5 *)context;
50+
if (music)
51+
music->playing = SDL_FALSE;
52+
53+
#ifdef HTML5_MIXER
54+
run_music_finished_hook();
55+
#endif
56+
}
57+
4358
static int MusicHTML5_Open(const SDL_AudioSpec *spec)
4459
{
4560
(void)spec;
@@ -48,6 +63,8 @@ static int MusicHTML5_Open(const SDL_AudioSpec *spec)
4863
return 0;
4964

5065
EM_ASM(({
66+
const wasmMusicStopped = $0;
67+
5168
Module["SDL2Mixer"] = {
5269
blob: {
5370
// URL.createObjectURL(...): numUses (int)
@@ -147,9 +164,7 @@ static int MusicHTML5_Open(const SDL_AudioSpec *spec)
147164
audio.dataset.playCount = 0;
148165
audio.currentTime = 0;
149166
audio.loop = false;
150-
151-
// Signal `true` to run hookMusicFinished
152-
return true;
167+
dynCall("vi", wasmMusicStopped, [audio.dataset.context]);
153168
}
154169
},
155170

@@ -166,22 +181,10 @@ static int MusicHTML5_Open(const SDL_AudioSpec *spec)
166181
audio.dataset.playCount = 0;
167182
audio.currentTime = 0;
168183
audio.loop = false;
184+
dynCall("vi", wasmMusicStopped, [audio.dataset.context]);
169185
}
170186
};
171-
}));
172-
173-
#ifdef HTML5_MIXER
174-
// HookMusicFinished support for html5_mixer (a minimal implementation of SDL2_mixer)
175-
EM_ASM({
176-
const hookMusicFinished = $0;
177-
Module["SDL2Mixer"].musicFinished = function(predefined) {
178-
return function(e) {
179-
if (predefined(e))
180-
dynCall("v", hookMusicFinished, []);
181-
}
182-
}(Module["SDL2Mixer"].musicFinished);
183-
}, run_music_finished_hook);
184-
#endif
187+
}), html5_handle_music_stopped);
185188

186189
return 0;
187190
}
@@ -191,7 +194,12 @@ static void *MusicHTML5_CreateFromRW(SDL_RWops *src, int freesrc)
191194
void *buf;
192195
int id = -1;
193196
int size = src->size(src);
194-
MusicHTML5 *music;
197+
MusicHTML5 *music = (MusicHTML5 *)SDL_calloc(1, sizeof *music);
198+
199+
if (music == NULL) {
200+
Mix_SetError("Out of memory");
201+
return NULL;
202+
}
195203

196204
if (src->type == SDL_RWOPS_STDFILE)
197205
buf = src->hidden.stdio.fp;
@@ -210,6 +218,7 @@ static void *MusicHTML5_CreateFromRW(SDL_RWops *src, int freesrc)
210218
id = EM_ASM_INT({
211219
const ptr = $0;
212220
const size = $1;
221+
const context = $2;
213222

214223
const arr = new Uint8Array(Module.HEAPU8.buffer, ptr, size);
215224
const blob = new Blob([arr], { type: "octet/stream" });
@@ -225,22 +234,22 @@ static void *MusicHTML5_CreateFromRW(SDL_RWops *src, int freesrc)
225234
Module["SDL2Mixer"].music[id] = new Audio(url);
226235
Module["SDL2Mixer"].music[id].addEventListener("ended", Module["SDL2Mixer"].musicFinished, false);
227236
Module["SDL2Mixer"].music[id].addEventListener("error", Module["SDL2Mixer"].musicError, false);
237+
Module["SDL2Mixer"].music[id].dataset.context = context;
228238
return id;
229-
}, buf, size);
239+
}, buf, size, music);
230240
}
231241

232242
if (id == -1)
233-
return NULL;
234-
235-
/* Allocate and fill the music structure */
236-
music = (MusicHTML5 *)SDL_calloc(1, sizeof *music);
237-
if (music == NULL) {
238-
Mix_SetError("Out of memory");
243+
{
244+
SDL_free(music);
239245
return NULL;
240246
}
247+
248+
/* Fill the music structure */
241249
music->id = id;
242250
music->src = src;
243251
music->freesrc = freesrc;
252+
music->playing = SDL_TRUE;
244253

245254
/* We're done */
246255
return music;
@@ -249,9 +258,14 @@ static void *MusicHTML5_CreateFromRW(SDL_RWops *src, int freesrc)
249258
/* Load a music stream from the given file */
250259
static void *MusicHTML5_CreateFromFile(const char *file)
251260
{
252-
MusicHTML5 *music;
261+
MusicHTML5 *music = (MusicHTML5 *)SDL_calloc(1, sizeof *music);;
253262
int id = -1;
254263

264+
if (music == NULL) {
265+
Mix_SetError("Out of memory");
266+
return NULL;
267+
}
268+
255269
SDL_RWops *src = SDL_RWFromFile(file, "rb");
256270
if (src != NULL)
257271
{
@@ -312,6 +326,7 @@ static void *MusicHTML5_CreateFromFile(const char *file)
312326
}
313327

314328
const url = UTF8ToString($0);
329+
const context = $1;
315330

316331
if (!isValidUrl) {
317332
console.error(`URL ${url} is invalid`);
@@ -322,21 +337,20 @@ static void *MusicHTML5_CreateFromFile(const char *file)
322337
Module["SDL2Mixer"].music[id] = new Audio(url);
323338
Module["SDL2Mixer"].music[id].addEventListener("ended", Module["SDL2Mixer"].musicFinished, false);
324339
Module["SDL2Mixer"].music[id].addEventListener("error", Module["SDL2Mixer"].musicError, false);
340+
Module["SDL2Mixer"].music[id].dataset.context = context;
325341
return id;
326-
}, file);
342+
}, file, music);
327343
}
328344

329-
if (id == -1)
330-
return NULL;
331-
332-
/* Allocate and fill the music structure */
333-
music = (MusicHTML5 *)SDL_calloc(1, sizeof *music);
334-
if (music == NULL) {
335-
Mix_SetError("Out of memory");
345+
if (id == -1) {
346+
SDL_free(music);
336347
return NULL;
337348
}
349+
350+
/* Fill the music structure */
338351
music->id = id;
339352
music->freesrc = SDL_FALSE;
353+
music->playing = SDL_TRUE;
340354

341355
/* We're done */
342356
return music;
@@ -404,15 +418,37 @@ static SDL_bool MusicHTML5_IsPlaying(void *context)
404418
{
405419
MusicHTML5 *music = (MusicHTML5 *)context;
406420

407-
return EM_ASM_INT({
421+
if (!music) {
422+
// Call "finished" handler in devappd/html5_mixer
423+
html5_handle_music_stopped(context);
424+
return SDL_FALSE;
425+
}
426+
427+
// We track a music->playing variable to play nice with music_mixer()'s
428+
// IsPlaying() check on every frame. E.g., the check will run when
429+
// <audio> is buffering and the music is technically not "playing".
430+
// Ergo, the HookMusicFinished() callback is called immediately when
431+
// the <audio> has not even begun playback.
432+
//
433+
// To resolve this, we rely on JavaScript callbacks to reset the
434+
// music->playing status on end, on error, etc.
435+
436+
int safeStatus = EM_ASM_INT({
408437
const id = $0;
409-
return Module["SDL2Mixer"].music[id]
410-
&& Module["SDL2Mixer"].music[id].currentTime > 0
411-
// SDL Mixer considers "paused" music as "playing"
412-
//&& !Module["SDL2Mixer"].music[id].paused
413-
&& !Module["SDL2Mixer"].music[id].ended
414-
&& Module["SDL2Mixer"].music[id].readyState > 2;
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+
;
415446
}, music->id);
447+
448+
if (!safeStatus)
449+
html5_handle_music_stopped(context);
450+
451+
return music->playing;
416452
}
417453

418454
/* Jump (seek) to a given position (time is in seconds) */
@@ -464,6 +500,8 @@ static void MusicHTML5_Stop(void *context)
464500
Module["SDL2Mixer"].music[id].currentTime = 0;
465501
Module["SDL2Mixer"].music[id].loop = false;
466502
}, music->id);
503+
504+
html5_handle_music_stopped(context);
467505
}
468506

469507
/* Close the given music stream */

0 commit comments

Comments
 (0)