@@ -31,6 +31,8 @@ typedef struct {
31
31
int id ;
32
32
SDL_RWops * src ;
33
33
SDL_bool freesrc ;
34
+ void * buf ;
35
+ SDL_bool freebuf ;
34
36
SDL_bool playing ;
35
37
} MusicHTML5 ;
36
38
@@ -74,6 +76,36 @@ static int MusicHTML5_Open(const SDL_AudioSpec *spec)
74
76
// randomId: new Audio(file);
75
77
},
76
78
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
+
77
109
deleteMusic : function (id ) {
78
110
if (!(id in this .music ))
79
111
return ;
@@ -90,11 +122,18 @@ static int MusicHTML5_Open(const SDL_AudioSpec *spec)
90
122
this .deleteBlob (url );
91
123
},
92
124
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 );
97
134
}
135
+
136
+ dynCall ("vi" , wasmMusicStopped , [context ]);
98
137
},
99
138
100
139
getNewId : function () {
@@ -137,7 +176,7 @@ static int MusicHTML5_Open(const SDL_AudioSpec *spec)
137
176
if (!audio )
138
177
audio = this .music [0 ] = new Audio ();
139
178
140
- return audio .canPlayType (formats [type ] || type );
179
+ return !! audio .canPlayType (formats [type ] || type );
141
180
},
142
181
143
182
canPlayFile : function (file ) {
@@ -160,12 +199,8 @@ static int MusicHTML5_Open(const SDL_AudioSpec *spec)
160
199
if (-- audio .dataset .playCount > 0 ) {
161
200
audio .currentTime = 0 ;
162
201
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 );
169
204
},
170
205
171
206
musicError : function (e ) {
@@ -177,11 +212,7 @@ static int MusicHTML5_Open(const SDL_AudioSpec *spec)
177
212
console .error ("Error " + audio .error .code + "; details: " + audio .error .message );
178
213
179
214
// 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 );
185
216
}
186
217
};
187
218
}), html5_handle_music_stopped );
@@ -192,6 +223,7 @@ static int MusicHTML5_Open(const SDL_AudioSpec *spec)
192
223
static void * MusicHTML5_CreateFromRW (SDL_RWops * src , int freesrc )
193
224
{
194
225
void * buf ;
226
+ SDL_bool freebuf = SDL_FALSE ;
195
227
int id = -1 ;
196
228
int size = src -> size (src );
197
229
MusicHTML5 * music = (MusicHTML5 * )SDL_calloc (1 , sizeof * music );
@@ -201,15 +233,41 @@ static void *MusicHTML5_CreateFromRW(SDL_RWops *src, int freesrc)
201
233
return NULL ;
202
234
}
203
235
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.
207
265
buf = src -> hidden .mem .base ;
208
266
else
209
267
{
210
268
Mix_SetError ("Unsupported RWops type: %d" , src -> type );
211
269
if (freesrc )
212
- src -> close (src );
270
+ SDL_RWclose (src );
213
271
return NULL ;
214
272
}
215
273
@@ -220,21 +278,10 @@ static void *MusicHTML5_CreateFromRW(SDL_RWops *src, int freesrc)
220
278
const size = $1 ;
221
279
const context = $2 ;
222
280
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 );
232
284
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 ;
238
285
return id ;
239
286
}, buf , size , music );
240
287
}
@@ -249,6 +296,8 @@ static void *MusicHTML5_CreateFromRW(SDL_RWops *src, int freesrc)
249
296
music -> id = id ;
250
297
music -> src = src ;
251
298
music -> freesrc = freesrc ;
299
+ music -> buf = buf ;
300
+ music -> freebuf = SDL_TRUE ;
252
301
music -> playing = SDL_TRUE ;
253
302
254
303
/* We're done */
@@ -266,53 +315,18 @@ static void *MusicHTML5_CreateFromFile(const char *file)
266
315
return NULL ;
267
316
}
268
317
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 ;
297
322
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
316
330
function isValidUrl (string ) {
317
331
let url ;
318
332
@@ -325,22 +339,17 @@ static void *MusicHTML5_CreateFromFile(const char *file)
325
339
return url .protocol == = "http:" || url .protocol == = "https:" ;
326
340
}
327
341
328
- const url = UTF8ToString ($0 );
329
- const context = $1 ;
330
-
331
- if (!isValidUrl ) {
342
+ if (isValidUrl (file ))
343
+ url = file ;
344
+ else {
332
345
console .error (`URL $ {url } is invalid `);
333
346
return -1 ;
334
347
}
348
+ }
335
349
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 );
344
353
345
354
if (id == -1 ) {
346
355
SDL_free (music );
@@ -350,6 +359,7 @@ static void *MusicHTML5_CreateFromFile(const char *file)
350
359
/* Fill the music structure */
351
360
music -> id = id ;
352
361
music -> freesrc = SDL_FALSE ;
362
+ music -> freebuf = SDL_FALSE ;
353
363
music -> playing = SDL_TRUE ;
354
364
355
365
/* We're done */
@@ -435,19 +445,21 @@ static SDL_bool MusicHTML5_IsPlaying(void *context)
435
445
436
446
int safeStatus = EM_ASM_INT ({
437
447
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 ]);
446
461
}, music -> id );
447
462
448
- if (!safeStatus )
449
- html5_handle_music_stopped (context );
450
-
451
463
return music -> playing ;
452
464
}
453
465
@@ -494,14 +506,8 @@ static void MusicHTML5_Stop(void *context)
494
506
495
507
EM_ASM ({
496
508
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 ]);
502
510
}, music -> id );
503
-
504
- html5_handle_music_stopped (context );
505
511
}
506
512
507
513
/* Close the given music stream */
@@ -519,6 +525,8 @@ static void MusicHTML5_Delete(void *context)
519
525
520
526
if (music -> freesrc && music -> src )
521
527
SDL_RWclose (music -> src );
528
+ if (music -> freebuf && music -> buf )
529
+ SDL_free (music -> buf );
522
530
523
531
SDL_free (music );
524
532
}
0 commit comments