Skip to content

audio: Support loading external MP3s#6738

Merged
Herschel merged 6 commits into
ruffle-rs:masterfrom
Herschel:dynamic-sound
Sep 7, 2022
Merged

audio: Support loading external MP3s#6738
Herschel merged 6 commits into
ruffle-rs:masterfrom
Herschel:dynamic-sound

Conversation

@Herschel

@Herschel Herschel commented Apr 17, 2022

Copy link
Copy Markdown
Member
  • Add AudioBackend::register_mp3 to register MP3 data
  • Add SoundLoader variant
  • avm1: Implement Sound.load, Sound.getBytesLoaded, Sound.getBytesTotal

TODO:

  • Currently minimp3-rs provides no way to seek thru MP3 frames without decoding, so we end up decoding the entire MP3 on desktop to discover its length.
    • This might be a good time to switch over to symphonia for our default MP3 decoder, as it allows for seeking thru frames without decoding audio. (You still have to step thru all of the MP3 frames, but this is the nature of MP3).
  • Figure out what to do on web. web: Decode and mix audio via Rust #4273 switched to decoding audio via Rust, so the web target should operate the same as desktop.

@Herschel Herschel added the A-audio Area: Audio label Apr 17, 2022
@Herschel Herschel marked this pull request as draft April 17, 2022 05:17
@Toad06

Toad06 commented Apr 17, 2022

Copy link
Copy Markdown
Member
  * We could use `HtmlAudioElement` to stream and decode MP3s.

FWIW I think this approach could be problematic on Safari (at least on iOS and iPadOS) because unlike all other browsers, each new <audio> node (or new Audio() call) requires a user gesture before playing the sound.

@Herschel

Copy link
Copy Markdown
Member Author
  * We could use `HtmlAudioElement` to stream and decode MP3s.

FWIW I think this approach could be problematic on Safari (at least on iOS and iPadOS) because unlike all other browsers, each new <audio> node (or new Audio() call) requires a user gesture before playing the sound.

Do you know if this includes playing the audio with a MediaElementAudioSourceNode attached to an audio context?

@Toad06

Toad06 commented Apr 18, 2022

Copy link
Copy Markdown
Member

Using MediaElementAudioSourceNode doesn't seem to change anything unfortunately. :(

In case this could be useful, I'm attaching the tests I've made:

  • index1.html: Plays a sound after a user gesture, then tries to create new Audio instances playing the same sound. Doesn't work in Safari.
  • index2.html: Plays a sound after a user gesture, then re-use the Audio instance to play another sound, changing its src. Works in Safari.
  • index3.html: Similar to the first test, but uses MediaElementAudioSourceNode. Also doesn't work in Safari.

@torokati44

Copy link
Copy Markdown
Member

I'd just like to bring up again one of the problems Ruffle faces on web even with embedded MP3s: the decodeAudioData function has no way of telling the browser what kind of content is supposed to be in there, so it has to find it out somehow by trying to find different kinds of headers, or resorting to heuristics.
This already causes some content to be mute in Firefox, but have sound in Chrome.
So, my point is, that at least from this perspective, if at all possible, having a method of decoding that provides a way to also supply a format or content type might be better.

@Herschel

Herschel commented Apr 18, 2022

Copy link
Copy Markdown
Member Author

<audio>/MediaElementAudioSourceNode is nice in that respect because it allows specifying a MIME type, but it still puts us at the mercy of browsers (such as the Safari autoplay behavior).

@sombraguerrero

Copy link
Copy Markdown
Contributor

As requested, examples of Onda's Animecuentos -> Toto activities to further examine the sound.onLoad issues.
@Herschel
toto.zip

@n0samu

n0samu commented May 11, 2022

Copy link
Copy Markdown
Member

With this PR, the external MP3 sound effects in the Flexitwins app work perfectly, but the background music (techno.mp3) that's supposed to play on the game instructions screen doesn't play. I found that this is because the offset and loop parameters of Sound.start are used when playing this MP3, whereas Sound.start is called without parameters when playing the other external MP3s.

@Herschel Herschel marked this pull request as ready for review July 29, 2022 20:00
@Herschel Herschel changed the title [wip] audio: Support loading external MP3s audio: Support loading external MP3s Jul 29, 2022
@Herschel

Copy link
Copy Markdown
Member Author

This is unblocked now that #4273 is merged and could use some testing!

@n0samu I tried the linked Flexitwins app and seems like it's good now, I hear the music on the instructions screen. Could you verify?

@Toad06

Toad06 commented Jul 29, 2022

Copy link
Copy Markdown
Member

This looks good to me, just a few observations/questions:

@n0samu

n0samu commented Jul 31, 2022

Copy link
Copy Markdown
Member

I tried the linked Flexitwins app and seems like it's good now, I hear the music on the instructions screen. Could you verify?

Yes, with the latest updates the extension is working perfectly now! It doesn't seem to work in the desktop app yet though.

@n0samu

n0samu commented Jul 31, 2022

Copy link
Copy Markdown
Member

Did some more testing:

  • In Eeekoworld, the external MP3s play fine! But when mousing over an object that plays an MP3, it doesn't first stop any MP3s that are already playing, like it does in Flash Player. Sounds start layering over each other if you mouse over more than one thing in a short timespan.
  • The intro of Binky's Facts and Opinions works! But for some reason, the lip-sync between the character's mouth and the audio is wrong and the character's mouth stops moving long before the audio ends. (The rest of the game won't work reliably on Wayback because of missing assets, but you can find the full game in Flashpoint if you want to test further.)
  • The MP3 voicelines in Morningstar work perfectly now! (The intro uses an external FLV, so it needs to be skipped by clicking the button in the bottom-right corner.)

@Herschel Herschel force-pushed the dynamic-sound branch 2 times, most recently from 12a8b2f to 83ac122 Compare August 1, 2022 04:16
@Herschel Herschel force-pushed the dynamic-sound branch 6 times, most recently from 999624a to 8a07645 Compare September 6, 2022 07:01
@Herschel

Herschel commented Sep 6, 2022

Copy link
Copy Markdown
Member Author

Thank you! Those cases should be fixed, although possibly the layering sounds has some edge cases still (controlled by the isStreaming flag of Sound.loadSound -- if this is true, the Sound object can only seem to play a single instance of the sound.)

@Herschel Herschel requested review from Toad06 and n0samu September 6, 2022 07:02
@Toad06

Toad06 commented Sep 6, 2022

Copy link
Copy Markdown
Member

Tested some good old MP3 players and they all worked great. 👍

https://www.argentine-music.com/ still has issues and seems to be one of these edge cases though. The main SWF loads a music when the file is ready to play, then the user can load another music from an inner SWF by clicking on a button. In any case, the loadSound method is called with the isStreaming argument to true. For some reason, this can cause different issues, depending on the user's interactions. I wrote down my observations as steps to reproduce below:

Steps to reproduce the issues on argentine-music.com

1- When the file is ready to play, a "main music" is loaded and then played. Click the note in the bottom left corner of the screen: this will stop the music. Click another time to play it again. Everything works perfectly here.

2- Click "Catalogue musical" on the left. Click on some pictures (randomly). Notice that the main music keeps playing. Again, this matches Flash.

3- Click the picture with a clown ("Drôle de jeu"). On the right, click the Play button on the same line as "Générique". Notice that the main music fades out (which is correct) but the new music also fades out, so you can just hear half of a second of it. Wait about 18 seconds, so that the music ends (the Play button changes back to its original aspect) and the main music fades in (which is correct).

4- Click on some pictures again. Notice that the main music unexpectedly stops.

5- Click the picture with a clown. Click the note in the bottom left corner twice (to play the main music again) and click the same Play button as before. While the sound plays, click the note twice again. Wait about 18 seconds and notice that the main music won't fade in this time. Click the Stop button.

6- Click the note twice and the same Play button. Click one of the category at the bottom ("Divertissement", "Fiction", "Documentaire" or "Divers"). Notice that both of the sounds fade in.

@Herschel

Herschel commented Sep 6, 2022

Copy link
Copy Markdown
Member Author

Thanks for testing! It seems like the behavior with loadSound/isStreaming is a little bit different -- usually setVolume affects the volume of all sounds played within the same clip (the clip you pass to new Sound()). But in the case of dynamically loaded sounds, it only affects that specific instance. AVM1 sound is strange!

So in argentine-music, there are separate new Sound() instances. Even though they are separate objects, calling setVolume on any of them would normally affect the global sound volume for every sound, and this is what Ruffle does currently. But in FP, after loadSound is called, setVolume only affects that specific loaded sound.

I'm thinking we'll need something like enum SoundType { Embedded; External; ExternalStreaming; } in SoundObject to differentiate between the behavior. I might defer to this to a later PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-audio Area: Audio

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants