After trying lots of threading techniques and them some digging around it appears Silverlight does not
yet have it's BackgroundWorker class ready, which makes it very difficult to initiate some UI changes from a separate thread (all UI changed must be done in the same thread that the UI was created in). I wanted to loop two sounds, without a gap between
them. Achievable with just one loop, but two loops of differing lengths and you run out of HtmlTimer's scope (unless I'm missing something). So a thread kicks off, and a super quick HtmlTimer (i.e. set to 1) keeps checking for any updates to the _queue, and
then performs them if necessary. Also, you may wonder why I create new MediaElements as opposed to restarting the old one's. It just performed a lot better this way round - which leads to garbage coleciton; I have done some in that I am removing the old MediaElements
when they're done from the Children collection. This got rid of 98% of garbage building up on each loop, but there is still a small memory leak in this code, probably because the MediaElements are not destroyed from memory.
A horrible, nasty, uncommented and unrefactored (it's too late for that sort of thing!) semi-solution to my problem (that doesn't get the two samples in perfect harmony, but darn
close.. roll on BackgroundWorker):
publicpartialclassPage :
Canvas { private
Dictionary<MediaElement,
int> _mediaElementPositions = new
Dictionary<MediaElement,
int>(); private
List<MediaElement> _loopedAudios =
newList<MediaElement>(); private
List<int> _queue =
newList<int>(); public
void Page_Loaded(object o,
EventArgs e) { // Required to initialize variables
publicclassAudioLoopedEventArgs :
EventArgs { private
int _loopedAudioIndex; public
int LoopedAudioIndex { get {
return _loopedAudioIndex; } set { _loopedAudioIndex =
value; } } }
classAudioLooper { public
eventAudioLoopedHandler AudioLooped; private
int _loopLength; private
int _loopedAudioIndex; public
int LoopedAudioIndex { get {
return _loopedAudioIndex; } set { _loopedAudioIndex =
value; } } public
int LoopLength { get {
return _loopLength; } }
public AudioLooper(int loopLength,
int loopedAudioIndex) { _loopedAudioIndex = loopedAudioIndex; _loopLength = loopLength; }
publicvoid Play() { while (true) { Thread.Sleep(LoopLength); AudioLoopedEventArgs e =
newAudioLoopedEventArgs(); e.LoopedAudioIndex = LoopedAudioIndex; OnAudioLooped(e); } }
joshcomley
Member
54 Points
46 Posts
Re: Looping MP3 or WMA without a gap
Aug 13, 2007 01:45 AM | LINK
After trying lots of threading techniques and them some digging around it appears Silverlight does not yet have it's BackgroundWorker class ready, which makes it very difficult to initiate some UI changes from a separate thread (all UI changed must be done in the same thread that the UI was created in). I wanted to loop two sounds, without a gap between them. Achievable with just one loop, but two loops of differing lengths and you run out of HtmlTimer's scope (unless I'm missing something). So a thread kicks off, and a super quick HtmlTimer (i.e. set to 1) keeps checking for any updates to the _queue, and then performs them if necessary. Also, you may wonder why I create new MediaElements as opposed to restarting the old one's. It just performed a lot better this way round - which leads to garbage coleciton; I have done some in that I am removing the old MediaElements when they're done from the Children collection. This got rid of 98% of garbage building up on each loop, but there is still a small memory leak in this code, probably because the MediaElements are not destroyed from memory.
A horrible, nasty, uncommented and unrefactored (it's too late for that sort of thing!) semi-solution to my problem (that doesn't get the two samples in perfect harmony, but darn close.. roll on BackgroundWorker):
public partial class Page : Canvas
{
private Dictionary<MediaElement, int> _mediaElementPositions = new Dictionary<MediaElement, int>();
private List<MediaElement> _loopedAudios = new List<MediaElement>();
private List<int> _queue = new List<int>();
public void Page_Loaded(object o, EventArgs e)
{
// Required to initialize variables
InitializeComponent();
PlayLoopedSound("Loop1.wma", 0.5, 1);
PlayLoopedSound("Loop2.wma", 0.5, -1);
}
void PlayLoopedSound(string source, double volume, double balance)
{
MediaElement me = new MediaElement();
_loopedAudios.Add(me);
me.Source = new Uri(source, UriKind.Relative);
me.Volume = volume;
me.Balance = balance;
me.MediaOpened += new EventHandler(SetToLoop);
Children.Add(me);
HtmlTimer timer = new HtmlTimer();
timer.Interval = 1;
timer.Tick += new EventHandler(timer_Tick);
timer.Enabled = true;
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
if (_queue.Count > 0)
{
CloneAndAddAudio(_loopedAudios[_queue[0]]);
_queue.RemoveAt(0);
}
}
void SetToLoop(object sender, EventArgs e)
{
MediaElement me = sender as MediaElement;
// The 50 milliseconds removes the gap between loops
AudioLooper al = new AudioLooper((int)me.NaturalDuration.TimeSpan.TotalMilliseconds - 50, _loopedAudios.IndexOf(me));
al.AudioLooped += new AudioLoopedHandler(al_AudioLooped);
ThreadStart ts = new ThreadStart(al.Play);
Thread t = new Thread(ts);
t.Start();
}
void al_AudioLooped(object sender, AudioLoopedEventArgs e)
{
_queue.Add(e.LoopedAudioIndex);
}
private void CloneAndAddAudio(MediaElement me)
{
MediaElement meNew = new MediaElement();
meNew.Source = me.Source;
meNew.Volume = me.Volume;
meNew.Balance = me.Balance;
_loopedAudios[_loopedAudios.IndexOf(me)] = meNew;
Children.Remove(me);
Children.Add(meNew);
}
}
public delegate void AudioLoopedHandler(object sender, AudioLoopedEventArgs e);
public class AudioLoopedEventArgs : EventArgs
{
private int _loopedAudioIndex;
public int LoopedAudioIndex
{
get { return _loopedAudioIndex; }
set { _loopedAudioIndex = value; }
}
}
class AudioLooper
{
public event AudioLoopedHandler AudioLooped;
private int _loopLength;
private int _loopedAudioIndex;
public int LoopedAudioIndex
{
get { return _loopedAudioIndex; }
set { _loopedAudioIndex = value; }
}
public int LoopLength
{
get { return _loopLength; }
}
public AudioLooper(int loopLength, int loopedAudioIndex)
{
_loopedAudioIndex = loopedAudioIndex;
_loopLength = loopLength;
}
public void Play()
{
while (true)
{
Thread.Sleep(LoopLength);
AudioLoopedEventArgs e = new AudioLoopedEventArgs();
e.LoopedAudioIndex = LoopedAudioIndex;
OnAudioLooped(e);
}
}
protected virtual void OnAudioLooped(AudioLoopedEventArgs e)
{
AudioLooped(this, e);
}
}