Is there any crossbrowser solution for playing flac? (or is it possible in theory to make one)

avasin picture avasin · Dec 13, 2013 · Viewed 8.5k times · Source

Not interested in silverlight. Flash/javascript/html5 solutions are acceptable.

If you do not know such solutions, could you please say is it possible to make such that or not?

Answer

Adria picture Adria · Jan 20, 2015

When I had to play FLAC in-browser, my starting point was also the Aurora framework.

However, the Aurora player is geared around using ScriptProcessorNode to decode chunks of audio on the fly. This didn't pan out for many reasons.

  1. Seeking Flac in Aurora was never implemented.
  2. Stuttering and unacceptable performance in Firefox, even on a mid-range 2014 desktop.
  3. Not feasable to offload decoding to a WebWorker.
  4. Doesn't inter-operate with audio formats the browser does support.
  5. I didn't want to be responsible for re-sampling the sample-rate, seeking, and other low-level audio tasks that Aurora necessarily assimilates.

Decoding offline: Flac to Wave

My solution was to decode the Flac to raw 16bit PCM audio, using a stripped down Aurora.js Assset class + dependencies.
Look in the source for Asset.get( 'format', callback ), Asset.fromFile, and Asset.prototype.decodeToBuffer.

Next, take the audio data, along with extracted values for sample-rate and channel count, and build a WAVE file. This can be played using an HTML5 audio element, sent though an audio graph using createMediaElementSource, or absolutely anything you can do with natively supported audio formats.

Note: Replace clz function in decoder.js with the native Math.clz32 to boost performance, and polyfill clz32 for old browsers.

Disadvantage

The decoding time. Around 5 seconds at ~100% CPU for an "average" 4min song.

Advantages

  1. Blob (opposed to arraybuffer) isn't constrained by RAM, and the browser can swap it to disk. Original Flac data can likely be discarded too.
  2. You get seeking for free.
  3. You get sample-rate re-sampling for free.
  4. CPU activity paid for upfront in WebWorker.
  5. Should browsers EVER gain native Flac support, very easy to rip out. It doesn't create a strong dependency on Aurora.

Here's the function to build the WAVE header, and turn the raw PCM data into something the browser can natively play.

function createWave( audioData, sampleRate, channelCount )
{
    const audioFormat  = 1, // 2    PCM = 1
    subChunk1Size= 16,      // 4    PCM = 16
    bitsPerSample= 16,      // 2
    blockAlign   = channelCount * (bitsPerSample >> 3), // 2
    byteRate     = blockAlign * sampleRate,             // 4
    subChunk2Size= blockAlign * audioData.size,         // 4
    chunkSize    = 36 + subChunk2Size,                  // 4
    // Total header size 44 bytes
    header = new DataView( new ArrayBuffer(44) );


    header.setUint32( 0, 0x52494646 ); // chunkId=RIFF
    header.setUint32( 4, chunkSize, true );
    header.setUint32( 8, 0x57415645 ); // format=WAVE
    header.setUint32( 12, 0x666d7420 ); // subChunk1Id=fmt
    header.setUint32( 16, subChunk1Size, true );

    header.setUint16( 20, audioFormat, true );
    header.setUint16( 22, channelCount, true );

    header.setUint32( 24, sampleRate, true );
    header.setUint32( 28, byteRate, true );

    header.setUint16( 32, blockAlign, true );
    header.setUint16( 34, bitsPerSample, true );

    header.setUint32( 36, 0x64617461 ); // subChunk2Id=data
    header.setUint32( 40, subChunk2Size, true );

    return URL.createObjectURL( new Blob( [header, audioData], {type: 'audio/wav'} ) );
}