goldfire / howler.js

Javascript audio library for the modern web.
https://howlerjs.com
MIT License
23.83k stars 2.23k forks source link

Volume and Mute Control iOS?? #415

Closed pinguinoholdings closed 8 years ago

pinguinoholdings commented 8 years ago

I've been fighting with this for a few days now and am finally throwing in the towel to ask for help...

My current project requires playing two audio files at the same time: one is a music track and the other is a fitness instructor speaking. I need to control the volume of the instructor independent of the music track.

I've been successful at getting both tracks to play in sync. However, none of the volume controls work in when I load the app up in the emulator nor on the actual device itself. All the interactions/volume controls work as expected on the desktop Safari browser, with no errors logged.

I have tried setting buffer to true and false, with no help. It's not a jQuery issue as the mute/unmute functions, as well as a display for volume on the music slider, all work in the emulator and device.

Any ideas here would be helpful. Thanks

index.html


<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
    <title></title>

    <link href="lib/ionic/css/ionic.css" rel="stylesheet">
    <link href="css/style.css" rel="stylesheet">

    <!-- IF using Sass (run gulp sass first), then uncomment below and remove the CSS includes above
    <link href="css/ionic.app.css" rel="stylesheet">
    -->

    <!-- ionic/angularjs js -->
    <script src="lib/ionic/js/ionic.bundle.js"></script>

    <!-- cordova script (this will be a 404 during development) -->
    <script src="cordova.js"></script>

    <!--jQuery-->
    <script src="js/jquery-1.9.1.js"></script>

    <!-- your app's js -->
    <script src="js/app.js"></script>

    <!--web audio js-->
    <script src="js/howler.js"></script>

      <!--web audio js-->
      <script src="js/webaudio-control.js"></script>

  </head>
  <body ng-app="starter">

    <ion-pane>
      <ion-header-bar class="bar-stable">
        <h1 class="title">Testing Web Audio API</h1>
      </ion-header-bar>
      <ion-content>

        <div class="card">
          <div class="item item-text-wrap text-center">
            Music Controls
            <br><br>

            <div class="item range">
              <i class="icon ion-volume-low"></i>
              <input id="music_volume" type="range" value="100" name="music_volume" onchange="adjust_music_volume()">
              <i class="icon ion-volume-high"></i>
                <span id="music_volume_display"></span>
            </div>

            <a class="button button-icon icon ion-play" id="play" onclick="play()">
            </a>

            <a class="button button-icon icon ion-pause" id="pause" onclick="pause()">
            </a>

          </div>

        </div>

        <div class="card">
              <div class="item item-text-wrap text-center">
                  Coach Controls
                  <br><br>

                  <div class="item range">
                      <i class="icon ion-volume-low"></i>
                      <input id="coach_volume" type="range" value="100" name="music_volume" onchange="adjust_coach_volume()">
                      <i class="icon ion-volume-high"></i>
                  </div>

                  <br>
                  <a class="button button-icon" id="mute" onclick="toggle_mute()">
                      <button id="mute_button" class="button-icon icon ion-android-microphone">
                      </button>
                  <span id="mute_text">
                    Mute Coach
                  </span>
                  </a>
              </div>

          </div>

      </ion-content>
    </ion-pane>
  </body>
</html>

web audio-control.js

var music = new Howl({
    urls: ['audio/music.mp3'],
    buffer: true

});

var coach = new Howl({
    urls: ['audio/coach.mp3'],
    buffer: true
});

function play(){

    //play the tracks
    music.play();
    coach.play();

}

function pause(){

    //pause the tracks
    music.pause();
    coach.pause();

}

function adjust_music_volume(){
    //adjust the master volume control
    var current_volume = $("#music_volume").val();
    var new_volume = current_volume / 100;
    //console.log( 'Music Volume: ' + new_volume );
    music.volume(new_volume);
    $("#music_volume_display").text(new_volume);
    //console.log(music);
}

function adjust_coach_volume(){
    //adjust the master volume control
    var current_volume = $("#coach_volume").val();
    var new_volume = current_volume / 100;
    //console.log( 'Coach Volume: ' + new_volume );
    coach.volume(new_volume);
}

function toggle_mute(){
    //mute coach
    var mute_text = $("#mute_text").text();

    if(mute_text == 'Un-Mute Coach')
    {
        coach.unmute();
        $("#mute_text").text('Mute Coach');
        $("#mute_button").removeClass('ion-android-microphone-off');
        $("#mute_button").addClass('ion-android-microphone');
    } else {
        coach.mute();
        $("#mute_text").text('Un-Mute Coach');
        $("#mute_button").removeClass('ion-android-microphone');
        $("#mute_button").addClass('ion-android-microphone-off');
    }
}
goldfire commented 8 years ago

I'm not seeing any red flags, and I also haven't been able to reproduce an issue with volume/mute on iOS. Could you possibly create a more minimal sample with jsfiddle to demonstrate the issue?

pinguinoholdings commented 8 years ago

Thanks for the reply! Sorry I didn't see that you had responded. I'm not sure how to replicate the issue with a js fiddle as the problem only appears in the emulator/device. The same code produces the desired result in a browser.

I've since moved on to using cordova media plugin, but it's not desirable as it doesn't support streaming...

Do I understand you correctly that it is possible to mute and/or control volume of an audio instance with howler.js on iOS?

Thanks again

goldfire commented 8 years ago

Yes, we have extensive use of howler.js on http://casinorpg.com and it all works great on iPad (works fine on iPhone too, but our UI doesn't scale that small yet).

smakinson commented 5 years ago

@pinguinoholdings @goldfire Its quite a while after this was closed, but I just noticed changing the volume is not taking affect for me on iOS and I wondered if you guys isolated any issue I should look at. I may try to see what happens in a codepen later. Thanks!

smakinson commented 5 years ago

@goldfire Does this volume slider work for you on iOS? https://codepen.io/smakinson/pen/BEMoqB?editors=1010

The page doesn't appear to finish loading on iOS for me, but I am able to play it. Since I see HTML5 Audio pool exhausted, returning potentially locked audio object. in the console, I'm not sure if its a good test though. I can play it and the volume change does not take affect for me on iOS. I'm using safari. It does work for me on OSX in safari & chrome. Would that potentially locked object not allow volume changes?

smakinson commented 5 years ago

I've managed to come up with a case where it looks like volume doesn't get applied in chrome also. If I can get it to reproduce on codepen I'll post it. I see the Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction. message when its unloaded. I believe this is how I have that one going:

(Note, in this case the play() gets called from a event handler on a slider click. If I go from the @click on a button I don't see this issue.)

Also note the onplay event is not called.

The same thing works after that, so it must be related to the unlock stuff. I'll look into the event. I'm posting this incase it could be related to iOS stuff somehow.

If I instead set the volume first and do the same steps, everything works as expected.

Edit: The onunlock event is called and onplay is not. It does play, so should the onplay event be called after the onunlock since it does play? checking playing() in the onunlock event returns false, despite hearing the audio, though if I delay the check it does report true.

I see the onplayerror event called after unload() is called and after I see the message about Playback was unable to start., so it is not possible to do the once() in onplayerror as noted here: https://github.com/goldfire/howler.js#mobilechrome-playback

Sorry, I don't mean this info to distract from the iOS volume subject of this issue.

smakinson commented 5 years ago

I'm just toying around in the code (there is a lot going on in there that would take a while to wrap my head around) and this is probably not the right way to handle it, but if I add:

node.onplay = function () {
   self._playLock = false;
}

just inside the try of var playHtml5 = function() {

then the volume can be changed when it couldn't in the last message in chrome. I don't expect this to help iOS though. I see that play()catch() is fired after unload() is called. Anyway, I'll mess around further and share tidbits I try, maybe they will be helpful.

smakinson commented 5 years ago

iOS is getting to self._emit('volume', sound._id); in volume(), it seems it isn't taking when .volume is set on the audio element. Hmm.

st-h commented 5 years ago

@smakinson I think, this is a known limitation. There basically are two apis two play back audio in the browser. There is the html5 audio element, which in my interpretation is intended to be used to stream audio files - as it usually requires the server to be able to handle range requests. On iOS devices, I think it is normal that calling volume() does not have any effect, as it is intended that the user uses the devices volume control. Then there is the web audio api, which mostly is designed to play back short audio files - you have to keep all data in memory. However, here we do have advanced ways to process the audio signal and schedule events. If you set html5 to true, then you are using the first one.

smakinson commented 5 years ago

@st-h Ah I see, thanks. I may just hide the volume slider for iOS then.

smakinson commented 5 years ago

@st-h Do you have any thoughts on that unrelated bit above regarding chrome? Worth talking about in another issue?

st-h commented 5 years ago

@st-h Ah I see, thanks. I may just hide the volume slider for iOS then.

or do not set the html5 flag to true. Then a gainNode will be used to change the volume, which should work on iOS. However, you might run into memory issues if your (decoded) files are large. Then there would actually be a way to use the audio element as an input source of a web audio graph - which essentially combines both apis. However, that just opens up a can of webkit related bugs and currently is not supported by howler afaik.

@st-h Do you have any thoughts on that unrelated bit above regarding chrome? Worth talking about in another issue?

If you are talking about the unlocking stuff - I can not comment on that, I never had any use case for unlocking audio other than within a user click/touch

smakinson commented 5 years ago

@st-h Thanks, ya the mp3 files I'm using are large and I don't like opening up cans of bugs 😄

st-h commented 5 years ago

@smakinson yes, this is quite an unfortunate situation. At some point I kind of got the impression that apple wants us to write native apps instead of web apps 🤣 Currently I do not know of a way to reliably play back large files with accurate timing while still being able to manipulate the audio stream. I read that AudioWorkletNode should finally solve this - however, at the moment that is not widely supported.

I filed a bug against the apple bug tracker with one of the issue I have found 2 months ago regarding the audio element, however I don't think anyone has taken any notice of that yet.

sbbeez commented 8 months ago

Still seeing same issue. Gonna hide slider for IOS/Iphone. This is a bit unfortunate to see similar issue happening in 2024, world is behind AI, we still basic stuff to make it work. when I dont use howler.js/html5: true, audio doesnt play at all in my iphone 12 mini.