Skip to content

Commit bcdeaad

Browse files
committed
Add an ability to preload a sound file without decoding
PR #2006 made the preloaded sounds be decoded right away, bringing back old memory consumption issues from before #431. For games that want to download all their assets during the loading screen, but don't want to actually decode all sounds right away because of memory consumption concerns, being able to dynamically load/unload Howler objects via events is not enough, as the application may already go out-of-memory during loading, and not doing that will cause the game to download additional assets during gameplay. This change adds a new property, "preloadInCache", which makes GDJS request the sound asset via XHR. This puts the file into browser cache, so it can be reasonably expected to be decoded later without issuing network requests. While at it, add user visible descriptions for all preload options.
1 parent 70226f4 commit bcdeaad

File tree

4 files changed

+52
-15
lines changed

4 files changed

+52
-15
lines changed

Core/GDCore/Project/ResourcesManager.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,17 @@ std::map<gd::String, gd::PropertyDescriptor> AudioResource::GetProperties()
184184
const {
185185
std::map<gd::String, gd::PropertyDescriptor> properties;
186186
properties[_("Preload as sound")]
187+
.SetDescription(_("Loads the fully decoded file into cache, so it can be played right away as Sound with no further delays."))
187188
.SetValue(preloadAsSound ? "true" : "false")
188189
.SetType("Boolean");
189190
properties[_("Preload as music")]
191+
.SetDescription(_("Prepares the file for immediate streaming as Music (does not wait for complete download)."))
190192
.SetValue(preloadAsMusic ? "true" : "false")
191193
.SetType("Boolean");
194+
properties[_("Preload in cache")]
195+
.SetDescription(_("Loads the complete file into cache, but does not decode it into memory until requested."))
196+
.SetValue(preloadInCache ? "true" : "false")
197+
.SetType("Boolean");
192198

193199
return properties;
194200
}
@@ -199,6 +205,8 @@ bool AudioResource::UpdateProperty(const gd::String& name,
199205
preloadAsSound = value == "1";
200206
else if (name == _("Preload as music"))
201207
preloadAsMusic = value == "1";
208+
else if (name == _("Preload in cache"))
209+
preloadInCache = value == "1";
202210

203211
return true;
204212
}

Core/GDCore/Project/ResourcesManager.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ class GD_CORE_API ImageResource : public Resource {
223223
*/
224224
class GD_CORE_API AudioResource : public Resource {
225225
public:
226-
AudioResource() : Resource(), preloadAsMusic(false), preloadAsSound(false) {
226+
AudioResource() : Resource(), preloadAsMusic(false), preloadAsSound(false), preloadInCache(false) {
227227
SetKind("audio");
228228
};
229229
virtual ~AudioResource(){};
@@ -263,10 +263,21 @@ class GD_CORE_API AudioResource : public Resource {
263263
*/
264264
void SetPreloadAsSound(bool enable = true) { preloadAsSound = enable; }
265265

266+
/**
267+
* \brief Return true if the audio resource should be preloaded in cache (without decoding into memory).
268+
*/
269+
bool PreloadInCache() const { return preloadInCache; }
270+
271+
/**
272+
* \brief Set if the audio resource should be preloaded in cache (without decoding into memory).
273+
*/
274+
void SetPreloadInCache(bool enable = true) { preloadInCache = enable; }
275+
266276
private:
267277
gd::String file;
268278
bool preloadAsSound;
269279
bool preloadAsMusic;
280+
bool preloadInCache;
270281
};
271282

272283
/**

GDJS/Runtime/howler-sound-manager/howler-sound-manager.ts

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -802,24 +802,41 @@ namespace gdjs {
802802
if (!filesToLoad.length) return;
803803
const file = filesToLoad.shift()!;
804804
const fileData = files[file][0];
805-
if (!fileData.preloadAsSound && !fileData.preloadAsMusic) {
806-
onLoad();
807-
} else if (fileData.preloadAsSound && fileData.preloadAsMusic) {
808-
let loadedOnce = false;
809-
const callback = (_, error) => {
810-
if (!loadedOnce) {
811-
loadedOnce = true;
812-
return;
813-
}
805+
806+
let loadCounter = 0;
807+
const callback = (_?: any, error?: string) => {
808+
loadCounter--;
809+
if (!loadCounter) {
814810
onLoad(_, error);
815-
};
811+
}
812+
};
816813

814+
if (fileData.preloadAsMusic) {
815+
loadCounter++;
817816
preloadAudioFile(file, callback, /* isMusic= */ true);
817+
}
818+
819+
if (fileData.preloadAsSound) {
820+
loadCounter++;
818821
preloadAudioFile(file, callback, /* isMusic= */ false);
819-
} else if (fileData.preloadAsSound) {
820-
preloadAudioFile(file, onLoad, /* isMusic= */ false);
821-
} else if (fileData.preloadAsMusic)
822-
preloadAudioFile(file, onLoad, /* isMusic= */ true);
822+
} else if (fileData.preloadInCache) {
823+
// preloading as sound already does a XHR request, hence "else if"
824+
loadCounter++;
825+
const sound = new XMLHttpRequest();
826+
sound.addEventListener('load', callback);
827+
sound.addEventListener('error', (_) =>
828+
callback(_, 'XHR error: ' + file)
829+
);
830+
sound.addEventListener('abort', (_) =>
831+
callback(_, 'XHR abort: ' + file)
832+
);
833+
sound.open('GET', file);
834+
sound.send();
835+
}
836+
837+
if (!loadCounter) {
838+
onLoad();
839+
}
823840
};
824841
loadNextFile();
825842
}

GDJS/Runtime/types/project-data.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ declare interface ResourceData {
220220
disablePreload?: boolean;
221221
preloadAsSound?: boolean;
222222
preloadAsMusic?: boolean;
223+
preloadInCache?: boolean;
223224
}
224225

225226
declare type ResourceKind =

0 commit comments

Comments
 (0)