Unpredicatable playback issues with videos recorded using Camera2 - javascript
My team is working on a mobile web application using the Cordova framework, initially targeting Android. A feature of the application is to record video on the user's phone via a custom media capture plugin, save it locally, read it using the Cordova File plugin (cordova-plugin-file), and stream it to a Node JS server for distribution to other connected users via the Stream API.
Devices receiving the stream save each incoming chunk within an array of ArrayBuffers, and then convert this to a Blob via the Blob constructor:
let receivedChunks: ArrayBuffer[] = [];
// chunks of video data received from node.js server
// each chunk saved as ArrayBuffer
// ArrayBuffer[] treated as blob parts
const videoBlob = new Blob( receivedChunks, { type: 'video/mp4' });
We then use the File plugin to write this blob to the Android cacheDirectory for our application, and get a file:/// URL in order to load the video into an HTML5 <video> element. The app queues up playback of these <video> elements using the Media API and Media Events.
None of the published Cordova (or PhoneGap) plugins quite suited our UI requirements, so we wrote our own, based on the Camera2 API (we've sacked off support for Android 4.x and below for the time being). We based our plugin on the Google samples, and it was working fine until we ran into the same issue as referenced by another StackOverflow user: Camera2 video recording without preview on Android: mp4 output file not fully playable
Turns out there are some issues with Deep Sleep on Samsung Galaxy devices running Android 6.0 Marshmallow. We implemented the workaround I described in my answer to that question, which partly solved the problem, but left us with scrambled metadata that meant we lost device orientation hints (our app uses sensorLandscape to keep the UI the right way up, so we have to apply orientation fixes to recorded video in order to prevent them from playing back upside-down).
So we took our workaround a little further and decided to re-encode the corrected video:
private void transcodeVideo(String pathToVideo)
throws IOException {
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(pathToVideo);
int trackCount = extractor.getTrackCount();
MediaMuxer muxer = new MediaMuxer(pathToVideo+ "transcoded", MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
HashMap<Integer, Integer> indexMap = new HashMap<Integer, Integer>(trackCount);
for (int i = 0; i < trackCount; i++) {
extractor.selectTrack(i);
MediaFormat format = extractor.getTrackFormat(i);
int dstIndex = muxer.addTrack(format);
indexMap.put(i, dstIndex);
}
boolean sawEOS = false;
int bufferSize = 256 * 1024;
int frameCount = 0;
int offset = 100;
ByteBuffer dstBuf = ByteBuffer.allocate(bufferSize);
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
muxer.setOrientationHint(DEFAULT_ORIENTATIONS.get(_savedVideoRotation));
muxer.start();
while (!sawEOS) {
bufferInfo.offset = offset;
bufferInfo.size = extractor.readSampleData(dstBuf, offset);
if (bufferInfo.size < 0) {
Log.d(TAG, "saw input EOS.");
sawEOS = true;
bufferInfo.size = 0;
} else {
bufferInfo.presentationTimeUs = extractor.getSampleTime();
bufferInfo.flags = MediaCodec.BUFFER_FLAG_KEY_FRAME;
int trackIndex = extractor.getSampleTrackIndex();
muxer.writeSampleData(indexMap.get(trackIndex), dstBuf,
bufferInfo);
extractor.advance();
frameCount++;
Log.d(TAG, "Frame (" + frameCount + ") " +
"PresentationTimeUs:" + bufferInfo.presentationTimeUs +
" Flags:" + bufferInfo.flags +
" TrackIndex:" + trackIndex +
" Size(KB) " + bufferInfo.size / 1024);
}
}
muxer.stop();
muxer.release();
}
This is where things get weird.
The re-encoded video seems to play back just fine on most other devices, including a rather long-in-the-tooth Moto G LTE (1st gen). However, when we stream and save more than a couple of videos at the same time on the Moto G, the re-encoded video stops playing properly. There's no audio or video, but the <video> element emits all the normal video events that we'd expect to see if the video was playing properly - in particular, the 'ended' event is fired after the expected duration.
If we only stream and save two videos, the Moto G can play the re-encoded video just fine. Other devices in the same session (all receiving the same set of videos delivered from the server) seem to have no problem with the S7's re-encoded video. If we remove the S7 from the set of devices in the same session, we sometimes see the same problem, but sometimes not - but it's 100% consistent when the S7 with the re-encoded video is involved.
Is there anything obviously wrong with our MP4 encoding? Is anyone aware of issues with simultaneously writing multiple files to the flash storage of a slower Android device like a Moto G? Has anyone else seen this odd playback behaviour, where a video element fires media events without actually playing audio or video?
I'm aware that this question may be a little lacking in focus, and there are a lot of variables involved (multiple possible points of failure in code, multiple devices, unclear on whether the problem is encoding, playback or something else), but if it rings a bell for anyone and they can provide a little insight then it would be greatly appreciated!
Related
Is there a way to get OscillatorNode to produce a sound on an iOS device?
I'm working on a music web app that has a piano keyboard. When a user presses a piano key, I'm using OscillatorNode to play a brief tone corresponding to the key: const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); function playNote(note) { let oscillator; let freq = notes[note]; console.debug(note + " (" + freq + " Hz)"); oscillator = audioCtx.createOscillator(); // create Oscillator node oscillator.type = wavetypeEl.val(); // triangle wave by default oscillator.frequency.setValueAtTime(freq, audioCtx.currentTime); // freq = value in hertz oscillator.connect(audioCtx.destination); oscillator.start(); oscillator.stop(audioCtx.currentTime + 0.5); } $('#keyboard button').on('click', (e) => { playNote(e.target.dataset.note); }); This works on all the desktop and Android browsers I've tried, but iOS stubbornly refuses to play any sound. I see that I need a user interaction to "unlock" an AudioContext on iOS, but I would have thought calling playNote() from my click function would have done the trick. According to Apple, I should be able to use noteOn() on my oscillator object, instead of oscillator.start() the way I've got it in my example. But that doesn't seem to be a valid method. I must be missing something simple here. Anybody know?
If everything seems to be working fine it could be that the device itself is on mute. For some reason (or for no reason at all) Safari doesn't play any sound coming from the Web Audio API when the device is muted. But it plays everything else. There are some hacky ways to circumvent this bug which basically work by playing something with an audio element first before using the Web Audio API. unmute-ios-audio is for example a library which implements the hack mentioned above.
I have an iPhone XS Max and the demo works on it producing sound as you have it right now... I've also read that for iOS the element needs both onClick handler and a style of {cursor: pointer} set to work properly.(as of a few years ago) seems like it's working though.
Is there a way to resample an audio stream using the Web Audio API?
I currently played around with the Web Audio API a little bit. I managed to "read" a microphone and play it to my speakers which worked quite seamlessly. Using the Web Audio API, I now would like to resample an incoming audio stream (aka. microphone) from 44.1kHz to 16kHz. 16kHz, because I am using some tools which require 16kHz. Since 44.1kHz divided by 16kHz is not an integer, I believe I cannot just simply use a low-pass filter and "skip samples", right? I also saw that some people suggested to use the .createScriptProcessor(), but since it is deprecated I feel kind of bad to use it, so I'm searching a different approach now. Also, I don't necessarily need the audioContext.Destination to hear it! It is still fine if I get the "raw" data of the resampled output. My approaches so far Creating an AudioContext({sampleRate: 16000}) --> throws an error: "Connecting AudioNodes from AudioContexts with different sample-rate is currently not supported." Using an OfflineAudioContext --> but it seems to have no option for streams (only for buffers) Using an AudioWorkletProcessor to resample. In this case, I think, that I could use the processor to actually resample the input and output the "resampled" source. But I couldn't really figure how to resample it. main.js ... microphoneGranted: async function(stream){ audioContext = new AudioContext(); var microphone = audioContext.createMediaStreamSource(stream); await audioContext.audioWorklet.addModule('resample_proc.js'); const resampleNode = new AudioWorkletNode(audioContext, 'resample_proc'); microphone.connect(resampleNode).connect(audioContext.destination); } ... resample_proc.js (assuming only one input and output channel) class ResampleProcesscor extends AudioWorkletProcessor { ... process(inputs, outputs, parameters) { const input = inputs[0]; const output = outputs[0]; if(input.length > 0){ const inputChannel0 = input[0]; const outputChannel0 = output[0]; for (let i = 0; i < inputChannel0.length; ++i) { //do something with resample here? } return true; } } } registerProcessor('resample_proc', ResampleProcesscor); Thank you!
Your general idea looks good. While I can't provide the code to do the resampling, I can point out that you might want to start with Sample-rate conversion. Method 1 would work here with L/M = 160/441. Designing the filters takes a bit of work but only needs to be done once. You can also search for polyphase filtering for hints on how to do this effectively. What chrome does in various parts is to use a windowed-sinc function to resample between any set of rates. This is method 2 in the wikipedia link.
The WebAudio API now allows to resample by passing the sample rate in the constructor. This code works in Chrome and Safari: const audioStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false }) const audioContext = new AudioContext({ sampleRate: 16000 }) const audioStreamSource = audioContext.createMediaStreamSource(audioStream); audioStreamSource.connect(audioContext.destination) But fails in Firefox that throws a NotSupportedError exception with AudioContext.createMediaStreamSource: Connecting AudioNodes from AudioContexts with different sample-rate is currently not supported. In the example below, I've downsampled the audio coming from the microphone to 8kHz and added a one second delay so we can clearly hear the effect of downsampling: https://codesandbox.io/s/magical-rain-xr4g80
Sound analysis without getUserMedia
I am trying to analyse the audio output from the browser, but I don't want the getUserMedia prompt to appear (which asks for microphone permission). The sound sources are SpeechSynthesis and an Mp3 file. Here's my code: return navigator.mediaDevices.getUserMedia({ audio: true }) .then(stream => new Promise(resolve => { const track = stream.getAudioTracks()[0]; this.mediaStream_.addTrack(track); this._source = this.audioContext.createMediaStreamSource(this.mediaStream_); this._source.connect(this.analyser); this.draw(this); })); This code is working fine, but it's asking for permission to use the microphone! I a not interested at all in the microphone I only need to gauge the audio output. If I check all available devices: navigator.mediaDevices.enumerateDevices() .then(function(devices) { devices.forEach(function(device) { console.log(device.kind + ": " + device.label + " id = " + device.deviceId); }); }) I get a list of available devices in the browser, including 'audiooutput'. So, is there a way to route the audio output in a media stream that can be then used inside 'createMediaStreamSource' function? I have checked all the documentation for the audio API but could not find it. Thanks for anyone that can help!
There are various ways to get a MediaStream which is originating from gUM, but you won't be able to catch all possible audio output... But, for your mp3 file, if you read it through an MediaElement (<audio> or <video>), and if this file is served without breaking CORS, then you can use MediaElement.captureStream. If you read it from WebAudioAPI, or if you target browsers that don't support captureStream, then you can use AudioContext.createMediaStreamDestination. For SpeechSynthesis, unfortunately you will need gUM... and a Virtual Audio Device: first you would have to set your default output to the VAB_out, then route your VAB_out to VAB_in and finally grab VAB_in from gUM... Not an easy nor universally doable task, moreover when IIRC SpeechSynthesis doesn't have any setSinkId method.
How to avoid playing back the sound that is being recorded
I'm talking about feedback - when you make a simple javascript application that opens a stream from the user and reads the frequency analysis (or whatever is it) it thorws all received data back to the headphones in both Google Chrome and Opera. Firefox is silent most of the time and randomly creates a huge mess with unstable feedback - it also closes the stream after few seconds. Generally the thing doesn't work in Firefox yet. I created a fiddle. If your browser doesn't support it you'll just get error in the console I assume. The critical part of the code is the function that is called when user accepts the request for the microphone access: //Not sure why do I do this var inputPoint = context.createGain(); // Create an AudioNode from the stream. var source = context.createMediaStreamSource(stream); source.connect(inputPoint); //Analyser - this converts raw data into spectral analysis window.analyser = context.createAnalyser(); //Mores stuff I know nothing about analyser.fftSize = 2048; //Sounds much like connecting nodes in MatLab, doesn't it? inputPoint.connect(analyser); analyser.connect(context.destination); ///THIS should probably make the sound silent (gain:0) but it doesn't var zeroGain = context.createGain(); zeroGain.gain.value = 0.0; //More connecting... are you already lost which node is which? Because I am. inputPoint.connect(zeroGain); zeroGain.connect(context.destination); Zero gain idea is not mine, I have stolen it from simple sound recorder demo. But what works for them doesn't work for me. The demo has also no problems in Firefox, like I do.
in function mediaGranted(stream) {... comment out .. Fiddle line #46: //analyser.connect(context.destination); .. more info https://mdn.mozillademos.org/files/5081/WebAudioBasics.png nice demo: http://mdn.github.io/voice-change-o-matic/
Sound effects in JavaScript / HTML5
I'm using HTML5 to program games; the obstacle I've run into now is how to play sound effects. The specific requirements are few in number: Play and mix multiple sounds, Play the same sample multiple times, possibly overlapping playbacks, Interrupt playback of a sample at any point, Preferably play WAV files containing (low quality) raw PCM, but I can convert these, of course. My first approach was to use the HTML5 <audio> element and define all sound effects in my page. Firefox plays the WAV files just peachy, but calling #play multiple times doesn't really play the sample multiple times. From my understanding of the HTML5 spec, the <audio> element also tracks playback state, so that explains why. My immediate thought was to clone the audio elements, so I created the following tiny JavaScript library to do that for me (depends on jQuery): var Snd = { init: function() { $("audio").each(function() { var src = this.getAttribute('src'); if (src.substring(0, 4) !== "snd/") { return; } // Cut out the basename (strip directory and extension) var name = src.substring(4, src.length - 4); // Create the helper function, which clones the audio object and plays it var Constructor = function() {}; Constructor.prototype = this; Snd[name] = function() { var clone = new Constructor(); clone.play(); // Return the cloned element, so the caller can interrupt the sound effect return clone; }; }); } }; So now I can do Snd.boom(); from the Firebug console and play snd/boom.wav, but I still can't play the same sample multiple times. It seems that the <audio> element is really more of a streaming feature rather than something to play sound effects with. Is there a clever way to make this happen that I'm missing, preferably using only HTML5 and JavaScript? I should also mention that, my test environment is Firefox 3.5 on Ubuntu 9.10. The other browsers I've tried - Opera, Midori, Chromium, Epiphany - produced varying results. Some don't play anything, and some throw exceptions.
HTML5 Audio objects You don't need to bother with <audio> elements. HTML 5 lets you access Audio objects directly: var snd = new Audio("file.wav"); // buffers automatically when created snd.play(); There's no support for mixing in current version of the spec. To play same sound multiple times, create multiple instances of the Audio object. You could also set snd.currentTime=0 on the object after it finishes playing. Since the JS constructor doesn't support fallback <source> elements, you should use (new Audio()).canPlayType("audio/ogg; codecs=vorbis") to test whether the browser supports Ogg Vorbis. If you're writing a game or a music app (more than just a player), you'll want to use more advanced Web Audio API, which is now supported by most browsers.
Web Audio API Edit: As of December 2021, Web Audio API is essentially supported in Chrome, Firefox, Safari and all the other major browsers (excluding IE, of course). As of July 2012, the Web Audio API is now supported in Chrome, and at least partly supported in Firefox, and is slated to be added to IOS as of version 6. Although the Audio element is robust enough to be used programmatically for basic tasks, it was never meant to provide full audio support for games and other complex applications. It was designed to allow a single piece of media to be embedded in a page, similar to an img tag. There are a lot of issues with trying to use the it for games: Timing slips are common with Audio elements You need an Audio element for each instance of a sound Load events aren't totally reliable, yet No common volume controls, no fading, no filters/effects Here are some good resources to get started with the Web Audio API: MDN documentaion Getting Started With WebAudio article The FieldRunners WebAudio Case Study is also a good read
howler.js For game authoring, one of the best solutions is to use a library which solves the many problems we face when writing code for the web, such as howler.js. howler.js abstracts the great (but low-level) Web Audio API into an easy to use framework. It will attempt to fall back to HTML5 Audio Element if Web Audio API is unavailable. var sound = new Howl({ urls: ['sound.mp3', 'sound.ogg'] }).play(); // it also provides calls for spatial/3d audio effects (most browsers) sound.pos3d(0.1,0.3,0.5); wad.js Another great library is wad.js, which is especially useful for producing synth audio, such as music and effects. For example: var saw = new Wad({source : 'sawtooth'}) saw.play({ volume : 0.8, wait : 0, // Time in seconds between calling play() and actually triggering the note. loop : false, // This overrides the value for loop on the constructor, if it was set. pitch : 'A4', // A4 is 440 hertz. label : 'A', // A label that identifies this note. env : {hold : 9001}, panning : [1, -1, 10], filter : {frequency : 900}, delay : {delayTime : .8} }) Sound for Games Another library similar to Wad.js is "Sound for Games", it has more focus on effects production, while providing a similar set of functionality through a relatively distinct (and perhaps more concise feeling) API: function shootSound() { soundEffect( 1046.5, //frequency 0, //attack 0.3, //decay "sawtooth", //waveform 1, //Volume -0.8, //pan 0, //wait before playing 1200, //pitch bend amount false, //reverse bend 0, //random pitch range 25, //dissonance [0.2, 0.2, 2000], //echo array: [delay, feedback, filter] undefined //reverb array: [duration, decay, reverse?] ); } Summary Each of these libraries are worth a look, whether you need to play back a single sound file, or perhaps create your own html-based music editor, effects generator, or video game.
You may also want to use this to detect HTML 5 audio in some cases: http://diveintohtml5.ep.io/everything.html HTML 5 JS Detect function function supportsAudio() { var a = document.createElement('audio'); return !!(a.canPlayType && a.canPlayType('audio/mpeg;').replace(/no/, '')); }
Here's one method for making it possible to play even same sound simultaneously. Combine with preloader, and you're all set. This works with Firefox 17.0.1 at least, haven't tested it with anything else yet. // collection of sounds that are playing var playing={}; // collection of sounds var sounds={step:"knock.ogg",throw:"swing.ogg"}; // function that is used to play sounds function player(x) { var a,b; b=new Date(); a=x+b.getTime(); playing[a]=new Audio(sounds[x]); // with this we prevent playing-object from becoming a memory-monster: playing[a].onended=function(){delete playing[a]}; playing[a].play(); } Bind this to a keyboard key, and enjoy: player("step");
To play the same sample multiple times, wouldn't it be possible to do something like this: e.pause(); // Perhaps optional e.currentTime = 0; e.play(); (e is the audio element) Perhaps I completely misunderstood your problem, do you want the sound effect to play multiple times at the same time? Then this is completely wrong.
Sounds like what you want is multi-channel sounds. Let's suppose you have 4 channels (like on really old 16-bit games), I haven't got round to playing with the HTML5 audio feature yet, but don't you just need 4 <audio> elements, and cycle which is used to play the next sound effect? Have you tried that? What happens? If it works: To play more sounds simultaneously, just add more <audio> elements. I have done this before without the HTML5 <audio> element, using a little Flash object from http://flash-mp3-player.net/ - I wrote a music quiz (http://webdeavour.appspot.com/) and used it to play clips of music when the user clicked the button for the question. Initially I had one player per question, and it was possible to play them over the top of each other, so I changed it so there was only one player, which I pointed at different music clips.
Have a look at the jai (-> mirror) (javascript audio interface) site. From looking at their source, they appear to be calling play() repeatedly, and they mention that their library might be appropriate for use in HTML5-based games. You can fire multiple audio events simultaneously, which could be used for creating Javascript games, or having a voice speaking over some background music
Here's an idea. Load all of your audio for a certain class of sounds into a single individual audio element where the src data is all of your samples in a contiguous audio file (probably want some silence between so you can catch and cut the samples with a timeout with less risk of bleeding to the next sample). Then, seek to the sample and play it when needed. If you need more than one of these to play you can create an additional audio element with the same src so that it is cached. Now, you effectively have multiple "tracks". You can utilize groups of tracks with your favorite resource allocation scheme like Round Robin etc. You could also specify other options like queuing sounds into a track to play when that resource becomes available or cutting a currently playing sample.
http://robert.ocallahan.org/2011/11/latency-of-html5-sounds.html http://people.mozilla.org/~roc/audio-latency-repeating.html Works OK in Firefox and Chrome for me. To stop a sound that you started, do var sound = document.getElementById("shot").cloneNode(true); sound.play(); and later sound.pause();
I would recommend using SoundJS, a library I've help develop. It allows you to write a single code base that works everywhere, with SoundJS picking web audio, html audio, or flash audio as appropriate. It will allow you to do all of the thing you want: Play and mix multiple sounds, Play the same sample multiple times, possibly overlapping playbacks Interrupt playback of a sample at any point play WAV files containing (depending on browser support) Hope that helps.
It's not possible to do multi-shot playing with a single <audio> element. You need to use multiple elements for this.
I ran into this while programming a musicbox card generator. Started with different libraries but everytime there was a glitch somehow. The lag on normal audio implementation was bad, no multiple plays... eventually ended up using lowlag library + soundmanager: http://lowlag.alienbill.com/ and http://www.schillmania.com/projects/soundmanager2/ You can check out the implementation here: http://musicbox.grit.it/ I generated wav + ogg files for multi browser plays. This musicbox player works responsive on ipad, iphone, Nexus, mac, pc,... works for me.
var AudioContextFunc = window.AudioContext || window.webkitAudioContext; var audioContext = new AudioContextFunc(); var player=new WebAudioFontPlayer(); var instrumVox,instrumApplause; var drumClap,drumLowTom,drumHighTom,drumSnare,drumKick,drumCrash; loadDrum(21,function(s){drumClap=s;}); loadDrum(30,function(s){drumLowTom=s;}); loadDrum(50,function(s){drumHighTom=s;}); loadDrum(15,function(s){drumSnare=s;}); loadDrum(5,function(s){drumKick=s;}); loadDrum(70,function(s){drumCrash=s;}); loadInstrument(594,function(s){instrumVox=s;}); loadInstrument(1367,function(s){instrumApplause=s;}); function loadDrum(n,callback){ var info=player.loader.drumInfo(n); player.loader.startLoad(audioContext, info.url, info.variable); player.loader.waitLoad(function () {callback(window[info.variable])}); } function loadInstrument(n,callback){ var info=player.loader.instrumentInfo(n); player.loader.startLoad(audioContext, info.url, info.variable); player.loader.waitLoad(function () {callback(window[info.variable])}); } function uhoh(){ var when=audioContext.currentTime; var b=0.1; player.queueWaveTable(audioContext, audioContext.destination, instrumVox, when+b*0, 60, b*1); player.queueWaveTable(audioContext, audioContext.destination, instrumVox, when+b*3, 56, b*4); } function applause(){ player.queueWaveTable(audioContext, audioContext.destination, instrumApplause, audioContext.currentTime, 54, 3); } function badumtss(){ var when=audioContext.currentTime; var b=0.11; player.queueWaveTable(audioContext, audioContext.destination, drumSnare, when+b*0, drumSnare.zones[0].keyRangeLow, 3.5); player.queueWaveTable(audioContext, audioContext.destination, drumLowTom, when+b*0, drumLowTom.zones[0].keyRangeLow, 3.5); player.queueWaveTable(audioContext, audioContext.destination, drumSnare, when+b*1, drumSnare.zones[0].keyRangeLow, 3.5); player.queueWaveTable(audioContext, audioContext.destination, drumHighTom, when+b*1, drumHighTom.zones[0].keyRangeLow, 3.5); player.queueWaveTable(audioContext, audioContext.destination, drumSnare, when+b*3, drumSnare.zones[0].keyRangeLow, 3.5); player.queueWaveTable(audioContext, audioContext.destination, drumKick, when+b*3, drumKick.zones[0].keyRangeLow, 3.5); player.queueWaveTable(audioContext, audioContext.destination, drumCrash, when+b*3, drumCrash.zones[0].keyRangeLow, 3.5); } <script src='https://surikov.github.io/webaudiofont/npm/dist/WebAudioFontPlayer.js'></script> <button onclick='badumtss();'>badumtss</button> <button onclick='uhoh();'>uhoh</button> <button onclick='applause();'>applause</button> <br/><a href='https://github.com/surikov/webaudiofont'>More sounds</a>
I know this is a total hack but thought I should add this sample open source audio library I put on github awhile ago... https://github.com/run-time/jThump After clicking the link below, type on the home row keys to play a blues riff (also type multiple keys at the same time etc.) Sample using jThump library >> http://davealger.com/apps/jthump/ It basically works by making invisible <iframe> elements that load a page that plays a sound onReady(). This is certainly not ideal but you could +1 this solution based on creativity alone (and the fact that it is open source and works in any browser that I've tried it on) I hope this gives someone else searching some ideas at least. :)
You can always try AudioContext it has limited support but it's a part of the web audio api working draft. It might be worth it if you are planing to release something in the future. And if you are only programing for chrome and Firefox you're golden.
Web Audio API is right tool for this job. There is little bit of work involved in loading sounds files and playing it. Luckily there are plenty of libraries out there that simplify the job. Being interested in sounds I also created a library called musquito you can check out that as well. Currently it supports only fading sound effect and I'm working on other things like 3D spatialization.
The selected answer will work in everything except IE. I wrote a tutorial on how to make it work cross browser. Here is the function I wrote: function playSomeSounds(soundPath) { var trident = !!navigator.userAgent.match(/Trident\/7.0/); var net = !!navigator.userAgent.match(/.NET4.0E/); var IE11 = trident && net var IEold = (navigator.userAgent.match(/MSIE/i) ? true : false); if (IE11 || IEold) { document.all.sound.src = soundPath; } else { var snd = new Audio(soundPath); // buffers automatically when created snd.play(); } } You also need to add the following tag to the html page: <bgsound id="sound"> Finally you can call the function and simply pass through the path here: playSomeSounds("sounds/welcome.wav");