pcall
only returns false if an error occurs. Since giving 'SoundId' an invalid sound does not raise an error, pcall
returns true.
It took me a bit to figure out how to do this, but I tested this and it works:
01 | function IsValidSound(soundId) |
02 | local con 1 , con 2 , yieldFolder, valid |
03 | local sound = Instance.new( "Sound" , workspace) |
04 | local function cleanup() |
07 | yieldFolder.Name = "Different" |
09 | yieldFolder = Instance.new( "Folder" ) |
10 | con 1 = game:GetService( "LogService" ).MessageOut:connect( function (msg, msgType) |
11 | if msgType ~ = Enum.MessageType.MessageError then return end |
12 | if not msg:find( tostring (soundId)) then return end |
16 | con 2 = sound.Loaded:connect( function () |
17 | valid = sound.TimeLength > 0 |
20 | sound.SoundId = soundId |
21 | if sound.IsLoaded then |
23 | if sound.TimeLength = = 0 then |
29 | yieldFolder.Changed:wait() |
31 | if not valid then sound:Destroy() end |
32 | return valid and sound |
I learned the following:
- The sound has to be fresh (that is, you cannot re-use an old Sound), or else
IsLoaded
will always be true
- The sound has to be parented into the game somewhere (I didn't test putting it in places other than the workspace, but that should be fine)
- Sometimes invalid sounds will load immediately (but have a TimeLength of 0), other times they will never load
- Calling IsValidSound 100x takes several seconds (if the particular soundIds haven't been loaded before and if they are all invalid) due to the errors in the output
Note that IsValidSound may not return immediately (it waits for the sound to load if needed) - that is, it may act like a "wait" command, so don't put it in a loop with hundreds of sound IDs to check (if you needed to do that, you'd probably want to use coroutines so each sound can load simultaneously instead of one at a time). In your case you can use it like this:
5 | print ( "song id is not valid" ) |
Don't forget to Destroy the sound when you're done (so that you're not left with lots of different Sounds in the workspace). Alternatively, since a sound only needs to load once before its ready to play, you could do:
4 | customsong.SoundId = sound.SoundId |
8 | print ( "song id is not valid" ) |
The IsValidSound
is a bit complicated, but in reality it's just doing this:
- Get the sound loading
- If it loads immediately, check the TimeLength property to see if it's a valid sound or not
- Otherwise, wait until Roblox prints out an error saying that the song is invalid or wait until the song loads. If it loads, check the TimeLength property to determine whether it's valid or not.
You cannot use 'coroutine.yield' reliably (Roblox often just resumes it after a brief delay), so I use a 'yieldFolder' instead. The idea is to pause the calling thread until one of the events we set up occur (at which point we change the name of the folder, allowing the calling thread to finish up). Do note that if neither event occur, IsValidSound would yield forever (much like calling wait(1000000000)
). However, based on my testing, that shouldn't happen. If you wanted to be absolutely safe, you could add a 3rd connection that just waits for a certain number of seconds to go by (ex, 5 seconds), and then - if valid
had not been set to a non-nil value by then - set valid
to false
and call cleanup()
.
(Edit)
One potential flaw with the above solution is that it assumes that Roblox will print the sound ID to the Output as an error when the ID is an invalid sound. If they change this behaviour for whatever reason (though I doubt they will), the function will stop working.
HappyWalker found a forum post talking about a function in the MarketplaceService - GetProductInfo - which can help us out. I played around with it a bit and came up with this function:
2 | local success, value = pcall ( function () |
3 | return game:GetService( "MarketplaceService" ):GetProductInfo(id).AssetTypeId = = 3 |
5 | return success and value |
In the case of failure, It is usually faster than "IsValidSound", but it has numerous problems:
- There is no guarantee that it is a valid sound (I don't know - is it possible to upload an invalid sound file to Roblox? If so, IsSound may return 'true' on a sound file that is invalid)
- Unlike in IsValidSound, the sound itself isn't loaded, so you end up losing time when the 'id' is actually a sound you wish to play
- The MarketplaceService errors if you ask it for too much information. When it does this, IsSound has little choice but to return false (I tried trying again, but this made things worse). I ran into this when asking it for hundreds of ids within a few seconds. Not only did the requests take a long time to return, they resulted in errors. (Specifically, I was scanning a section of IDs that had 2 valid sounds and it didn't always find them.)
- Sometimes, even when sending just a single request, it would take > 5 (perhaps even 10) seconds to respond
At best it can be used to "help out" the IsValidSound function by filtering out IDs that aren't sounds - thereby saving IsValidSound from raising more errors (which may slow down the place if you try to do hundreds per second). It should only be used if you expect the IDs to be invalid, or if you have to go through a lot of them. For the occasional use by players requesting music, IsSound should not be used.