WAV playback in Internet Explorer (IE10,11) without ActiveX

One of the problems that has existed since the demise of Flash is the lack of a consistent media experience.  Flash definitely had its problems... but it was reasonably consistent across platforms and browsers.  Things are getting better, but that is still not the case with modern audio/video tag based playback, MSE, etc.

One odd outlier is playback of raw pcm data or WAV files.  Recent versions of Chrome, Edge and Firefox all play back WAV files with an audio tag without any problem.  Internet explorer will not.  MSE also does not specify an option for PCM or WAV playback.  MSE also requires IE 11.  The Web Audio APIs / AudioBuffer allow this, but they are not available in any version of IE.

I thought about the problem a bit and remembered the <bgsound> tag that has existed since early versions of IE... and confirmed that it will indeed play WAV files.  The only problem is, there is no way of managing controls / seeking.  I put together a proof of concept to work around this limitation.

The WAV file format is easy to parse.  My implementation here is taking quite a few shortcuts... so it might not work with all files.  The solution is pretty simple... read the WAV file as an ArrayBuffer with XHR, seek by modifying the size fields in the header and cutting the output... then feed a Blob as a URL for the src attribute to <bgsound>.  If you were working with larger files, you could use 2 range requests – 1 for the header and 1 for the body.  My method below works under the assumption that seek performance will be improved by the browser cache.

function playWavIE(url, seek, callback) {
	xhr = new XMLHttpRequest();
	xhr.onload = function(e) {
		var view = new DataView(xhr.response);
		var fmt = {
			fileSize: view.getUint32(4,true),
			dataType: view.getUint16(20,true),
			channels: view.getUint16(22,true),
			sampleRate: view.getUint32(24,true),
			sampleValue: view.getUint32(28,true),
			sampleBytes: view.getUint16(32,true),
			bitsPerSample: view.getUint16(34,true),
			dataSize: view.getUint32(40,true)
		};
		fmt.duration = fmt.dataSize / fmt.sampleValue;

		if(seek> 0) {
			var cutBytes =  Math.floor(fmt.sampleValue*seek);
			var header = xhr.response.slice(0, 44);
			var hview = new DataView(header);
			hview.setUint32(4, fmt.fileSize - cutBytes,true);
			hview.setUint32(40, fmt.dataSize - cutBytes,true);

			blob = new Blob([header, xhr.response.slice(hview.byteLength + cutBytes)], {"type": "audio/x-wav"});
			url = URL.createObjectURL(blob);
		}

		if(document.getElementsByTagName("bgsound").length) {
			var bgsound = document.getElementsByTagName("bgsound")[0]
		}
		else {
			bgsound = document.createElement("bgsound");
			document.body.appendChild(bgsound);
		}

		bgsound.src = url;
		if(callback !== undefined) {
			callback(fmt);
		}

	}
	xhr.open('GET', url, true);
	xhr.responseType = 'arraybuffer';
	xhr.send();
}

function playCB(fmt) {
	console.log("playCB called");
	console.log(fmt);
}