this is a follow up post to this one.

I’ve continued building on http://blog.andre-michelle.com/2010/playback-mp3-loop-gapless/ . And I’ve come up with some improvements:

  • make the loop a separate class
  • allow switching from one loop to another seamlessly.
  • fix a bug that what harmless in a browser but that broke the app on an iphone. In certain cases the previous “extract” method would read beyond the buffer causing the looping to stop.

See below for the code.

I apologize for the formatting, just copy and paste it and you should be able to read it properly.

To use it you still need to calculate the number of samples (see previous post).

For example:

const out: Sound = new Sound(); // Use for output stream

var loop:Mp3Loop = new Mp3Loop(out);

loop.loadMp3(“yourloop.mp3″, 213120l);


package
{
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.MouseEvent;
import flash.events.ProgressEvent;
import flash.events.SampleDataEvent;
import flash.media.Sound;
import flash.net.URLRequest;
import flash.utils.ByteArray;

public class Mp3Loop
{
private const MAGIC_DELAY:Number = 2257.0; // LAME 3.98.2 + flash.media.Sound Delay

private const BUFFER_SIZE: int = 4096; // Stable playback
// Use for decoding
private var _activeMp3: Sound;
private var _inactiveMp3: Sound;
private var _inactiveMp3SamplesTotal:uint;
private var _activeMp3SamplesTotal:uint;

// Use for output stream
private var _out: Sound;

private var _samplesPosition: int = 0;

private var _swapPending:Boolean;

private var _isPlaying:Boolean;

public function Mp3Loop(outputSound:Sound)
{
_out = outputSound;
_out.addEventListener( SampleDataEvent.SAMPLE_DATA, sampleData );
_out.play();

}

public function loadMp3(url:String, samplesTotal:uint):void{
_inactiveMp3SamplesTotal = samplesTotal;
_inactiveMp3 = new Sound();
_inactiveMp3.addEventListener( Event.COMPLETE, mp3Loaded, false, 0, true );
_inactiveMp3.addEventListener( IOErrorEvent.IO_ERROR, mp3Error, false, 0, true );
_inactiveMp3.load( new URLRequest(url) );

}

private function swap():void{

_swapPending = false;
_activeMp3 = _inactiveMp3;
_inactiveMp3 = null;
_activeMp3SamplesTotal = _inactiveMp3SamplesTotal;
_inactiveMp3SamplesTotal = 0;

}

private function mp3Loaded( event:Event ):void
{

if(!_isPlaying){
swap();
_isPlaying = true;
}else{
//loop is playing, wait for end
_swapPending = true;
}

}

private function sampleData( event:SampleDataEvent ):void
{
if(_isPlaying )
{
extract( event.data, BUFFER_SIZE );
}
else
{
silent( event.data, BUFFER_SIZE );
}
}

/**
* This methods extracts audio data from the mp3 and wraps it automatically with respect to encoder delay
*
* @param target The ByteArray where to write the audio data
* @param length The amount of samples to be read
*/
private function extract( target: ByteArray, length:int ):void
{
while( 0 < length )
{
if( _samplesPosition + length + MAGIC_DELAY > _activeMp3SamplesTotal )
{
var read: int = _activeMp3SamplesTotal - _samplesPosition - MAGIC_DELAY;

_activeMp3.extract( target, read, _samplesPosition + MAGIC_DELAY );

_samplesPosition += read;

length -= read;

}
else
{
_activeMp3.extract( target, length, _samplesPosition + MAGIC_DELAY );

_samplesPosition += length;

length = 0;
}

if( _samplesPosition == _activeMp3SamplesTotal - MAGIC_DELAY) // END OF LOOP > WRAP
{
_samplesPosition = 0;
if(_swapPending){
swap();
}
}
}
}

private function silent( target:ByteArray, length:int ):void
{
target.position = 0;

while( length-- )
{
target.writeFloat( 0.0 );
target.writeFloat( 0.0 );
}
}

private function mp3Error( event:IOErrorEvent ):void
{
trace( event );
}

public function get isPlaying():Boolean
{
return _isPlaying;
}

public function stop():void{
_inactiveMp3 = _activeMp3 = null;
_inactiveMp3SamplesTotal = _activeMp3SamplesTotal = 0;
_isPlaying = false;
_samplesPosition = 0;
}

}
}

Pin It