I want background music with a beat to back my game. It's only a short loop, different on each level.. but when it loops there is an evident gap. The solution I am using to create looping is the same one that a forum search returned, i.e. attaching a MediaEnded
event, setting the start pos and playing again. If anyone has any pointers for gapless looping in Silverlight that would be much appreciated!
Interestingly, I thought I'd try using WAV files to eliminate the gap, but then I get an AG_E_INVALID_FILE_FORMAT error! Silverlight supports wave files, so not sure what that is about...
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); } }
This can be achieved by adding Markers to your MediaElement. Add a marker on the time where you want to loop the file. When the marker is reached, set the position of the MediaElement to 0.
Something like this:
void mdeBackgroundMusic_MediaOpened(object sender,
EventArgs e)
{
TimelineMarker marker =
new TimelineMarker();
marker.Time = TimeSpan.FromMilliseconds(TIME_OF_MP3_IN_MILLISECONDS);
marker.Text = "foo";
// Dummy value requred
marker.Type = "yada";
// Dummy value requred
Has anybody got Billy Porter's marker method to work without a gap? I haven't. If I use the MediaElement's duration as TIME_OF_MP3 then the MarkerVentHandler is never called. I figured I'd subtract some milliseconds, which works, but I still get a gap.
One thing you can not do when looping sounds is use Natural Duration. It is
way off. You have to figure out the duration with another program. I used Audacity and zoomed in as far as possible and get a passable result.
... even then you can't rely on getting an accurate result because you don't know when exactly the thread that is responsible is going to go, and the point you wanted may have passed.
As we all know by now, wav files aren't supported yet. The problem is that mp3 files add silence to the start of a file, and wma add silence to the end of a file, coupled with the fact that the time it takes for an audio file to restart or reposition is
not constant. With this in mind it seems that when trying to do seamless looping with the SL Media Player, results will always be at best erratic. - My workaround was to us the "bgsound" HTML object, which DOES support wav files and looping.
So with your WAV file at the ready, add the following line to your html Page:-
<
bgsound
id="oBGSound"
loop="-1"
>
the following xaml page gives you a button to start the loop, and a slider to control the volume :-
joshcomley
Member
54 Points
46 Posts
Looping MP3 or WMA without a gap
Aug 12, 2007 10:07 PM | LINK
I want background music with a beat to back my game. It's only a short loop, different on each level.. but when it loops there is an evident gap. The solution I am using to create looping is the same one that a forum search returned, i.e. attaching a MediaEnded event, setting the start pos and playing again. If anyone has any pointers for gapless looping in Silverlight that would be much appreciated!
Cheers!
Josh
mp3 wma looping silverlight media gap gapless
joshcomley
Member
54 Points
46 Posts
Re: Looping MP3 or WMA without a gap
Aug 12, 2007 10:10 PM | LINK
Interestingly, I thought I'd try using WAV files to eliminate the gap, but then I get an AG_E_INVALID_FILE_FORMAT error! Silverlight supports wave files, so not sure what that is about...
joshcomley
Member
54 Points
46 Posts
Re: Looping MP3 or WMA without a gap
Aug 13, 2007 02: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);
}
}
Billy Porter
Member
46 Points
23 Posts
Re: Looping MP3 or WMA without a gap
Dec 28, 2007 01:54 AM | LINK
This can be achieved by adding Markers to your MediaElement. Add a marker on the time where you want to loop the file. When the marker is reached, set the position of the MediaElement to 0.
Something like this:
void mdeBackgroundMusic_MediaOpened(object sender, EventArgs e){
TimelineMarker marker = new TimelineMarker();
marker.Time = TimeSpan.FromMilliseconds(TIME_OF_MP3_IN_MILLISECONDS);
marker.Text = "foo"; // Dummy value requred
marker.Type = "yada"; // Dummy value requred
mdeBackgroundMusic.Markers.Add(marker);
mdeBackgroundMusic.MarkerReached += new TimelineMarkerEventHandler(mdeBackgroundMusic_MarkerReached);
} void mdeBackgroundMusic_MarkerReached(object sender, TimelineMarkerEventArgs e)
{
mdeBackgroundMusic.Position = TimeSpan.FromMilliseconds(0);
}
HTH,
Billy
GearWorld
Participant
1661 Points
2024 Posts
Re: Looping MP3 or WMA without a gap
Jan 18, 2008 11:12 PM | LINK
Hi,
Then is there any reason why my SilverLight app doesn't play wav file. Its a valid wav file that Media Player 11 plays well.
The same sound plays as .mp3 but .wav NOPE !
DDtMM
Member
90 Points
103 Posts
Re: Looping MP3 or WMA without a gap
Jun 03, 2008 06:25 PM | LINK
Has anybody got Billy Porter's marker method to work without a gap? I haven't. If I use the MediaElement's duration as TIME_OF_MP3 then the MarkerVentHandler is never called. I figured I'd subtract some milliseconds, which works, but I still get a gap.
DDtMM
Member
90 Points
103 Posts
Re: Looping MP3 or WMA without a gap
Dec 01, 2008 08:10 PM | LINK
One thing you can not do when looping sounds is use Natural Duration. It is way off. You have to figure out the duration with another program. I used Audacity and zoomed in as far as possible and get a passable result.
... even then you can't rely on getting an accurate result because you don't know when exactly the thread that is responsible is going to go, and the point you wanted may have passed.
So truely accurate looping is impossible.
djprod
Member
348 Points
67 Posts
Re: Looping MP3 or WMA without a gap
Dec 03, 2008 08:13 PM | LINK
As we all know by now, wav files aren't supported yet. The problem is that mp3 files add silence to the start of a file, and wma add silence to the end of a file, coupled with the fact that the time it takes for an audio file to restart or reposition is not constant. With this in mind it seems that when trying to do seamless looping with the SL Media Player, results will always be at best erratic. - My workaround was to us the "bgsound" HTML object, which DOES support wav files and looping.
So with your WAV file at the ready, add the following line to your html Page:-
<
bgsound id="oBGSound" loop="-1" >the following xaml page gives you a button to start the loop, and a slider to control the volume :-
THE XAML MARKUP-
<
UserControl x:Class="SilverlightApplication24.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Canvas x:Name="LayoutRoot" Background="White"> <Button Height="25" Width="70" Content="Play Loop" Click="playme"></Button> <TextBlock x:Name="volcount" Text="100" Canvas.Left="190" Canvas.Top="4" FontSize="12"></TextBlock> <Slider x:Name="volslid" Value="0" ValueChanged="setvol" Canvas.Top="4" Canvas.Left="80" Width="100" Minimum="-7000" Maximum="0"></Slider> </Canvas></
UserControl>END XAML MARKUP
the page.xaml.cs gives you the functions:-
page.xaml.cs MARKUP:-
using
System;using
System.Windows.Browser;using
System.Windows.Controls; namespace SilverlightApplication24{
public partial class Page : UserControl{
public Page(){
InitializeComponent();
}
private void playme(object sender, EventArgs e){
HtmlPage.Document.GetElementById("oBGSound").SetProperty("src", "raw1.wav");}
private void setvol(object sender, EventArgs e){
HtmlPage.Document.GetElementById("oBGSound").SetProperty("volume", volslid.Value);volcount.Text = "" + Math.Round((volslid.Value/70)+(100*1)/1);}
}
}
More info on the bgsound object here :- http://msdn.microsoft.com/en-gb/library/ms535198(VS.85).aspx
Hope this helps
Regards, Marc.