diff --git a/Core/GDCore/Project/ResourcesManager.cpp b/Core/GDCore/Project/ResourcesManager.cpp index e31ad179e647..6c80981f785d 100644 --- a/Core/GDCore/Project/ResourcesManager.cpp +++ b/Core/GDCore/Project/ResourcesManager.cpp @@ -184,11 +184,17 @@ std::map<gd::String, gd::PropertyDescriptor> AudioResource::GetProperties() const { std::map<gd::String, gd::PropertyDescriptor> properties; properties[_("Preload as sound")] + .SetDescription(_("Loads the fully decoded file into cache, so it can be played right away as Sound with no further delays.")) .SetValue(preloadAsSound ? "true" : "false") .SetType("Boolean"); properties[_("Preload as music")] + .SetDescription(_("Prepares the file for immediate streaming as Music (does not wait for complete download).")) .SetValue(preloadAsMusic ? "true" : "false") .SetType("Boolean"); + properties[_("Preload in cache")] + .SetDescription(_("Loads the complete file into cache, but does not decode it into memory until requested.")) + .SetValue(preloadInCache ? "true" : "false") + .SetType("Boolean"); return properties; } @@ -199,6 +205,8 @@ bool AudioResource::UpdateProperty(const gd::String& name, preloadAsSound = value == "1"; else if (name == _("Preload as music")) preloadAsMusic = value == "1"; + else if (name == _("Preload in cache")) + preloadInCache = value == "1"; return true; } @@ -569,6 +577,7 @@ void AudioResource::UnserializeFrom(const SerializerElement& element) { SetFile(element.GetStringAttribute("file")); SetPreloadAsMusic(element.GetBoolAttribute("preloadAsMusic")); SetPreloadAsSound(element.GetBoolAttribute("preloadAsSound")); + SetPreloadInCache(element.GetBoolAttribute("preloadInCache")); } void AudioResource::SerializeTo(SerializerElement& element) const { @@ -576,6 +585,7 @@ void AudioResource::SerializeTo(SerializerElement& element) const { element.SetAttribute("file", GetFile()); element.SetAttribute("preloadAsMusic", PreloadAsMusic()); element.SetAttribute("preloadAsSound", PreloadAsSound()); + element.SetAttribute("preloadInCache", PreloadInCache()); } void FontResource::SetFile(const gd::String& newFile) { diff --git a/Core/GDCore/Project/ResourcesManager.h b/Core/GDCore/Project/ResourcesManager.h index fd62a6c1cd91..a2b9a41b6b23 100644 --- a/Core/GDCore/Project/ResourcesManager.h +++ b/Core/GDCore/Project/ResourcesManager.h @@ -223,7 +223,7 @@ class GD_CORE_API ImageResource : public Resource { */ class GD_CORE_API AudioResource : public Resource { public: - AudioResource() : Resource(), preloadAsMusic(false), preloadAsSound(false) { + AudioResource() : Resource(), preloadAsMusic(false), preloadAsSound(false), preloadInCache(false) { SetKind("audio"); }; virtual ~AudioResource(){}; @@ -263,10 +263,21 @@ class GD_CORE_API AudioResource : public Resource { */ void SetPreloadAsSound(bool enable = true) { preloadAsSound = enable; } + /** + * \brief Return true if the audio resource should be preloaded in cache (without decoding into memory). + */ + bool PreloadInCache() const { return preloadInCache; } + + /** + * \brief Set if the audio resource should be preloaded in cache (without decoding into memory). + */ + void SetPreloadInCache(bool enable = true) { preloadInCache = enable; } + private: gd::String file; bool preloadAsSound; bool preloadAsMusic; + bool preloadInCache; }; /** diff --git a/GDJS/Runtime/howler-sound-manager/howler-sound-manager.ts b/GDJS/Runtime/howler-sound-manager/howler-sound-manager.ts index a4795c8a15df..94202489d39c 100644 --- a/GDJS/Runtime/howler-sound-manager/howler-sound-manager.ts +++ b/GDJS/Runtime/howler-sound-manager/howler-sound-manager.ts @@ -802,24 +802,41 @@ namespace gdjs { if (!filesToLoad.length) return; const file = filesToLoad.shift()!; const fileData = files[file][0]; - if (!fileData.preloadAsSound && !fileData.preloadAsMusic) { - onLoad(); - } else if (fileData.preloadAsSound && fileData.preloadAsMusic) { - let loadedOnce = false; - const callback = (_, error) => { - if (!loadedOnce) { - loadedOnce = true; - return; - } + + let loadCounter = 0; + const callback = (_?: any, error?: string) => { + loadCounter--; + if (!loadCounter) { onLoad(_, error); - }; + } + }; + if (fileData.preloadAsMusic) { + loadCounter++; preloadAudioFile(file, callback, /* isMusic= */ true); + } + + if (fileData.preloadAsSound) { + loadCounter++; preloadAudioFile(file, callback, /* isMusic= */ false); - } else if (fileData.preloadAsSound) { - preloadAudioFile(file, onLoad, /* isMusic= */ false); - } else if (fileData.preloadAsMusic) - preloadAudioFile(file, onLoad, /* isMusic= */ true); + } else if (fileData.preloadInCache) { + // preloading as sound already does a XHR request, hence "else if" + loadCounter++; + const sound = new XMLHttpRequest(); + sound.addEventListener('load', callback); + sound.addEventListener('error', (_) => + callback(_, 'XHR error: ' + file) + ); + sound.addEventListener('abort', (_) => + callback(_, 'XHR abort: ' + file) + ); + sound.open('GET', file); + sound.send(); + } + + if (!loadCounter) { + onLoad(); + } }; loadNextFile(); } diff --git a/GDJS/Runtime/types/project-data.d.ts b/GDJS/Runtime/types/project-data.d.ts index db9d58f289fc..afd62e1d84e8 100644 --- a/GDJS/Runtime/types/project-data.d.ts +++ b/GDJS/Runtime/types/project-data.d.ts @@ -220,6 +220,7 @@ declare interface ResourceData { disablePreload?: boolean; preloadAsSound?: boolean; preloadAsMusic?: boolean; + preloadInCache?: boolean; } declare type ResourceKind =