HTML5 How to prevent audio from being blocked on HTML5 - GMS 2.3

PREVENT AUDIO FROM BEING BLOCKED ON HTML5
1216733-2.png

GM Version: 2.2x - 2.3x
Target Platform: HTML5
Download: N/A
Links: N/A

Summary:

Have you ever wondered why your browser won't play your HTML5 application audio?
That's because modern browsers block all the audio sources as long as you do not interact with the page.
I found people suggesting making a Play Button before the main room actually start, so that the browser receive that click event and resumes the audio, but that does not seem to always work, unfortunately.
So here's how I managed to solve it.
Hope that will be useful for all of you struggling with this awful "bug".

Tutorial:


SETTING UP A NATIVE HTML BUTTON

So, after millions of tests, I found a solution:
I created an actual native HTML button: that way we make sure that the click event is managed directly by the browser.

Here's how to do it inside the index.html output file of your exported HTML5 application.

We simply add inside the body, inside the <div class="gm4html5_div_class" id="gm4html5_div_id"> an anchor tag which will represent our button.
HTML:
<a class="play-btn" id = "btn"></a>
Then we style our button inside the <style> tag, so that it looks cool:
I took a cool button style from this link: https://codepen.io/b29/pen/jmNvJq

CSS:
.play-btn{
    width: 100px;
    height: 100px;
    background: radial-gradient( rgba(255, 0, 128, 0.8) 60%, rgba(255, 255, 255, 1) 62%);
    border-radius: 50%;
    position: absolute;
    left: 45%;
    top: 40%;
    display: block;
    /*margin: 100px auto;*/
    box-shadow: 0px 0px 25px 3px rgba(255, 0, 128, 0.8);
}


/* triangle */
.play-btn::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    -webkit-transform: translateX(-40%) translateY(-50%);
    transform: translateX(-40%) translateY(-50%);
    transform-origin: center center;
    width: 0;
    height: 0;
    border-top: 15px solid transparent;
    border-bottom: 15px solid transparent;
    border-left: 25px solid #fff;
    z-index: 100;
    -webkit-transition: all 400ms cubic-bezier(0.55, 0.055, 0.675, 0.19);
    transition: all 400ms cubic-bezier(0.55, 0.055, 0.675, 0.19);
}

/* pulse wave */
.play-btn:before{
    content: "";
    position: absolute;
    width: 150%;
    height: 150%;
    -webkit-animation-delay: 0s;
    animation-delay: 0s;
    -webkit-animation: pulsate1 2s;
    animation: pulsate1 2s;
    -webkit-animation-direction: forwards;
    animation-direction: forwards;
    -webkit-animation-iteration-count: infinite;
    animation-iteration-count: infinite;
    -webkit-animation-timing-function: steps;
    animation-timing-function: steps;
    opacity: 1;
    border-radius: 50%;
    border: 5px solid rgba(255, 255, 255, .75);
    top: -30%;
    left: -30%;
    background: rgba(198, 16, 0, 0);
}
Then we add a simple script inside the <script> tag which will remove our button once clicked and start the GameMaker application calling the GameMaker_Init() Javascript function.

HTML:
<script>
    var el = document.getElementById("btn")
        el.onclick = function() {
        GameMaker_Init();
        el.remove();
    }
</script>
Here's the final html file after all the implementations:

HTML:
<!DOCTYPE html>
<html lang="en">
    <head>
        <!-- Generated by GameMaker:Studio http://www.yoyogames.com/gamemaker/studio -->
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta http-equiv="pragma" content="no-cache"/>
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta name ="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
        <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
        <meta charset="utf-8"/>

        <!-- Set the title bar of the page -->
        <title>Created with GameMaker Studio 2</title>

        <!-- Set the background colour of the document -->
        <style>
            body {
              background: #0;
              color:#cccccc;
              margin: 0px;
              padding: 0px;
              border: 0px;
            }
            canvas {
                      image-rendering: optimizeSpeed;
                      -webkit-interpolation-mode: nearest-neighbor;
                      -ms-touch-action: none;
                      margin: 0px;
                      padding: 0px;
                      border: 0px;
            }
            :-webkit-full-screen #canvas {
                 width: 100%;
                 height: 100%;
            }
            div.gm4html5_div_class
            {
              margin: 0px;
              padding: 0px;
              border: 0px;
            }
            /* START - Login Dialog Box */
            div.gm4html5_login
            {
                 padding: 20px;
                 position: absolute;
                 border: solid 2px #000000;
                 background-color: #404040;
                 color:#00ff00;
                 border-radius: 15px;
                 box-shadow: #101010 20px 20px 40px;
            }
            div.gm4html5_cancel_button
            {
                 float: right;
            }
            div.gm4html5_login_button
            {
                 float: left;
            }
            div.gm4html5_login_header
            {
                 text-align: center;
            }
            /* END - Login Dialog Box */
            :-webkit-full-screen {
               width: 100%;
               height: 100%;
            }

            .play-btn{
              width: 100px;
              height: 100px;
              background: radial-gradient( rgba(255, 0, 128, 0.8) 60%, rgba(255, 255, 255, 1) 62%);
              border-radius: 50%;
              position: absolute;
              left: 45%;
              top: 40%;
              display: block;
              /*margin: 100px auto;*/
              box-shadow: 0px 0px 25px 3px rgba(255, 0, 128, 0.8);
            }


            /* triangle */
            .play-btn::after {
              content: "";
              position: absolute;
              left: 50%;
              top: 50%;
              -webkit-transform: translateX(-40%) translateY(-50%);
              transform: translateX(-40%) translateY(-50%);
              transform-origin: center center;
              width: 0;
              height: 0;
              border-top: 15px solid transparent;
              border-bottom: 15px solid transparent;
              border-left: 25px solid #fff;
              z-index: 100;
              -webkit-transition: all 400ms cubic-bezier(0.55, 0.055, 0.675, 0.19);
              transition: all 400ms cubic-bezier(0.55, 0.055, 0.675, 0.19);
            }

            /* pulse wave */
            .play-btn:before{
              content: "";
              position: absolute;
              width: 150%;
              height: 150%;
              -webkit-animation-delay: 0s;
              animation-delay: 0s;
              -webkit-animation: pulsate1 2s;
              animation: pulsate1 2s;
              -webkit-animation-direction: forwards;
              animation-direction: forwards;
              -webkit-animation-iteration-count: infinite;
              animation-iteration-count: infinite;
              -webkit-animation-timing-function: steps;
              animation-timing-function: steps;
              opacity: 1;
              border-radius: 50%;
              border: 5px solid rgba(255, 255, 255, .75);
              top: -30%;
              left: -30%;
              background: rgba(198, 16, 0, 0);
            }

            .body {
              background-color: #2068bd
            }



            @-webkit-keyframes pulsate1 {
              0% {
                -webkit-transform: scale(0.6);
                transform: scale(0.6);
                opacity: 1;
                box-shadow: inset 0px 0px 25px 3px rgba(255, 255, 255, 0.75), 0px 0px 25px 10px rgba(255, 255, 255, 0.75);
              }
              100% {
                -webkit-transform: scale(1);
                transform: scale(1);
                opacity: 0;
                box-shadow: none;

              }
            }

            @keyframes pulsate1 {
              0% {
                -webkit-transform: scale(0.6);
                transform: scale(0.6);
                opacity: 1;
                box-shadow: inset 0px 0px 25px 3px rgba(255, 255, 255, 0.75), 0px 0px 25px 10px rgba(255, 255, 255, 0.75);
              }
              100% {
                -webkit-transform: scale(1, 1);
                transform: scale(1);
                opacity: 0;
                box-shadow: none;

              }
            }


        </style>
    </head>

    <body>
        <div class="gm4html5_div_class" id="gm4html5_div_id">
            <!-- Create the canvas element the game draws to -->
            <canvas id="canvas" width="1066" height="594" >
                     <p>Your browser doesn't support HTML5 canvas.</p>
            </canvas>
            <!--href="javascript:GameMaker_Init()"--->
            <a class="play-btn" id = "btn"></a>

        </div>


        <script>
            var el = document.getElementById("btn")
            el.onclick = function() {
                GameMaker_Init();
                el.remove();
            }

        </script>

        <!-- Run the game code -->
        <script type="text/javascript" src="html5game/NAME_OF_YOUR_GAME"></script>
    </body>
</html>

This way, our browser will not complain anymore about the audio, as we interacted with an element of the page.

This method has been tested on mobile and desktop devices with the HTML5 application being hosted on hosting sites such as itch.io and Netlify.
But it should also work elsewhere.

LOADING AUDIOGROUPS

Our final step is setting up our audiogroups so that they are effectively loaded on our webpage.

Inside the Tools > Audiogroups let's define our custom audiogroup by clicking on Add New and inserting our audio resources by clicking on the Add Resource button.
In this case I called my custom audiogroup "myAudioGroup" but you can name it the way you want.

Schermata 2021-08-20 alle 14.45.28.png

Then we create an object (I called it oAudiogroupLoader) and we place it in a specific room that will be used to manage all the audio loading.
Basically we'll use a function called audiogroup_load() that will fire an Async Save / Load event.
Inside that event we'll check if the audiogroup is loaded, if so we'll go to the game room.

STEPS:

1. Create a new room and set it as the home room (the room that will be played first as the game starts).
Schermata 2021-08-20 alle 14.50.59.png

2. Place the oAudiogroupLoader inside that room.

3: Inside the Create Event of the oAudiogroupLoader object let's put this code:
GML:
audio_group_load(myAudioGroup);
4: In the Async - Save / Load Event let's put:
GML:
if (audio_group_is_loaded(myAudioGroup)) {
    room_goto(rmGame);
}
5: EXTRA: We can draw some text to let the user know our game is loading all the audio resources.
Draw Event:
Code:
draw_set_halign(fa_center);
draw_set_valign(fa_middle);
draw_set_font(fntLoading);
draw_text(room_width/2, room_height/2, "Loading Resources...");
draw_set_halign(fa_left);
draw_set_valign(fa_top);

That's it guys!
That should definitely make your HTML5 game audio PLAYYYYYYYYY!!!

Let me know it if that works for you, too!
 
Last edited:
I released an extension on the marketplace a couple of weeks ago dealing with this issue. Lol. Albeit I solved it with a much simpler approach. 1 line of gml required and an extension with a bit of js. No button creation required.

I must admit working out the issue did take a fair bit of time, as you also found. As far as I'm aware from testing it works everywhere. Can see it in action on this html5 game
https://www.michael-bateman.com/play-games/veggiepatch/. And the marketplace extension is available at audio fix html5.

Silly this functionality isn't in gms as standard, but we all know how much work the html5 export still requires!
 
I released an extension on the marketplace a couple of weeks ago dealing with this issue. Lol. Albeit I solved it with a much simpler approach. 1 line of gml required and an extension with a bit of js. No button creation required.

I must admit working out the issue did take a fair bit of time, as you also found. As far as I'm aware from testing it works everywhere. Can see it in action on this html5 game
https://www.michael-bateman.com/play-games/veggiepatch/. And the marketplace extension is available at audio fix html5.

Silly this functionality isn't in gms as standard, but we all know how much work the html5 export still requires!
Hi Michael, you can’t imagine how much time I’ve been struggling to make this work.
I’ve tried your game you linked but unfortunately audio is not playing 😔. I’m using Safari on an iPhone X.
If you want I can console debug it by connecting my iPhone to my MacBook and tell you what’s going wrong.
I think the HTML button solution is what makes the audio working 99,9% of the times.
It’s awful that there’s no official solution to this problem.
 
Hi Michael, you can’t imagine how much time I’ve been struggling to make this work.
I’ve tried your game you linked but unfortunately audio is not playing 😔. I’m using Safari on an iPhone X.
If you want I can console debug it by connecting my iPhone to my MacBook and tell you what’s going wrong.
I think the HTML button solution is what makes the audio working 99,9% of the times.
It’s awful that there’s no official solution to this problem.
Whaaaaat? It's not working?! Lol. Hmmm. I've tested it on an iPhone 7 on iOS 14.7.1 which is latest version. So that's indeed odd.

I know this sounds basic, but I have done this myself, you have definitely not got it on silent mode?...
 
Whaaaaat? It's not working?! Lol. Hmmm. I've tested it on an iPhone 7 on iOS 14.7.1 which is latest version. So that's indeed odd.

I know this sounds basic, but I have done this myself, you have definitely not got it on silent mode?...
No, it’s not on silent mode
I’ve tested it again and now I can hear the UI sound when I select an item.
Is there a backing track in the game?
Here’s some more specs:
iPhone X
iOS 14.7.1
Tested on Safari and Chrome
I hope with the new Opera GX acquisition there will be more support for the HTML5 export 🤞🏼
 
No, it’s not on silent mode
I’ve tested it again and now I can hear the UI sound when I select an item.
Is there a backing track in the game?
Here’s some more specs:
iPhone X
iOS 14.7.1
Tested on Safari and Chrome
I hope with the new Opera GX acquisition there will be more support for the HTML5 export 🤞🏼
Thanks dude. Yeah it's got a backing track. And you are correct. It's not working properly; I've just done some more testing and whether the sound works or not Is completely erratic. I don't know why this is when it was fine a week ago. Sigh. More digging required.

I might try playing all sound files via vanilla js through an extension and see if i can get it working that way. I refuse to be beaten!

Yeah Opera acquisition and the free export to html5 opera gx should help push users to html5 and flush out the bugs. Although I must say, this has only become an issue recently because of Apple going extreme on user protection. It was fine 2 years ago when I last dabble in GMS. Ho hum.
 

rwkay

GameMaker Staff
GameMaker Dev.
Thanks dude. Yeah it's got a backing track. And you are correct. It's not working properly; I've just done some more testing and whether the sound works or not Is completely erratic. I don't know why this is when it was fine a week ago. Sigh. More digging required.

I might try playing all sound files via vanilla js through an extension and see if i can get it working that way. I refuse to be beaten!

Yeah Opera acquisition and the free export to html5 opera gx should help push users to html5 and flush out the bugs. Although I must say, this has only become an issue recently because of Apple going extreme on user protection. It was fine 2 years ago when I last dabble in GMS. Ho hum.
Please file a bug with a project showing the problem and we will take a look - sounds like Apple have changed the rules again.

Russell
 
Top