Lovelock
Lovelock

Reputation: 8075

Web Audio API not playing sound sample on device, but works in browser

I have an Ionic app that is a metronome. Using Web Audio API I have made everything work using the oscillator feature, but when switching to use a wav file no audio is playing on a real device (iPhone).

When testing in the browser using Ionic Serve (chrome) the audio plays fine.

Here is what I have:

function snare(e) {
    var audioSource = 'assets/audio/snare1.wav';
    var request = new XMLHttpRequest();
    request.open('GET', audioSource, true);
    request.responseType = 'arraybuffer';

    // Decode asynchronously
    request.onload = function() {
        audioContext.decodeAudioData(request.response, function(theBuffer) {
            buffer = theBuffer;
            playSound(buffer);
        });
    }
    request.send();
}

function playSound(buffer) {
    var source = audioContext.createBufferSource();
    source.buffer = buffer;
    source.connect(audioContext.destination);
    source.start(0);
}

The audio sample is in www/assets/audio.

Any ideas where this could be going wrong?

Upvotes: 0

Views: 3285

Answers (4)

vvolhejn
vvolhejn

Reputation: 178

For me, the source of the issue was simpler: my iPhone was on silent mode. That's the hardware toggle button on the side, not the volume controls. The semantics are weird:

  • if you use headphones: audio works
  • if you use speakers:
    • no silent mode: audio works
    • silent mode:
      • if you've asked for microphone permission (navigator.mediaDevices.getUserMedia({ video: false, audio: true })): audio works
      • otherwise: audio doesn't work

It makes sense that they're trying to prevent you from accidentally playing sounds out loud, but I never found anything about this when googling the issue.

I only realized this after two nights of debugging because I wanted to play a DJ mix from Soundcloud and I couldn't figure out how to make the Soundcloud app to use my Sonos speaker, so I put on headphones and noticed it suddenly works 🫠

Upvotes: 0

klues
klues

Reputation: 937

I had a similar issue in current iOS (15). I tried to play base64 encoded binary data which worked in all browsers, but not on iOS.

Finally reordering of the statements solved my issue:

let buffer = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
let context = new AudioContext();

// these lines were within "play()" before
audioSource = context.createBufferSource();
audioSource.connect(context.destination);
audioSource.start(0);
// ---

context.decodeAudioData(buffer.buffer, play, (e) => {
    console.warn("error decoding audio", e)
});

function play(audioBuffer) {
    audioSource.buffer = audioBuffer;
}

Also see this commit in my project.

I assume that calling audioSource.start(0) within the play() method was somehow too late because it's within a callback after context.decodeAudioData() and therefore maybe "too far away" from a user interaction for the standards of iOS.

Upvotes: 0

happymapper
happymapper

Reputation: 1

It's July 2017, iOS 10.3.2 and we're still finding this issue on Safari on iPhones. Interestingly Safari on a MacBook is fine. @Raymond Toy's general observation still appears to be true. But @padenot's approach (via https://gist.github.com/laziel/7aefabe99ee57b16081c) did not work for me in a situation where I wanted to play a sound in response to some external event/trigger.

Using the original poster's code, I've had some success with this

var buffer; // added to make it work with OP's code

// keep the original function snare()

function playSound() { // dropped the argument for simplicity.
   var source = audioContext.createBufferSource();
   source.buffer = buffer;
   source.connect(audioContext.destination);
   source.start(0);
}

function triggerSound() {

    function playSoundIos(event) {
        document.removeEventListener('touchstart', playSoundIos);
        playSound();
    }

    if (/iPad|iPhone/.test(navigator.userAgent)) {
        document.addEventListener('touchstart', playSoundIos);
    }
    else { // Android etc. or Safari, but not on iPhone 
        playSound();
    }
}

Now calling triggerSound() will produce the sound immediately on Android and will produce the sound on iOS after the browser page has been touched.

Still not ideal, but better than no sound at all...

Upvotes: 0

Raymond Toy
Raymond Toy

Reputation: 6048

I believe iOS devices require a user-gesture of some sort to allow playing of audio.

Upvotes: 3

Related Questions