diff --git a/flixel/sound/FlxSound.hx b/flixel/sound/FlxSound.hx index 168c52000a..41dd031786 100644 --- a/flixel/sound/FlxSound.hx +++ b/flixel/sound/FlxSound.hx @@ -333,56 +333,96 @@ class FlxSound extends FlxBasic } /** - * One of the main setup functions for sounds, this function loads a sound from an embedded MP3. - * + * Loads a sound from the provided sound asset. + * The asset can be an OpenFL Sound instance, embedded sound, file path or byte array. + * * **Note:** If the `FLX_DEFAULT_SOUND_EXT` flag is enabled, you may omit the file extension - * - * @param EmbeddedSound An embedded Class object representing an MP3 file. - * @param Looped Whether or not this sound should loop endlessly. - * @param AutoDestroy Whether or not this FlxSound instance should be destroyed when the sound finishes playing. - * Default value is false, but `FlxG.sound.play()` and `FlxG.sound.stream()` will set it to true by default. - * @param OnComplete Called when the sound finished playing - * @return This FlxSound instance (nice for chaining stuff together, if you're into that). + * + * @param sound The sound asset to load. + * @param looped Whether or not this sound should loop endlessly. + * @param autoDestroy Whether or not this FlxSound instance should be destroyed when the sound finishes playing. + * @param onComplete Called when the sound finishes playing. + * @return This FlxSound instance (nice for chaining stuff together, if you're into that). + * + * @since 6.2.0 */ - public function loadEmbedded(EmbeddedSound:FlxSoundAsset, Looped:Bool = false, AutoDestroy:Bool = false, ?OnComplete:Void->Void):FlxSound + public function load(sound:FlxSoundAsset, looped:Bool = false, autoDestroy:Bool = false, ?onComplete:Void->Void):FlxSound { - if (EmbeddedSound == null) + if (sound == null) return this; cleanup(true); - if ((EmbeddedSound is Sound)) + if ((sound is Sound)) { - _sound = EmbeddedSound; + _sound = sound; } - else if ((EmbeddedSound is Class)) + else if ((sound is Class)) { - _sound = Type.createInstance(EmbeddedSound, []); + _sound = Type.createInstance(sound, []); } - else if ((EmbeddedSound is String)) + else if ((sound is String)) { - if (FlxG.assets.exists(EmbeddedSound, SOUND)) - _sound = FlxG.assets.getSoundUnsafe(EmbeddedSound); + if (FlxG.assets.exists(sound, SOUND)) + _sound = FlxG.assets.getSoundUnsafe(sound); else - FlxG.log.error('Could not find a Sound asset with an ID of \'$EmbeddedSound\'.'); + FlxG.log.error('Could not find a Sound asset with an ID of \'$sound\'.'); + } + else if ((sound is ByteArrayData)) + { + var bytes:ByteArray = cast sound; + + _sound = new Sound(); + _sound.addEventListener(Event.ID3, gotID3); + _sound.loadCompressedDataFromByteArray(bytes, bytes.length); } // NOTE: can't pull ID3 info from embedded sound currently - return init(Looped, AutoDestroy, OnComplete); + return init(looped, autoDestroy, onComplete); } - + /** - * One of the main setup functions for sounds, this function loads a sound from a URL. - * - * @param SoundURL A string representing the URL of the MP3 file you want to play. - * @param Looped Whether or not this sound should loop endlessly. - * @param AutoDestroy Whether or not this FlxSound instance should be destroyed when the sound finishes playing. - * Default value is false, but `FlxG.sound.play()` and `FlxG.sound.stream()` will set it to true by default. - * @param OnComplete Called when the sound finished playing - * @param OnLoad Called when the sound finished loading. - * @return This FlxSound instance (nice for chaining stuff together, if you're into that). + * Loads a streamed sound from the provided file path. + * This does not load sounds from web locations. Use `loadFromURL()` for that, instead. + * + * If sound streaming is not supported, a normal sound will be returned. + * + * **Note:** If the `FLX_DEFAULT_SOUND_EXT` flag is enabled, you may omit the file extension + * + * @param path The path to the sound asset. + * @param looped Whether or not this sound should loop endlessly. + * @param autoDestroy Whether or not this FlxSound instance should be destroyed when the sound finishes playing. + * @param onComplete Called when the sound finishes playing. + * @return This FlxSound instance (nice for chaining stuff together, if you're into that). + * + * @since 6.2.0 */ - public function loadStream(SoundURL:String, Looped:Bool = false, AutoDestroy:Bool = false, ?OnComplete:Void->Void, ?OnLoad:Void->Void):FlxSound + public function loadStreamed(path:String, looped:Bool = false, autoDestroy:Bool = false, ?onComplete:Void->Void):FlxSound + { + cleanup(true); + + if (FlxG.assets.exists(path, MUSIC)) + _sound = FlxG.assets.getMusicUnsafe(path); + else + FlxG.log.error('Could not find a Sound asset with an ID of \'$path\'.'); + + // NOTE: can't pull ID3 info from embedded sound currently + return init(looped, autoDestroy, onComplete); + } + + /** + * Loads a sound from the provided URL. + * + * @param soundURL A string representing the URL of the sound you want to play. + * @param looped Whether or not this sound should loop endlessly. + * @param autoDestroy Whether or not this FlxSound instance should be destroyed when the sound finishes playing. + * @param onComplete Called when the sound finishes playing. + * @param onLoad Called when the sound finishes loading. + * @return This FlxSound instance (nice for chaining stuff together, if you're into that). + * + * @since 6.2.0 + */ + public function loadFromURL(soundURL:String, looped:Bool = false, autoDestroy:Bool = false, ?onComplete:Void->Void, ?onLoad:Void->Void):FlxSound { cleanup(true); @@ -396,15 +436,50 @@ class FlxSound extends FlxBasic if (_sound == e.target) { _length = _sound.length; - if (OnLoad != null) - OnLoad(); + if (onLoad != null) + onLoad(); } } // Use a weak reference so this can be garbage collected if destroyed before loading. _sound.addEventListener(Event.COMPLETE, loadCallback, false, 0, true); - _sound.load(new URLRequest(SoundURL)); + _sound.load(new URLRequest(soundURL)); - return init(Looped, AutoDestroy, OnComplete); + return init(looped, autoDestroy, onComplete); + } + + /** + * One of the main setup functions for sounds, this function loads a sound from an embedded MP3. + * + * **Note:** If the `FLX_DEFAULT_SOUND_EXT` flag is enabled, you may omit the file extension + * + * @param EmbeddedSound An embedded Class object representing an MP3 file. + * @param Looped Whether or not this sound should loop endlessly. + * @param AutoDestroy Whether or not this FlxSound instance should be destroyed when the sound finishes playing. + * Default value is false, but `FlxG.sound.play()` and `FlxG.sound.loadFromURL()` will set it to true by default. + * @param OnComplete Called when the sound finished playing + * @return This FlxSound instance (nice for chaining stuff together, if you're into that). + */ + @:deprecated("loadEmbedded() is deprecated, use load() instead.") + public function loadEmbedded(EmbeddedSound:FlxSoundAsset, Looped:Bool = false, AutoDestroy:Bool = false, ?OnComplete:Void->Void):FlxSound + { + return load(EmbeddedSound, Looped, AutoDestroy, OnComplete); + } + + /** + * One of the main setup functions for sounds, this function loads a sound from a URL. + * + * @param SoundURL A string representing the URL of the MP3 file you want to play. + * @param Looped Whether or not this sound should loop endlessly. + * @param AutoDestroy Whether or not this FlxSound instance should be destroyed when the sound finishes playing. + * Default value is false, but `FlxG.sound.play()` and `FlxG.sound.loadFromURL()` will set it to true by default. + * @param OnComplete Called when the sound finished playing + * @param OnLoad Called when the sound finished loading. + * @return This FlxSound instance (nice for chaining stuff together, if you're into that). + */ + @:deprecated("loadStream() is deprecated, use loadFromURL() instead.") + public function loadStream(SoundURL:String, Looped:Bool = false, AutoDestroy:Bool = false, ?OnComplete:Void->Void, ?OnLoad:Void->Void):FlxSound + { + return loadFromURL(SoundURL, Looped, AutoDestroy, OnComplete, OnLoad); } /** @@ -413,18 +488,13 @@ class FlxSound extends FlxBasic * @param Bytes A ByteArray object. * @param Looped Whether or not this sound should loop endlessly. * @param AutoDestroy Whether or not this FlxSound instance should be destroyed when the sound finishes playing. - * Default value is false, but `FlxG.sound.play()` and `FlxG.sound.stream()` will set it to true by default. + * Default value is false, but `FlxG.sound.play()` and `FlxG.sound.loadFromURL()` will set it to true by default. * @return This FlxSound instance (nice for chaining stuff together, if you're into that). */ + @:deprecated("loadByteArray() is deprecated, use load() instead.") public function loadByteArray(Bytes:ByteArray, Looped:Bool = false, AutoDestroy:Bool = false, ?OnComplete:Void->Void):FlxSound { - cleanup(true); - - _sound = new Sound(); - _sound.addEventListener(Event.ID3, gotID3); - _sound.loadCompressedDataFromByteArray(Bytes, Bytes.length); - - return init(Looped, AutoDestroy, OnComplete); + return load(Bytes, Looped, AutoDestroy, OnComplete); } function init(Looped:Bool = false, AutoDestroy:Bool = false, ?OnComplete:Void->Void):FlxSound diff --git a/flixel/system/FlxAssets.hx b/flixel/system/FlxAssets.hx index 93c256537a..9486f40167 100644 --- a/flixel/system/FlxAssets.hx +++ b/flixel/system/FlxAssets.hx @@ -35,7 +35,7 @@ class VirtualInputData extends #if nme ByteArray #else ByteArrayData #end {} typedef FlxTexturePackerJsonAsset = FlxJsonAsset; typedef FlxAsepriteJsonAsset = FlxJsonAsset; -typedef FlxSoundAsset = OneOfThree>; +typedef FlxSoundAsset = OneOfFour, ByteArray>; typedef FlxGraphicAsset = OneOfThree; typedef FlxTilemapGraphicAsset = OneOfFour; typedef FlxBitmapFontGraphicAsset = OneOfFour; diff --git a/flixel/system/frontEnds/AssetFrontEnd.hx b/flixel/system/frontEnds/AssetFrontEnd.hx index 47fe7fe95d..dae45250fe 100644 --- a/flixel/system/frontEnds/AssetFrontEnd.hx +++ b/flixel/system/frontEnds/AssetFrontEnd.hx @@ -15,6 +15,11 @@ import openfl.utils.AssetCache; import openfl.utils.Future; import openfl.text.Font; +#if lime_vorbis +import lime.media.vorbis.VorbisFile; +import lime.media.AudioBuffer; +#end + using StringTools; /** @@ -123,7 +128,9 @@ class AssetFrontEnd // Check cache case IMAGE if (canUseCache && Assets.cache.hasBitmapData(id)): Assets.cache.getBitmapData(id); - case SOUND if (canUseCache && Assets.cache.hasSound(id)): + // Lime doesn't expose a way to detect if sound streaming is supported so we have to check ourselves + // MUSIC behaves like SOUND when sound streaming is unsupported. + case SOUND #if !lime_vorbis , MUSIC #end if (canUseCache && Assets.cache.hasSound(id)): Assets.cache.getSound(id); case FONT if (canUseCache && Assets.cache.hasFont(id)): Assets.cache.getFont(id); @@ -134,11 +141,19 @@ class AssetFrontEnd if (canUseCache) Assets.cache.setBitmapData(id, bitmap); bitmap; - case SOUND: + case SOUND #if !lime_vorbis , MUSIC #end: final sound = Sound.fromFile(getPath(id)); if (canUseCache) Assets.cache.setSound(id, sound); sound; + #if lime_vorbis + case MUSIC: + // Ignore cache when streaming sounds + final vorbisFile = VorbisFile.fromFile(getPath(id)); + final buffer = AudioBuffer.fromVorbisFile(buffer); + final sound = Sound.fromAudioBuffer(buffer); + sound; + #end case FONT: final font = Font.fromFile(getPath(id)); if (canUseCache) @@ -159,6 +174,7 @@ class AssetFrontEnd case BINARY: Assets.getBytes(id); case IMAGE: Assets.getBitmapData(id, useCache); case SOUND: Assets.getSound(id, useCache); + case MUSIC: Assets.getMusic(id, useCache); case FONT: Assets.getFont(id, useCache); } } @@ -224,6 +240,7 @@ class AssetFrontEnd case BINARY: Assets.loadBytes(id); case IMAGE: Assets.loadBitmapData(id, useCache); case SOUND: Assets.loadSound(id, useCache); + case MUSIC: Assets.loadMusic(id, useCache); case FONT: Assets.loadFont(id, useCache); } } @@ -239,7 +256,7 @@ class AssetFrontEnd { #if FLX_DEFAULT_SOUND_EXT // add file extension - if (type == SOUND) + if (type == SOUND || type == MUSIC) id = addSoundExt(id); #end @@ -267,7 +284,7 @@ class AssetFrontEnd { #if FLX_DEFAULT_SOUND_EXT // add file extension - if (type == SOUND) + if (type == SOUND || type == MUSIC) id = addSoundExt(id); #end @@ -341,12 +358,26 @@ class AssetFrontEnd * * @param id The ID or asset path for the sound * @param useCache Whether to allow use of the asset cache (if one exists) - * @return A new `Sound` object Note: Dos not return a `FlxSound` + * @return A new `Sound` object Note: Does not return a `FlxSound` */ public inline function getSoundUnsafe(id:String, useCache = true):Sound { return cast getAssetUnsafe(addSoundExtIf(id), SOUND, useCache); } + + /** + * Gets an instance of a streamed sound. Unlike its "safe" counterpart, there is no log on missing assets + * + * If sound streaming is not supported a normal sound will be returned instead. + * + * @param id The ID or asset path for the sound + * @param useCache Whether to allow use of the asset cache (if one exists) + * @return A new `Sound` object Note: Does not return a `FlxSound` + */ + public inline function getMusicUnsafe(id:String, useCache = true):Sound + { + return cast getAssetUnsafe(addSoundExtIf(id), MUSIC, useCache); + } /** * Gets an instance of a sound, logs when the asset is not found. @@ -356,12 +387,29 @@ class AssetFrontEnd * @param id The ID or asset path for the sound * @param useCache Whether to allow use of the asset cache (if one exists) * @param logStyle How to log, if the asset is not found. Uses `LogStyle.ERROR` by default - * @return A new `Sound` object Note: Dos not return a `FlxSound` + * @return A new `Sound` object Note: Does not return a `FlxSound` */ public inline function getSound(id:String, useCache = true, ?logStyle:LogStyle):Sound { return cast getAsset(addSoundExtIf(id), SOUND, useCache, logStyle); } + + /** + * Gets an instance of a streamed sound, logs when the asset is not found. + * + * If sound streaming is not supported a normal sound will be returned instead. + * + * **Note:** If the `FLX_DEFAULT_SOUND_EXT` flag is enabled, you may omit the file extension + * + * @param id The ID or asset path for the sound + * @param useCache Whether to allow use of the asset cache (if one exists) + * @param logStyle How to log, if the asset is not found. Uses `LogStyle.ERROR` by default + * @return A new `Sound` object Note: Does not return a `FlxSound` + */ + public inline function getMusic(id:String, useCache = true, ?logStyle:LogStyle):Sound + { + return cast getAsset(addSoundExtIf(id), MUSIC, useCache, logStyle); + } /** * Gets an instance of a sound, logs when the asset is not found @@ -369,12 +417,27 @@ class AssetFrontEnd * @param id The ID or asset path for the sound * @param useCache Whether to allow use of the asset cache (if one exists) * @param logStyle How to log, if the asset is not found. Uses `LogStyle.ERROR` by default - * @return A new `Sound` object Note: Dos not return a `FlxSound` + * @return A new `Sound` object Note: Does not return a `FlxSound` */ public inline function getSoundAddExt(id:String, useCache = true, ?logStyle:LogStyle):Sound { return getSound(addSoundExt(id), useCache, logStyle); } + + /** + * Gets an instance of a streamed sound, logs when the asset is not found + * + * If sound streaming is not supported a normal sound will be returned instead. + * + * @param id The ID or asset path for the sound + * @param useCache Whether to allow use of the asset cache (if one exists) + * @param logStyle How to log, if the asset is not found. Uses `LogStyle.ERROR` by default + * @return A new `Sound` object Note: Does not return a `FlxSound` + */ + public inline function getMusicAddExt(id:String, useCache = true, ?logStyle:LogStyle):Sound + { + return getMusic(addSoundExt(id), useCache, logStyle); + } inline function addSoundExtIf(id:String) { @@ -387,7 +450,8 @@ class AssetFrontEnd inline function addSoundExt(id:String) { - if (!id.endsWith(".mp3") && !id.endsWith(".ogg") && !id.endsWith(".wav")) + final needsExt = Path.extension(id).length == 0; + if (needsExt) return id + defaultSoundExtension; return id; @@ -556,6 +620,20 @@ class AssetFrontEnd { return cast loadAsset(id, SOUND, useCache); } + + /** + * Loads a streamed sound asset asynchronously + * + * If sound streaming is not supported a normal sound will be returned instead. + * + * @param id The ID or asset path for the asset + * @param useCache Whether to allow use of the asset cache (if one exists) + * @return Returns a `Future` which allows listeners to be added via methods like `onComplete` + */ + public inline function loadMusic(id:String, useCache = true):Future + { + return cast loadAsset(id, MUSIC, useCache); + } /** * Loads a text asset asynchronously @@ -671,6 +749,9 @@ enum abstract FlxAssetType(String) /** Audio assets, such as *.ogg or *.wav files */ var SOUND = "sound"; + + /** Streamed audio assets, such as *.ogg or *.wav files */ + var MUSIC = "music"; /** Text assets */ var TEXT = "text"; @@ -683,6 +764,7 @@ enum abstract FlxAssetType(String) case FONT: AssetType.FONT; case IMAGE: AssetType.IMAGE; case SOUND: AssetType.SOUND; + case MUSIC: AssetType.MUSIC; case TEXT: AssetType.TEXT; } } diff --git a/flixel/system/frontEnds/SoundFrontEnd.hx b/flixel/system/frontEnds/SoundFrontEnd.hx index fcefac22f5..1d1acce241 100644 --- a/flixel/system/frontEnds/SoundFrontEnd.hx +++ b/flixel/system/frontEnds/SoundFrontEnd.hx @@ -86,7 +86,7 @@ class SoundFrontEnd public var defaultMusicGroup:FlxSoundGroup = new FlxSoundGroup(); /** - * The group sounds in load() / play() / stream() are added to unless specified otherwise. + * The group sounds in load() / loadFromURL() / play() are added to unless specified otherwise. */ public var defaultSoundGroup:FlxSoundGroup = new FlxSoundGroup(); @@ -124,7 +124,7 @@ class SoundFrontEnd music.stop(); } - music.loadEmbedded(embeddedMusic, looped); + music.load(embeddedMusic, looped); music.volume = volume; music.persist = true; group.add(music); @@ -161,7 +161,7 @@ class SoundFrontEnd if (embeddedSound != null) { - sound.loadEmbedded(embeddedSound, looped, autoDestroy, onComplete); + sound.load(embeddedSound, looped, autoDestroy, onComplete); loadHelper(sound, volume, group, autoPlay); // Call OnlLoad() because the sound already loaded if (onLoad != null && sound._sound != null) @@ -182,7 +182,7 @@ class SoundFrontEnd } } - sound.loadStream(url, looped, autoDestroy, onComplete, loadCallback); + sound.loadFromURL(url, looped, autoDestroy, onComplete, loadCallback); loadHelper(sound, volume, group); } @@ -251,7 +251,7 @@ class SoundFrontEnd { embeddedSound = cache(embeddedSound); } - var sound = list.recycle(FlxSound).loadEmbedded(embeddedSound, looped, autoDestroy, onComplete); + var sound = list.recycle(FlxSound).load(embeddedSound, looped, autoDestroy, onComplete); return loadHelper(sound, volume, group, true); } @@ -269,12 +269,33 @@ class SoundFrontEnd * @param onLoad Called when the sound finished loading. * @return A FlxSound object. */ - public function stream(url:String, volume = 1.0, looped = false, ?group:FlxSoundGroup, autoDestroy = true, ?onComplete:Void->Void, + public function loadFromURL(url:String, volume = 1.0, looped = false, ?group:FlxSoundGroup, autoDestroy = true, ?onComplete:Void->Void, ?onLoad:Void->Void):FlxSound { return load(null, volume, looped, group, autoDestroy, true, url, onComplete, onLoad); } + /** + * Plays a sound from a URL. Tries to recycle a cached sound first. + * NOTE: Just calls FlxG.sound.load() with AutoPlay == true. + * + * @param url Load a sound from an external web resource instead. + * @param volume How loud to play it (0 to 1). + * @param looped Whether to loop this sound. + * @param group The group to add this sound to. + * @param autoDestroy Whether to destroy this sound when it finishes playing. + * Leave this value set to "false" if you want to re-use this FlxSound instance. + * @param onComplete Called when the sound finished playing + * @param onLoad Called when the sound finished loading. + * @return A FlxSound object. + */ + @:deprecated("FlxG.sound.stream() is deprecated, use FlxG.sound.loadFromURL() instead") + public function stream(url:String, volume = 1.0, looped = false, ?group:FlxSoundGroup, autoDestroy = true, ?onComplete:Void->Void, + ?onLoad:Void->Void):FlxSound + { + return loadFromURL(url, volume, looped, group, autoDestroy, onComplete, onLoad); + } + /** * Pause all sounds currently playing. */ diff --git a/tests/unit/src/flixel/sound/FlxSoundTest.hx b/tests/unit/src/flixel/sound/FlxSoundTest.hx index 9a8ea01f9c..a43e729940 100644 --- a/tests/unit/src/flixel/sound/FlxSoundTest.hx +++ b/tests/unit/src/flixel/sound/FlxSoundTest.hx @@ -3,10 +3,10 @@ package flixel.sound; class FlxSoundTest { @Test // #1511 - function testLoadEmbeddedInvalidSoundPathNoCrash() + function testLoadInvalidSoundPathNoCrash() { var sound = new FlxSound(); - sound.loadEmbedded("assets/invalid"); + sound.load("assets/invalid"); sound.play(); } }