mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Add error handling for Howler sound methods (#7853)
* Specifically for "Maximum call stack size exceeded" errors.
This commit is contained in:
@@ -30,6 +30,36 @@ namespace gdjs {
|
||||
return volume;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles errors that occur when calling Howler sound methods.
|
||||
*
|
||||
* This function provides special handling for "Maximum call stack size exceeded" errors
|
||||
* that can occur in Howler.js due to recursive method calls in volume(), seek(), fade(),
|
||||
* and other sound manipulation methods. Instead of crashing the application, these
|
||||
* specific errors are logged as warnings to allow the application to continue running.
|
||||
*
|
||||
* @param error - The error that occurred during the method call
|
||||
* @param methodName - The name of the Howler method that caused the error (e.g., 'volume', 'seek')
|
||||
*
|
||||
* @throws {Error} Re-throws the original error if it's not a stack overflow error
|
||||
*
|
||||
*/
|
||||
const handleHowlerSoundMethodError = (error: unknown, methodName: string) => {
|
||||
if (
|
||||
error instanceof Error &&
|
||||
error.message &&
|
||||
typeof error.message === 'string' &&
|
||||
error.message.startsWith('Maximum call stack size exceeded')
|
||||
) {
|
||||
console.warn(
|
||||
`An error occurred when call method "${methodName}":`,
|
||||
error
|
||||
);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A thin wrapper around a Howl object with:
|
||||
* * Handling of callbacks when the sound is not yet loaded.
|
||||
@@ -103,33 +133,36 @@ namespace gdjs {
|
||||
* @returns The current instance for chaining.
|
||||
*/
|
||||
play(): this {
|
||||
if (this.isLoaded()) {
|
||||
const newID = this._howl.play(
|
||||
this._id === null ? '__default' : this._id
|
||||
);
|
||||
this._id = newID;
|
||||
try {
|
||||
if (this.isLoaded()) {
|
||||
const newID = this._howl.play(
|
||||
this._id === null ? '__default' : this._id
|
||||
);
|
||||
this._id = newID;
|
||||
|
||||
// Set the howl properties as soon as the sound is played and we have its ID.
|
||||
this._howl.volume(this._initialVolume, newID); // this._initialVolume is already clamped between 0 and 1.
|
||||
this._howl.loop(this._loop, newID);
|
||||
// this._rate is not clamped, but we need to clamp it when passing it to Howler.js as it
|
||||
// only supports a specific range.
|
||||
this._howl.rate(gdjs.HowlerSoundManager.clampRate(this._rate), newID);
|
||||
|
||||
// Manually handle the play event before we have an ID.
|
||||
// Before loading, howler won't register events as without an ID we cannot set a listener.
|
||||
// Once we have an ID, we can transfer control of the events to howler.
|
||||
// We also need to call them once as Howler doesn't for the first play event.
|
||||
this._onPlay.forEach((func) => {
|
||||
// Transfer the event to howler now that we have an ID
|
||||
this.on('play', func);
|
||||
func(newID);
|
||||
});
|
||||
this._oncePlay.forEach((func) => func(newID));
|
||||
this._onPlay = [];
|
||||
this._oncePlay = [];
|
||||
} else this._howl.once('load', () => this.play()); // Play only once the howl is fully loaded
|
||||
// Set the howl properties as soon as the sound is played and we have its ID.
|
||||
this._howl.volume(this._initialVolume, newID); // this._initialVolume is already clamped between 0 and 1.
|
||||
this._howl.loop(this._loop, newID);
|
||||
// this._rate is not clamped, but we need to clamp it when passing it to Howler.js as it
|
||||
// only supports a specific range.
|
||||
this._howl.rate(gdjs.HowlerSoundManager.clampRate(this._rate), newID);
|
||||
|
||||
// Manually handle the play event before we have an ID.
|
||||
// Before loading, howler won't register events as without an ID we cannot set a listener.
|
||||
// Once we have an ID, we can transfer control of the events to howler.
|
||||
// We also need to call them once as Howler doesn't for the first play event.
|
||||
this._onPlay.forEach((func) => {
|
||||
// Transfer the event to howler now that we have an ID
|
||||
this.on('play', func);
|
||||
func(newID);
|
||||
});
|
||||
this._oncePlay.forEach((func) => func(newID));
|
||||
this._onPlay = [];
|
||||
this._oncePlay = [];
|
||||
} else this._howl.once('load', () => this.play()); // Play only once the howl is fully loaded
|
||||
} catch (error) {
|
||||
handleHowlerSoundMethodError(error, 'play');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -138,7 +171,11 @@ namespace gdjs {
|
||||
* @returns The current instance for chaining.
|
||||
*/
|
||||
pause(): this {
|
||||
if (this._id !== null) this._howl.pause(this._id);
|
||||
try {
|
||||
if (this._id !== null) this._howl.pause(this._id);
|
||||
} catch (error) {
|
||||
handleHowlerSoundMethodError(error, 'pause');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -147,7 +184,11 @@ namespace gdjs {
|
||||
* @returns The current instance for chaining.
|
||||
*/
|
||||
stop(): this {
|
||||
if (this._id !== null) this._howl.stop(this._id);
|
||||
try {
|
||||
if (this._id !== null) this._howl.stop(this._id);
|
||||
} catch (error) {
|
||||
handleHowlerSoundMethodError(error, 'stop');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -196,11 +237,15 @@ namespace gdjs {
|
||||
* @returns The current instance for chaining.
|
||||
*/
|
||||
setRate(rate: float): this {
|
||||
this._rate = rate;
|
||||
// If the sound has already started playing, then change the value directly.
|
||||
if (this._id !== null) {
|
||||
rate = gdjs.HowlerSoundManager.clampRate(rate);
|
||||
this._howl.rate(rate, this._id);
|
||||
try {
|
||||
this._rate = rate;
|
||||
// If the sound has already started playing, then change the value directly.
|
||||
if (this._id !== null) {
|
||||
rate = gdjs.HowlerSoundManager.clampRate(rate);
|
||||
this._howl.rate(rate, this._id);
|
||||
}
|
||||
} catch (error) {
|
||||
handleHowlerSoundMethodError(error, 'rate');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -217,9 +262,13 @@ namespace gdjs {
|
||||
* @returns The current instance for chaining.
|
||||
*/
|
||||
setLoop(loop: boolean): this {
|
||||
this._loop = loop;
|
||||
// If the sound has already started playing, then change the value directly.
|
||||
if (this._id !== null) this._howl.loop(loop, this._id);
|
||||
try {
|
||||
this._loop = loop;
|
||||
// If the sound has already started playing, then change the value directly.
|
||||
if (this._id !== null) this._howl.loop(loop, this._id);
|
||||
} catch (error) {
|
||||
handleHowlerSoundMethodError(error, 'loop');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -239,10 +288,14 @@ namespace gdjs {
|
||||
* @returns The current instance for chaining.
|
||||
*/
|
||||
setVolume(volume: float): this {
|
||||
this._initialVolume = clampVolume(volume);
|
||||
try {
|
||||
this._initialVolume = clampVolume(volume);
|
||||
|
||||
// If the sound has already started playing, then change the value directly.
|
||||
if (this._id !== null) this._howl.volume(this._initialVolume, this._id);
|
||||
// If the sound has already started playing, then change the value directly.
|
||||
if (this._id !== null) this._howl.volume(this._initialVolume, this._id);
|
||||
} catch (error) {
|
||||
handleHowlerSoundMethodError(error, 'volume');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -259,7 +312,11 @@ namespace gdjs {
|
||||
* @returns The current instance for chaining.
|
||||
*/
|
||||
setMute(mute: boolean): this {
|
||||
if (this._id !== null) this._howl.mute(mute, this._id);
|
||||
try {
|
||||
if (this._id !== null) this._howl.mute(mute, this._id);
|
||||
} catch (error) {
|
||||
handleHowlerSoundMethodError(error, 'mute');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -276,7 +333,11 @@ namespace gdjs {
|
||||
* @returns The current instance for chaining.
|
||||
*/
|
||||
setSeek(seek: float): this {
|
||||
if (this._id !== null) this._howl.seek(seek, this._id);
|
||||
try {
|
||||
if (this._id !== null) this._howl.seek(seek, this._id);
|
||||
} catch (error) {
|
||||
handleHowlerSoundMethodError(error, 'seek');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -302,8 +363,17 @@ namespace gdjs {
|
||||
* @returns The current instance for chaining.
|
||||
*/
|
||||
fade(from: float, to: float, duration: float): this {
|
||||
if (this._id !== null)
|
||||
this._howl.fade(clampVolume(from), clampVolume(to), duration, this._id);
|
||||
try {
|
||||
if (this._id !== null)
|
||||
this._howl.fade(
|
||||
clampVolume(from),
|
||||
clampVolume(to),
|
||||
duration,
|
||||
this._id
|
||||
);
|
||||
} catch (error) {
|
||||
handleHowlerSoundMethodError(error, 'fade');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user